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