package org.webpieces.frontend.impl; import static org.webpieces.httpparser.api.dto.HttpRequest.HttpScheme.HTTPS; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import java.util.Optional; import org.webpieces.data.api.DataWrapper; import org.webpieces.data.api.DataWrapperGenerator; import org.webpieces.data.api.DataWrapperGeneratorFactory; import org.webpieces.frontend.api.FrontendConfig; import org.webpieces.frontend.api.HttpServerSocket; import org.webpieces.httpcommon.api.RequestId; import org.webpieces.httpcommon.api.ResponseSender; import org.webpieces.httpcommon.api.exceptions.HttpClientException; import org.webpieces.httpcommon.api.exceptions.HttpException; import org.webpieces.httpcommon.impl.Http2EngineImpl; import org.webpieces.httpparser.api.HttpParser; import org.webpieces.httpparser.api.Memento; import org.webpieces.httpparser.api.ParseException; import org.webpieces.httpparser.api.UnparsedState; import org.webpieces.httpparser.api.common.Header; import org.webpieces.httpparser.api.common.KnownHeaderName; import org.webpieces.httpparser.api.dto.HttpMessageType; import org.webpieces.httpparser.api.dto.HttpPayload; import org.webpieces.httpparser.api.dto.HttpRequest; import org.webpieces.httpparser.api.dto.HttpResponse; import org.webpieces.httpparser.api.dto.HttpResponseStatus; import org.webpieces.httpparser.api.dto.HttpResponseStatusLine; import org.webpieces.httpparser.api.dto.KnownStatusCode; import org.webpieces.nio.api.channels.Channel; import org.webpieces.nio.api.channels.ChannelSession; import org.webpieces.util.logging.Logger; import org.webpieces.util.logging.LoggerFactory; public class Http11Layer { private static final DataWrapperGenerator generator = DataWrapperGeneratorFactory.createDataWrapperGenerator(); private HttpParser parser; private TimedRequestListener listener; private FrontendConfig config; private static final Logger log = LoggerFactory.getLogger(Http2EngineImpl.class); Http11Layer(HttpParser parser2, TimedRequestListener listener, FrontendConfig config) { this.parser = parser2; this.listener = listener; this.config = config; } void deserialize(Channel channel, ByteBuffer chunk) { List<HttpRequest> parsedRequests = doTheWork(channel, chunk); // TODO: if we get chunks, send these to incomingData.. right now we don't support receiving chunks on the server side. for(HttpRequest req : parsedRequests) { // Check for an HTTP2 upgrade, if not SSL if(!channel.isSslChannel()) { List<Header> reqHeaders = req.getHeaders(); String upgradeHeader = null; String settingsFrame = null; for (Header header : reqHeaders) { if (header.getKnownName() == KnownHeaderName.UPGRADE) { upgradeHeader = header.getValue(); } if (header.getKnownName() == KnownHeaderName.HTTP2_SETTINGS) { settingsFrame = header.getValue(); } } if (upgradeHeader != null && upgradeHeader.toLowerCase().equals("h2c")) { final Optional<String> maybeSettingsPayload = Optional.of(settingsFrame); log.info("got http2 upgrade with settings: " + settingsFrame); // Create the upgrade response HttpResponse response = new HttpResponse(); HttpResponseStatusLine statusLine = new HttpResponseStatusLine(); HttpResponseStatus status = new HttpResponseStatus(); status.setKnownStatus(KnownStatusCode.HTTP_101_SWITCHING_PROTOCOLS); statusLine.setStatus(status); response.setStatusLine(statusLine); response.addHeader(new Header("Connection", "Upgrade")); response.addHeader(new Header("Upgrade", "h2c")); HttpServerSocket socket = getHttpServerSocketForChannel(channel); ResponseSender http11Sender = socket.getResponseSender(); socket.upgradeHttp2(maybeSettingsPayload); // Send the upgrade accept response using the old sender and then pass the request on // to the requestlistener with the new sender. http11Sender.sendResponse(response, req, new RequestId(0), true) .thenAccept( responseId -> { socket.sendLocalRequestedSettings(); // Send the request to listener (requestid is 1 for this first request) listener.incomingRequest(req, new RequestId(0x1), true, socket.getResponseSender()); } ); // Drop all subsequent requests? break; } } if(req.isHasChunkedTransferHeader()) throw new UnsupportedOperationException(); else listener.incomingRequest(req, new RequestId(0), true, getResponseSenderForChannel(channel)); } } private List<HttpRequest> doTheWork(Channel channel, ByteBuffer chunk) { ChannelSession session = channel.getSession(); Memento memento = (Memento) session.get("memento"); if(memento == null) { memento = parser.prepareToParse(); session.put("memento", memento); } DataWrapper dataWrapper = generator.wrapByteBuffer(chunk); Memento resultMemento = parse(memento, dataWrapper); List<HttpPayload> parsedMsgs = resultMemento.getParsedMessages(); List<HttpRequest> parsedRequests = new ArrayList<>(); for(HttpPayload msg : parsedMsgs) { if(msg.getMessageType() != HttpMessageType.REQUEST) throw new ParseException("Wrong message type="+msg.getMessageType()+" should be="+HttpMessageType.REQUEST); HttpRequest req = msg.getHttpRequest(); if(channel.isSslChannel()) req.setHttpScheme(HTTPS); parsedRequests.add(msg.getHttpRequest()); } return parsedRequests; } private Memento parse(Memento memento, DataWrapper dataWrapper) { Memento resultMemento = parser.parse(memento, dataWrapper); UnparsedState unParsedState = resultMemento.getUnParsedState(); switch (unParsedState.getCurrentlyParsing()) { case HEADERS: if(unParsedState.getCurrentUnparsedSize() > config.maxHeaderSize) throw new HttpClientException("Max heaader size="+config.maxHeaderSize+" was exceeded", KnownStatusCode.HTTP_431_REQUEST_HEADERS_TOO_LARGE); break; case BODY: case CHUNK: if(unParsedState.getCurrentUnparsedSize() > config.maxBodyOrChunkSize) throw new HttpClientException("Body or chunk size limit exceeded", KnownStatusCode.HTTP_413_PAYLOAD_TOO_LARGE); default: break; } return resultMemento; } void sendServerException(Channel channel, HttpException exc) { listener.incomingError(exc, getHttpServerSocketForChannel(channel)); } void farEndClosed(Channel channel) { listener.channelClosed(getHttpServerSocketForChannel(channel), true); } void applyWriteBackPressure(Channel channel) { ResponseSender responseSender = getResponseSenderForChannel(channel); listener.applyWriteBackPressure(responseSender); } void releaseBackPressure(Channel channel) { ResponseSender responseSender = getResponseSenderForChannel(channel); listener.releaseBackPressure(responseSender); } private HttpServerSocket getHttpServerSocketForChannel(Channel channel) { ChannelSession session = channel.getSession(); return (HttpServerSocket) session.get("webpieces.httpServerSocket"); } private ResponseSender getResponseSenderForChannel(Channel channel) { HttpServerSocket httpServerSocket = getHttpServerSocketForChannel(channel); return httpServerSocket.getResponseSender(); } }