1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package io.netty5.handler.codec.http2;
16
17 import io.netty5.buffer.api.Buffer;
18 import io.netty5.buffer.api.BufferAllocator;
19 import io.netty5.channel.ChannelHandlerContext;
20 import io.netty5.handler.codec.http.DefaultFullHttpRequest;
21 import io.netty5.handler.codec.http.FullHttpMessage;
22 import io.netty5.handler.codec.http.FullHttpRequest;
23 import io.netty5.handler.codec.http.FullHttpResponse;
24 import io.netty5.handler.codec.http.HttpHeaderNames;
25 import io.netty5.handler.codec.http.HttpStatusClass;
26 import io.netty5.handler.codec.http.HttpUtil;
27 import io.netty5.util.internal.UnstableApi;
28
29 import static io.netty5.handler.codec.http.HttpResponseStatus.OK;
30 import static io.netty5.handler.codec.http2.Http2Error.INTERNAL_ERROR;
31 import static io.netty5.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
32 import static io.netty5.handler.codec.http2.Http2Exception.connectionError;
33 import static io.netty5.util.internal.ObjectUtil.checkPositive;
34 import static java.util.Objects.requireNonNull;
35
36
37
38
39
40
41
42 @UnstableApi
43 public class InboundHttp2ToHttpAdapter extends Http2EventAdapter {
44 private static final ImmediateSendDetector DEFAULT_SEND_DETECTOR = new ImmediateSendDetector() {
45 @Override
46 public boolean mustSendImmediately(FullHttpMessage<?> msg) {
47 if (msg instanceof FullHttpResponse) {
48 return ((FullHttpResponse) msg).status().codeClass() == HttpStatusClass.INFORMATIONAL;
49 }
50 if (msg instanceof FullHttpRequest) {
51 return msg.headers().contains(HttpHeaderNames.EXPECT);
52 }
53 return false;
54 }
55
56 @Override
57 public FullHttpMessage<?> copyIfNeeded(BufferAllocator allocator, FullHttpMessage<?> msg) {
58 if (msg instanceof FullHttpRequest) {
59 final FullHttpRequest original = (FullHttpRequest) msg;
60 FullHttpRequest copy = new DefaultFullHttpRequest(original.protocolVersion(), original.method(),
61 original.uri(), allocator.allocate(0), original.headers(), original.trailingHeaders());
62 copy.headers().remove(HttpHeaderNames.EXPECT);
63 return copy;
64 }
65 return null;
66 }
67 };
68
69 private final int maxContentLength;
70 private final ImmediateSendDetector sendDetector;
71 private final Http2Connection.PropertyKey messageKey;
72 private final boolean propagateSettings;
73 protected final Http2Connection connection;
74 protected final boolean validateHttpHeaders;
75
76 protected InboundHttp2ToHttpAdapter(Http2Connection connection, int maxContentLength,
77 boolean validateHttpHeaders, boolean propagateSettings) {
78
79 requireNonNull(connection, "connection");
80 this.connection = connection;
81 this.maxContentLength = checkPositive(maxContentLength, "maxContentLength");
82 this.validateHttpHeaders = validateHttpHeaders;
83 this.propagateSettings = propagateSettings;
84 sendDetector = DEFAULT_SEND_DETECTOR;
85 messageKey = connection.newKey();
86 }
87
88
89
90
91
92
93 protected final void removeMessage(Http2Stream stream, boolean release) {
94 FullHttpMessage<?> msg = stream.removeProperty(messageKey);
95 if (release && msg != null) {
96 msg.close();
97 }
98 }
99
100
101
102
103
104
105 protected final FullHttpMessage<?> getMessage(Http2Stream stream) {
106 return stream.getProperty(messageKey);
107 }
108
109
110
111
112
113
114 protected final void putMessage(Http2Stream stream, FullHttpMessage<?> message) {
115 FullHttpMessage<?> previous = stream.setProperty(messageKey, message);
116 if (previous != message && previous != null) {
117 previous.close();
118 }
119 }
120
121 @Override
122 public void onStreamRemoved(Http2Stream stream) {
123 removeMessage(stream, true);
124 }
125
126
127
128
129
130
131
132
133
134 protected void fireChannelRead(ChannelHandlerContext ctx, FullHttpMessage<?> msg, boolean release,
135 Http2Stream stream) {
136 removeMessage(stream, release);
137 HttpUtil.setContentLength(msg, msg.payload().readableBytes());
138 ctx.fireChannelRead(msg);
139 }
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155 protected FullHttpMessage<?> newMessage(Http2Stream stream, Http2Headers headers, boolean validateHttpHeaders,
156 BufferAllocator alloc) throws Http2Exception {
157 return connection.isServer() ? HttpConversionUtil.toFullHttpRequest(stream.id(), headers, alloc,
158 validateHttpHeaders) : HttpConversionUtil.toFullHttpResponse(stream.id(), headers, alloc,
159 validateHttpHeaders);
160 }
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186 protected FullHttpMessage<?> processHeadersBegin(ChannelHandlerContext ctx, Http2Stream stream,
187 Http2Headers headers, boolean endOfStream, boolean allowAppend,
188 boolean appendToTrailer)
189 throws Http2Exception {
190 FullHttpMessage<?> msg = getMessage(stream);
191 boolean release = true;
192 if (msg == null) {
193 msg = newMessage(stream, headers, validateHttpHeaders, ctx.bufferAllocator());
194 } else if (allowAppend) {
195 release = false;
196 HttpConversionUtil.addHttp2ToHttpHeaders(stream.id(), headers, msg, appendToTrailer);
197 } else {
198 release = false;
199 msg = null;
200 }
201
202 if (sendDetector.mustSendImmediately(msg)) {
203
204
205 final FullHttpMessage<?> copy = endOfStream ? null : sendDetector.copyIfNeeded(ctx.bufferAllocator(), msg);
206 fireChannelRead(ctx, msg, release, stream);
207 return copy;
208 }
209
210 return msg;
211 }
212
213
214
215
216
217
218
219
220
221
222 private void processHeadersEnd(ChannelHandlerContext ctx, Http2Stream stream, FullHttpMessage<?> msg,
223 boolean endOfStream) {
224 if (endOfStream) {
225
226 fireChannelRead(ctx, msg, getMessage(stream) != msg, stream);
227 } else {
228 putMessage(stream, msg);
229 }
230 }
231
232 @Override
233 public int onDataRead(ChannelHandlerContext ctx, int streamId, Buffer data, int padding, boolean endOfStream)
234 throws Http2Exception {
235 Http2Stream stream = connection.stream(streamId);
236 FullHttpMessage<?> msg = getMessage(stream);
237 if (msg == null) {
238 throw connectionError(PROTOCOL_ERROR, "Data Frame received for unknown stream id %d", streamId);
239 }
240
241 Buffer content = msg.payload();
242 final int dataReadableBytes = data.readableBytes();
243 if (content.readableBytes() > maxContentLength - dataReadableBytes) {
244 throw connectionError(INTERNAL_ERROR,
245 "Content length exceeded max of %d for stream id %d", maxContentLength, streamId);
246 }
247
248 content.ensureWritable(dataReadableBytes);
249 content.writeBytes(data);
250
251 if (endOfStream) {
252 fireChannelRead(ctx, msg, false, stream);
253 }
254
255
256 return dataReadableBytes + padding;
257 }
258
259 @Override
260 public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int padding,
261 boolean endOfStream) throws Http2Exception {
262 Http2Stream stream = connection.stream(streamId);
263 FullHttpMessage<?> msg = processHeadersBegin(ctx, stream, headers, endOfStream, true, true);
264 if (msg != null) {
265 processHeadersEnd(ctx, stream, msg, endOfStream);
266 }
267 }
268
269 @Override
270 public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int streamDependency,
271 short weight, boolean exclusive, int padding, boolean endOfStream)
272 throws Http2Exception {
273 Http2Stream stream = connection.stream(streamId);
274 FullHttpMessage<?> msg = processHeadersBegin(ctx, stream, headers, endOfStream, true, true);
275 if (msg != null) {
276
277
278 if (streamDependency != Http2CodecUtil.CONNECTION_STREAM_ID) {
279 msg.headers().setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_DEPENDENCY_ID.text(),
280 streamDependency);
281 }
282 msg.headers().setShort(HttpConversionUtil.ExtensionHeaderNames.STREAM_WEIGHT.text(), weight);
283
284 processHeadersEnd(ctx, stream, msg, endOfStream);
285 }
286 }
287
288 @Override
289 public void onRstStreamRead(ChannelHandlerContext ctx, int streamId, long errorCode) throws Http2Exception {
290 Http2Stream stream = connection.stream(streamId);
291 FullHttpMessage<?> msg = getMessage(stream);
292 if (msg != null) {
293 onRstStreamRead(stream, msg);
294 }
295 ctx.fireChannelExceptionCaught(Http2Exception.streamError(streamId, Http2Error.valueOf(errorCode),
296 "HTTP/2 to HTTP layer caught stream reset"));
297 }
298
299 @Override
300 public void onPushPromiseRead(ChannelHandlerContext ctx, int streamId, int promisedStreamId,
301 Http2Headers headers, int padding) throws Http2Exception {
302
303 Http2Stream promisedStream = connection.stream(promisedStreamId);
304 if (headers.status() == null) {
305
306
307
308
309
310 headers.status(OK.codeAsText());
311 }
312 FullHttpMessage<?> msg = processHeadersBegin(ctx, promisedStream, headers, false, false, false);
313 if (msg == null) {
314 throw connectionError(PROTOCOL_ERROR, "Push Promise Frame received for pre-existing stream id %d",
315 promisedStreamId);
316 }
317
318 msg.headers().setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_PROMISE_ID.text(), streamId);
319 msg.headers().setShort(HttpConversionUtil.ExtensionHeaderNames.STREAM_WEIGHT.text(),
320 Http2CodecUtil.DEFAULT_PRIORITY_WEIGHT);
321
322 processHeadersEnd(ctx, promisedStream, msg, false);
323 }
324
325 @Override
326 public void onSettingsRead(ChannelHandlerContext ctx, Http2Settings settings) throws Http2Exception {
327 if (propagateSettings) {
328
329 ctx.fireChannelRead(settings);
330 }
331 }
332
333
334
335
336 protected void onRstStreamRead(Http2Stream stream, FullHttpMessage<?> msg) {
337 removeMessage(stream, true);
338 }
339
340
341
342
343
344 private interface ImmediateSendDetector {
345
346
347
348
349
350
351
352 boolean mustSendImmediately(FullHttpMessage<?> msg);
353
354
355
356
357
358
359
360
361
362
363
364
365 FullHttpMessage<?> copyIfNeeded(BufferAllocator allocator, FullHttpMessage<?> msg);
366 }
367 }