private void handleEndOfStream(final ChannelHandlerContext context, final int streamId) { this.encoder().writeHeaders(context, streamId, SUCCESS_HEADERS, 0, true, context.channel().newPromise()); }
private void _writeData(Http2Stream stream, ByteBuf chunk, boolean end, ChannelPromise promise) { encoder().writeData(chctx, stream.id(), chunk, 0, end, promise); Http2RemoteFlowController controller = encoder().flowController(); if (!controller.isWritable(stream) || end) { try { encoder().flowController().writePendingBytes(); } catch (Http2Exception e) { onError(chctx, true, e); } } chctx.channel().flush(); }
@Override public void userEventTriggered(final ChannelHandlerContext context, final Object event) throws Exception { if (event instanceof IdleStateEvent) { log.trace("Sending ping due to inactivity."); this.encoder().writePing(context, false, System.currentTimeMillis(), context.newPromise()).addListener(new GenericFutureListener<ChannelFuture>() { @Override public void operationComplete(final ChannelFuture future) { if (!future.isSuccess()) { log.debug("Failed to write PING frame.", future.cause()); future.channel().close(); } } }); this.pingTimeoutFuture = context.channel().eventLoop().schedule(new Runnable() { @Override public void run() { log.debug("Closing channel due to ping timeout."); context.channel().close(); } }, pingTimeoutMillis, TimeUnit.MILLISECONDS); this.flush(context); } super.userEventTriggered(context, event); }
@Override protected ChannelFuture doWriteReset(int id, int streamId, Http2Error error) { final Http2Stream stream = encoder.connection().stream(streamId); // Send a RST_STREAM frame only for an active stream which did not send a RST_STREAM frame already. if (stream != null && !stream.isResetSent()) { return encoder.writeRstStream(ctx, streamId, error.code(), ctx.newPromise()); } return ctx.writeAndFlush(Unpooled.EMPTY_BUFFER); }
@Override public void handlerAdded(ChannelHandlerContext ctx) throws Exception { // Initialize the encoder, decoder, flow controllers, and internal state. encoder.lifecycleManager(this); decoder.lifecycleManager(this); encoder.flowController().channelHandlerContext(ctx); decoder.flowController().channelHandlerContext(ctx); byteDecoder = new PrefaceDecoder(ctx); }
/** * Sends the HTTP/2 connection preface upon establishment of the connection, if not already sent. */ private void sendPreface(ChannelHandlerContext ctx) throws Exception { if (prefaceSent || !ctx.channel().isActive()) { return; } prefaceSent = true; final boolean isClient = !connection().isServer(); if (isClient) { // Clients must send the preface string as the first bytes on the connection. ctx.write(connectionPrefaceBuf()).addListener(ChannelFutureListener.CLOSE_ON_FAILURE); } // Both client and server must send their initial settings. encoder.writeSettings(ctx, initialSettings, ctx.newPromise()).addListener( ChannelFutureListener.CLOSE_ON_FAILURE); if (isClient) { // If this handler is extended by the user and we directly fire the userEvent from this context then // the user will not see the event. We should fire the event starting with this handler so this class // (and extending classes) have a chance to process the event. userEventTriggered(ctx, Http2ConnectionPrefaceAndSettingsFrameWrittenEvent.INSTANCE); } } }
ctx.write(msg, promise); return; LOG.debug("Sent HTTP/2 HEADERS frame, stream={}, end={}, headers={}, padding={}bytes", new Object[] { streamId, NOT_END_STREAM, http2Headers.size(), NO_PADDING}); headersFuture = encoder.writeHeaders(ctx, streamId, http2Headers, NO_PADDING, NOT_END_STREAM, promise); headersFuture.addListener(future -> { if (future.isSuccess()) LOG.debug("Sent HTTP/2 HEADERS frame, stream={}, end={}, headers={}, padding={}bytes", new Object[] { streamId, NOT_END_STREAM, headers.size(), NO_PADDING}); headersFuture = encoder.writeHeaders(ctx, streamId, headers, NO_PADDING, NOT_END_STREAM, promise); headersFuture.addListener(future -> { if (future.isSuccess()) LOG.debug("Sent HTTP/2 DATA frame, stream={}, end={}, data={}bytes, padding={}bytes", new Object[]{streamId, END_STREAM, data.readableBytes(), NO_PADDING}); encoder.writeData(ctx, streamId, data, NO_PADDING, END_STREAM, ctx.newPromise()); ctx.channel().flush(); final TimeoutAsyncPoolHandle<Channel> handle = (TimeoutAsyncPoolHandle<Channel>) ((RequestWithCallback) msg).handle(); headersFuture.addListener(future -> { if (future.isSuccess()) if (connection().stream(streamId) != null)
@Override public void push(final String method, final String path, final Map<String, Object> headers) { ctx.channel().eventLoop().execute(() -> { AsciiString streamIdHeader = HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(); Http2Connection connection = encoder.connection(); int nextStreamId = connection.local().incrementAndGetNextStreamId(); Http2Headers h2headers = new DefaultHttp2Headers() .path(path) .method(method) .authority(authority) .scheme(scheme); headers.forEach((n, v) -> h2headers.add(n, v.toString())); encoder.writePushPromise(ctx, streamId, nextStreamId, h2headers, 0, ctx.newPromise()); // TODO: Is there another way of handling a push promise? DefaultFullHttpRequest pushRequest = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.valueOf(method.toUpperCase()), path, Unpooled.EMPTY_BUFFER, new DefaultHttpHeaders(false).set(streamIdHeader, nextStreamId), EmptyHttpHeaders.INSTANCE); ctx.pipeline().fireChannelRead(pushRequest); ctx.pipeline().fireChannelReadComplete(); }); }
@Override public void onDataAvailable(final ByteString data) { ByteBuf content = Unpooled.wrappedBuffer(data.asByteBuffer()); _encoder.writeData(_ctx, _streamId, content, NO_PADDING, NOT_END_STREAM, _ctx.channel().newPromise()) .addListener(future -> _readHandle.request(1)); LOG.debug("Sent HTTP/2 DATA frame, stream={}, end={}, data={}bytes, padding={}bytes", new Object[] { _streamId, NOT_END_STREAM, content.readableBytes(), NO_PADDING }); _notFlushedBytes += data.length(); _notFlushedChunks++; if (_notFlushedBytes >= FLUSH_THRESHOLD || _notFlushedChunks == MAX_BUFFERED_CHUNKS) { _ctx.channel().flush(); _notFlushedBytes = 0; _notFlushedChunks = 0; } }
private void writePushNotification(final ChannelHandlerContext context, final PushNotificationPromise responsePromise, final ChannelPromise writePromise) { if (context.channel().isActive()) { final int streamId = this.connection().local().incrementAndGetNextStreamId(); final ChannelPromise headersPromise = context.newPromise(); this.encoder().writeHeaders(context, streamId, headers, 0, false, headersPromise); log.trace("Wrote headers on stream {}: {}", streamId, headers); final ByteBuf payloadBuffer = context.alloc().ioBuffer(INITIAL_PAYLOAD_BUFFER_CAPACITY); payloadBuffer.writeBytes(pushNotification.getPayload().getBytes(StandardCharsets.UTF_8)); final ChannelPromise dataPromise = context.newPromise(); this.encoder().writeData(context, streamId, payloadBuffer, 0, true, dataPromise); log.trace("Wrote payload on stream {}: {}", streamId, pushNotification.getPayload());
ctx.write(msg, promise); return; new SimpleChannelPromiseAggregator(promise, ctx.channel(), ctx.executor()); try { Http2ConnectionEncoder encoder = encoder(); endStream = isLastContent && trailers.isEmpty(); release = false; encoder.writeData(ctx, currentStreamId, content, 0, endStream, promiseAggregator.newPromise());
@Override public CompletableFuture<StreamWriteOperation> writeData(int streamId, byte[] data, boolean endStream) { return doInEventLoop((cf, channelPromise) -> { log.info("write data on connection {}, stream id: {}, size : {}", ctx.channel().id(), streamId, data.length); cf.setResult(new StreamWriteOperation(http2Connection.stream(streamId), this)); encoder.writeData(ctx, streamId, Unpooled.wrappedBuffer(data), 0, endStream, channelPromise); ctx.pipeline().flush(); }); }
@Override public void userEventTriggered(final ChannelHandlerContext context, final Object event) throws Exception { if (event instanceof IdleStateEvent) { assert PING_TIMEOUT < ApnsClient.PING_IDLE_TIME_MILLIS; log.trace("Sending ping due to inactivity."); final ByteBuf pingDataBuffer = context.alloc().ioBuffer(8, 8); pingDataBuffer.writeLong(this.nextPingId++); this.encoder().writePing(context, false, pingDataBuffer, context.newPromise()).addListener(new GenericFutureListener<ChannelFuture>() { @Override public void operationComplete(final ChannelFuture future) throws Exception { if (future.isSuccess()) { ApnsClientHandler.this.pingTimeoutFuture = future.channel().eventLoop().schedule(new Runnable() { @Override public void run() { log.debug("Closing channel due to ping timeout."); future.channel().close(); } }, PING_TIMEOUT, TimeUnit.SECONDS); } else { log.debug("Failed to write PING frame.", future.cause()); future.channel().close(); } } }); this.flush(context); } super.userEventTriggered(context, event); }
@Override public CompletableFuture<Connection> writeRst(int streamId, int errorCode) { return doInEventLoop((cf, channelPromise) -> { log.info("write data on connection {}, stream id: {}, error code: {}", ctx.channel().id(), streamId, errorCode); cf.setResult(this); encoder.writeRstStream(ctx, streamId, errorCode, channelPromise); ctx.pipeline().flush(); }); }
@Override public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception { // Writability is expected to change while we are writing. We cannot allow this event to trigger reentering // the allocation and write loop. Reentering the event loop will lead to over or illegal allocation. try { if (ctx.channel().isWritable()) { flush(ctx); } encoder.flowController().channelWritabilityChanged(); } finally { super.channelWritabilityChanged(ctx); } }
if (msg instanceof Http2DataFrame) { Http2DataFrame dataFrame = (Http2DataFrame) msg; encoder().writeData(ctx, dataFrame.stream().id(), dataFrame.content(), dataFrame.padding(), dataFrame.isEndStream(), promise); } else if (msg instanceof Http2HeadersFrame) { encoder().writeRstStream(ctx, rstFrame.stream().id(), rstFrame.errorCode(), promise); } else if (msg instanceof Http2PingFrame) { Http2PingFrame frame = (Http2PingFrame) msg; encoder().writePing(ctx, frame.ack(), frame.content(), promise); } else if (msg instanceof Http2SettingsFrame) { encoder().writeSettings(ctx, ((Http2SettingsFrame) msg).settings(), promise); } else if (msg instanceof Http2GoAwayFrame) { writeGoAwayFrame(ctx, (Http2GoAwayFrame) msg, promise); } else if (msg instanceof Http2UnknownFrame) { Http2UnknownFrame unknownFrame = (Http2UnknownFrame) msg; encoder().writeFrame(ctx, unknownFrame.frameType(), unknownFrame.stream().id(), unknownFrame.flags(), unknownFrame.content(), promise); } else if (!(msg instanceof Http2Frame)) { ctx.write(msg, promise); } else { ReferenceCountUtil.release(msg);
@Override public void onDone() { _encoder.writeData(_ctx, _streamId, Unpooled.EMPTY_BUFFER, NO_PADDING, END_STREAM, _ctx.channel().newPromise()); LOG.debug("Sent HTTP/2 DATA frame, stream={}, end={}, data={}bytes, padding={}bytes", new Object[] { _streamId, END_STREAM, NO_DATA, NO_PADDING }); _ctx.channel().flush(); }
@Override protected ChannelFuture doWriteData(int id, int streamId, HttpData data, boolean endStream) { if (isStreamPresentAndWritable(streamId)) { // Write to an existing stream. return encoder.writeData(ctx, streamId, toByteBuf(data), 0, endStream, ctx.newPromise()); } if (encoder.connection().local().mayHaveCreatedStream(streamId)) { // Can't write to an outdated (closed) stream. ReferenceCountUtil.safeRelease(data); return data.isEmpty() ? ctx.writeAndFlush(Unpooled.EMPTY_BUFFER) : newFailedFuture(ClosedPublisherException.get()); } // Cannot start a new stream with a DATA frame. It must start with a HEADERS frame. ReferenceCountUtil.safeRelease(data); return newFailedFuture(new IllegalStateException( "cannot start a new stream " + streamId + " with a DATA frame")); }
private void _writeReset(int streamId, long code) { encoder().writeRstStream(chctx, streamId, code, chctx.newPromise()); chctx.flush(); }