1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
53
54
55
56
57
58
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
96
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
143
144
145
146
147
148
149
150
151
152
153 @Override
154 protected void encode(ChannelHandlerContext ctx, HttpObject obj, List<Object> out) throws Exception {
155
156
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
163
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
225
226
227
228
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
258
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
274
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 }