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.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   * Default implementation of {@link Http2ConnectionEncoder}.
42   */
43  public class DefaultHttp2ConnectionEncoder implements Http2ConnectionEncoder, Http2SettingsReceivedConsumer {
44      private final Http2FrameWriter frameWriter;
45      private final Http2Connection connection;
46      private Http2LifecycleManager lifecycleManager;
47      // We prefer ArrayDeque to LinkedList because later will produce more GC.
48      // This initial capacity is plenty for SETTINGS traffic.
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             // Servers ignore the MAX_HEADER_LIST_SIZE setting from clients.
108             // It's advisory in spec (RFC 9113 ยง6.5.2) and best praxis is to ignore it.
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             // Verify that the stream is in the appropriate state for sending DATA frames.
132             switch (stream.state()) {
133                 case OPEN:
134                 case HALF_CLOSED_REMOTE:
135                     // Allowed sending DATA frames in these states.
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         // Hand control of the frame to the flow controller.
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      * Write headers via {@link Http2FrameWriter}. If {@code hasPriority} is {@code false} it will ignore the
176      * {@code streamDependency}, {@code weight} and {@code exclusive} parameters.
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                     // We don't create the stream in a `halfClosed` state because if this is an initial
200                     // HEADERS frame we don't want the connection state to signify that the HEADERS have
201                     // been sent until after they have been encoded and placed in the outbound buffer.
202                     // Therefore, we let the `LifeCycleManager` will take care of transitioning the state
203                     // as appropriate.
204                     stream = connection.local().createStream(streamId, /*endOfStream*/ 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                         // Allowed sending headers in these states.
220                         break;
221                     default:
222                         throw new IllegalStateException("Stream " + stream.id() + " in unexpected state " +
223                                                         stream.state());
224                 }
225             }
226 
227             // Trailing headers must go through flow control if there are other frames queued in flow control
228             // for this stream.
229             Http2RemoteFlowController flowController = flowController();
230             if (!endOfStream || !flowController.hasFlowControlled(stream)) {
231                 // The behavior here should mirror that in FlowControlledHeaders
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                 // Writing headers may fail during the encode state if they violate HPACK limits.
240                 Throwable failureCause = future.cause();
241                 if (failureCause == null) {
242                     // Synchronously set the headersSent flag to ensure that we do not subsequently write
243                     // other headers containing pseudo-header fields.
244                     //
245                     // This just sets internal stream state which is used elsewhere in the codec and doesn't
246                     // necessarily mean the write will complete successfully.
247                     stream.headersSent(isInformational);
248 
249                     if (!future.isSuccess()) {
250                         // Either the future is not done or failed in the meantime.
251                         notifyLifecycleManagerOnError(future, ctx);
252                     }
253                 } else {
254                     lifecycleManager.onError(ctx, true, failureCause);
255                 }
256 
257                 if (endOfStream) {
258                     // Must handle calling onError before calling closeStreamLocal, otherwise the error handler will
259                     // incorrectly think the stream no longer exists and so may not send RST_STREAM or perform similar
260                     // appropriate action.
261                     lifecycleManager.closeStreamLocal(stream, future);
262                 }
263 
264                 return future;
265             } else {
266                 // Pass headers to the flow-controller so it can maintain their sequence relative to DATA frames.
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         // Delegate to the lifecycle manager for proper updating of connection state.
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         // Acknowledge receipt of the settings. We should do this before we process the settings to ensure our
329         // remote peer applies these settings before any subsequent frames that we may send which depend upon
330         // these new settings. See https://github.com/netty/netty/issues/6520.
331         frameWriter.writeSettingsAck(ctx, aggregator.newPromise());
332 
333         // We create a "new promise" to make sure that status from both the write and the application are taken into
334         // account independently.
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             // Reserve the promised stream.
361             connection.local().reservePushStream(promisedStreamId, stream);
362 
363             promise = promise.unvoid();
364             ChannelFuture future = frameWriter.writePushPromise(ctx, streamId, promisedStreamId, headers, padding,
365                                                                 promise);
366             // Writing headers may fail during the encode state if they violate HPACK limits.
367             Throwable failureCause = future.cause();
368             if (failureCause == null) {
369                 // This just sets internal stream state which is used elsewhere in the codec and doesn't
370                 // necessarily mean the write will complete successfully.
371                 stream.pushPromiseSent();
372 
373                 if (!future.isSuccess()) {
374                     // Either the future is not done or failed in the meantime.
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      * Wrap a DATA frame so it can be written subject to flow-control. Note that this implementation assumes it
462      * only writes padding once for the entire payload as opposed to writing it once per-frame. This makes the
463      * {@link #size} calculation deterministic thereby greatly simplifying the implementation.
464      * <p>
465      * If frame-splitting is required to fit within max-frame-size and flow-control constraints we ensure that
466      * the passed promise is not completed until last frame write.
467      * </p>
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             // Don't update dataSize because we need to ensure the size() method returns a consistent size even after
490             // error so we don't invalidate flow control when returning bytes to flow control.
491             //
492             // That said we will set dataSize and padding to 0 in the write(...) method if we cleared the queue
493             // because of an error.
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                         // When the queue is empty it means we did clear it because of an error(...) call
504                         // (as otherwise we will have at least 1 entry in there), which will happen either when called
505                         // explicit or when the write itself fails. In this case just set dataSize and padding to 0
506                         // which will signal back that the whole frame was consumed.
507                         //
508                         // See https://github.com/netty/netty/issues/8707.
509                         padding = dataSize = 0;
510                     } else {
511                         // There's no need to write any data frames because there are only empty data frames in the
512                         // queue and it is not end of stream yet. Just complete their promises by getting the buffer
513                         // corresponding to 0 bytes and writing it to the channel (to preserve notification order).
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             // Determine how much data to write.
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             // The queue reported writableData readable bytes but produced fewer: a queued buffer was released or
532             // consumed while still referenced by the queue, so the stream's data is corrupted. Fail the stream.
533             int producedBytes = toWrite.readableBytes();
534             if (producedBytes < writableData) {
535                 ReferenceCountUtil.safeRelease(toWrite);
536                 // Set dataSize and padding to 0 to signal that the whole frame was consumed, so it is removed and
537                 // its bytes are returned to flow control (matching the error path above).
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             // Determine how much padding to write.
546             int writablePadding = min(allowedBytes - writableData, padding);
547             padding -= writablePadding;
548 
549             // Write the frame(s).
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             // Given that we're merging data into a frame it doesn't really make sense to accumulate padding.
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      * Wrap headers so they can be written subject to flow-control. While headers do not have cost against the
584      * flow-control window their order with respect to other frames must be maintained, hence if a DATA frame is
585      * blocked on flow-control a HEADER frame must wait until this frame has been written.
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             // The code is currently requiring adding this listener before writing, in order to call onError() before
622             // closeStreamLocal().
623             promise.addListener(this);
624 
625             ChannelFuture f = sendHeaders(frameWriter, ctx, stream.id(), headers, hasPriority, streamDependency,
626                     weight, exclusive, padding, endOfStream, promise);
627             // Writing headers may fail during the encode state if they violate HPACK limits.
628             Throwable failureCause = f.cause();
629             if (failureCause == null) {
630                 // This just sets internal stream state which is used elsewhere in the codec and doesn't
631                 // necessarily mean the write will complete successfully.
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      * Common base type for payloads to deliver via flow-control.
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 }