1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package io.netty.handler.codec.http2;
16
17 import io.netty.buffer.ByteBuf;
18 import io.netty.channel.ChannelFuture;
19 import io.netty.channel.ChannelFutureListener;
20 import io.netty.channel.ChannelHandlerContext;
21 import io.netty.channel.ChannelPromise;
22 import io.netty.channel.CoalescingBufferQueue;
23 import io.netty.handler.codec.http.HttpStatusClass;
24 import io.netty.handler.codec.http2.Http2CodecUtil.SimpleChannelPromiseAggregator;
25 import io.netty.util.ReferenceCountUtil;
26
27 import java.util.ArrayDeque;
28 import java.util.Queue;
29
30 import static io.netty.handler.codec.http.HttpStatusClass.INFORMATIONAL;
31 import static io.netty.handler.codec.http2.Http2Error.INTERNAL_ERROR;
32 import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
33 import static io.netty.handler.codec.http2.Http2Exception.connectionError;
34 import static io.netty.handler.codec.http2.Http2Exception.streamError;
35 import static io.netty.util.internal.ObjectUtil.checkNotNull;
36 import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero;
37 import static java.lang.Integer.MAX_VALUE;
38 import static java.lang.Math.min;
39
40
41
42
43 public class DefaultHttp2ConnectionEncoder implements Http2ConnectionEncoder, Http2SettingsReceivedConsumer {
44 private final Http2FrameWriter frameWriter;
45 private final Http2Connection connection;
46 private Http2LifecycleManager lifecycleManager;
47
48
49 private final Queue<Http2Settings> outstandingLocalSettingsQueue = new ArrayDeque<Http2Settings>(4);
50 private Queue<Http2Settings> outstandingRemoteSettingsQueue;
51
52 public DefaultHttp2ConnectionEncoder(Http2Connection connection, Http2FrameWriter frameWriter) {
53 this.connection = checkNotNull(connection, "connection");
54 this.frameWriter = checkNotNull(frameWriter, "frameWriter");
55 if (connection.remote().flowController() == null) {
56 connection.remote().flowController(new DefaultHttp2RemoteFlowController(connection));
57 }
58 }
59
60 @Override
61 public void lifecycleManager(Http2LifecycleManager lifecycleManager) {
62 this.lifecycleManager = checkNotNull(lifecycleManager, "lifecycleManager");
63 }
64
65 @Override
66 public Http2FrameWriter frameWriter() {
67 return frameWriter;
68 }
69
70 @Override
71 public Http2Connection connection() {
72 return connection;
73 }
74
75 @Override
76 public final Http2RemoteFlowController flowController() {
77 return connection().remote().flowController();
78 }
79
80 @Override
81 public void remoteSettings(Http2Settings settings) throws Http2Exception {
82 Boolean pushEnabled = settings.pushEnabled();
83 Http2FrameWriter.Configuration config = configuration();
84 Http2HeadersEncoder.Configuration outboundHeaderConfig = config.headersConfiguration();
85 Http2FrameSizePolicy outboundFrameSizePolicy = config.frameSizePolicy();
86 if (pushEnabled != null) {
87 if (!connection.isServer() && pushEnabled) {
88 throw connectionError(PROTOCOL_ERROR,
89 "Client received a value of ENABLE_PUSH specified to other than 0");
90 }
91 connection.remote().allowPushTo(pushEnabled);
92 }
93
94 Long maxConcurrentStreams = settings.maxConcurrentStreams();
95 if (maxConcurrentStreams != null) {
96 connection.local().maxActiveStreams((int) min(maxConcurrentStreams, MAX_VALUE));
97 }
98
99 Long headerTableSize = settings.headerTableSize();
100 if (headerTableSize != null) {
101 outboundHeaderConfig.maxHeaderTableSize(headerTableSize);
102 }
103
104 Long maxHeaderListSize = settings.maxHeaderListSize();
105 if (maxHeaderListSize != null && !connection.isServer()) {
106
107
108 outboundHeaderConfig.maxHeaderListSize(maxHeaderListSize);
109 }
110
111 Integer maxFrameSize = settings.maxFrameSize();
112 if (maxFrameSize != null) {
113 outboundFrameSizePolicy.maxFrameSize(maxFrameSize);
114 }
115
116 Integer initialWindowSize = settings.initialWindowSize();
117 if (initialWindowSize != null) {
118 flowController().initialWindowSize(initialWindowSize);
119 }
120 }
121
122 @Override
123 public ChannelFuture writeData(final ChannelHandlerContext ctx, final int streamId, ByteBuf data, int padding,
124 final boolean endOfStream, ChannelPromise promise) {
125 promise = promise.unvoid();
126 final Http2Stream stream;
127 try {
128 stream = requireStream(streamId);
129
130
131 switch (stream.state()) {
132 case OPEN:
133 case HALF_CLOSED_REMOTE:
134
135 break;
136 default:
137 throw new IllegalStateException("Stream " + stream.id() + " in unexpected state " + stream.state());
138 }
139 } catch (Throwable e) {
140 data.release();
141 return promise.setFailure(e);
142 }
143
144
145 flowController().addFlowControlled(stream,
146 new FlowControlledData(stream, data, padding, endOfStream, promise));
147 return promise;
148 }
149
150 @Override
151 public ChannelFuture writeHeaders(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int padding,
152 boolean endStream, ChannelPromise promise) {
153 return writeHeaders0(ctx, streamId, headers, false, 0, (short) 0, false, padding, endStream, promise);
154 }
155
156 private static boolean validateHeadersSentState(Http2Stream stream, Http2Headers headers, boolean isServer,
157 boolean endOfStream) {
158 boolean isInformational = isServer && HttpStatusClass.valueOf(headers.status()) == INFORMATIONAL;
159 if ((isInformational || !endOfStream) && stream.isHeadersSent() || stream.isTrailersSent()) {
160 throw new IllegalStateException("Stream " + stream.id() + " sent too many headers EOS: " + endOfStream);
161 }
162 return isInformational;
163 }
164
165 @Override
166 public ChannelFuture writeHeaders(final ChannelHandlerContext ctx, final int streamId,
167 final Http2Headers headers, final int streamDependency, final short weight,
168 final boolean exclusive, final int padding, final boolean endOfStream, ChannelPromise promise) {
169 return writeHeaders0(ctx, streamId, headers, true, streamDependency,
170 weight, exclusive, padding, endOfStream, promise);
171 }
172
173
174
175
176
177 private static ChannelFuture sendHeaders(Http2FrameWriter frameWriter, ChannelHandlerContext ctx, int streamId,
178 Http2Headers headers, final boolean hasPriority,
179 int streamDependency, final short weight,
180 boolean exclusive, final int padding,
181 boolean endOfStream, ChannelPromise promise) {
182 if (hasPriority) {
183 return frameWriter.writeHeaders(ctx, streamId, headers, streamDependency,
184 weight, exclusive, padding, endOfStream, promise);
185 }
186 return frameWriter.writeHeaders(ctx, streamId, headers, padding, endOfStream, promise);
187 }
188
189 private ChannelFuture writeHeaders0(final ChannelHandlerContext ctx, final int streamId,
190 final Http2Headers headers, final boolean hasPriority,
191 final int streamDependency, final short weight,
192 final boolean exclusive, final int padding,
193 final boolean endOfStream, ChannelPromise promise) {
194 try {
195 Http2Stream stream = connection.stream(streamId);
196 if (stream == null) {
197 try {
198
199
200
201
202
203 stream = connection.local().createStream(streamId, false);
204 } catch (Http2Exception cause) {
205 if (connection.remote().mayHaveCreatedStream(streamId)) {
206 promise.tryFailure(new IllegalStateException("Stream no longer exists: " + streamId, cause));
207 return promise;
208 }
209 throw cause;
210 }
211 } else {
212 switch (stream.state()) {
213 case RESERVED_LOCAL:
214 stream.open(endOfStream);
215 break;
216 case OPEN:
217 case HALF_CLOSED_REMOTE:
218
219 break;
220 default:
221 throw new IllegalStateException("Stream " + stream.id() + " in unexpected state " +
222 stream.state());
223 }
224 }
225
226
227
228 Http2RemoteFlowController flowController = flowController();
229 if (!endOfStream || !flowController.hasFlowControlled(stream)) {
230
231
232 promise = promise.unvoid();
233 boolean isInformational = validateHeadersSentState(stream, headers, connection.isServer(), endOfStream);
234
235 ChannelFuture future = sendHeaders(frameWriter, ctx, streamId, headers, hasPriority, streamDependency,
236 weight, exclusive, padding, endOfStream, promise);
237
238
239 Throwable failureCause = future.cause();
240 if (failureCause == null) {
241
242
243
244
245
246 stream.headersSent(isInformational);
247
248 if (!future.isSuccess()) {
249
250 notifyLifecycleManagerOnError(future, ctx);
251 }
252 } else {
253 lifecycleManager.onError(ctx, true, failureCause);
254 }
255
256 if (endOfStream) {
257
258
259
260 lifecycleManager.closeStreamLocal(stream, future);
261 }
262
263 return future;
264 } else {
265
266 flowController.addFlowControlled(stream,
267 new FlowControlledHeaders(stream, headers, hasPriority, streamDependency,
268 weight, exclusive, padding, true, promise));
269 return promise;
270 }
271 } catch (Throwable t) {
272 lifecycleManager.onError(ctx, true, t);
273 promise.tryFailure(t);
274 return promise;
275 }
276 }
277
278 @Override
279 public ChannelFuture writePriority(ChannelHandlerContext ctx, int streamId, int streamDependency, short weight,
280 boolean exclusive, ChannelPromise promise) {
281 return frameWriter.writePriority(ctx, streamId, streamDependency, weight, exclusive, promise);
282 }
283
284 @Override
285 public ChannelFuture writeRstStream(ChannelHandlerContext ctx, int streamId, long errorCode,
286 ChannelPromise promise) {
287
288 return lifecycleManager.resetStream(ctx, streamId, errorCode, promise);
289 }
290
291 @Override
292 public ChannelFuture writeSettings(ChannelHandlerContext ctx, Http2Settings settings,
293 ChannelPromise promise) {
294 outstandingLocalSettingsQueue.add(settings);
295 try {
296 Boolean pushEnabled = settings.pushEnabled();
297 if (pushEnabled != null && connection.isServer()) {
298 throw connectionError(PROTOCOL_ERROR, "Server sending SETTINGS frame with ENABLE_PUSH specified");
299 }
300 } catch (Throwable e) {
301 return promise.setFailure(e);
302 }
303
304 return frameWriter.writeSettings(ctx, settings, promise);
305 }
306
307 @Override
308 public ChannelFuture writeSettingsAck(ChannelHandlerContext ctx, ChannelPromise promise) {
309 if (outstandingRemoteSettingsQueue == null) {
310 return frameWriter.writeSettingsAck(ctx, promise);
311 }
312 Http2Settings settings = outstandingRemoteSettingsQueue.poll();
313 if (settings == null) {
314 return promise.setFailure(new Http2Exception(INTERNAL_ERROR, "attempted to write a SETTINGS ACK with no " +
315 " pending SETTINGS"));
316 }
317 SimpleChannelPromiseAggregator aggregator = new SimpleChannelPromiseAggregator(promise, ctx.channel(),
318 ctx.executor());
319
320
321
322 frameWriter.writeSettingsAck(ctx, aggregator.newPromise());
323
324
325
326 ChannelPromise applySettingsPromise = aggregator.newPromise();
327 try {
328 remoteSettings(settings);
329 applySettingsPromise.setSuccess();
330 } catch (Throwable e) {
331 applySettingsPromise.setFailure(e);
332 lifecycleManager.onError(ctx, true, e);
333 }
334 return aggregator.doneAllocatingPromises();
335 }
336
337 @Override
338 public ChannelFuture writePing(ChannelHandlerContext ctx, boolean ack, long data, ChannelPromise promise) {
339 return frameWriter.writePing(ctx, ack, data, promise);
340 }
341
342 @Override
343 public ChannelFuture writePushPromise(ChannelHandlerContext ctx, int streamId, int promisedStreamId,
344 Http2Headers headers, int padding, ChannelPromise promise) {
345 try {
346 if (connection.goAwayReceived()) {
347 throw connectionError(PROTOCOL_ERROR, "Sending PUSH_PROMISE after GO_AWAY received.");
348 }
349
350 Http2Stream stream = requireStream(streamId);
351
352 connection.local().reservePushStream(promisedStreamId, stream);
353
354 promise = promise.unvoid();
355 ChannelFuture future = frameWriter.writePushPromise(ctx, streamId, promisedStreamId, headers, padding,
356 promise);
357
358 Throwable failureCause = future.cause();
359 if (failureCause == null) {
360
361
362 stream.pushPromiseSent();
363
364 if (!future.isSuccess()) {
365
366 notifyLifecycleManagerOnError(future, ctx);
367 }
368 } else {
369 lifecycleManager.onError(ctx, true, failureCause);
370 }
371 return future;
372 } catch (Throwable t) {
373 lifecycleManager.onError(ctx, true, t);
374 promise.tryFailure(t);
375 return promise;
376 }
377 }
378
379 @Override
380 public ChannelFuture writeGoAway(ChannelHandlerContext ctx, int lastStreamId, long errorCode, ByteBuf debugData,
381 ChannelPromise promise) {
382 return lifecycleManager.goAway(ctx, lastStreamId, errorCode, debugData, promise);
383 }
384
385 @Override
386 public ChannelFuture writeWindowUpdate(ChannelHandlerContext ctx, int streamId, int windowSizeIncrement,
387 ChannelPromise promise) {
388 return promise.setFailure(new UnsupportedOperationException("Use the Http2[Inbound|Outbound]FlowController" +
389 " objects to control window sizes"));
390 }
391
392 @Override
393 public ChannelFuture writeFrame(ChannelHandlerContext ctx, byte frameType, int streamId, Http2Flags flags,
394 ByteBuf payload, ChannelPromise promise) {
395 return frameWriter.writeFrame(ctx, frameType, streamId, flags, payload, promise);
396 }
397
398 @Override
399 public void close() {
400 frameWriter.close();
401 }
402
403 @Override
404 public Http2Settings pollSentSettings() {
405 return outstandingLocalSettingsQueue.poll();
406 }
407
408 @Override
409 public Configuration configuration() {
410 return frameWriter.configuration();
411 }
412
413 private Http2Stream requireStream(int streamId) {
414 Http2Stream stream = connection.stream(streamId);
415 if (stream == null) {
416 final String message;
417 if (connection.streamMayHaveExisted(streamId)) {
418 message = "Stream no longer exists: " + streamId;
419 } else {
420 message = "Stream does not exist: " + streamId;
421 }
422 throw new IllegalArgumentException(message);
423 }
424 return stream;
425 }
426
427 @Override
428 public void consumeReceivedSettings(Http2Settings settings) {
429 if (outstandingRemoteSettingsQueue == null) {
430 outstandingRemoteSettingsQueue = new ArrayDeque<Http2Settings>(2);
431 }
432 outstandingRemoteSettingsQueue.add(settings);
433 }
434
435
436
437
438
439
440
441
442
443
444 private final class FlowControlledData extends FlowControlledBase {
445 private final CoalescingBufferQueue queue;
446 private int dataSize;
447
448 FlowControlledData(Http2Stream stream, ByteBuf buf, int padding, boolean endOfStream,
449 ChannelPromise promise) {
450 super(stream, padding, endOfStream, promise);
451 queue = new CoalescingBufferQueue(promise.channel());
452 queue.add(buf, promise);
453 dataSize = queue.readableBytes();
454 }
455
456 @Override
457 public int size() {
458 return dataSize + padding;
459 }
460
461 @Override
462 public void error(ChannelHandlerContext ctx, Throwable cause) {
463 queue.releaseAndFailAll(cause);
464
465
466
467
468
469 lifecycleManager.onError(ctx, true, cause);
470 }
471
472 @Override
473 public void write(ChannelHandlerContext ctx, int allowedBytes) {
474 int queuedData = queue.readableBytes();
475 if (!endOfStream) {
476 if (queuedData == 0) {
477 if (queue.isEmpty()) {
478
479
480
481
482
483
484 padding = dataSize = 0;
485 } else {
486
487
488
489 ChannelPromise writePromise = ctx.newPromise().addListener(this);
490 ctx.write(queue.remove(0, writePromise), writePromise);
491 }
492 return;
493 }
494
495 if (allowedBytes == 0) {
496 return;
497 }
498 }
499
500
501 int writableData = min(queuedData, allowedBytes);
502 ChannelPromise writePromise = ctx.newPromise().addListener(this);
503 ByteBuf toWrite = queue.remove(writableData, writePromise);
504 dataSize = queue.readableBytes();
505
506
507
508 int producedBytes = toWrite.readableBytes();
509 if (producedBytes < writableData) {
510 ReferenceCountUtil.safeRelease(toWrite);
511
512
513 padding = dataSize = 0;
514 writePromise.tryFailure(streamError(stream.id(), INTERNAL_ERROR,
515 "Stream %d flow-controlled queue produced %d bytes but reported %d",
516 stream.id(), producedBytes, writableData));
517 return;
518 }
519
520
521 int writablePadding = min(allowedBytes - writableData, padding);
522 padding -= writablePadding;
523
524
525 frameWriter().writeData(ctx, stream.id(), toWrite, writablePadding,
526 endOfStream && size() == 0, writePromise);
527 }
528
529 @Override
530 public boolean merge(ChannelHandlerContext ctx, Http2RemoteFlowController.FlowControlled next) {
531 FlowControlledData nextData;
532 if (FlowControlledData.class != next.getClass() ||
533 MAX_VALUE - (nextData = (FlowControlledData) next).size() < size()) {
534 return false;
535 }
536 nextData.queue.copyTo(queue);
537 dataSize = queue.readableBytes();
538
539 padding = Math.max(padding, nextData.padding);
540 endOfStream = nextData.endOfStream;
541 return true;
542 }
543 }
544
545 private void notifyLifecycleManagerOnError(ChannelFuture future, final ChannelHandlerContext ctx) {
546 future.addListener(future1 -> {
547 Throwable cause = future1.cause();
548 if (cause != null) {
549 lifecycleManager.onError(ctx, true, cause);
550 }
551 });
552 }
553
554
555
556
557
558
559 private final class FlowControlledHeaders extends FlowControlledBase {
560 private final Http2Headers headers;
561 private final boolean hasPriority;
562 private final int streamDependency;
563 private final short weight;
564 private final boolean exclusive;
565
566 FlowControlledHeaders(Http2Stream stream, Http2Headers headers, boolean hasPriority,
567 int streamDependency, short weight, boolean exclusive,
568 int padding, boolean endOfStream, ChannelPromise promise) {
569 super(stream, padding, endOfStream, promise.unvoid());
570 this.headers = headers;
571 this.hasPriority = hasPriority;
572 this.streamDependency = streamDependency;
573 this.weight = weight;
574 this.exclusive = exclusive;
575 }
576
577 @Override
578 public int size() {
579 return 0;
580 }
581
582 @Override
583 public void error(ChannelHandlerContext ctx, Throwable cause) {
584 if (ctx != null) {
585 lifecycleManager.onError(ctx, true, cause);
586 }
587 promise.tryFailure(cause);
588 }
589
590 @Override
591 public void write(ChannelHandlerContext ctx, int allowedBytes) {
592 boolean isInformational = validateHeadersSentState(stream, headers, connection.isServer(), endOfStream);
593
594
595 promise.addListener(this);
596
597 ChannelFuture f = sendHeaders(frameWriter, ctx, stream.id(), headers, hasPriority, streamDependency,
598 weight, exclusive, padding, endOfStream, promise);
599
600 Throwable failureCause = f.cause();
601 if (failureCause == null) {
602
603
604 stream.headersSent(isInformational);
605 }
606 }
607
608 @Override
609 public boolean merge(ChannelHandlerContext ctx, Http2RemoteFlowController.FlowControlled next) {
610 return false;
611 }
612 }
613
614
615
616
617 public abstract class FlowControlledBase implements Http2RemoteFlowController.FlowControlled,
618 ChannelFutureListener {
619 protected final Http2Stream stream;
620 protected ChannelPromise promise;
621 protected boolean endOfStream;
622 protected int padding;
623
624 FlowControlledBase(final Http2Stream stream, int padding, boolean endOfStream,
625 final ChannelPromise promise) {
626 checkPositiveOrZero(padding, "padding");
627 this.padding = padding;
628 this.endOfStream = endOfStream;
629 this.stream = stream;
630 this.promise = promise;
631 }
632
633 @Override
634 public void writeComplete() {
635 if (endOfStream) {
636 lifecycleManager.closeStreamLocal(stream, promise);
637 }
638 }
639
640 @Override
641 public void operationComplete(ChannelFuture future) throws Exception {
642 if (!future.isSuccess()) {
643 error(flowController().channelHandlerContext(), future.cause());
644 }
645 }
646 }
647 }