/* * 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.handler.codec.http.DefaultHttpContent; import io.netty.handler.codec.http.DefaultHttpRequest; import io.netty.handler.codec.http.HttpHeaderNames; import io.netty.handler.codec.http.HttpHeaderValues; import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.LastHttpContent; import io.netty.handler.codec.http.multipart.Attribute; import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder; import io.netty.handler.codec.http.multipart.InterfaceHttpData; import io.netty.handler.codec.http2.Http2Headers; import io.netty.handler.codec.http2.Http2Stream; import io.vertx.codegen.annotations.Nullable; import io.vertx.core.Handler; import io.vertx.core.MultiMap; import io.vertx.core.buffer.Buffer; import io.vertx.core.http.CaseInsensitiveHeaders; import io.vertx.core.http.HttpConnection; import io.vertx.core.http.HttpMethod; import io.vertx.core.http.HttpServerFileUpload; import io.vertx.core.http.HttpServerRequest; import io.vertx.core.http.HttpVersion; import io.vertx.core.http.ServerWebSocket; import io.vertx.core.http.StreamResetException; import io.vertx.core.http.HttpFrame; import io.vertx.core.logging.Logger; import io.vertx.core.logging.LoggerFactory; import io.vertx.core.net.NetSocket; import io.vertx.core.net.SocketAddress; import io.vertx.core.spi.metrics.HttpServerMetrics; import javax.net.ssl.SSLPeerUnverifiedException; import javax.security.cert.X509Certificate; import java.net.URISyntaxException; import java.nio.channels.ClosedChannelException; /** * @author <a href="mailto:julien@julienviet.com">Julien Viet</a> */ public class Http2ServerRequestImpl extends VertxHttp2Stream<Http2ServerConnection> implements HttpServerRequest { private static final Logger log = LoggerFactory.getLogger(HttpServerRequestImpl.class); private final String serverOrigin; private final Http2ServerResponseImpl response; private final Http2Headers headers; private MultiMap headersMap; private MultiMap params; private HttpMethod method; private String rawMethod; private String absoluteURI; private String uri; private String path; private String query; private MultiMap attributes; private Handler<Buffer> dataHandler; private Handler<Void> endHandler; private boolean ended; private long bytesRead; private Handler<HttpServerFileUpload> uploadHandler; private HttpPostRequestDecoder postRequestDecoder; private Handler<Throwable> exceptionHandler; private Handler<HttpFrame> customFrameHandler; private NetSocket netSocket; public Http2ServerRequestImpl(Http2ServerConnection conn, Http2Stream stream, HttpServerMetrics metrics, String serverOrigin, Http2Headers headers, String contentEncoding, boolean writable) { super(conn, stream, writable); this.serverOrigin = serverOrigin; this.headers = headers; String host = host(); if (host == null) { int idx = serverOrigin.indexOf("://"); host = serverOrigin.substring(idx + 3); } Object metric = metrics.isEnabled() ? metrics.requestBegin(conn.metric(), this) : null; this.response = new Http2ServerResponseImpl(conn, this, metric, false, contentEncoding, host); } @Override void handleInterestedOpsChanged() { response.writabilityChanged(); } @Override void handleException(Throwable cause) { if (exceptionHandler != null) { exceptionHandler.handle(cause); response.handleError(cause); } } @Override void handleClose() { if (!ended) { ended = true; if (exceptionHandler != null) { exceptionHandler.handle(new ClosedChannelException()); } } response.handleClose(); } @Override void handleCustomFrame(int type, int flags, Buffer buff) { if (customFrameHandler != null) { customFrameHandler.handle(new HttpFrameImpl(type, flags, buff)); } } void handleData(Buffer data) { bytesRead += data.length(); if (postRequestDecoder != null) { try { postRequestDecoder.offer(new DefaultHttpContent(data.getByteBuf())); } catch (Exception e) { handleException(e); } } if (dataHandler != null) { dataHandler.handle(data); } } void handleEnd(MultiMap trailers) { ended = true; conn.reportBytesRead(bytesRead); if (postRequestDecoder != null) { try { postRequestDecoder.offer(LastHttpContent.EMPTY_LAST_CONTENT); while (postRequestDecoder.hasNext()) { InterfaceHttpData data = postRequestDecoder.next(); if (data instanceof Attribute) { Attribute attr = (Attribute) data; try { formAttributes().add(attr.getName(), attr.getValue()); } catch (Exception e) { // Will never happen, anyway handle it somehow just in case handleException(e); } } } } catch (HttpPostRequestDecoder.EndOfDataDecoderException e) { // ignore this as it is expected } catch (Exception e) { handleException(e); } finally { postRequestDecoder.destroy(); } } if (endHandler != null) { endHandler.handle(null); } } @Override void handleReset(long errorCode) { ended = true; if (exceptionHandler != null) { exceptionHandler.handle(new StreamResetException(errorCode)); } response.callReset(errorCode); if (endHandler != null) { endHandler.handle(null); } } private void checkEnded() { if (ended) { throw new IllegalStateException("Request has already been read"); } } @Override public HttpServerRequest exceptionHandler(Handler<Throwable> handler) { synchronized (conn) { exceptionHandler = handler; } return this; } @Override public HttpServerRequest handler(Handler<Buffer> handler) { synchronized (conn) { checkEnded(); dataHandler = handler; } return this; } @Override public HttpServerRequest pause() { synchronized (conn) { doPause(); } return this; } @Override public HttpServerRequest resume() { synchronized (conn) { doResume(); } return this; } @Override public HttpServerRequest endHandler(Handler<Void> handler) { synchronized (conn) { checkEnded(); endHandler = handler; } return this; } @Override public HttpVersion version() { return HttpVersion.HTTP_2; } @Override public HttpMethod method() { synchronized (conn) { if (method == null) { String sMethod = headers.method().toString(); method = HttpUtils.toVertxMethod(sMethod); } return method; } } @Override public String rawMethod() { synchronized (conn) { if (rawMethod == null) { rawMethod = headers.method().toString(); } return rawMethod; } } @Override public boolean isSSL() { return conn.isSsl(); } @Override public String uri() { synchronized (conn) { if (uri == null) { CharSequence path = headers.path(); if (path != null) { uri = path.toString(); } } return uri; } } @Override public String path() { synchronized (conn) { if (path == null) { CharSequence path = headers.path(); if (path != null) { this.path = HttpUtils.parsePath(path.toString()); } } return path; } } @Override public String query() { synchronized (conn) { if (query == null) { CharSequence path = headers.path(); if (path != null) { this.query = HttpUtils.parseQuery(path.toString()); } } return query; } } @Override public String scheme() { CharSequence scheme = headers.scheme(); return scheme != null ? scheme.toString() : null; } @Override public String host() { CharSequence authority = headers.authority(); return authority != null ? authority.toString() : null; } @Override public Http2ServerResponseImpl response() { return response; } @Override public MultiMap headers() { synchronized (conn) { if (headersMap == null) { headersMap = new Http2HeadersAdaptor(headers); } return headersMap; } } @Override public String getHeader(String headerName) { return headers().get(headerName); } @Override public String getHeader(CharSequence headerName) { return headers().get(headerName); } @Override public MultiMap params() { synchronized (conn) { if (params == null) { params = HttpUtils.params(uri()); } return params; } } @Override public String getParam(String paramName) { return params().get(paramName); } @Override public SocketAddress remoteAddress() { return conn.remoteAddress(); } @Override public SocketAddress localAddress() { return conn.localAddress(); } @Override public X509Certificate[] peerCertificateChain() throws SSLPeerUnverifiedException { return conn.peerCertificateChain(); } @Override public String absoluteURI() { if (method() == HttpMethod.CONNECT) { return null; } synchronized (conn) { if (absoluteURI == null) { try { absoluteURI = HttpUtils.absoluteURI(serverOrigin, this); } catch (URISyntaxException e) { log.error("Failed to create abs uri", e); } } return absoluteURI; } } @Override public NetSocket netSocket() { synchronized (conn) { checkEnded(); if (netSocket == null) { response.toNetSocket(); netSocket = conn.toNetSocket(this); } return netSocket; } } @Override public HttpServerRequest setExpectMultipart(boolean expect) { synchronized (conn) { checkEnded(); if (expect) { if (postRequestDecoder == null) { CharSequence contentType = headers.get(HttpHeaderNames.CONTENT_TYPE); if (contentType != null) { io.netty.handler.codec.http.HttpMethod method = io.netty.handler.codec.http.HttpMethod.valueOf(headers.method().toString()); String lowerCaseContentType = contentType.toString().toLowerCase(); boolean isURLEncoded = lowerCaseContentType.startsWith(HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED.toString()); if ((lowerCaseContentType.startsWith(HttpHeaderValues.MULTIPART_FORM_DATA.toString()) || isURLEncoded) && (method == io.netty.handler.codec.http.HttpMethod.POST || method == io.netty.handler.codec.http.HttpMethod.PUT || method == io.netty.handler.codec.http.HttpMethod.PATCH || method == io.netty.handler.codec.http.HttpMethod.DELETE)) { HttpRequest req = new DefaultHttpRequest( io.netty.handler.codec.http.HttpVersion.HTTP_1_1, method, headers.path().toString()); req.headers().add(HttpHeaderNames.CONTENT_TYPE, contentType); postRequestDecoder = new HttpPostRequestDecoder(new NettyFileUploadDataFactory(vertx, this, () -> uploadHandler), req); } } } } else { postRequestDecoder = null; } } return this; } @Override public boolean isExpectMultipart() { synchronized (conn) { return postRequestDecoder != null; } } @Override public HttpServerRequest uploadHandler(@Nullable Handler<HttpServerFileUpload> handler) { synchronized (conn) { checkEnded(); uploadHandler = handler; return this; } } @Override public MultiMap formAttributes() { synchronized (conn) { // Create it lazily if (attributes == null) { attributes = new CaseInsensitiveHeaders(); } return attributes; } } @Override public String getFormAttribute(String attributeName) { return formAttributes().get(attributeName); } @Override public ServerWebSocket upgrade() { throw new UnsupportedOperationException("HTTP/2 request cannot be upgraded to a websocket"); } @Override public boolean isEnded() { synchronized (conn) { return ended; } } @Override public HttpServerRequest customFrameHandler(Handler<HttpFrame> handler) { synchronized (conn) { customFrameHandler = handler; return this; } } @Override public HttpConnection connection() { return conn; } }