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

io.inverno.mod.http.server.internal.http1x.Http1xConnection 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.http1x;

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.base.internal.ws.GenericWebSocketFrame;
import io.inverno.mod.http.base.internal.ws.GenericWebSocketMessage;
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.Unpooled;
import io.netty.channel.ChannelDuplexHandler;
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.TooLongFrameException;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpObject;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.LastHttpContent;
import io.netty.handler.codec.http.websocketx.CorruptedWebSocketFrameException;
import io.netty.handler.codec.http.websocketx.WebSocketHandshakeException;
import reactor.core.publisher.Sinks;

/**
 * 

* HTTP/1.x connection. *

* *

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

* * @author Jeremy Kuhn * @since 1.0 */ public class Http1xConnection extends ChannelDuplexHandler implements Http1xConnectionEncoder, AbstractExchange.Handler { 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 GenericWebSocketFrame.GenericFactory webSocketFrameFactory; private final GenericWebSocketMessage.GenericFactory webSocketMessageFactory; private Http1xExchange requestingExchange; private Http1xExchange respondingExchange; private Http1xExchange exchangeQueue; private boolean read; private boolean flush; /** *

* Creates a HTTP1.x connection. *

* * @param configuration the server configuration * @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 webSocketFrameFactory the WebSocket frame factory * @param webSocketMessageFactory the WebSocket message factory */ public Http1xConnection( HttpServerConfiguration configuration, ServerController, ErrorExchange> controller, HeaderService headerService, ObjectConverter parameterConverter, MultipartDecoder urlEncodedBodyDecoder, MultipartDecoder multipartBodyDecoder, GenericWebSocketFrame.GenericFactory webSocketFrameFactory, GenericWebSocketMessage.GenericFactory webSocketMessageFactory) { this.configuration = configuration; this.controller = controller; this.headerService = headerService; this.parameterConverter = parameterConverter; this.urlEncodedBodyDecoder = urlEncodedBodyDecoder; this.multipartBodyDecoder = multipartBodyDecoder; this.webSocketFrameFactory = webSocketFrameFactory; this.webSocketMessageFactory = webSocketMessageFactory; } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { if(msg instanceof HttpObject) { this.read = true; if(msg instanceof HttpRequest) { HttpRequest httpRequest = (HttpRequest)msg; if(this.validateHttpObject(ctx, httpRequest.protocolVersion(), httpRequest)) { return; } this.requestingExchange = new Http1xExchange( this.configuration, ctx, httpRequest.protocolVersion(), httpRequest, this, this.headerService, this.parameterConverter, this.urlEncodedBodyDecoder, this.multipartBodyDecoder, this.controller, this.webSocketFrameFactory, this.webSocketMessageFactory ); if(this.exchangeQueue == null) { this.exchangeQueue = this.requestingExchange; this.requestingExchange.start(this); } else { this.exchangeQueue.next = this.requestingExchange; this.exchangeQueue = this.requestingExchange; } } else if(this.requestingExchange != null) { HttpVersion version = this.requestingExchange.version; if(msg == LastHttpContent.EMPTY_LAST_CONTENT) { this.requestingExchange.request().data().ifPresent(sink -> sink.tryEmitComplete()); } else { HttpContent httpContent = (HttpContent)msg; if(this.validateHttpObject(ctx, version, httpContent)) { return; } this.requestingExchange.request().data().ifPresentOrElse( sink -> { if(sink.tryEmitNext(httpContent.content()) != Sinks.EmitResult.OK) { httpContent.release(); } }, () -> httpContent.release() ); if(httpContent instanceof LastHttpContent) { this.requestingExchange.request().data().ifPresent(sink -> sink.tryEmitComplete()); } } } else { // This can happen when an exchange has been disposed before we actually // received all the data in that case we have to dismiss the content and wait // for the next request ((HttpContent)msg).release(); } } else { // This is required for WebSocket super.channelRead(ctx, msg); } } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { if(this.read) { this.read = false; if(this.flush) { ctx.flush(); this.flush = false; } } } private boolean validateHttpObject(ChannelHandlerContext ctx, HttpVersion version, HttpObject httpObject) throws Exception { if(!httpObject.decoderResult().isFailure()) { return false; } Throwable cause = httpObject.decoderResult().cause(); if (cause instanceof TooLongFrameException) { String causeMsg = cause.getMessage(); HttpResponseStatus status; if(causeMsg.startsWith("An HTTP line is larger than")) { status = HttpResponseStatus.REQUEST_URI_TOO_LONG; } else if(causeMsg.startsWith("HTTP header is larger than")) { status = HttpResponseStatus.REQUEST_HEADER_FIELDS_TOO_LARGE; } else { status = HttpResponseStatus.BAD_REQUEST; } ChannelPromise writePromise = ctx.newPromise(); ctx.write(new DefaultFullHttpResponse(version, status), writePromise); writePromise.addListener(res -> { this.exceptionCaught(ctx, cause); }); } else { this.exceptionCaught(ctx, cause); } return true; } @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { // TODO idle ctx.fireUserEventTriggered(evt); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { if(cause instanceof WebSocketHandshakeException || cause instanceof CorruptedWebSocketFrameException) { // Delegate this to the WebSocket protocol handler super.exceptionCaught(ctx, cause); } else { if(this.respondingExchange != null) { this.respondingExchange.dispose(true); ChannelPromise errorPromise = ctx.newPromise(); this.respondingExchange.finalizeExchange(errorPromise, () -> ctx.close()); errorPromise.tryFailure(cause); } else { ctx.close(); } } } @Override public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception { ctx.close(); } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { // TODO this created DB connections not returned to pool // If the purpose is to clean resources, I think we should see why this happens before the responding exchange response publisher did not finish // one explanation could be that response events are not published on the channel event loop: the connection might be closed/end while the onComplete events hasn't been processed // In any case this is disturbing and not easy to troubleshoot, we'll see in the future if this is a real issue or not /*if(this.respondingExchange != null) { this.respondingExchange.dispose(); }*/ if(this.exchangeQueue != null) { this.exchangeQueue.next = null; } } @Override public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception { // super.channelWritabilityChanged(ctx); } @Override public ChannelFuture writeFrame(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) { if(this.read) { this.flush = true; return ctx.write(msg, promise); } else { return ctx.writeAndFlush(msg, promise); } } @Override public void exchangeStart(ChannelHandlerContext ctx, AbstractExchange exchange) { this.respondingExchange = (Http1xExchange)exchange; } @Override public void exchangeError(ChannelHandlerContext ctx, Throwable t) { // If we get there it means we weren't able to properly handle the error before if(this.flush) { ctx.flush(); } // We have to release data... if(this.respondingExchange.next != null) { this.respondingExchange.next.dispose(true); } // ...and close the connection ctx.close(); } @Override public void exchangeComplete(ChannelHandlerContext ctx) { if(this.respondingExchange.keepAlive) { if(this.respondingExchange.next != null) { this.respondingExchange.next.start(this); } else { this.exchangeQueue = null; this.respondingExchange = null; } } else { if(this.respondingExchange.next != null) { this.respondingExchange.next.dispose(true); } ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy