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    *   https://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.HttpStatusClass;
41  import io.netty.handler.codec.http.HttpUtil;
42  import io.netty.handler.codec.http.HttpVersion;
43  import io.netty.handler.codec.http.LastHttpContent;
44  import io.netty.handler.ssl.SslHandler;
45  import io.netty.util.Attribute;
46  import io.netty.util.AttributeKey;
47  import io.netty.util.internal.UnstableApi;
48  
49  import java.util.List;
50  
51  /**
52   * This handler converts from {@link Http2StreamFrame} to {@link HttpObject},
53   * and back. It can be used as an adapter in conjunction with {@link
54   * Http2MultiplexCodec} to make http/2 connections backward-compatible with
55   * {@link ChannelHandler}s expecting {@link HttpObject}
56   *
57   * For simplicity, it converts to chunked encoding unless the entire stream
58   * is a single header.
59   */
60  @UnstableApi
61  @Sharable
62  public class Http2StreamFrameToHttpObjectCodec extends MessageToMessageCodec<Http2StreamFrame, HttpObject> {
63  
64      private static final AttributeKey<HttpScheme> SCHEME_ATTR_KEY =
65          AttributeKey.valueOf(HttpScheme.class, "STREAMFRAMECODEC_SCHEME");
66  
67      private final boolean isServer;
68      private final boolean validateHeaders;
69  
70      public Http2StreamFrameToHttpObjectCodec(final boolean isServer,
71                                               final boolean validateHeaders) {
72          this.isServer = isServer;
73          this.validateHeaders = validateHeaders;
74      }
75  
76      public Http2StreamFrameToHttpObjectCodec(final boolean isServer) {
77          this(isServer, true);
78      }
79  
80      @Override
81      public boolean acceptInboundMessage(Object msg) throws Exception {
82          return msg instanceof Http2HeadersFrame || msg instanceof Http2DataFrame;
83      }
84  
85      @Override
86      protected void decode(ChannelHandlerContext ctx, Http2StreamFrame frame, List<Object> out) throws Exception {
87          if (frame instanceof Http2HeadersFrame) {
88              Http2HeadersFrame headersFrame = (Http2HeadersFrame) frame;
89              Http2Headers headers = headersFrame.headers();
90              Http2FrameStream stream = headersFrame.stream();
91              int id = stream == null ? 0 : stream.id();
92  
93              final CharSequence status = headers.status();
94  
95              // 1xx response (excluding 101) is a special case where Http2HeadersFrame#isEndStream=false
96              // but we need to decode it as a FullHttpResponse to play nice with HttpObjectAggregator.
97              if (null != status && isInformationalResponseHeaderFrame(status)) {
98                  final FullHttpMessage fullMsg = newFullMessage(id, headers, ctx.alloc());
99                  out.add(fullMsg);
100                 return;
101             }
102 
103             if (headersFrame.isEndStream()) {
104                 if (headers.method() == null && status == null) {
105                     LastHttpContent last = new DefaultLastHttpContent(Unpooled.EMPTY_BUFFER, validateHeaders);
106                     HttpConversionUtil.addHttp2ToHttpHeaders(id, headers, last.trailingHeaders(),
107                                                              HttpVersion.HTTP_1_1, true, true);
108                     out.add(last);
109                 } else {
110                     FullHttpMessage full = newFullMessage(id, headers, ctx.alloc());
111                     out.add(full);
112                 }
113             } else {
114                 HttpMessage req = newMessage(id, headers);
115                 if ((status == null || !isContentAlwaysEmpty(status)) && !HttpUtil.isContentLengthSet(req)) {
116                     req.headers().add(HttpHeaderNames.TRANSFER_ENCODING, HttpHeaderValues.CHUNKED);
117                 }
118                 out.add(req);
119             }
120         } else if (frame instanceof Http2DataFrame) {
121             Http2DataFrame dataFrame = (Http2DataFrame) frame;
122             if (dataFrame.isEndStream()) {
123                 out.add(new DefaultLastHttpContent(dataFrame.content().retain(), validateHeaders));
124             } else {
125                 out.add(new DefaultHttpContent(dataFrame.content().retain()));
126             }
127         }
128     }
129 
130     private void encodeLastContent(LastHttpContent last, List<Object> out) {
131         boolean needFiller = !(last instanceof FullHttpMessage) && last.trailingHeaders().isEmpty();
132         if (last.content().isReadable() || needFiller) {
133             out.add(new DefaultHttp2DataFrame(last.content().retain(), last.trailingHeaders().isEmpty()));
134         }
135         if (!last.trailingHeaders().isEmpty()) {
136             Http2Headers headers = HttpConversionUtil.toHttp2Headers(last.trailingHeaders(), validateHeaders);
137             out.add(new DefaultHttp2HeadersFrame(headers, true));
138         }
139     }
140 
141     /**
142      * Encode from an {@link HttpObject} to an {@link Http2StreamFrame}. This method will
143      * be called for each written message that can be handled by this encoder.
144      *
145      * NOTE: 100-Continue responses that are NOT {@link FullHttpResponse} will be rejected.
146      *
147      * @param ctx           the {@link ChannelHandlerContext} which this handler belongs to
148      * @param obj           the {@link HttpObject} message to encode
149      * @param out           the {@link List} into which the encoded msg should be added
150      *                      needs to do some kind of aggregation
151      * @throws Exception    is thrown if an error occurs
152      */
153     @Override
154     protected void encode(ChannelHandlerContext ctx, HttpObject obj, List<Object> out) throws Exception {
155         // 1xx (excluding 101) is typically a FullHttpResponse, but the decoded
156         // Http2HeadersFrame should not be marked as endStream=true
157         if (obj instanceof HttpResponse) {
158             final HttpResponse res = (HttpResponse) obj;
159             final HttpResponseStatus status = res.status();
160             final int code = status.code();
161             final HttpStatusClass statusClass = status.codeClass();
162             // An informational response using a 1xx status code other than 101 is
163             // transmitted as a HEADERS frame
164             if (statusClass == HttpStatusClass.INFORMATIONAL && code != 101) {
165                 if (res instanceof FullHttpResponse) {
166                     final Http2Headers headers = toHttp2Headers(ctx, res);
167                     out.add(new DefaultHttp2HeadersFrame(headers, false));
168                     return;
169                 } else {
170                     throw new EncoderException(status + " must be a FullHttpResponse");
171                 }
172             }
173         }
174 
175         if (obj instanceof HttpMessage) {
176             Http2Headers headers = toHttp2Headers(ctx, (HttpMessage) obj);
177             boolean noMoreFrames = false;
178             if (obj instanceof FullHttpMessage) {
179                 FullHttpMessage full = (FullHttpMessage) obj;
180                 noMoreFrames = !full.content().isReadable() && full.trailingHeaders().isEmpty();
181             }
182 
183             out.add(new DefaultHttp2HeadersFrame(headers, noMoreFrames));
184         }
185 
186         if (obj instanceof LastHttpContent) {
187             LastHttpContent last = (LastHttpContent) obj;
188             encodeLastContent(last, out);
189         } else if (obj instanceof HttpContent) {
190             HttpContent cont = (HttpContent) obj;
191             out.add(new DefaultHttp2DataFrame(cont.content().retain(), false));
192         }
193     }
194 
195     private Http2Headers toHttp2Headers(final ChannelHandlerContext ctx, final HttpMessage msg) {
196         if (msg instanceof HttpRequest) {
197             msg.headers().set(
198                     HttpConversionUtil.ExtensionHeaderNames.SCHEME.text(),
199                     connectionScheme(ctx));
200         }
201 
202         return HttpConversionUtil.toHttp2Headers(msg, validateHeaders);
203     }
204 
205     private HttpMessage newMessage(final int id,
206                                    final Http2Headers headers) throws Http2Exception {
207         return isServer ?
208                 HttpConversionUtil.toHttpRequest(id, headers, validateHeaders) :
209                 HttpConversionUtil.toHttpResponse(id, headers, validateHeaders);
210     }
211 
212     private FullHttpMessage newFullMessage(final int id,
213                                            final Http2Headers headers,
214                                            final ByteBufAllocator alloc) throws Http2Exception {
215         return isServer ?
216                 HttpConversionUtil.toFullHttpRequest(id, headers, alloc, validateHeaders) :
217                 HttpConversionUtil.toFullHttpResponse(id, headers, alloc, validateHeaders);
218     }
219 
220     @Override
221     public void handlerAdded(final ChannelHandlerContext ctx) throws Exception {
222         super.handlerAdded(ctx);
223 
224         // this handler is typically used on an Http2StreamChannel. At this
225         // stage, ssl handshake should've been established. checking for the
226         // presence of SslHandler in the parent's channel pipeline to
227         // determine the HTTP scheme should suffice, even for the case where
228         // SniHandler is used.
229         final Attribute<HttpScheme> schemeAttribute = connectionSchemeAttribute(ctx);
230         if (schemeAttribute.get() == null) {
231             final HttpScheme scheme = isSsl(ctx) ? HttpScheme.HTTPS : HttpScheme.HTTP;
232             schemeAttribute.set(scheme);
233         }
234     }
235 
236     protected boolean isSsl(final ChannelHandlerContext ctx) {
237         final Channel connChannel = connectionChannel(ctx);
238         return null != connChannel.pipeline().get(SslHandler.class);
239     }
240 
241     private static HttpScheme connectionScheme(ChannelHandlerContext ctx) {
242         final HttpScheme scheme = connectionSchemeAttribute(ctx).get();
243         return scheme == null ? HttpScheme.HTTP : scheme;
244     }
245 
246     private static Attribute<HttpScheme> connectionSchemeAttribute(ChannelHandlerContext ctx) {
247         final Channel ch = connectionChannel(ctx);
248         return ch.attr(SCHEME_ATTR_KEY);
249     }
250 
251     private static Channel connectionChannel(ChannelHandlerContext ctx) {
252         final Channel ch = ctx.channel();
253         return ch instanceof Http2StreamChannel ? ch.parent() : ch;
254     }
255 
256     /**
257      *    An informational response using a 1xx status code other than 101 is
258      *    transmitted as a HEADERS frame
259      */
260     private static boolean isInformationalResponseHeaderFrame(CharSequence status) {
261         if (status.length() == 3) {
262             char char0 = status.charAt(0);
263             char char1 = status.charAt(1);
264             char char2 = status.charAt(2);
265             return char0 == '1'
266                 && char1 >= '0' && char1 <= '9'
267                 && char2 >= '0' && char2 <= '9' && char2 != '1';
268         }
269         return false;
270     }
271 
272     /*
273      * https://datatracker.ietf.org/doc/html/rfc9113#section-8.1.1
274      * '204' or '304' responses contain no content
275      */
276     private static boolean isContentAlwaysEmpty(CharSequence status) {
277         if (status.length() == 3) {
278             char char0 = status.charAt(0);
279             char char1 = status.charAt(1);
280             char char2 = status.charAt(2);
281             return (char0 == '2' || char0 == '3')
282                 && char1 == '0'
283                 && char2 == '4';
284         }
285         return false;
286     }
287 }