All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.inverno.mod.http.server.internal.http2.Http2Connection Maven / Gradle / Ivy

/*
 * Copyright 2020 Jeremy KUHN
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.inverno.mod.http.server.internal.http2;

import io.inverno.mod.base.converter.ObjectConverter;
import io.inverno.mod.http.base.ExchangeContext;
import io.inverno.mod.http.base.Parameter;
import io.inverno.mod.http.base.header.HeaderService;
import io.inverno.mod.http.server.ErrorExchange;
import io.inverno.mod.http.server.Exchange;
import io.inverno.mod.http.server.HttpServerConfiguration;
import io.inverno.mod.http.server.Part;
import io.inverno.mod.http.server.ServerController;
import io.inverno.mod.http.server.internal.AbstractExchange;
import io.inverno.mod.http.server.internal.multipart.MultipartDecoder;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http2.DelegatingDecompressorFrameListener;
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.Http2Error;
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.netty.util.concurrent.PromiseCombiner;
import reactor.core.publisher.Sinks.EmitResult;

/**
 * 

* HTTP/2 connection. *

* *

* This is the entry point of a HTTP client connection to the HTTP server using version 2 of the HTTP protocol. *

* * @author Jeremy Kuhn * @since 1.0 */ public class Http2Connection extends Http2ConnectionHandler implements Http2FrameListener, io.netty.handler.codec.http2.Http2Connection.Listener { private final HttpServerConfiguration configuration; private final ServerController, ErrorExchange> controller; private final HeaderService headerService; private final ObjectConverter parameterConverter; private final MultipartDecoder urlEncodedBodyDecoder; private final MultipartDecoder multipartBodyDecoder; private final Http2ContentEncodingResolver contentEncodingResolver; private final IntObjectMap serverStreams; /** *

* Creates a HTTP/2 connection. *

* * @param configuration the HTTP server configuration * @param decoder HTTP/2 connection decoder * @param encoder HTTP/2 connection encoder * @param initialSettings HTTP/2 initial settings * @param controller the server controller * @param headerService the header service * @param parameterConverter a string object converter * @param urlEncodedBodyDecoder the application/x-www-form-urlencoded body decoder * @param multipartBodyDecoder the multipart/form-data body decoder * @param contentEncodingResolver a content encoding resolver */ public Http2Connection( HttpServerConfiguration configuration, Http2ConnectionDecoder decoder, Http2ConnectionEncoder encoder, Http2Settings initialSettings, ServerController, ErrorExchange> controller, HeaderService headerService, ObjectConverter parameterConverter, MultipartDecoder urlEncodedBodyDecoder, MultipartDecoder multipartBodyDecoder, Http2ContentEncodingResolver contentEncodingResolver) { super(decoder, encoder, initialSettings); this.configuration = configuration; this.controller = controller; this.headerService = headerService; this.parameterConverter = parameterConverter; this.urlEncodedBodyDecoder = urlEncodedBodyDecoder; this.multipartBodyDecoder = multipartBodyDecoder; this.contentEncodingResolver = contentEncodingResolver; this.serverStreams = new IntObjectHashMap<>(); this.connection().addListener(this); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { PromiseCombiner finalPromise = new PromiseCombiner(ctx.executor()); for(Http2Exchange exchange : this.serverStreams.values()) { exchange.dispose(); ChannelPromise errorPromise = ctx.newPromise(); exchange.finalizeExchange(errorPromise, null); errorPromise.tryFailure(cause); finalPromise.add((ChannelFuture)errorPromise); } finalPromise.finish(ctx.newPromise().addListener(ChannelFutureListener.CLOSE)); } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { super.channelInactive(ctx); } @Override public void onError(ChannelHandlerContext ctx, boolean outbound, Throwable cause) { super.onError(ctx, outbound, cause); ctx.close(); } @Override public int onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding, boolean endOfStream) throws Http2Exception { // TODO flow control? int processed = data.readableBytes() + padding; Http2Exchange serverStream = this.serverStreams.get(streamId); if (serverStream != null) { serverStream.request().data().ifPresent(sink -> { data.retain(); if(sink.tryEmitNext(data) != EmitResult.OK) { data.release(); } }); if (endOfStream) { serverStream.request().data().ifPresent(sink -> sink.tryEmitComplete()); } } else { // TODO this should never happen? throw new IllegalStateException("Unable to push data to unmanaged stream " + streamId); } return processed; } @Override public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int padding, boolean endOfStream) throws Http2Exception { Http2Exchange exchange = this.serverStreams.get(streamId); if (exchange == null) { Http2Exchange streamExchange = new Http2Exchange(ctx, this.connection().stream(streamId), headers, this.encoder(), this.headerService, this.parameterConverter, this.urlEncodedBodyDecoder, this.multipartBodyDecoder, this.controller); if(this.configuration.compression_enabled()) { String acceptEncoding = headers.get(HttpHeaderNames.ACCEPT_ENCODING) != null ? headers.get(HttpHeaderNames.ACCEPT_ENCODING).toString() : null; if(acceptEncoding != null) { streamExchange.setContentEncoding(this.contentEncodingResolver.resolve(acceptEncoding)); } } this.serverStreams.put(streamId, streamExchange); if (endOfStream) { streamExchange.request().data().ifPresent(sink -> sink.tryEmitComplete()); } streamExchange.start(new AbstractExchange.Handler() { @Override public void exchangeError(ChannelHandlerContext ctx, Throwable t) { Http2Connection.this.resetStream(ctx, streamId, Http2Error.INTERNAL_ERROR.code(), ctx.voidPromise()); } }); } else { // Continuation frame ((Http2RequestHeaders) exchange.request().headers()).getUnderlyingHeaders().add(headers); } } @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 void onPriorityRead(ChannelHandlerContext ctx, int streamId, int streamDependency, short weight, boolean exclusive) throws Http2Exception { } @Override public void onRstStreamRead(ChannelHandlerContext ctx, int streamId, long errorCode) throws Http2Exception { Http2Exchange serverStream = this.serverStreams.remove(streamId); if (serverStream != null) { serverStream.dispose(); } else { // TODO this should never happen? // System.err.println("Unable to reset unmanaged stream " + streamId); // throw new IllegalStateException("Unable to reset unmanaged stream " + streamId); } } @Override public void onSettingsAckRead(ChannelHandlerContext ctx) throws Http2Exception { } @Override public void onSettingsRead(ChannelHandlerContext ctx, Http2Settings settings) throws Http2Exception { if (this.configuration.decompression_enabled()) { this.decoder().frameListener(new DelegatingDecompressorFrameListener(decoder().connection(), this)); } else { this.decoder().frameListener(this); } } @Override public void onPingRead(ChannelHandlerContext ctx, long data) throws Http2Exception { } @Override public void onPingAckRead(ChannelHandlerContext ctx, long data) throws Http2Exception { } @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) throws Http2Exception { } @Override public void onWindowUpdateRead(ChannelHandlerContext ctx, int streamId, int windowSizeIncrement) throws Http2Exception { } @Override public void onUnknownFrame(ChannelHandlerContext ctx, byte frameType, int streamId, Http2Flags flags, ByteBuf payload) throws Http2Exception { } @Override public void onStreamAdded(Http2Stream stream) { } @Override public void onStreamActive(Http2Stream stream) { } @Override public void onStreamHalfClosed(Http2Stream stream) { } @Override public void onStreamClosed(Http2Stream stream) { // TODO same issue as for Http1xChannelHandler#channelInactive // Canceling subscriptions here might raise other issues so we prefer to let things crash when one will try to read or write to a closed stream /*Http2Exchange serverStream = this.serverStreams.remove(stream.id()); if (serverStream != null) { serverStream.dispose(); } else { // TODO this should never happen? // System.err.println("Unable to reset unmanaged stream " + stream.id()); // throw new IllegalStateException("Unable to reset unmanaged stream " + stream.id()); }*/ } @Override public void onStreamRemoved(Http2Stream stream) { } @Override public void onGoAwaySent(int lastStreamId, long errorCode, ByteBuf debugData) { } @Override public void onGoAwayReceived(int lastStreamId, long errorCode, ByteBuf debugData) { } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy