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()); promiseCombiner.finish(writePromise); writePromise.addListener(new GenericFutureListener<ChannelPromise>() { writePromise.tryFailure(STREAMS_EXHAUSTED_EXCEPTION); context.channel().close(); writePromise.tryFailure(STREAM_CLOSED_BEFORE_REPLY_EXCEPTION);
Http2FrameCodec(Http2ConnectionEncoder encoder, Http2ConnectionDecoder decoder, Http2Settings initialSettings) { super(decoder, encoder, initialSettings); decoder.frameListener(new FrameListener()); connection().addListener(new ConnectionListener()); connection().remote().flowController().listener(new Http2RemoteFlowControllerListener()); streamKey = connection().newKey(); upgradeKey = connection().newKey(); initialFlowControlWindowSize = initialSettings.initialWindowSize(); }
final boolean exclusive, final int padding, final boolean endOfStream, ChannelPromise promise) { try { Http2Stream stream = connection.stream(streamId); if (stream == null) { try { stream = connection.local().createStream(streamId, endOfStream); } catch (Http2Exception cause) { if (connection.remote().mayHaveCreatedStream(streamId)) { promise.tryFailure(new IllegalStateException("Stream no longer exists: " + streamId, cause)); return promise; switch (stream.state()) { case RESERVED_LOCAL: stream.open(endOfStream); break; case OPEN: throw new IllegalStateException("Stream " + stream.id() + " in unexpected state " + stream.state()); promise = promise.unvoid().addListener(closeStreamLocalListener);
@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(); }); }
if (streamCreatedAfterGoAwaySent(streamId)) { logger.info("{} ignoring {} frame for stream {}. Stream sent after GOAWAY sent", ctx.channel(), frameName, streamId); return true; } else if (stream.isResetSent() || streamCreatedAfterGoAwaySent(streamId)) { logger.info("{} ignoring {} frame for stream {} {}", ctx.channel(), frameName, stream.isResetSent() ? "RST_STREAM sent." : ("Stream created after GOAWAY sent. Last known stream by peer " + connection.remote().lastStreamKnownByPeer()));
@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")); }
encoder().writeHeaders(ctx, headersFrame.stream().id(), headersFrame.headers(), headersFrame.padding(), headersFrame.isEndStream(), promise); } else { final DefaultHttp2FrameStream stream = (DefaultHttp2FrameStream) headersFrame.stream(); final Http2Connection connection = connection(); final int streamId = connection.local().incrementAndGetNextStreamId(); if (streamId < 0) { promise.setFailure(new Http2NoMoreStreamIdsException()); return; final ChannelPromise writePromise = ctx.newPromise(); encoder().writeHeaders(ctx, streamId, headersFrame.headers(), headersFrame.padding(), headersFrame.isEndStream(), writePromise); if (writePromise.isDone()) { notifyHeaderWritePromise(writePromise, promise); } else { numBufferedStreams++; writePromise.addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception {
final int lastStreamId = conn.local().lastStreamKnownByPeer(); if (lastStreamId < 0 || // Did not receive a GOAWAY yet or encoder.writeRstStream(ctx, streamId, Http2Error.CANCEL.code(), ctx.newPromise()); ctx.flush(); } else {
void writePushPromise(int streamId, Http2Headers headers, Handler<AsyncResult<Integer>> completionHandler) { int promisedStreamId = connection().local().incrementAndGetNextStreamId(); ChannelPromise promise = chctx.newPromise(); promise.addListener(fut -> { if (fut.isSuccess()) { completionHandler.handle(Future.succeededFuture(promisedStreamId)); } else { completionHandler.handle(Future.failedFuture(fut.cause())); } }); EventExecutor executor = chctx.executor(); if (executor.inEventLoop()) { _writePushPromise(streamId, promisedStreamId, headers, promise); } else { executor.execute(() -> { _writePushPromise(streamId, promisedStreamId, headers, promise); }); } }
@Override public Http2Settings localSettings() { Http2Settings settings = new Http2Settings(); Http2FrameReader.Configuration config = frameReader.configuration(); Http2HeadersDecoder.Configuration headersConfig = config.headersConfiguration(); Http2FrameSizePolicy frameSizePolicy = config.frameSizePolicy(); settings.initialWindowSize(flowController().initialWindowSize()); settings.maxConcurrentStreams(connection.remote().maxActiveStreams()); settings.headerTableSize(headersConfig.maxHeaderTableSize()); settings.maxFrameSize(frameSizePolicy.maxFrameSize()); settings.maxHeaderListSize(headersConfig.maxHeaderListSize()); if (!connection.isServer()) { // Only set the pushEnabled flag if this is a client endpoint. settings.pushEnabled(connection.local().allowPushTo()); } return settings; }
@Override public CompletableFuture<StreamWriteOperation> writeHeaders(Http2Headers headers, boolean endStream, Http2StreamListener http2StreamListener) { return doInEventLoop((cf, channelPromise) -> { log.debug("write headers {}", headers); int streamId = http2Connection.local().incrementAndGetNextStreamId(); Http2Stream stream = http2Connection.stream(streamId); if (stream == null) { try { stream = http2Connection.local().createStream(streamId, false); } catch (Http2Exception e) { throw new IotClientException(e); } } if (http2StreamListener != null) { setStreamListener(stream, http2StreamListener); } cf.setResult(new StreamWriteOperation(stream, this)); encoder.writeHeaders(ctx, streamId, headers, 0, endStream, channelPromise); ctx.pipeline().flush(); }); }
@SuppressWarnings("unused") Throwable cause, StreamException http2Ex) { final int streamId = http2Ex.streamId(); Http2Stream stream = connection().stream(streamId); connection().isServer()) { stream = encoder.connection().remote().createStream(streamId, true); } catch (Http2Exception e) { resetUnknownStream(ctx, streamId, http2Ex.error().code(), ctx.newPromise()); return; if (stream != null && !stream.isHeadersSent()) { try { handleServerHeaderDecodeSizeError(ctx, stream); if (!outbound || connection().local().mayHaveCreatedStream(streamId)) { resetUnknownStream(ctx, streamId, http2Ex.error().code(), ctx.newPromise()); resetStream(ctx, stream, http2Ex.error().code(), ctx.newPromise());
final ByteBuf debugData, ChannelPromise promise) { try { promise = promise.unvoid(); final Http2Connection connection = connection(); if (connection().goAwaySent()) { if (lastStreamId == connection().remote().lastStreamKnownByPeer()) { return promise.setSuccess(); if (lastStreamId > connection.remote().lastStreamKnownByPeer()) { throw connectionError(PROTOCOL_ERROR, "Last stream identifier must not increase between " + "sending multiple GOAWAY frames (was '%d', is '%d').", connection.remote().lastStreamKnownByPeer(), lastStreamId); } catch (Throwable cause) { // Make sure to catch Throwable because we are doing a retain() in this method. debugData.release(); return promise.setFailure(cause);
@Override protected ChannelFuture doWriteHeaders(int id, int streamId, HttpHeaders headers, boolean endStream) { final Http2Connection conn = encoder.connection(); final boolean server = conn.isServer(); if (isStreamPresentAndWritable(streamId)) { // Writing to an existing stream. return encoder.writeHeaders(ctx, streamId, ArmeriaHttpUtil.toNettyHttp2(headers, server), 0, endStream, ctx.newPromise()); } if (server) { // One of the following cases: // - Stream has been closed already. // - (bug) Server tried to send a response HEADERS frame before receiving a request HEADERS frame. return newFailedFuture(ClosedPublisherException.get()); } if (conn.local().mayHaveCreatedStream(streamId)) { // Stream has been closed. return newFailedFuture(ClosedPublisherException.get()); } // Client starts a new stream. return encoder.writeHeaders( ctx, streamId, ArmeriaHttpUtil.toNettyHttp2(headers, server), 0, endStream, ctx.newPromise()); }
@Override public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int streamDependency, short weight, boolean exclusive, int padding, boolean endOfStream) throws Http2Exception { Http2Stream stream = connection.stream(streamId); boolean allowHalfClosedRemote = false; if (stream == null && !connection.streamMayHaveExisted(streamId)) { stream = connection.remote().createStream(streamId, endOfStream); allowHalfClosedRemote = stream.state() == HALF_CLOSED_REMOTE; boolean isInformational = !connection.isServer() && HttpStatusClass.valueOf(headers.status()) == INFORMATIONAL; if ((isInformational || !endOfStream) && stream.isHeadersReceived() || stream.isTrailersReceived()) { throw streamError(streamId, PROTOCOL_ERROR, "Stream %d received too many headers EOS: %s state: %s", stream.headersReceived(isInformational); encoder.flowController().updateDependencyTree(streamId, streamDependency, weight, exclusive); lifecycleManager.closeStreamRemote(stream, ctx.newSucceededFuture());
/** * Close the remote endpoint with with a {@code GO_AWAY} frame. Does <strong>not</strong> flush * immediately, this is the responsibility of the caller. */ private ChannelFuture goAway(ChannelHandlerContext ctx, Http2Exception cause) { long errorCode = cause != null ? cause.error().code() : NO_ERROR.code(); int lastKnownStream = connection().remote().lastStreamCreated(); return goAway(ctx, lastKnownStream, errorCode, Http2CodecUtil.toByteBuf(ctx, cause), ctx.newPromise()); }
private void onStreamActive0(Http2Stream stream) { if (connection().local().isValidStreamId(stream.id())) { return; } DefaultHttp2FrameStream stream2 = newStream().setStreamAndProperty(streamKey, stream); onHttp2StreamStateChanged(ctx, stream2); }
final boolean consumeBytes(int streamId, int bytes) throws Http2Exception { Http2Stream stream = connection().stream(streamId); // Upgraded requests are ineligible for stream control. We add the null check // in case the stream has been deregistered. if (stream != null && streamId == Http2CodecUtil.HTTP_UPGRADE_STREAM_ID) { Boolean upgraded = stream.getProperty(upgradeKey); if (Boolean.TRUE.equals(upgraded)) { return false; } } return connection().local().flowController().consumeBytes(stream, bytes); }
@Override public void createStream(Handler<AsyncResult<HttpClientStream>> completionHandler) { Future<HttpClientStream> fut; synchronized (this) { Http2Connection conn = handler.connection(); try { int id = conn.local().lastStreamCreated() == 0 ? 1 : conn.local().lastStreamCreated() + 2; Http2ClientStream stream = createStream(conn.local().createStream(id, false)); fut = Future.succeededFuture(stream); } catch (Exception e) { fut = Future.failedFuture(e); } } completionHandler.handle(fut); }
private void increaseInitialConnectionWindow(int deltaBytes) throws Http2Exception { // The LocalFlowController is responsible for detecting over/under flow. connection().local().flowController().incrementWindowSize(connection().connectionStream(), deltaBytes); }