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    * http://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.LastHttpContent;
27  import io.netty.handler.codec.http2.Http2CodecUtil.SimpleChannelPromiseAggregator;
28  import io.netty.util.ReferenceCountUtil;
29  import io.netty.util.internal.UnstableApi;
30  
31  /**
32   * Translates HTTP/1.x object writes into HTTP/2 frames.
33   * <p>
34   * See {@link InboundHttp2ToHttpAdapter} to get translation from HTTP/2 frames to HTTP/1.x objects.
35   */
36  @UnstableApi
37  public class HttpToHttp2ConnectionHandler extends Http2ConnectionHandler {
38  
39      private final boolean validateHeaders;
40      private int currentStreamId;
41  
42      protected HttpToHttp2ConnectionHandler(Http2ConnectionDecoder decoder, Http2ConnectionEncoder encoder,
43                                             Http2Settings initialSettings, boolean validateHeaders) {
44          super(decoder, encoder, initialSettings);
45          this.validateHeaders = validateHeaders;
46      }
47  
48      /**
49       * Get the next stream id either from the {@link HttpHeaders} object or HTTP/2 codec
50       *
51       * @param httpHeaders The HTTP/1.x headers object to look for the stream id
52       * @return The stream id to use with this {@link HttpHeaders} object
53       * @throws Exception If the {@code httpHeaders} object specifies an invalid stream id
54       */
55      private int getStreamId(HttpHeaders httpHeaders) throws Exception {
56          return httpHeaders.getInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(),
57                                    connection().local().incrementAndGetNextStreamId());
58      }
59  
60      /**
61       * Handles conversion of {@link HttpMessage} and {@link HttpContent} to HTTP/2 frames.
62       */
63      @Override
64      public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
65  
66          if (!(msg instanceof HttpMessage || msg instanceof HttpContent)) {
67              ctx.write(msg, promise);
68              return;
69          }
70  
71          boolean release = true;
72          SimpleChannelPromiseAggregator promiseAggregator =
73                  new SimpleChannelPromiseAggregator(promise, ctx.channel(), ctx.executor());
74          try {
75              Http2ConnectionEncoder encoder = encoder();
76              boolean endStream = false;
77              if (msg instanceof HttpMessage) {
78                  final HttpMessage httpMsg = (HttpMessage) msg;
79  
80                  // Provide the user the opportunity to specify the streamId
81                  currentStreamId = getStreamId(httpMsg.headers());
82  
83                  // Convert and write the headers.
84                  Http2Headers http2Headers = HttpConversionUtil.toHttp2Headers(httpMsg, validateHeaders);
85                  endStream = msg instanceof FullHttpMessage && !((FullHttpMessage) msg).content().isReadable();
86                  writeHeaders(ctx, encoder, currentStreamId, httpMsg.headers(), http2Headers,
87                          endStream, promiseAggregator);
88              }
89  
90              if (!endStream && msg instanceof HttpContent) {
91                  boolean isLastContent = false;
92                  HttpHeaders trailers = EmptyHttpHeaders.INSTANCE;
93                  Http2Headers http2Trailers = EmptyHttp2Headers.INSTANCE;
94                  if (msg instanceof LastHttpContent) {
95                      isLastContent = true;
96  
97                      // Convert any trailing headers.
98                      final LastHttpContent lastContent = (LastHttpContent) msg;
99                      trailers = lastContent.trailingHeaders();
100                     http2Trailers = HttpConversionUtil.toHttp2Headers(trailers, validateHeaders);
101                 }
102 
103                 // Write the data
104                 final ByteBuf content = ((HttpContent) msg).content();
105                 endStream = isLastContent && trailers.isEmpty();
106                 release = false;
107                 encoder.writeData(ctx, currentStreamId, content, 0, endStream, promiseAggregator.newPromise());
108 
109                 if (!trailers.isEmpty()) {
110                     // Write trailing headers.
111                     writeHeaders(ctx, encoder, currentStreamId, trailers, http2Trailers, true, promiseAggregator);
112                 }
113             }
114         } catch (Throwable t) {
115             onError(ctx, t);
116             promiseAggregator.setFailure(t);
117         } finally {
118             if (release) {
119                 ReferenceCountUtil.release(msg);
120             }
121             promiseAggregator.doneAllocatingPromises();
122         }
123     }
124 
125     private static void writeHeaders(ChannelHandlerContext ctx, Http2ConnectionEncoder encoder, int streamId,
126                                      HttpHeaders headers, Http2Headers http2Headers, boolean endStream,
127                                      SimpleChannelPromiseAggregator promiseAggregator) {
128         int dependencyId = headers.getInt(
129                 HttpConversionUtil.ExtensionHeaderNames.STREAM_DEPENDENCY_ID.text(), 0);
130         short weight = headers.getShort(
131                 HttpConversionUtil.ExtensionHeaderNames.STREAM_WEIGHT.text(), Http2CodecUtil.DEFAULT_PRIORITY_WEIGHT);
132         encoder.writeHeaders(ctx, streamId, http2Headers, dependencyId, weight, false,
133                 0, endStream, promiseAggregator.newPromise());
134     }
135 }