View Javadoc
1   /*
2    * Copyright 2016 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a 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
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  
17  package io.netty.handler.codec.http2;
18  
19  import io.netty.buffer.ByteBufAllocator;
20  import io.netty.buffer.Unpooled;
21  import io.netty.channel.Channel;
22  import io.netty.channel.ChannelHandler;
23  import io.netty.channel.ChannelHandler.Sharable;
24  import io.netty.channel.ChannelHandlerContext;
25  import io.netty.handler.codec.EncoderException;
26  import io.netty.handler.codec.MessageToMessageCodec;
27  import io.netty.handler.codec.http.DefaultHttpContent;
28  import io.netty.handler.codec.http.DefaultLastHttpContent;
29  import io.netty.handler.codec.http.FullHttpMessage;
30  import io.netty.handler.codec.http.FullHttpResponse;
31  import io.netty.handler.codec.http.HttpContent;
32  import io.netty.handler.codec.http.HttpHeaderNames;
33  import io.netty.handler.codec.http.HttpHeaderValues;
34  import io.netty.handler.codec.http.HttpMessage;
35  import io.netty.handler.codec.http.HttpObject;
36  import io.netty.handler.codec.http.HttpRequest;
37  import io.netty.handler.codec.http.HttpResponse;
38  import io.netty.handler.codec.http.HttpResponseStatus;
39  import io.netty.handler.codec.http.HttpScheme;
40  import io.netty.handler.codec.http.HttpUtil;
41  import io.netty.handler.codec.http.HttpVersion;
42  import io.netty.handler.codec.http.LastHttpContent;
43  import io.netty.handler.ssl.SslHandler;
44  import io.netty.util.internal.UnstableApi;
45  
46  import java.util.List;
47  
48  /**
49   * This handler converts from {@link Http2StreamFrame} to {@link HttpObject},
50   * and back. It can be used as an adapter in conjunction with {@link
51   * Http2MultiplexCodec} to make http/2 connections backward-compatible with
52   * {@link ChannelHandler}s expecting {@link HttpObject}
53   *
54   * For simplicity, it converts to chunked encoding unless the entire stream
55   * is a single header.
56   */
57  @UnstableApi
58  @Sharable
59  public class Http2StreamFrameToHttpObjectCodec extends MessageToMessageCodec<Http2StreamFrame, HttpObject> {
60      private final boolean isServer;
61      private final boolean validateHeaders;
62  
63      private HttpScheme scheme;
64  
65      public Http2StreamFrameToHttpObjectCodec(final boolean isServer,
66                                               final boolean validateHeaders) {
67          this.isServer = isServer;
68          this.validateHeaders = validateHeaders;
69          scheme = HttpScheme.HTTP;
70      }
71  
72      public Http2StreamFrameToHttpObjectCodec(final boolean isServer) {
73          this(isServer, true);
74      }
75  
76      @Override
77      public boolean acceptInboundMessage(Object msg) throws Exception {
78          return (msg instanceof Http2HeadersFrame) || (msg instanceof Http2DataFrame);
79      }
80  
81      @Override
82      protected void decode(ChannelHandlerContext ctx, Http2StreamFrame frame, List<Object> out) throws Exception {
83          if (frame instanceof Http2HeadersFrame) {
84              Http2HeadersFrame headersFrame = (Http2HeadersFrame) frame;
85              Http2Headers headers = headersFrame.headers();
86              Http2FrameStream stream = headersFrame.stream();
87              int id = stream == null ? 0 : stream.id();
88  
89              final CharSequence status = headers.status();
90  
91              // 100-continue response is a special case where Http2HeadersFrame#isEndStream=false
92              // but we need to decode it as a FullHttpResponse to play nice with HttpObjectAggregator.
93              if (null != status && HttpResponseStatus.CONTINUE.codeAsText().contentEquals(status)) {
94                  final FullHttpMessage fullMsg = newFullMessage(id, headers, ctx.alloc());
95                  out.add(fullMsg);
96                  return;
97              }
98  
99              if (headersFrame.isEndStream()) {
100                 if (headers.method() == null && status == null) {
101                     LastHttpContent last = new DefaultLastHttpContent(Unpooled.EMPTY_BUFFER, validateHeaders);
102                     HttpConversionUtil.addHttp2ToHttpHeaders(id, headers, last.trailingHeaders(),
103                                                              HttpVersion.HTTP_1_1, true, true);
104                     out.add(last);
105                 } else {
106                     FullHttpMessage full = newFullMessage(id, headers, ctx.alloc());
107                     out.add(full);
108                 }
109             } else {
110                 HttpMessage req = newMessage(id, headers);
111                 if (!HttpUtil.isContentLengthSet(req)) {
112                     req.headers().add(HttpHeaderNames.TRANSFER_ENCODING, HttpHeaderValues.CHUNKED);
113                 }
114                 out.add(req);
115             }
116         } else if (frame instanceof Http2DataFrame) {
117             Http2DataFrame dataFrame = (Http2DataFrame) frame;
118             if (dataFrame.isEndStream()) {
119                 out.add(new DefaultLastHttpContent(dataFrame.content().retain(), validateHeaders));
120             } else {
121                 out.add(new DefaultHttpContent(dataFrame.content().retain()));
122             }
123         }
124     }
125 
126     private void encodeLastContent(LastHttpContent last, List<Object> out) {
127         boolean needFiller = !(last instanceof FullHttpMessage) && last.trailingHeaders().isEmpty();
128         if (last.content().isReadable() || needFiller) {
129             out.add(new DefaultHttp2DataFrame(last.content().retain(), last.trailingHeaders().isEmpty()));
130         }
131         if (!last.trailingHeaders().isEmpty()) {
132             Http2Headers headers = HttpConversionUtil.toHttp2Headers(last.trailingHeaders(), validateHeaders);
133             out.add(new DefaultHttp2HeadersFrame(headers, true));
134         }
135     }
136 
137     /**
138      * Encode from an {@link HttpObject} to an {@link Http2StreamFrame}. This method will
139      * be called for each written message that can be handled by this encoder.
140      *
141      * NOTE: 100-Continue responses that are NOT {@link FullHttpResponse} will be rejected.
142      *
143      * @param ctx           the {@link ChannelHandlerContext} which this handler belongs to
144      * @param obj           the {@link HttpObject} message to encode
145      * @param out           the {@link List} into which the encoded msg should be added
146      *                      needs to do some kind of aggregation
147      * @throws Exception    is thrown if an error occurs
148      */
149     @Override
150     protected void encode(ChannelHandlerContext ctx, HttpObject obj, List<Object> out) throws Exception {
151         // 100-continue is typically a FullHttpResponse, but the decoded
152         // Http2HeadersFrame should not be marked as endStream=true
153         if (obj instanceof HttpResponse) {
154             final HttpResponse res = (HttpResponse) obj;
155             if (res.status().equals(HttpResponseStatus.CONTINUE)) {
156                 if (res instanceof FullHttpResponse) {
157                     final Http2Headers headers = toHttp2Headers(res);
158                     out.add(new DefaultHttp2HeadersFrame(headers, false));
159                     return;
160                 } else {
161                     throw new EncoderException(
162                             HttpResponseStatus.CONTINUE.toString() + " must be a FullHttpResponse");
163                 }
164             }
165         }
166 
167         if (obj instanceof HttpMessage) {
168             Http2Headers headers = toHttp2Headers((HttpMessage) obj);
169             boolean noMoreFrames = false;
170             if (obj instanceof FullHttpMessage) {
171                 FullHttpMessage full = (FullHttpMessage) obj;
172                 noMoreFrames = !full.content().isReadable() && full.trailingHeaders().isEmpty();
173             }
174 
175             out.add(new DefaultHttp2HeadersFrame(headers, noMoreFrames));
176         }
177 
178         if (obj instanceof LastHttpContent) {
179             LastHttpContent last = (LastHttpContent) obj;
180             encodeLastContent(last, out);
181         } else if (obj instanceof HttpContent) {
182             HttpContent cont = (HttpContent) obj;
183             out.add(new DefaultHttp2DataFrame(cont.content().retain(), false));
184         }
185     }
186 
187     private Http2Headers toHttp2Headers(final HttpMessage msg) {
188         if (msg instanceof HttpRequest) {
189             msg.headers().set(
190                     HttpConversionUtil.ExtensionHeaderNames.SCHEME.text(),
191                     scheme.name());
192         }
193 
194         return HttpConversionUtil.toHttp2Headers(msg, validateHeaders);
195     }
196 
197     private HttpMessage newMessage(final int id,
198                                    final Http2Headers headers) throws Http2Exception {
199         return isServer ?
200                 HttpConversionUtil.toHttpRequest(id, headers, validateHeaders) :
201                 HttpConversionUtil.toHttpResponse(id, headers, validateHeaders);
202     }
203 
204     private FullHttpMessage newFullMessage(final int id,
205                                            final Http2Headers headers,
206                                            final ByteBufAllocator alloc) throws Http2Exception {
207         return isServer ?
208                 HttpConversionUtil.toFullHttpRequest(id, headers, alloc, validateHeaders) :
209                 HttpConversionUtil.toFullHttpResponse(id, headers, alloc, validateHeaders);
210     }
211 
212     @Override
213     public void handlerAdded(final ChannelHandlerContext ctx) throws Exception {
214         super.handlerAdded(ctx);
215 
216         // this handler is typically used on an Http2StreamChannel. at this
217         // stage, ssl handshake should've been established. checking for the
218         // presence of SslHandler in the parent's channel pipeline to
219         // determine the HTTP scheme should suffice, even for the case where
220         // SniHandler is used.
221         scheme = isSsl(ctx) ? HttpScheme.HTTPS : HttpScheme.HTTP;
222     }
223 
224     protected boolean isSsl(final ChannelHandlerContext ctx) {
225         final Channel ch = ctx.channel();
226         final Channel connChannel = (ch instanceof Http2StreamChannel) ? ch.parent() : ch;
227         return null != connChannel.pipeline().get(SslHandler.class);
228     }
229 }