View Javadoc
1   /*
2    * Copyright 2014 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License, version 2.0 (the
5    * "License"); you may not use this file except in compliance with the License. You may obtain a
6    * copy of the License at:
7    *
8    * https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software distributed under the License
11   * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12   * or implied. See the License for the specific language governing permissions and limitations under
13   * the License.
14   */
15  
16  package io.netty.handler.codec.http2;
17  
18  import io.netty.buffer.ByteBuf;
19  import io.netty.channel.ChannelHandlerContext;
20  import io.netty.channel.ChannelPromise;
21  import io.netty.handler.codec.http.EmptyHttpHeaders;
22  import io.netty.handler.codec.http.FullHttpMessage;
23  import io.netty.handler.codec.http.HttpContent;
24  import io.netty.handler.codec.http.HttpHeaders;
25  import io.netty.handler.codec.http.HttpMessage;
26  import io.netty.handler.codec.http.HttpScheme;
27  import io.netty.handler.codec.http.LastHttpContent;
28  import io.netty.handler.codec.http2.Http2CodecUtil.SimpleChannelPromiseAggregator;
29  import io.netty.util.ReferenceCountUtil;
30  import io.netty.util.internal.UnstableApi;
31  
32  /**
33   * Translates HTTP/1.x object writes into HTTP/2 frames.
34   * <p>
35   * See {@link InboundHttp2ToHttpAdapter} to get translation from HTTP/2 frames to HTTP/1.x objects.
36   */
37  @UnstableApi
38  public class HttpToHttp2ConnectionHandler extends Http2ConnectionHandler {
39  
40      private final boolean validateHeaders;
41      private int currentStreamId;
42      private HttpScheme httpScheme;
43  
44      protected HttpToHttp2ConnectionHandler(Http2ConnectionDecoder decoder, Http2ConnectionEncoder encoder,
45                                             Http2Settings initialSettings, boolean validateHeaders) {
46          super(decoder, encoder, initialSettings);
47          this.validateHeaders = validateHeaders;
48      }
49  
50      protected HttpToHttp2ConnectionHandler(Http2ConnectionDecoder decoder, Http2ConnectionEncoder encoder,
51                                             Http2Settings initialSettings, boolean validateHeaders,
52                                             boolean decoupleCloseAndGoAway) {
53          this(decoder, encoder, initialSettings, validateHeaders, decoupleCloseAndGoAway, null);
54      }
55  
56      protected HttpToHttp2ConnectionHandler(Http2ConnectionDecoder decoder, Http2ConnectionEncoder encoder,
57                                             Http2Settings initialSettings, boolean validateHeaders,
58                                             boolean decoupleCloseAndGoAway, HttpScheme httpScheme) {
59          super(decoder, encoder, initialSettings, decoupleCloseAndGoAway);
60          this.validateHeaders = validateHeaders;
61          this.httpScheme = httpScheme;
62      }
63  
64      protected HttpToHttp2ConnectionHandler(Http2ConnectionDecoder decoder, Http2ConnectionEncoder encoder,
65                                             Http2Settings initialSettings, boolean validateHeaders,
66                                             boolean decoupleCloseAndGoAway, boolean flushPreface,
67                                             HttpScheme httpScheme) {
68          super(decoder, encoder, initialSettings, decoupleCloseAndGoAway, flushPreface);
69          this.validateHeaders = validateHeaders;
70          this.httpScheme = httpScheme;
71      }
72  
73      /**
74       * Get the next stream id either from the {@link HttpHeaders} object or HTTP/2 codec
75       *
76       * @param httpHeaders The HTTP/1.x headers object to look for the stream id
77       * @return The stream id to use with this {@link HttpHeaders} object
78       * @throws Exception If the {@code httpHeaders} object specifies an invalid stream id
79       */
80      private int getStreamId(HttpHeaders httpHeaders) throws Exception {
81          return httpHeaders.getInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(),
82                                    connection().local().incrementAndGetNextStreamId());
83      }
84  
85      /**
86       * Handles conversion of {@link HttpMessage} and {@link HttpContent} to HTTP/2 frames.
87       */
88      @Override
89      public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
90  
91          if (!(msg instanceof HttpMessage || msg instanceof HttpContent)) {
92              ctx.write(msg, promise);
93              return;
94          }
95  
96          boolean release = true;
97          SimpleChannelPromiseAggregator promiseAggregator =
98                  new SimpleChannelPromiseAggregator(promise, ctx.channel(), ctx.executor());
99          try {
100             Http2ConnectionEncoder encoder = encoder();
101             boolean endStream = false;
102             if (msg instanceof HttpMessage) {
103                 final HttpMessage httpMsg = (HttpMessage) msg;
104 
105                 // Provide the user the opportunity to specify the streamId
106                 currentStreamId = getStreamId(httpMsg.headers());
107 
108                 // Add HttpScheme if it's defined in constructor and header does not contain it.
109                 if (httpScheme != null &&
110                         !httpMsg.headers().contains(HttpConversionUtil.ExtensionHeaderNames.SCHEME.text())) {
111                     httpMsg.headers().set(HttpConversionUtil.ExtensionHeaderNames.SCHEME.text(), httpScheme.name());
112                 }
113 
114                 // Convert and write the headers.
115                 Http2Headers http2Headers = HttpConversionUtil.toHttp2Headers(httpMsg, validateHeaders);
116                 endStream = msg instanceof FullHttpMessage && !((FullHttpMessage) msg).content().isReadable();
117                 writeHeaders(ctx, encoder, currentStreamId, httpMsg.headers(), http2Headers,
118                         endStream, promiseAggregator);
119             }
120 
121             if (!endStream && msg instanceof HttpContent) {
122                 boolean isLastContent = false;
123                 HttpHeaders trailers = EmptyHttpHeaders.INSTANCE;
124                 Http2Headers http2Trailers = EmptyHttp2Headers.INSTANCE;
125                 if (msg instanceof LastHttpContent) {
126                     isLastContent = true;
127 
128                     // Convert any trailing headers.
129                     final LastHttpContent lastContent = (LastHttpContent) msg;
130                     trailers = lastContent.trailingHeaders();
131                     http2Trailers = HttpConversionUtil.toHttp2Headers(trailers, validateHeaders);
132                 }
133 
134                 // Write the data
135                 final ByteBuf content = ((HttpContent) msg).content();
136                 endStream = isLastContent && trailers.isEmpty();
137                 encoder.writeData(ctx, currentStreamId, content, 0, endStream, promiseAggregator.newPromise());
138                 release = false;
139 
140                 if (!trailers.isEmpty()) {
141                     // Write trailing headers.
142                     writeHeaders(ctx, encoder, currentStreamId, trailers, http2Trailers, true, promiseAggregator);
143                 }
144             }
145         } catch (Throwable t) {
146             onError(ctx, true, t);
147             promiseAggregator.setFailure(t);
148         } finally {
149             if (release) {
150                 ReferenceCountUtil.release(msg);
151             }
152             promiseAggregator.doneAllocatingPromises();
153         }
154     }
155 
156     private static void writeHeaders(ChannelHandlerContext ctx, Http2ConnectionEncoder encoder, int streamId,
157                                      HttpHeaders headers, Http2Headers http2Headers, boolean endStream,
158                                      SimpleChannelPromiseAggregator promiseAggregator) {
159         int dependencyId = headers.getInt(
160                 HttpConversionUtil.ExtensionHeaderNames.STREAM_DEPENDENCY_ID.text(), 0);
161         short weight = headers.getShort(
162                 HttpConversionUtil.ExtensionHeaderNames.STREAM_WEIGHT.text(), Http2CodecUtil.DEFAULT_PRIORITY_WEIGHT);
163         encoder.writeHeaders(ctx, streamId, http2Headers, dependencyId, weight, false,
164                 0, endStream, promiseAggregator.newPromise());
165     }
166 }