/* * Copyright (c) 2011-2013 The original author or authors * ------------------------------------------------------ * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * and Apache License v2.0 which accompanies this distribution. * * The Eclipse Public License is available at * http://www.eclipse.org/legal/epl-v10.html * * The Apache License v2.0 is available at * http://www.opensource.org/licenses/apache2.0.php * * You may elect to redistribute this code under either of these licenses. */ package io.vertx.core.http.impl; import io.netty.buffer.ByteBuf; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelPromise; import io.netty.handler.codec.http2.Http2Connection; import io.netty.handler.codec.http2.Http2ConnectionDecoder; import io.netty.handler.codec.http2.Http2ConnectionEncoder; import io.netty.handler.codec.http2.Http2ConnectionHandler; import io.netty.handler.codec.http2.Http2Exception; import io.netty.handler.codec.http2.Http2Flags; import io.netty.handler.codec.http2.Http2Headers; import io.netty.handler.codec.http2.Http2RemoteFlowController; import io.netty.handler.codec.http2.Http2Settings; import io.netty.handler.codec.http2.Http2Stream; import io.netty.handler.timeout.IdleState; import io.netty.handler.timeout.IdleStateEvent; import io.netty.util.concurrent.EventExecutor; import io.vertx.core.AsyncResult; import io.vertx.core.Future; import io.vertx.core.Handler; import java.util.Map; import java.util.function.Function; /** * @author <a href="mailto:julien@julienviet.com">Julien Viet</a> */ class VertxHttp2ConnectionHandler<C extends Http2ConnectionBase> extends Http2ConnectionHandler implements Http2Connection.Listener { private final Map<Channel, ? super C> connectionMap; C connection; private ChannelHandlerContext ctx; public VertxHttp2ConnectionHandler( Map<Channel, ? super C> connectionMap, Http2ConnectionDecoder decoder, Http2ConnectionEncoder encoder, Http2Settings initialSettings, Function<VertxHttp2ConnectionHandler<C>, C> connectionFactory) { super(decoder, encoder, initialSettings); this.connectionMap = connectionMap; this.connection = connectionFactory.apply(this); encoder().flowController().listener(s -> connection.onStreamwritabilityChanged(s)); connection().addListener(this); } @Override public void handlerAdded(ChannelHandlerContext ctx) throws Exception { super.handlerAdded(ctx); this.ctx = ctx; connection.setHandlerContext(ctx); connectionMap.put(ctx.channel(), connection); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { super.exceptionCaught(ctx, cause); ctx.close(); } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { super.channelActive(ctx); } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { super.channelInactive(ctx); connectionMap.remove(ctx.channel()); connection.getContext().executeFromIO(connection::handleClosed); } @Override protected void onConnectionError(ChannelHandlerContext ctx, Throwable cause, Http2Exception http2Ex) { connection.getContext().executeFromIO(() -> { connection.onConnectionError(cause); }); // Default behavior send go away super.onConnectionError(ctx, cause, http2Ex); } @Override protected void onStreamError(ChannelHandlerContext ctx, Throwable cause, Http2Exception.StreamException http2Ex) { connection.getContext().executeFromIO(() -> { connection.onStreamError(http2Ex.streamId(), http2Ex); }); // Default behavior reset stream super.onStreamError(ctx, cause, http2Ex); } @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { try { super.userEventTriggered(ctx, evt); } finally { if (evt instanceof IdleStateEvent && ((IdleStateEvent) evt).state() == IdleState.ALL_IDLE) { ctx.close(); } } } // @Override public void onStreamClosed(Http2Stream stream) { connection.onStreamClosed(stream); } @Override public void onStreamAdded(Http2Stream stream) { } @Override public void onStreamActive(Http2Stream stream) { } @Override public void onStreamHalfClosed(Http2Stream stream) { } @Override public void onStreamRemoved(Http2Stream stream) { } @Override public void onPriorityTreeParentChanged(Http2Stream stream, Http2Stream oldParent) { } @Override public void onPriorityTreeParentChanging(Http2Stream stream, Http2Stream newParent) { } @Override public void onWeightChanged(Http2Stream stream, short oldWeight) { } @Override public void onGoAwaySent(int lastStreamId, long errorCode, ByteBuf debugData) { connection.onGoAwaySent(lastStreamId, errorCode, debugData); } @Override public void onGoAwayReceived(int lastStreamId, long errorCode, ByteBuf debugData) { connection.onGoAwayReceived(lastStreamId, errorCode, debugData); } // void writeHeaders(Http2Stream stream, Http2Headers headers, boolean end) { EventExecutor executor = ctx.executor(); if (executor.inEventLoop()) { _writeHeaders(stream, headers, end); } else { executor.execute(() -> { _writeHeaders(stream, headers, end); }); } } private void _writeHeaders(Http2Stream stream, Http2Headers headers, boolean end) { encoder().writeHeaders(ctx, stream.id(), headers, 0, end, ctx.newPromise());; } void writeData(Http2Stream stream, ByteBuf chunk, boolean end) { EventExecutor executor = ctx.executor(); if (executor.inEventLoop()) { _writeData(stream, chunk, end); } else { executor.execute(() -> { _writeData(stream, chunk, end); }); } } private void _writeData(Http2Stream stream, ByteBuf chunk, boolean end) { encoder().writeData(ctx, stream.id(), chunk, 0, end, ctx.newPromise()); Http2RemoteFlowController controller = encoder().flowController(); if (!controller.isWritable(stream) || end) { try { encoder().flowController().writePendingBytes(); } catch (Http2Exception e) { onError(ctx, e); } } ctx.channel().flush(); } ChannelFuture writePing(ByteBuf data) { ChannelPromise promise = ctx.newPromise(); EventExecutor executor = ctx.executor(); if (executor.inEventLoop()) { _writePing(data, promise); } else { executor.execute(() -> { _writePing(data, promise); }); } return promise; } private void _writePing(ByteBuf data, ChannelPromise promise) { encoder().writePing(ctx, false, data, promise); ctx.channel().flush(); } /** * Consume {@code numBytes} for {@code stream} in the flow controller, this must be called from event loop. */ void consume(Http2Stream stream, int numBytes) { try { boolean windowUpdateSent = decoder().flowController().consumeBytes(stream, numBytes); if (windowUpdateSent) { ctx.channel().flush(); } } catch (Http2Exception e) { onError(ctx, e); } } void writeFrame(Http2Stream stream, byte type, short flags, ByteBuf payload) { EventExecutor executor = ctx.executor(); if (executor.inEventLoop()) { _writeFrame(stream, type, flags, payload); } else { executor.execute(() -> { _writeFrame(stream, type, flags, payload); }); } } private void _writeFrame(Http2Stream stream, byte type, short flags, ByteBuf payload) { encoder().writeFrame(ctx, type, stream.id(), new Http2Flags(flags), payload, ctx.newPromise()); ctx.flush(); } void writeReset(int streamId, long code) { EventExecutor executor = ctx.executor(); if (executor.inEventLoop()) { _writeReset(streamId, code); } else { executor.execute(() -> { _writeReset(streamId, code); }); } } private void _writeReset(int streamId, long code) { encoder().writeRstStream(ctx, streamId, code, ctx.newPromise()); ctx.flush(); } void writeGoAway(long errorCode, int lastStreamId, ByteBuf debugData) { EventExecutor executor = ctx.executor(); if (executor.inEventLoop()) { _writeGoAway(errorCode, lastStreamId, debugData); } else { executor.execute(() -> { _writeGoAway(errorCode, lastStreamId, debugData); }); } } private void _writeGoAway(long errorCode, int lastStreamId, ByteBuf debugData) { encoder().writeGoAway(ctx, lastStreamId, errorCode, debugData, ctx.newPromise()); ctx.flush(); } ChannelFuture writeSettings(Http2Settings settingsUpdate) { ChannelPromise promise = ctx.newPromise(); EventExecutor executor = ctx.executor(); if (executor.inEventLoop()) { _writeSettings(settingsUpdate, promise); } else { executor.execute(() -> { _writeSettings(settingsUpdate, promise); }); } return promise; } private void _writeSettings(Http2Settings settingsUpdate, ChannelPromise promise) { encoder().writeSettings(ctx, settingsUpdate, promise); ctx.flush(); } void writePushPromise(int streamId, Http2Headers headers, Handler<AsyncResult<Integer>> completionHandler) { int promisedStreamId = connection().local().incrementAndGetNextStreamId(); ChannelPromise promise = ctx.newPromise(); promise.addListener(fut -> { if (fut.isSuccess()) { completionHandler.handle(Future.succeededFuture(promisedStreamId)); } else { completionHandler.handle(Future.failedFuture(fut.cause())); } }); EventExecutor executor = ctx.executor(); if (executor.inEventLoop()) { _writePushPromise(streamId, promisedStreamId, headers, promise); } else { executor.execute(() -> { _writePushPromise(streamId, promisedStreamId, headers, promise); }); } } private void _writePushPromise(int streamId, int promisedStreamId, Http2Headers headers, ChannelPromise promise) { encoder().writePushPromise(ctx, streamId, promisedStreamId, headers, 0, promise); } }