1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.netty.handler.codec.http2;
17
18 import io.netty.buffer.ByteBuf;
19 import io.netty.channel.Channel;
20 import io.netty.channel.ChannelFuture;
21 import io.netty.channel.ChannelFutureListener;
22 import io.netty.channel.ChannelHandlerContext;
23 import io.netty.channel.ChannelInboundHandler;
24 import io.netty.channel.ChannelPromise;
25 import io.netty.handler.codec.UnsupportedMessageTypeException;
26 import io.netty.handler.codec.http.HttpServerUpgradeHandler.UpgradeEvent;
27 import io.netty.handler.codec.http2.Http2Connection.PropertyKey;
28 import io.netty.handler.codec.http2.Http2Stream.State;
29 import io.netty.handler.codec.http2.StreamBufferingEncoder.Http2ChannelClosedException;
30 import io.netty.handler.codec.http2.StreamBufferingEncoder.Http2GoAwayException;
31 import io.netty.util.ReferenceCountUtil;
32 import io.netty.util.ReferenceCounted;
33 import io.netty.util.collection.IntObjectHashMap;
34 import io.netty.util.collection.IntObjectMap;
35 import io.netty.util.internal.UnstableApi;
36 import io.netty.util.internal.logging.InternalLogger;
37 import io.netty.util.internal.logging.InternalLoggerFactory;
38
39 import static io.netty.buffer.ByteBufUtil.writeAscii;
40 import static io.netty.handler.codec.http2.Http2CodecUtil.HTTP_UPGRADE_STREAM_ID;
41 import static io.netty.handler.codec.http2.Http2CodecUtil.isStreamIdValid;
42 import static io.netty.handler.codec.http2.Http2Error.NO_ERROR;
43 import static io.netty.util.internal.logging.InternalLogLevel.DEBUG;
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147 @UnstableApi
148 public class Http2FrameCodec extends Http2ConnectionHandler {
149
150 private static final InternalLogger LOG = InternalLoggerFactory.getInstance(Http2FrameCodec.class);
151
152 private static final Class<?>[] SUPPORTED_MESSAGES = new Class[] {
153 Http2DataFrame.class, Http2HeadersFrame.class, Http2WindowUpdateFrame.class, Http2ResetFrame.class,
154 Http2PingFrame.class, Http2SettingsFrame.class, Http2SettingsAckFrame.class, Http2GoAwayFrame.class,
155 Http2PushPromiseFrame.class, Http2PriorityFrame.class, Http2UnknownFrame.class };
156
157 protected final PropertyKey streamKey;
158 private final PropertyKey upgradeKey;
159
160 private final Integer initialFlowControlWindowSize;
161
162 ChannelHandlerContext ctx;
163
164
165
166
167 private int numBufferedStreams;
168 private final IntObjectMap<DefaultHttp2FrameStream> frameStreamToInitializeMap =
169 new IntObjectHashMap<DefaultHttp2FrameStream>(8);
170
171 Http2FrameCodec(Http2ConnectionEncoder encoder, Http2ConnectionDecoder decoder, Http2Settings initialSettings,
172 boolean decoupleCloseAndGoAway, boolean flushPreface) {
173 super(decoder, encoder, initialSettings, decoupleCloseAndGoAway, flushPreface);
174
175 decoder.frameListener(new FrameListener());
176 connection().addListener(new ConnectionListener());
177 connection().remote().flowController().listener(new Http2RemoteFlowControllerListener());
178 streamKey = connection().newKey();
179 upgradeKey = connection().newKey();
180 initialFlowControlWindowSize = initialSettings.initialWindowSize();
181 }
182
183
184
185
186 DefaultHttp2FrameStream newStream() {
187 return new DefaultHttp2FrameStream();
188 }
189
190
191
192
193
194
195 final void forEachActiveStream(final Http2FrameStreamVisitor streamVisitor) throws Http2Exception {
196 assert ctx.executor().inEventLoop();
197 if (connection().numActiveStreams() > 0) {
198 connection().forEachActiveStream(new Http2StreamVisitor() {
199 @Override
200 public boolean visit(Http2Stream stream) {
201 try {
202 return streamVisitor.visit((Http2FrameStream) stream.getProperty(streamKey));
203 } catch (Throwable cause) {
204 onError(ctx, false, cause);
205 return false;
206 }
207 }
208 });
209 }
210 }
211
212
213
214
215
216
217 int numInitializingStreams() {
218 return frameStreamToInitializeMap.size();
219 }
220
221 @Override
222 public final void handlerAdded(ChannelHandlerContext ctx) throws Exception {
223 this.ctx = ctx;
224 super.handlerAdded(ctx);
225 handlerAdded0(ctx);
226
227
228 Http2Connection connection = connection();
229 if (connection.isServer()) {
230 tryExpandConnectionFlowControlWindow(connection);
231 }
232 }
233
234 private void tryExpandConnectionFlowControlWindow(Http2Connection connection) throws Http2Exception {
235 if (initialFlowControlWindowSize != null) {
236
237
238 Http2Stream connectionStream = connection.connectionStream();
239 Http2LocalFlowController localFlowController = connection.local().flowController();
240 final int delta = initialFlowControlWindowSize - localFlowController.initialWindowSize(connectionStream);
241
242 if (delta > 0) {
243
244 localFlowController.incrementWindowSize(connectionStream, Math.max(delta << 1, delta));
245 flush(ctx);
246 }
247 }
248 }
249
250 void handlerAdded0(@SuppressWarnings("unsed") ChannelHandlerContext ctx) throws Exception {
251
252 }
253
254
255
256
257
258 @Override
259 public final void userEventTriggered(final ChannelHandlerContext ctx, final Object evt) throws Exception {
260 if (evt == Http2ConnectionPrefaceAndSettingsFrameWrittenEvent.INSTANCE) {
261
262 tryExpandConnectionFlowControlWindow(connection());
263
264
265
266
267 ctx.executor().execute(new Runnable() {
268 @Override
269 public void run() {
270 ctx.fireUserEventTriggered(evt);
271 }
272 });
273 } else if (evt instanceof UpgradeEvent) {
274 UpgradeEvent upgrade = (UpgradeEvent) evt;
275 try {
276 onUpgradeEvent(ctx, upgrade.retain());
277 Http2Stream stream = connection().stream(HTTP_UPGRADE_STREAM_ID);
278 if (stream.getProperty(streamKey) == null) {
279
280
281
282 onStreamActive0(stream);
283 }
284 upgrade.upgradeRequest().headers().setInt(
285 HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), HTTP_UPGRADE_STREAM_ID);
286 stream.setProperty(upgradeKey, true);
287 InboundHttpToHttp2Adapter.handle(
288 ctx, connection(), decoder().frameListener(), upgrade.upgradeRequest().retain());
289 } finally {
290 upgrade.release();
291 }
292 } else {
293 onUserEventTriggered(ctx, evt);
294 ctx.fireUserEventTriggered(evt);
295 }
296 }
297
298 void onUserEventTriggered(final ChannelHandlerContext ctx, final Object evt) throws Exception {
299
300 }
301
302
303
304
305
306 @Override
307 public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
308 if (msg instanceof Http2DataFrame) {
309 Http2DataFrame dataFrame = (Http2DataFrame) msg;
310 encoder().writeData(ctx, dataFrame.stream().id(), dataFrame.content(),
311 dataFrame.padding(), dataFrame.isEndStream(), promise);
312 } else if (msg instanceof Http2HeadersFrame) {
313 writeHeadersFrame(ctx, (Http2HeadersFrame) msg, promise);
314 } else if (msg instanceof Http2WindowUpdateFrame) {
315 Http2WindowUpdateFrame frame = (Http2WindowUpdateFrame) msg;
316 Http2FrameStream frameStream = frame.stream();
317
318
319 try {
320 if (frameStream == null) {
321 increaseInitialConnectionWindow(frame.windowSizeIncrement());
322 } else {
323 consumeBytes(frameStream.id(), frame.windowSizeIncrement());
324 }
325 promise.setSuccess();
326 } catch (Throwable t) {
327 promise.setFailure(t);
328 }
329 } else if (msg instanceof Http2ResetFrame) {
330 Http2ResetFrame rstFrame = (Http2ResetFrame) msg;
331 int id = rstFrame.stream().id();
332
333
334 if (connection().streamMayHaveExisted(id)) {
335 encoder().writeRstStream(ctx, rstFrame.stream().id(), rstFrame.errorCode(), promise);
336 } else {
337 ReferenceCountUtil.release(rstFrame);
338 promise.setFailure(Http2Exception.streamError(
339 rstFrame.stream().id(), Http2Error.PROTOCOL_ERROR, "Stream never existed"));
340 }
341 } else if (msg instanceof Http2PingFrame) {
342 Http2PingFrame frame = (Http2PingFrame) msg;
343 encoder().writePing(ctx, frame.ack(), frame.content(), promise);
344 } else if (msg instanceof Http2SettingsFrame) {
345 encoder().writeSettings(ctx, ((Http2SettingsFrame) msg).settings(), promise);
346 } else if (msg instanceof Http2SettingsAckFrame) {
347
348
349 encoder().writeSettingsAck(ctx, promise);
350 } else if (msg instanceof Http2GoAwayFrame) {
351 writeGoAwayFrame(ctx, (Http2GoAwayFrame) msg, promise);
352 } else if (msg instanceof Http2PushPromiseFrame) {
353 Http2PushPromiseFrame pushPromiseFrame = (Http2PushPromiseFrame) msg;
354 writePushPromise(ctx, pushPromiseFrame, promise);
355 } else if (msg instanceof Http2PriorityFrame) {
356 Http2PriorityFrame priorityFrame = (Http2PriorityFrame) msg;
357 encoder().writePriority(ctx, priorityFrame.stream().id(), priorityFrame.streamDependency(),
358 priorityFrame.weight(), priorityFrame.exclusive(), promise);
359 } else if (msg instanceof Http2UnknownFrame) {
360 Http2UnknownFrame unknownFrame = (Http2UnknownFrame) msg;
361 encoder().writeFrame(ctx, unknownFrame.frameType(), unknownFrame.stream().id(),
362 unknownFrame.flags(), unknownFrame.content(), promise);
363 } else if (!(msg instanceof Http2Frame)) {
364 ctx.write(msg, promise);
365 } else {
366 ReferenceCountUtil.release(msg);
367 throw new UnsupportedMessageTypeException(msg, SUPPORTED_MESSAGES);
368 }
369 }
370
371 private void increaseInitialConnectionWindow(int deltaBytes) throws Http2Exception {
372
373 connection().local().flowController().incrementWindowSize(connection().connectionStream(), deltaBytes);
374 }
375
376 final boolean consumeBytes(int streamId, int bytes) throws Http2Exception {
377 Http2Stream stream = connection().stream(streamId);
378
379
380 if (stream != null && streamId == Http2CodecUtil.HTTP_UPGRADE_STREAM_ID) {
381 Boolean upgraded = stream.getProperty(upgradeKey);
382 if (Boolean.TRUE.equals(upgraded)) {
383 return false;
384 }
385 }
386
387 return connection().local().flowController().consumeBytes(stream, bytes);
388 }
389
390 private void writeGoAwayFrame(ChannelHandlerContext ctx, Http2GoAwayFrame frame, ChannelPromise promise) {
391 if (frame.lastStreamId() > -1) {
392 frame.release();
393 throw new IllegalArgumentException("Last stream id must not be set on GOAWAY frame");
394 }
395
396 int lastStreamCreated = connection().remote().lastStreamCreated();
397 long lastStreamId = lastStreamCreated + ((long) frame.extraStreamIds()) * 2;
398
399 if (lastStreamId > Integer.MAX_VALUE) {
400 lastStreamId = Integer.MAX_VALUE;
401 }
402 goAway(ctx, (int) lastStreamId, frame.errorCode(), frame.content(), promise);
403 }
404
405 private void writeHeadersFrame(final ChannelHandlerContext ctx, Http2HeadersFrame headersFrame,
406 ChannelPromise promise) {
407
408 if (isStreamIdValid(headersFrame.stream().id())) {
409 encoder().writeHeaders(ctx, headersFrame.stream().id(), headersFrame.headers(), headersFrame.padding(),
410 headersFrame.isEndStream(), promise);
411 } else if (initializeNewStream(ctx, (DefaultHttp2FrameStream) headersFrame.stream(), promise)) {
412 promise = promise.unvoid();
413
414 final int streamId = headersFrame.stream().id();
415
416 encoder().writeHeaders(ctx, streamId, headersFrame.headers(), headersFrame.padding(),
417 headersFrame.isEndStream(), promise);
418
419 if (!promise.isDone()) {
420 numBufferedStreams++;
421
422
423 promise.addListener(new ChannelFutureListener() {
424 @Override
425 public void operationComplete(ChannelFuture channelFuture) {
426 numBufferedStreams--;
427 handleHeaderFuture(channelFuture, streamId);
428 }
429 });
430 } else {
431 handleHeaderFuture(promise, streamId);
432 }
433 }
434 }
435
436 private void writePushPromise(final ChannelHandlerContext ctx, Http2PushPromiseFrame pushPromiseFrame,
437 final ChannelPromise promise) {
438 if (isStreamIdValid(pushPromiseFrame.pushStream().id())) {
439 encoder().writePushPromise(ctx, pushPromiseFrame.stream().id(), pushPromiseFrame.pushStream().id(),
440 pushPromiseFrame.http2Headers(), pushPromiseFrame.padding(), promise);
441 } else if (initializeNewStream(ctx, (DefaultHttp2FrameStream) pushPromiseFrame.pushStream(), promise)) {
442 final int streamId = pushPromiseFrame.stream().id();
443 encoder().writePushPromise(ctx, streamId, pushPromiseFrame.pushStream().id(),
444 pushPromiseFrame.http2Headers(), pushPromiseFrame.padding(), promise);
445
446 if (promise.isDone()) {
447 handleHeaderFuture(promise, streamId);
448 } else {
449 numBufferedStreams++;
450
451
452 promise.addListener(new ChannelFutureListener() {
453 @Override
454 public void operationComplete(ChannelFuture channelFuture) {
455 numBufferedStreams--;
456 handleHeaderFuture(channelFuture, streamId);
457 }
458 });
459 }
460 }
461 }
462
463 private boolean initializeNewStream(ChannelHandlerContext ctx, DefaultHttp2FrameStream http2FrameStream,
464 ChannelPromise promise) {
465 final Http2Connection connection = connection();
466 final int streamId = connection.local().incrementAndGetNextStreamId();
467 if (streamId < 0) {
468 promise.setFailure(new Http2NoMoreStreamIdsException());
469
470
471
472 onHttp2Frame(ctx, new DefaultHttp2GoAwayFrame(connection.isServer() ? Integer.MAX_VALUE :
473 Integer.MAX_VALUE - 1, NO_ERROR.code(),
474 writeAscii(ctx.alloc(), "Stream IDs exhausted on local stream creation")));
475
476 return false;
477 }
478 http2FrameStream.id = streamId;
479
480
481
482
483
484
485 Object old = frameStreamToInitializeMap.put(streamId, http2FrameStream);
486
487
488 assert old == null;
489 return true;
490 }
491
492 private void handleHeaderFuture(ChannelFuture channelFuture, int streamId) {
493 if (!channelFuture.isSuccess()) {
494 frameStreamToInitializeMap.remove(streamId);
495 }
496 }
497
498 private void onStreamActive0(Http2Stream stream) {
499 if (stream.id() != Http2CodecUtil.HTTP_UPGRADE_STREAM_ID &&
500 connection().local().isValidStreamId(stream.id())) {
501 return;
502 }
503
504 DefaultHttp2FrameStream stream2 = newStream().setStreamAndProperty(streamKey, stream);
505 onHttp2StreamStateChanged(ctx, stream2);
506 }
507
508 private final class ConnectionListener extends Http2ConnectionAdapter {
509 @Override
510 public void onStreamAdded(Http2Stream stream) {
511 DefaultHttp2FrameStream frameStream = frameStreamToInitializeMap.remove(stream.id());
512
513 if (frameStream != null) {
514 frameStream.setStreamAndProperty(streamKey, stream);
515 }
516 }
517
518 @Override
519 public void onStreamActive(Http2Stream stream) {
520 onStreamActive0(stream);
521 }
522
523 @Override
524 public void onStreamClosed(Http2Stream stream) {
525 onHttp2StreamStateChanged0(stream);
526 }
527
528 @Override
529 public void onStreamHalfClosed(Http2Stream stream) {
530 onHttp2StreamStateChanged0(stream);
531 }
532
533 private void onHttp2StreamStateChanged0(Http2Stream stream) {
534 DefaultHttp2FrameStream stream2 = stream.getProperty(streamKey);
535 if (stream2 != null) {
536 onHttp2StreamStateChanged(ctx, stream2);
537 }
538 }
539 }
540
541 @Override
542 protected void onConnectionError(
543 ChannelHandlerContext ctx, boolean outbound, Throwable cause, Http2Exception http2Ex) {
544 if (!outbound) {
545
546
547
548
549 ctx.fireExceptionCaught(cause);
550 }
551 super.onConnectionError(ctx, outbound, cause, http2Ex);
552 }
553
554
555
556
557
558 @Override
559 protected final void onStreamError(ChannelHandlerContext ctx, boolean outbound, Throwable cause,
560 Http2Exception.StreamException streamException) {
561 int streamId = streamException.streamId();
562 Http2Stream connectionStream = connection().stream(streamId);
563 if (connectionStream == null) {
564 onHttp2UnknownStreamError(ctx, cause, streamException);
565
566 super.onStreamError(ctx, outbound, cause, streamException);
567 return;
568 }
569
570 Http2FrameStream stream = connectionStream.getProperty(streamKey);
571 if (stream == null) {
572 LOG.warn("Stream exception thrown without stream object attached.", cause);
573
574 super.onStreamError(ctx, outbound, cause, streamException);
575 return;
576 }
577
578 if (!outbound) {
579
580 onHttp2FrameStreamException(ctx, new Http2FrameStreamException(stream, streamException.error(), cause));
581 }
582 }
583
584 private static void onHttp2UnknownStreamError(@SuppressWarnings("unused") ChannelHandlerContext ctx,
585 Throwable cause, Http2Exception.StreamException streamException) {
586
587
588
589
590
591 LOG.log(DEBUG, "Stream exception thrown for unknown stream {}.", streamException.streamId(), cause);
592 }
593
594 @Override
595 protected final boolean isGracefulShutdownComplete() {
596 return super.isGracefulShutdownComplete() && numBufferedStreams == 0;
597 }
598
599 private final class FrameListener implements Http2FrameListener {
600
601 @Override
602 public void onUnknownFrame(
603 ChannelHandlerContext ctx, byte frameType, int streamId, Http2Flags flags, ByteBuf payload) {
604 if (streamId == 0) {
605
606 return;
607 }
608 onHttp2Frame(ctx, new DefaultHttp2UnknownFrame(frameType, flags, payload)
609 .stream(requireStream(streamId)).retain());
610 }
611
612 @Override
613 public void onSettingsRead(ChannelHandlerContext ctx, Http2Settings settings) {
614 onHttp2Frame(ctx, new DefaultHttp2SettingsFrame(settings));
615 }
616
617 @Override
618 public void onPingRead(ChannelHandlerContext ctx, long data) {
619 onHttp2Frame(ctx, new DefaultHttp2PingFrame(data, false));
620 }
621
622 @Override
623 public void onPingAckRead(ChannelHandlerContext ctx, long data) {
624 onHttp2Frame(ctx, new DefaultHttp2PingFrame(data, true));
625 }
626
627 @Override
628 public void onRstStreamRead(ChannelHandlerContext ctx, int streamId, long errorCode) {
629 onHttp2Frame(ctx, new DefaultHttp2ResetFrame(errorCode).stream(requireStream(streamId)));
630 }
631
632 @Override
633 public void onWindowUpdateRead(ChannelHandlerContext ctx, int streamId, int windowSizeIncrement) {
634 if (streamId == 0) {
635
636 return;
637 }
638 onHttp2Frame(ctx, new DefaultHttp2WindowUpdateFrame(windowSizeIncrement).stream(requireStream(streamId)));
639 }
640
641 @Override
642 public void onHeadersRead(ChannelHandlerContext ctx, int streamId,
643 Http2Headers headers, int streamDependency, short weight, boolean
644 exclusive, int padding, boolean endStream) {
645 onHeadersRead(ctx, streamId, headers, padding, endStream);
646 }
647
648 @Override
649 public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
650 int padding, boolean endOfStream) {
651 onHttp2Frame(ctx, new DefaultHttp2HeadersFrame(headers, endOfStream, padding)
652 .stream(requireStream(streamId)));
653 }
654
655 @Override
656 public int onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
657 boolean endOfStream) {
658 onHttp2Frame(ctx, new DefaultHttp2DataFrame(data, endOfStream, padding)
659 .stream(requireStream(streamId)).retain());
660
661 return 0;
662 }
663
664 @Override
665 public void onGoAwayRead(ChannelHandlerContext ctx, int lastStreamId, long errorCode, ByteBuf debugData) {
666 onHttp2Frame(ctx, new DefaultHttp2GoAwayFrame(lastStreamId, errorCode, debugData).retain());
667 }
668
669 @Override
670 public void onPriorityRead(ChannelHandlerContext ctx, int streamId, int streamDependency,
671 short weight, boolean exclusive) {
672
673 Http2Stream stream = connection().stream(streamId);
674 if (stream == null) {
675
676 return;
677 }
678 onHttp2Frame(ctx, new DefaultHttp2PriorityFrame(streamDependency, weight, exclusive)
679 .stream(requireStream(streamId)));
680 }
681
682 @Override
683 public void onSettingsAckRead(ChannelHandlerContext ctx) {
684 onHttp2Frame(ctx, Http2SettingsAckFrame.INSTANCE);
685 }
686
687 @Override
688 public void onPushPromiseRead(ChannelHandlerContext ctx, int streamId, int promisedStreamId,
689 Http2Headers headers, int padding) {
690 onHttp2Frame(ctx, new DefaultHttp2PushPromiseFrame(headers, padding, promisedStreamId)
691 .pushStream(new DefaultHttp2FrameStream()
692 .setStreamAndProperty(streamKey, connection().stream(promisedStreamId)))
693 .stream(requireStream(streamId)));
694 }
695
696 private Http2FrameStream requireStream(int streamId) {
697 Http2FrameStream stream = connection().stream(streamId).getProperty(streamKey);
698 if (stream == null) {
699 throw new IllegalStateException("Stream object required for identifier: " + streamId);
700 }
701 return stream;
702 }
703 }
704
705 private void onUpgradeEvent(ChannelHandlerContext ctx, UpgradeEvent evt) {
706 ctx.fireUserEventTriggered(evt);
707 }
708
709 private void onHttp2StreamWritabilityChanged(ChannelHandlerContext ctx, DefaultHttp2FrameStream stream,
710 @SuppressWarnings("unused") boolean writable) {
711 ctx.fireUserEventTriggered(stream.writabilityChanged);
712 }
713
714 void onHttp2StreamStateChanged(ChannelHandlerContext ctx, DefaultHttp2FrameStream stream) {
715 ctx.fireUserEventTriggered(stream.stateChanged);
716 }
717
718 void onHttp2Frame(ChannelHandlerContext ctx, Http2Frame frame) {
719 ctx.fireChannelRead(frame);
720 }
721
722 void onHttp2FrameStreamException(ChannelHandlerContext ctx, Http2FrameStreamException cause) {
723 ctx.fireExceptionCaught(cause);
724 }
725
726 private final class Http2RemoteFlowControllerListener implements Http2RemoteFlowController.Listener {
727 @Override
728 public void writabilityChanged(Http2Stream stream) {
729 DefaultHttp2FrameStream frameStream = stream.getProperty(streamKey);
730 if (frameStream == null) {
731 return;
732 }
733 onHttp2StreamWritabilityChanged(
734 ctx, frameStream, connection().remote().flowController().isWritable(stream));
735 }
736 }
737
738
739
740
741
742 static class DefaultHttp2FrameStream implements Http2FrameStream {
743
744 private volatile int id = -1;
745 private volatile Http2Stream stream;
746
747 final Http2FrameStreamEvent stateChanged = Http2FrameStreamEvent.stateChanged(this);
748 final Http2FrameStreamEvent writabilityChanged = Http2FrameStreamEvent.writabilityChanged(this);
749
750 Channel attachment;
751
752 DefaultHttp2FrameStream setStreamAndProperty(PropertyKey streamKey, Http2Stream stream) {
753 assert id == -1 || stream.id() == id;
754 this.stream = stream;
755 stream.setProperty(streamKey, this);
756 return this;
757 }
758
759 @Override
760 public int id() {
761 Http2Stream stream = this.stream;
762 return stream == null ? id : stream.id();
763 }
764
765 @Override
766 public State state() {
767 Http2Stream stream = this.stream;
768 return stream == null ? State.IDLE : stream.state();
769 }
770
771 @Override
772 public String toString() {
773 return String.valueOf(id());
774 }
775 }
776 }