/* * 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.buffer.ByteBufAllocator; import io.netty.buffer.CompositeByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http2.Http2Connection; import io.netty.handler.codec.http2.Http2Exception; import io.netty.handler.codec.http2.Http2Flags; import io.netty.handler.codec.http2.Http2FrameListener; import io.netty.handler.codec.http2.Http2Headers; import io.netty.handler.codec.http2.Http2Settings; import io.netty.handler.codec.http2.Http2Stream; import io.netty.util.collection.IntObjectHashMap; import io.netty.util.collection.IntObjectMap; import io.vertx.codegen.annotations.Nullable; import io.vertx.core.AsyncResult; import io.vertx.core.Context; import io.vertx.core.Future; import io.vertx.core.Handler; import io.vertx.core.VertxException; import io.vertx.core.buffer.Buffer; import io.vertx.core.http.GoAway; import io.vertx.core.http.HttpConnection; import io.vertx.core.impl.ContextImpl; import io.vertx.core.impl.VertxInternal; import io.vertx.core.net.NetSocket; import io.vertx.core.net.impl.ConnectionBase; import java.util.ArrayDeque; import java.util.Map; import java.util.Objects; /** * @author <a href="mailto:julien@julienviet.com">Julien Viet</a> */ abstract class Http2ConnectionBase extends ConnectionBase implements Http2FrameListener, HttpConnection { /** * Return a buffer from HTTP/2 codec that Vert.x can use: * * - if it's a direct buffer (coming likely from OpenSSL) : we get a heap buffer version * - if it's a composite buffer we do the same * - otherwise we increase the ref count */ static ByteBuf safeBuffer(ByteBuf buf, ByteBufAllocator allocator) { if (buf == Unpooled.EMPTY_BUFFER) { return buf; } if (buf.isDirect() || buf instanceof CompositeByteBuf) { if (buf.isReadable()) { ByteBuf buffer = allocator.heapBuffer(buf.readableBytes()); buffer.writeBytes(buf); return buffer; } else { return Unpooled.EMPTY_BUFFER; } } return buf.retain(); } protected final IntObjectMap<VertxHttp2Stream> streams = new IntObjectHashMap<>(); protected ChannelHandlerContext handlerContext; protected final Channel channel; protected final VertxHttp2ConnectionHandler handler; private boolean shutdown; private Handler<io.vertx.core.http.Http2Settings> clientSettingsHandler; private final ArrayDeque<Runnable> updateSettingsHandlers = new ArrayDeque<>(4); private final ArrayDeque<Handler<AsyncResult<Buffer>>> pongHandlers = new ArrayDeque<>(); private Http2Settings serverSettings = new Http2Settings(); private Handler<GoAway> goAwayHandler; private Handler<Void> shutdownHandler; private Handler<Buffer> pingHandler; private boolean closed; private int windowSize; public Http2ConnectionBase(Channel channel, ContextImpl context, VertxHttp2ConnectionHandler handler) { super((VertxInternal) context.owner(), channel, context); this.channel = channel; this.handlerContext = channel.pipeline().context(handler); this.handler = handler; this.windowSize = handler.connection().local().flowController().windowSize(handler.connection().connectionStream()); } void setHandlerContext(ChannelHandlerContext handlerContext) { this.handlerContext = handlerContext; } VertxInternal vertx() { return vertx; } NetSocket toNetSocket(VertxHttp2Stream stream) { VertxHttp2NetSocket<Http2ConnectionBase> rempl = new VertxHttp2NetSocket<>(this, stream.stream, !stream.isNotWritable()); streams.put(stream.stream.id(), rempl); return rempl; } @Override public synchronized void handleClosed() { closed = true; super.handleClosed(); } @Override public ContextImpl getContext() { return super.getContext(); } @Override protected void handleInterestedOpsChanged() { // Handled by HTTP/2 } synchronized boolean isClosed() { return closed; } synchronized void onConnectionError(Throwable cause) { synchronized (this) { for (VertxHttp2Stream stream : streams.values()) { context.runOnContext(v -> { synchronized (Http2ConnectionBase.this) { stream.handleException(cause); } }); } handleException(cause); } } synchronized void onStreamError(int streamId, Throwable cause) { VertxHttp2Stream stream = streams.get(streamId); if (stream != null) { stream.handleException(cause); } } synchronized void onStreamwritabilityChanged(Http2Stream s) { VertxHttp2Stream stream = streams.get(s.id()); if (stream != null) { context.executeFromIO(stream::onWritabilityChanged); } } synchronized void onStreamClosed(Http2Stream stream) { checkShutdownHandler(); VertxHttp2Stream removed = streams.remove(stream.id()); if (removed != null) { context.executeFromIO(() -> { removed.handleClose(); }); } } void onGoAwaySent(int lastStreamId, long errorCode, ByteBuf debugData) { } synchronized void onGoAwayReceived(int lastStreamId, long errorCode, ByteBuf debugData) { Handler<GoAway> handler = goAwayHandler; if (handler != null) { Buffer buffer = Buffer.buffer(debugData); context.executeFromIO(() -> { handler.handle(new GoAway().setErrorCode(errorCode).setLastStreamId(lastStreamId).setDebugData(buffer)); }); } checkShutdownHandler(); } // Http2FrameListener @Override public void onPriorityRead(ChannelHandlerContext ctx, int streamId, int streamDependency, short weight, boolean exclusive) { } @Override public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int streamDependency, short weight, boolean exclusive, int padding, boolean endOfStream) throws Http2Exception { onHeadersRead(ctx, streamId, headers, padding, endOfStream); } @Override public synchronized void onSettingsAckRead(ChannelHandlerContext ctx) { Runnable handler = updateSettingsHandlers.poll(); if (handler != null) { // No need to run on a particular context it shall be done by the handler instead handler.run(); } } @Override public synchronized void onSettingsRead(ChannelHandlerContext ctx, Http2Settings settings) { Handler<io.vertx.core.http.Http2Settings> handler = clientSettingsHandler; if (handler != null) { context.executeFromIO(() -> { handler.handle(HttpUtils.toVertxSettings(settings)); }); } } @Override public synchronized void onPingRead(ChannelHandlerContext ctx, ByteBuf data) { Handler<Buffer> handler = pingHandler; if (handler != null) { Buffer buff = Buffer.buffer(safeBuffer(data, ctx.alloc())); context.executeFromIO(() -> { handler.handle(buff); }); } } @Override public synchronized void onPingAckRead(ChannelHandlerContext ctx, ByteBuf data) { Handler<AsyncResult<Buffer>> handler = pongHandlers.poll(); if (handler != null) { context.executeFromIO(() -> { Buffer buff = Buffer.buffer(safeBuffer(data, ctx.alloc())); handler.handle(Future.succeededFuture(buff)); }); } } @Override public void onPushPromiseRead(ChannelHandlerContext ctx, int streamId, int promisedStreamId, Http2Headers headers, int padding) throws Http2Exception { } @Override public void onGoAwayRead(ChannelHandlerContext ctx, int lastStreamId, long errorCode, ByteBuf debugData) { } @Override public void onWindowUpdateRead(ChannelHandlerContext ctx, int streamId, int windowSizeIncrement) { } @Override public synchronized void onUnknownFrame(ChannelHandlerContext ctx, byte frameType, int streamId, Http2Flags flags, ByteBuf payload) { VertxHttp2Stream req = streams.get(streamId); if (req != null) { Buffer buff = Buffer.buffer(safeBuffer(payload, ctx.alloc())); context.executeFromIO(() -> { req.handleCustomFrame(frameType, flags.value(), buff); }); } } @Override public synchronized void onRstStreamRead(ChannelHandlerContext ctx, int streamId, long errorCode) { VertxHttp2Stream req = streams.get(streamId); if (req != null) { context.executeFromIO(() -> { req.onResetRead(errorCode); }); } } @Override public synchronized int onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding, boolean endOfStream) { int[] consumed = { padding }; VertxHttp2Stream req = streams.get(streamId); if (req != null) { data = safeBuffer(data, ctx.alloc()); Buffer buff = Buffer.buffer(data); context.executeFromIO(() -> { int len = buff.length(); if (req.onDataRead(buff)) { consumed[0] += len; } }); if (endOfStream) { context.executeFromIO(req::onEnd); } } return consumed[0]; } @Override public int getWindowSize() { return windowSize; } @Override public HttpConnection setWindowSize(int windowSize) { try { Http2Stream stream = handler.encoder().connection().connectionStream(); int delta = windowSize - this.windowSize; handler.decoder().flowController().incrementWindowSize(stream, delta); this.windowSize = windowSize; return this; } catch (Http2Exception e) { throw new VertxException(e); } } @Override public synchronized HttpConnection goAway(long errorCode, int lastStreamId, Buffer debugData) { if (errorCode < 0) { throw new IllegalArgumentException(); } if (lastStreamId < 0) { throw new IllegalArgumentException(); } handler.writeGoAway(errorCode, lastStreamId, debugData != null ? debugData.getByteBuf() : Unpooled.EMPTY_BUFFER); return this; } @Override public synchronized HttpConnection goAwayHandler(Handler<GoAway> handler) { goAwayHandler = handler; return this; } @Override public synchronized HttpConnection shutdownHandler(Handler<Void> handler) { shutdownHandler = handler; return this; } @Override public synchronized HttpConnection shutdown(long timeout) { if (timeout < 0) { throw new IllegalArgumentException("Invalid timeout value " + timeout); } handler.gracefulShutdownTimeoutMillis(timeout); channel.close(); return this; } @Override public HttpConnection shutdown() { return shutdown(30000); } @Override public Http2ConnectionBase closeHandler(Handler<Void> handler) { return (Http2ConnectionBase) super.closeHandler(handler); } @Override public void close() { endReadAndFlush(); shutdown(0L); } @Override public synchronized HttpConnection remoteSettingsHandler(Handler<io.vertx.core.http.Http2Settings> handler) { clientSettingsHandler = handler; return this; } @Override public synchronized io.vertx.core.http.Http2Settings remoteSettings() { io.vertx.core.http.Http2Settings a = new io.vertx.core.http.Http2Settings(); a.setPushEnabled(handler.connection().remote().allowPushTo()); a.setMaxConcurrentStreams((long) handler.connection().local().maxActiveStreams()); a.setMaxHeaderListSize(handler.encoder().configuration().headersConfiguration().maxHeaderListSize()); a.setHeaderTableSize(handler.encoder().configuration().headersConfiguration().maxHeaderTableSize()); a.setMaxFrameSize(handler.encoder().configuration().frameSizePolicy().maxFrameSize()); a.setInitialWindowSize(handler.encoder().flowController().initialWindowSize()); return a; } @Override public synchronized io.vertx.core.http.Http2Settings settings() { return HttpUtils.toVertxSettings(serverSettings); } @Override public HttpConnection updateSettings(io.vertx.core.http.Http2Settings settings) { return updateSettings(settings, null); } @Override public HttpConnection updateSettings(io.vertx.core.http.Http2Settings settings, @Nullable Handler<AsyncResult<Void>> completionHandler) { Http2Settings settingsUpdate = HttpUtils.fromVertxSettings(settings); updateSettings(settingsUpdate, completionHandler); return this; } protected synchronized void updateSettings(Http2Settings settingsUpdate, Handler<AsyncResult<Void>> completionHandler) { Context completionContext = completionHandler != null ? context.owner().getOrCreateContext() : null; Http2Settings current = handler.decoder().localSettings(); for (Map.Entry<Character, Long> entry : current.entrySet()) { Character key = entry.getKey(); if (Objects.equals(settingsUpdate.get(key), entry.getValue())) { settingsUpdate.remove(key); } } handler.writeSettings(settingsUpdate).addListener(fut -> { if (fut.isSuccess()) { synchronized (Http2ConnectionBase.this) { updateSettingsHandlers.add(() -> { serverSettings.putAll(settingsUpdate); if (completionHandler != null) { completionContext.runOnContext(v -> { completionHandler.handle(Future.succeededFuture()); }); } }); } } else { if (completionHandler != null) { completionContext.runOnContext(v -> { completionHandler.handle(Future.failedFuture(fut.cause())); }); } } }); } @Override public synchronized HttpConnection ping(Buffer data, Handler<AsyncResult<Buffer>> pongHandler) { if (data.length() != 8) { throw new IllegalArgumentException("Ping data must be exactly 8 bytes"); } handler.writePing(data.getByteBuf()).addListener(fut -> { if (fut.isSuccess()) { pongHandlers.add(pongHandler); } else { pongHandler.handle(Future.failedFuture(fut.cause())); } }); return this; } @Override public synchronized HttpConnection pingHandler(Handler<Buffer> handler) { pingHandler = handler; return this; } @Override public synchronized Http2ConnectionBase exceptionHandler(Handler<Throwable> handler) { return (Http2ConnectionBase) super.exceptionHandler(handler); } /** * @return the Netty channel - for internal usage only */ public Channel channel() { return channel; } // Private private void checkShutdownHandler() { if (!shutdown) { Http2Connection conn = handler.connection(); if ((conn.goAwayReceived() || conn.goAwaySent()) && conn.numActiveStreams() == 0) { shutdown = true; Handler<Void> handler = shutdownHandler; if (handler != null) { context.executeFromIO(() -> { shutdownHandler.handle(null); }); } } } } }