View Javadoc
1   /*
2    * Copyright 2014 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License, version 2.0 (the
5    * "License"); you may not use this file except in compliance with the License. You may obtain a
6    * copy of the License at:
7    *
8    * https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software distributed under the License
11   * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12   * or implied. See the License for the specific language governing permissions and limitations under
13   * the License.
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   * Default implementation of {@link Http2ConnectionEncoder}.
43   */
44  public class DefaultHttp2ConnectionEncoder implements Http2ConnectionEncoder, Http2SettingsReceivedConsumer {
45      // Bound the number of received but not yet ACKed SETTINGS frames we buffer for a connection. Without this a
46      // peer could send an unbounded number of SETTINGS frames (which are not subject to flow-control) while the
47      // application is slow (or never) calls writeSettingsAck, exhausting the heap.
48      static final int MAX_OUTSTANDING_REMOTE_SETTINGS = 128;
49      private final Http2FrameWriter frameWriter;
50      private final Http2Connection connection;
51      private Http2LifecycleManager lifecycleManager;
52      // We prefer ArrayDeque to LinkedList because later will produce more GC.
53      // This initial capacity is plenty for SETTINGS traffic.
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             // Servers ignore the MAX_HEADER_LIST_SIZE setting from clients.
113             // It's advisory in spec (RFC 9113 §6.5.2) and best praxis is to ignore it.
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             // Verify that the stream is in the appropriate state for sending DATA frames.
137             switch (stream.state()) {
138                 case OPEN:
139                 case HALF_CLOSED_REMOTE:
140                     // Allowed sending DATA frames in these states.
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         // Hand control of the frame to the flow controller.
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      * Write headers via {@link Http2FrameWriter}. If {@code hasPriority} is {@code false} it will ignore the
181      * {@code streamDependency}, {@code weight} and {@code exclusive} parameters.
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                     // We don't create the stream in a `halfClosed` state because if this is an initial
205                     // HEADERS frame we don't want the connection state to signify that the HEADERS have
206                     // been sent until after they have been encoded and placed in the outbound buffer.
207                     // Therefore, we let the `LifeCycleManager` will take care of transitioning the state
208                     // as appropriate.
209                     stream = connection.local().createStream(streamId, /*endOfStream*/ 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                         // Allowed sending headers in these states.
225                         break;
226                     default:
227                         throw new IllegalStateException("Stream " + stream.id() + " in unexpected state " +
228                                                         stream.state());
229                 }
230             }
231 
232             // Trailing headers must go through flow control if there are other frames queued in flow control
233             // for this stream.
234             Http2RemoteFlowController flowController = flowController();
235             if (!endOfStream || !flowController.hasFlowControlled(stream)) {
236                 // The behavior here should mirror that in FlowControlledHeaders
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                 // Writing headers may fail during the encode state if they violate HPACK limits.
245                 Throwable failureCause = future.cause();
246                 if (failureCause == null) {
247                     // Synchronously set the headersSent flag to ensure that we do not subsequently write
248                     // other headers containing pseudo-header fields.
249                     //
250                     // This just sets internal stream state which is used elsewhere in the codec and doesn't
251                     // necessarily mean the write will complete successfully.
252                     stream.headersSent(isInformational);
253 
254                     if (!future.isSuccess()) {
255                         // Either the future is not done or failed in the meantime.
256                         notifyLifecycleManagerOnError(future, ctx);
257                     }
258                 } else {
259                     lifecycleManager.onError(ctx, true, failureCause);
260                 }
261 
262                 if (endOfStream) {
263                     // Must handle calling onError before calling closeStreamLocal, otherwise the error handler will
264                     // incorrectly think the stream no longer exists and so may not send RST_STREAM or perform similar
265                     // appropriate action.
266                     lifecycleManager.closeStreamLocal(stream, future);
267                 }
268 
269                 return future;
270             } else {
271                 // Pass headers to the flow-controller so it can maintain their sequence relative to DATA frames.
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         // Delegate to the lifecycle manager for proper updating of connection state.
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         // Acknowledge receipt of the settings. We should do this before we process the settings to ensure our
334         // remote peer applies these settings before any subsequent frames that we may send which depend upon
335         // these new settings. See https://github.com/netty/netty/issues/6520.
336         frameWriter.writeSettingsAck(ctx, aggregator.newPromise());
337 
338         // We create a "new promise" to make sure that status from both the write and the application are taken into
339         // account independently.
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             // Reserve the promised stream.
366             connection.local().reservePushStream(promisedStreamId, stream);
367 
368             promise = promise.unvoid();
369             ChannelFuture future = frameWriter.writePushPromise(ctx, streamId, promisedStreamId, headers, padding,
370                                                                 promise);
371             // Writing headers may fail during the encode state if they violate HPACK limits.
372             Throwable failureCause = future.cause();
373             if (failureCause == null) {
374                 // This just sets internal stream state which is used elsewhere in the codec and doesn't
375                 // necessarily mean the write will complete successfully.
376                 stream.pushPromiseSent();
377 
378                 if (!future.isSuccess()) {
379                     // Either the future is not done or failed in the meantime.
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             // The peer is sending SETTINGS frames faster than we (the application) are acknowledging them via
464             // writeSettingsAck(...). SETTINGS frames are not subject to flow-control, so without this cap the
465             // queue could grow without bound and exhaust the heap. Treat this as a connection error.
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      * Wrap a DATA frame so it can be written subject to flow-control. Note that this implementation assumes it
481      * only writes padding once for the entire payload as opposed to writing it once per-frame. This makes the
482      * {@link #size} calculation deterministic thereby greatly simplifying the implementation.
483      * <p>
484      * If frame-splitting is required to fit within max-frame-size and flow-control constraints we ensure that
485      * the passed promise is not completed until last frame write.
486      * </p>
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             // Don't update dataSize because we need to ensure the size() method returns a consistent size even after
509             // error so we don't invalidate flow control when returning bytes to flow control.
510             //
511             // That said we will set dataSize and padding to 0 in the write(...) method if we cleared the queue
512             // because of an error.
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                         // When the queue is empty it means we did clear it because of an error(...) call
523                         // (as otherwise we will have at least 1 entry in there), which will happen either when called
524                         // explicit or when the write itself fails. In this case just set dataSize and padding to 0
525                         // which will signal back that the whole frame was consumed.
526                         //
527                         // See https://github.com/netty/netty/issues/8707.
528                         padding = dataSize = 0;
529                     } else {
530                         // There's no need to write any data frames because there are only empty data frames in the
531                         // queue and it is not end of stream yet. Just complete their promises by getting the buffer
532                         // corresponding to 0 bytes and writing it to the channel (to preserve notification order).
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             // Determine how much data to write.
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             // The queue reported writableData readable bytes but produced fewer: a queued buffer was released or
551             // consumed while still referenced by the queue, so the stream's data is corrupted. Fail the stream.
552             int producedBytes = toWrite.readableBytes();
553             if (producedBytes < writableData) {
554                 ReferenceCountUtil.safeRelease(toWrite);
555                 // Set dataSize and padding to 0 to signal that the whole frame was consumed, so it is removed and
556                 // its bytes are returned to flow control (matching the error path above).
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             // Determine how much padding to write.
565             int writablePadding = min(allowedBytes - writableData, padding);
566             padding -= writablePadding;
567 
568             // Write the frame(s).
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             // Given that we're merging data into a frame it doesn't really make sense to accumulate padding.
583             padding = Math.max(padding, nextData.padding);
584             endOfStream = nextData.endOfStream;
585             // The promise of the first frame may be completed long before the merged data is written in full.
586             // Use the promise of the last frame, so that the stream is only closed once all of its data was
587             // written. Otherwise a graceful shutdown could close the connection before the rest is flushed.
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      * Wrap headers so they can be written subject to flow-control. While headers do not have cost against the
604      * flow-control window their order with respect to other frames must be maintained, hence if a DATA frame is
605      * blocked on flow-control a HEADER frame must wait until this frame has been written.
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             // The code is currently requiring adding this listener before writing, in order to call onError() before
642             // closeStreamLocal().
643             promise.addListener(this);
644 
645             ChannelFuture f = sendHeaders(frameWriter, ctx, stream.id(), headers, hasPriority, streamDependency,
646                     weight, exclusive, padding, endOfStream, promise);
647             // Writing headers may fail during the encode state if they violate HPACK limits.
648             Throwable failureCause = f.cause();
649             if (failureCause == null) {
650                 // This just sets internal stream state which is used elsewhere in the codec and doesn't
651                 // necessarily mean the write will complete successfully.
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      * Common base type for payloads to deliver via flow-control.
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 }