/* * 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.HttpHeaderNames; import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpMethod; import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.LastHttpContent; import io.netty.handler.codec.http.QueryStringDecoder; 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.util.CharsetUtil; 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.HttpServerFileUpload; import io.vertx.core.http.HttpServerRequest; import io.vertx.core.http.HttpServerResponse; import io.vertx.core.http.HttpVersion; import io.vertx.core.http.ServerWebSocket; 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 javax.net.ssl.SSLPeerUnverifiedException; import javax.security.cert.X509Certificate; import java.net.URISyntaxException; /** * This class is optimised for performance when used on the same event loop that is was passed to the handler with. * However it can be used safely from other threads. * * The internal state is protected by using the connection as a lock. If always used on the same event loop, then * we benefit from biased locking which makes the overhead of synchronized near zero. * * It's important we don't have different locks for connection and request/response to avoid deadlock conditions * * @author <a href="http://tfox.org">Tim Fox</a> */ public class HttpServerRequestImpl implements HttpServerRequest { private static final Logger log = LoggerFactory.getLogger(HttpServerRequestImpl.class); private final ServerConnection conn; private final HttpRequest request; private final HttpServerResponse response; private io.vertx.core.http.HttpVersion version; private io.vertx.core.http.HttpMethod method; private String rawMethod; private String uri; private String path; private String query; private Handler<Buffer> dataHandler; private Handler<Throwable> exceptionHandler; //Cache this for performance private MultiMap params; private MultiMap headers; private String absoluteURI; private NetSocket netSocket; private Handler<HttpServerFileUpload> uploadHandler; private Handler<Void> endHandler; private MultiMap attributes; private HttpPostRequestDecoder decoder; private boolean ended; HttpServerRequestImpl(ServerConnection conn, HttpRequest request, HttpServerResponse response) { this.conn = conn; this.request = request; this.response = response; } @Override public io.vertx.core.http.HttpVersion version() { if (version == null) { io.netty.handler.codec.http.HttpVersion nettyVersion = request.getProtocolVersion(); if (nettyVersion == io.netty.handler.codec.http.HttpVersion.HTTP_1_0) { version = HttpVersion.HTTP_1_0; } else if (nettyVersion == io.netty.handler.codec.http.HttpVersion.HTTP_1_1) { version = HttpVersion.HTTP_1_1; } else { sendNotImplementedAndClose(); throw new IllegalStateException("Unsupported HTTP version: " + nettyVersion); } } return version; } @Override public io.vertx.core.http.HttpMethod method() { if (method == null) { String sMethod = request.method().toString(); try { method = io.vertx.core.http.HttpMethod.valueOf(sMethod); } catch (IllegalArgumentException e) { method = io.vertx.core.http.HttpMethod.OTHER; } } return method; } @Override public String rawMethod() { if (rawMethod == null) { rawMethod = request.method().toString(); } return rawMethod; } @Override public String uri() { if (uri == null) { uri = request.uri(); } return uri; } @Override public String path() { if (path == null) { path = HttpUtils.parsePath(uri()); } return path; } @Override public String query() { if (query == null) { query = HttpUtils.parseQuery(uri()); } return query; } @Override public @Nullable String host() { return getHeader(HttpHeaderNames.HOST); } @Override public HttpServerResponse response() { return response; } @Override public MultiMap headers() { if (headers == null) { headers = new HeadersAdaptor(request.headers()); } return headers; } @Override public String getHeader(String headerName) { return headers().get(headerName); } @Override public String getHeader(CharSequence headerName) { return headers().get(headerName); } @Override public MultiMap params() { if (params == null) { params = HttpUtils.params(uri()); } return params; } @Override public String getParam(String paramName) { return params().get(paramName); } @Override public HttpServerRequest handler(Handler<Buffer> dataHandler) { synchronized (conn) { checkEnded(); this.dataHandler = dataHandler; return this; } } @Override public HttpServerRequest exceptionHandler(Handler<Throwable> handler) { synchronized (conn) { this.exceptionHandler = handler; return this; } } @Override public HttpServerRequest pause() { synchronized (conn) { conn.pause(); return this; } } @Override public HttpServerRequest resume() { synchronized (conn) { conn.resume(); return this; } } @Override public HttpServerRequest endHandler(Handler<Void> handler) { synchronized (conn) { checkEnded(); this.endHandler = handler; return this; } } @Override public String scheme() { return isSSL() ? "https" : "http"; } @Override public boolean isSSL() { return conn.isSSL(); } @Override public SocketAddress remoteAddress() { return conn.remoteAddress(); } @Override public String absoluteURI() { if (absoluteURI == null) { try { absoluteURI = HttpUtils.absoluteURI(conn.getServerOrigin(), this); } catch (URISyntaxException e) { log.error("Failed to create abs uri", e); } } return absoluteURI; } @Override public X509Certificate[] peerCertificateChain() throws SSLPeerUnverifiedException { return conn.peerCertificateChain(); } @Override public NetSocket netSocket() { if (netSocket == null) { netSocket = conn.createNetSocket(); } return netSocket; } @Override public HttpServerRequest uploadHandler(Handler<HttpServerFileUpload> handler) { synchronized (conn) { checkEnded(); this.uploadHandler = handler; return this; } } @Override public MultiMap formAttributes() { return attributes(); } @Override public String getFormAttribute(String attributeName) { return formAttributes().get(attributeName); } @Override public ServerWebSocket upgrade() { return conn.upgrade(this, request); } @Override public HttpServerRequest setExpectMultipart(boolean expect) { synchronized (conn) { checkEnded(); if (expect) { if (decoder == null) { String contentType = request.headers().get(HttpHeaders.Names.CONTENT_TYPE); if (contentType != null) { HttpMethod method = request.getMethod(); String lowerCaseContentType = contentType.toLowerCase(); boolean isURLEncoded = lowerCaseContentType.startsWith(HttpHeaders.Values.APPLICATION_X_WWW_FORM_URLENCODED); if ((lowerCaseContentType.startsWith(HttpHeaders.Values.MULTIPART_FORM_DATA) || isURLEncoded) && (method.equals(HttpMethod.POST) || method.equals(HttpMethod.PUT) || method.equals(HttpMethod.PATCH) || method.equals(HttpMethod.DELETE))) { decoder = new HttpPostRequestDecoder(new NettyFileUploadDataFactory(conn.vertx(), this, () -> uploadHandler), request); } } } } else { decoder = null; } return this; } } @Override public boolean isExpectMultipart() { synchronized (conn) { return decoder != null; } } @Override public SocketAddress localAddress() { return conn.localAddress(); } @Override public boolean isEnded() { synchronized (conn) { return ended; } } @Override public HttpServerRequest customFrameHandler(Handler<HttpFrame> handler) { return this; } @Override public HttpConnection connection() { return conn; } void handleData(Buffer data) { synchronized (conn) { if (decoder != null) { try { decoder.offer(new DefaultHttpContent(data.getByteBuf())); } catch (HttpPostRequestDecoder.ErrorDataDecoderException e) { handleException(e); } } if (dataHandler != null) { dataHandler.handle(data); } } } void handleEnd() { synchronized (conn) { ended = true; if (decoder != null) { try { decoder.offer(LastHttpContent.EMPTY_LAST_CONTENT); while (decoder.hasNext()) { InterfaceHttpData data = decoder.next(); if (data instanceof Attribute) { Attribute attr = (Attribute) data; try { attributes().add(attr.getName(), attr.getValue()); } catch (Exception e) { // Will never happen, anyway handle it somehow just in case handleException(e); } } } } catch (HttpPostRequestDecoder.ErrorDataDecoderException e) { handleException(e); } catch (HttpPostRequestDecoder.EndOfDataDecoderException e) { // ignore this as it is expected } finally { decoder.destroy(); } } // If there have been uploads then we let the last one call the end handler once any fileuploads are complete if (endHandler != null) { endHandler.handle(null); } } } void handleException(Throwable t) { synchronized (conn) { if (exceptionHandler != null) { exceptionHandler.handle(t); } } } private void sendNotImplementedAndClose() { response().setStatusCode(501).end(); response().close(); } private void checkEnded() { if (ended) { throw new IllegalStateException("Request has already been read"); } } private MultiMap attributes() { // Create it lazily if (attributes == null) { attributes = new CaseInsensitiveHeaders(); } return attributes; } private static String urlDecode(String str) { return QueryStringDecoder.decodeComponent(str, CharsetUtil.UTF_8); } }