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