/** * Returns the client preface string if this is a client connection, otherwise returns {@code null}. */ private static ByteBuf clientPrefaceString(Http2Connection connection) { return connection.isServer() ? connectionPrefaceBuf() : null; }
@Override public final void handlerAdded(ChannelHandlerContext ctx) throws Exception { this.ctx = ctx; super.handlerAdded(ctx); handlerAdded0(ctx); // Must be after Http2ConnectionHandler does its initialization in handlerAdded above. // The server will not send a connection preface so we are good to send a window update. Http2Connection connection = connection(); if (connection.isServer()) { tryExpandConnectionFlowControlWindow(connection); } }
/** * 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); } } }
@Override public ChannelFuture writeSettings(ChannelHandlerContext ctx, Http2Settings settings, ChannelPromise promise) { outstandingLocalSettingsQueue.add(settings); try { Boolean pushEnabled = settings.pushEnabled(); if (pushEnabled != null && connection.isServer()) { throw connectionError(PROTOCOL_ERROR, "Server sending SETTINGS frame with ENABLE_PUSH specified"); } } catch (Throwable e) { return promise.setFailure(e); } return frameWriter.writeSettings(ctx, settings, promise); }
@Override public final void handlerAdded(ChannelHandlerContext ctx) throws Exception { this.ctx = ctx; super.handlerAdded(ctx); handlerAdded0(ctx); // Must be after Http2ConnectionHandler does its initialization in handlerAdded above. // The server will not send a connection preface so we are good to send a window update. Http2Connection connection = connection(); if (connection.isServer()) { tryExpandConnectionFlowControlWindow(connection); } }
/** * Create a new {@link FullHttpMessage} based upon the current connection parameters * * @param stream The stream to create a message for * @param headers The headers associated with {@code stream} * @param validateHttpHeaders * <ul> * <li>{@code true} to validate HTTP headers in the http-codec</li> * <li>{@code false} not to validate HTTP headers in the http-codec</li> * </ul> * @param alloc The {@link ByteBufAllocator} to use to generate the content of the message * @throws Http2Exception */ protected FullHttpMessage newMessage(Http2Stream stream, Http2Headers headers, boolean validateHttpHeaders, ByteBufAllocator alloc) throws Http2Exception { return connection.isServer() ? HttpConversionUtil.toFullHttpRequest(stream.id(), headers, alloc, validateHttpHeaders) : HttpConversionUtil.toFullHttpResponse(stream.id(), headers, alloc, validateHttpHeaders); }
@Override public void write(ChannelHandlerContext ctx, int allowedBytes) { boolean isInformational = validateHeadersSentState(stream, headers, connection.isServer(), endOfStream); if (promise.isVoid()) { promise = ctx.newPromise(); } promise.addListener(this); ChannelFuture f = frameWriter.writeHeaders(ctx, stream.id(), headers, streamDependency, weight, exclusive, padding, endOfStream, promise); // Writing headers may fail during the encode state if they violate HPACK limits. Throwable failureCause = f.cause(); if (failureCause == null) { // This just sets internal stream state which is used elsewhere in the codec and doesn't // necessarily mean the write will complete successfully. stream.headersSent(isInformational); } }
@Override public final void handlerAdded(ChannelHandlerContext ctx) throws Exception { this.ctx = ctx; super.handlerAdded(ctx); handlerAdded0(ctx); // Must be after Http2ConnectionHandler does its initialization in handlerAdded above. // The server will not send a connection preface so we are good to send a window update. Http2Connection connection = connection(); if (connection.isServer()) { tryExpandConnectionFlowControlWindow(connection); } }
/** * Handles the client-side (cleartext) upgrade from HTTP to HTTP/2. * Reserves local stream 1 for the HTTP/2 response. */ public void onHttpClientUpgrade() throws Http2Exception { if (connection().isServer()) { throw connectionError(PROTOCOL_ERROR, "Client-side HTTP upgrade requested for a server"); } if (!prefaceSent()) { // If the preface was not sent yet it most likely means the handler was not added to the pipeline before // calling this method. throw connectionError(INTERNAL_ERROR, "HTTP upgrade must occur after preface was sent"); } if (decoder.prefaceReceived()) { throw connectionError(PROTOCOL_ERROR, "HTTP upgrade must occur before HTTP/2 preface is received"); } // Create a local stream used for the HTTP cleartext upgrade. connection().local().createStream(HTTP_UPGRADE_STREAM_ID, true); }
/** * Handles the server-side (cleartext) upgrade from HTTP to HTTP/2. * @param settings the settings for the remote endpoint. */ public void onHttpServerUpgrade(Http2Settings settings) throws Http2Exception { if (!connection().isServer()) { throw connectionError(PROTOCOL_ERROR, "Server-side HTTP upgrade requested for a client"); } if (!prefaceSent()) { // If the preface was not sent yet it most likely means the handler was not added to the pipeline before // calling this method. throw connectionError(INTERNAL_ERROR, "HTTP upgrade must occur after preface was sent"); } if (decoder.prefaceReceived()) { throw connectionError(PROTOCOL_ERROR, "HTTP upgrade must occur before HTTP/2 preface is received"); } // Apply the settings but no ACK is necessary. encoder.remoteSettings(settings); // Create a stream in the half-closed state. connection().remote().createStream(HTTP_UPGRADE_STREAM_ID, true); }
Http2FrameSizePolicy outboundFrameSizePolicy = config.frameSizePolicy(); if (pushEnabled != null) { if (!connection.isServer() && pushEnabled) { throw connectionError(PROTOCOL_ERROR, "Client received a value of ENABLE_PUSH specified to other than 0");
boolean isInformational = validateHeadersSentState(stream, headers, connection.isServer(), endOfStream); if (endOfStream) { final Http2Stream finalStream = stream;
private T buildFromConnection(Http2Connection connection) { Long maxHeaderListSize = initialSettings.maxHeaderListSize(); Http2FrameReader reader = new DefaultHttp2FrameReader(new DefaultHttp2HeadersDecoder(isValidateHeaders(), maxHeaderListSize == null ? DEFAULT_HEADER_LIST_SIZE : maxHeaderListSize, initialHuffmanDecodeCapacity)); Http2FrameWriter writer = encoderIgnoreMaxHeaderListSize == null ? new DefaultHttp2FrameWriter(headerSensitivityDetector()) : new DefaultHttp2FrameWriter(headerSensitivityDetector(), encoderIgnoreMaxHeaderListSize); if (frameLogger != null) { reader = new Http2InboundFrameLogger(reader, frameLogger); writer = new Http2OutboundFrameLogger(writer, frameLogger); } Http2ConnectionEncoder encoder = new DefaultHttp2ConnectionEncoder(connection, writer); boolean encoderEnforceMaxConcurrentStreams = encoderEnforceMaxConcurrentStreams(); if (encoderEnforceMaxConcurrentStreams) { if (connection.isServer()) { encoder.close(); reader.close(); throw new IllegalArgumentException( "encoderEnforceMaxConcurrentStreams: " + encoderEnforceMaxConcurrentStreams + " not supported for server"); } encoder = new StreamBufferingEncoder(encoder); } Http2ConnectionDecoder decoder = new DefaultHttp2ConnectionDecoder(connection, encoder, reader); return buildFromCodec(decoder, encoder); }
@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()); }
final Http2FrameSizePolicy frameSizePolicy = config.frameSizePolicy(); if (pushEnabled != null) { if (connection.isServer()) { throw connectionError(PROTOCOL_ERROR, "Server sending SETTINGS frame with ENABLE_PUSH specified");
@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; }
Http2Headers headers, int padding) throws Http2Exception { if (connection().isServer()) { throw connectionError(PROTOCOL_ERROR, "A client cannot push.");
connection().isServer()) {
boolean isInformational = !connection.isServer() && HttpStatusClass.valueOf(headers.status()) == INFORMATIONAL; if ((isInformational || !endOfStream) && stream.isHeadersReceived() || stream.isTrailersReceived()) {
@Override public final void handlerAdded(ChannelHandlerContext ctx) throws Exception { this.ctx = ctx; super.handlerAdded(ctx); handlerAdded0(ctx); // Must be after Http2ConnectionHandler does its initialization in handlerAdded above. // The server will not send a connection preface so we are good to send a window update. Http2Connection connection = connection(); if (connection.isServer()) { tryExpandConnectionFlowControlWindow(connection); } }