
org.wisdom.framework.vertx.RequestFromVertx Maven / Gradle / Ivy
/*
* #%L
* Wisdom-Framework
* %%
* Copyright (C) 2013 - 2014 Wisdom Framework
* %%
* 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.
* #L%
*/
package org.wisdom.framework.vertx;
import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.net.MediaType;
import org.vertx.java.core.Handler;
import org.vertx.java.core.MultiMap;
import org.vertx.java.core.buffer.Buffer;
import org.vertx.java.core.http.HttpServerFileUpload;
import org.vertx.java.core.http.HttpServerRequest;
import org.wisdom.api.configuration.ApplicationConfiguration;
import org.wisdom.api.cookies.Cookie;
import org.wisdom.api.cookies.Cookies;
import org.wisdom.api.http.HeaderNames;
import org.wisdom.api.http.MimeTypes;
import org.wisdom.api.http.Request;
import org.wisdom.framework.vertx.cookies.CookiesImpl;
import org.wisdom.framework.vertx.file.DiskFileUpload;
import org.wisdom.framework.vertx.file.MixedFileUpload;
import org.wisdom.framework.vertx.file.VertxFileUpload;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
/**
* An implementation of {@link org.wisdom.api.http.Request} based on Vert.X Request
* ({@link org.vertx.java.core.http.HttpServerRequest}).
*/
public class RequestFromVertx extends Request {
private final HttpServerRequest request;
private final Cookies cookies;
/**
* List of uploaded files.
*/
private List files = Lists.newArrayList();
/**
* The raw body.
*/
private Buffer raw = new Buffer(0);
/**
* The map used to store data shared in the request scope.
*/
private final Map data;
private Map> formData;
private Map> headers;
/**
* Creates a {@link org.wisdom.framework.vertx.RequestFromVertx} object
*
* @param context the HTTP content
* @param request the Vertx Request
* @param configuration the application configuration
*/
public RequestFromVertx(final ContextFromVertx context, final HttpServerRequest request,
final ApplicationConfiguration configuration) {
this.request = request;
if (HttpUtils.isPostOrPut(request)) {
this.request.expectMultiPart(true);
this.request.uploadHandler(new Handler() {
public void handle(HttpServerFileUpload upload) {
files.add(new MixedFileUpload(context.vertx(), upload,
configuration.getLongWithDefault("http.upload.disk.threshold", DiskFileUpload.MINSIZE),
configuration.getLongWithDefault("http.upload.max", -1l)));
}
});
}
this.cookies = new CookiesImpl(request);
this.data = new HashMap<>();
this.request.dataHandler(new Handler() {
@Override
public void handle(Buffer event) {
if (event == null) {
return;
}
// We may have the content in different HTTP message, check if we already have a content.
// Issue #257.
// To avoid we run out of memory we cut the read body to 100Kb. This can be configured using the
// "request.body.max.size" property.
boolean exceeded = raw.length() >=
configuration.getIntegerWithDefault("request.body.max.size", 100 * 1024);
if (!exceeded) {
raw.appendBuffer(event);
}
}
});
}
/**
* The Content-Type header field indicates the media type of the request
* body sent to the recipient. E.g. {@code Content-Type: text/html;
* charset=ISO-8859-4}
*
* @return the content type of the incoming request.
* @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
*/
@Override
public String contentType() {
return request.headers().get(HeaderNames.CONTENT_TYPE);
}
/**
* Gets the encoding that is acceptable for the client. E.g. Accept-Encoding:
* compress, gzip
*
* The Accept-Encoding request-header field is similar to Accept, but
* restricts the content-codings that are acceptable in the response.
*
* @return the encoding that is acceptable for the client
* @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
*/
@Override
public String encoding() {
return request.headers().get(HeaderNames.ACCEPT_ENCODING);
}
/**
* Gets the language that is acceptable for the client. E.g. Accept-Language:
* da, en-gb;q=0.8, en;q=0.7
*
* The Accept-Language request-header field is similar to Accept, but
* restricts the set of natural languages that are preferred as a response
* to the request.
*
* @return the language that is acceptable for the client
* @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
*/
@Override
public String language() {
return request.headers().get(HeaderNames.ACCEPT_LANGUAGE);
}
/**
* Gets the charset that is acceptable for the client. E.g. Accept-Charset:
* iso-8859-5, unicode-1-1;q=0.8
*
* The Accept-Charset request-header field can be used to indicate what
* character sets are acceptable for the response. This field allows clients
* capable of understanding more comprehensive or special- purpose character
* sets to signal that capability to a server which is capable of
* representing documents in those character sets.
*
* @return the charset that is acceptable for the client
* @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
*/
@Override
public String charset() {
return request.headers().get(HeaderNames.ACCEPT_CHARSET);
}
/**
* The complete request URI, containing both path and query string.
*/
@Override
public String uri() {
return request.uri();
}
/**
* Returns the name of the HTTP method with which this
* request was made, for example, GET, POST, or PUT.
* Same as the value of the CGI variable REQUEST_METHOD.
*
* @return a String
* specifying the name
* of the method with which
* this request was made (eg GET, POST, PUT...)
*/
@Override
public String method() {
return request.method();
}
/**
* The client IP address.
*
* If the X-Forwarded-For
header is present, then this method will return the value in that header
* if either the local address is 127.0.0.1, or if trustxforwarded
is configured to be true in the
* application configuration file.
*/
@Override
public String remoteAddress() {
if (headers().containsKey(HeaderNames.X_FORWARD_FOR)) {
return getHeader(HeaderNames.X_FORWARD_FOR);
} else {
InetSocketAddress remote = request.remoteAddress();
return remote.getAddress().getHostAddress();
}
}
/**
* The request host.
*/
@Override
public String host() {
InetSocketAddress remote = request.remoteAddress();
return remote.getHostName();
}
/**
* The URI path, without the query part.
*/
@Override
public String path() {
try {
return new URI(request.uri()).getRawPath();
} catch (URISyntaxException e) { //NOSONAR
// Should never be the case.
return uri();
}
}
/**
* Get the preferred content media type that is acceptable for the client. For instance, in Accept: text/*;q=0.3,
* text/html;q=0.7, text/html;level=1,text/html;level=2;q=0.4, text/html is returned.
*
* The Accept request-header field can be used to specify certain media
* types which are acceptable for the response. Accept headers can be used
* to indicate that the request is specifically limited to a small set of
* desired types, as in the case of a request for an in-line image.
*
* @return a MediaType that is acceptable for the
* client or {@see MediaType#HTML_UTF_8} if not set
* @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
*/
@Override
public MediaType mediaType() {
Collection types = mediaTypes();
if (types == null || types.isEmpty()) {
return MediaType.ANY_TEXT_TYPE;
} else if (types.size() == 1 && types.iterator().next().equals(MediaType.ANY_TYPE)) {
return MediaType.ANY_TEXT_TYPE;
} else {
return types.iterator().next();
}
}
/**
* Get the content media type that is acceptable for the client. E.g. Accept: text/*;q=0.3, text/html;q=0.7,
* text/html;level=1,text/html;level=2;q=0.4
*
* The Accept request-header field can be used to specify certain media
* types which are acceptable for the response. Accept headers can be used
* to indicate that the request is specifically limited to a small set of
* desired types, as in the case of a request for an in-line image.
*
* @return a MediaType that is acceptable for the
* client or {@see MediaType#ANY_TEXT_TYPE} if not set
* @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
*/
@Override
public Collection mediaTypes() {
String contentType = request.headers().get(HeaderNames.ACCEPT);
if (contentType == null) {
// Any text by default.
return ImmutableList.of(MediaType.ANY_TEXT_TYPE);
}
TreeSet set = new TreeSet<>(new Comparator() {
@Override
public int compare(MediaType o1, MediaType o2) {
double q1 = 1.0, q2 = 1.0;
List ql1 = o1.parameters().get("q");
List ql2 = o2.parameters().get("q");
if (ql1 != null && !ql1.isEmpty()) {
q1 = Double.parseDouble(ql1.get(0));
}
if (ql2 != null && !ql2.isEmpty()) {
q2 = Double.parseDouble(ql2.get(0));
}
return new Double(q2).compareTo(q1);
}
});
// Split and sort.
String[] segments = contentType.split(",");
for (String segment : segments) {
MediaType type = MediaType.parse(segment.trim());
set.add(type);
}
return set;
}
/**
* Check if this request accepts a given media type.
*
* @return true if mimeType
is in the Accept header, otherwise false
*/
@Override
public boolean accepts(String mimeType) {
String contentType = request.headers().get(HeaderNames.ACCEPT);
if (contentType == null) {
contentType = MimeTypes.HTML;
}
// For performance reason, we first try a full match:
if (contentType.contains(mimeType)) {
return true;
}
// Else check the media types:
MediaType input = MediaType.parse(mimeType);
for (MediaType type : mediaTypes()) {
if (input.is(type)) {
return true;
}
}
return false;
}
/**
* Gets the list of cookies.
*
* @return the request cookies
*/
@Override
public Cookies cookies() {
return cookies;
}
/**
* Gets a cookie with the given name.
*
* @param name the cookie to retrieve
* @return the cookie, {@code null} if no cookie have the given name
*/
public Cookie cookie(String name) {
return cookies.get(name);
}
/**
* Retrieves all headers.
*
* @return headers
*/
@Override
public Map> headers() {
if (headers != null) {
return headers;
}
headers = new HashMap<>();
final MultiMap requestHeaders = request.headers();
Set names = requestHeaders.names();
for (String name : names) {
headers.put(name, requestHeaders.getAll(name));
}
return headers;
}
/**
* Get the parameter with the given key from the request. The parameter may
* either be a query parameter, or in the case of form submissions, may be a
* form parameter.
*
* When the parameter is multivalued, returns the first value.
*
* The parameter is decoded by default.
*
* @param name The key of the parameter
* @return The value, or null if no parameter was found.
* @see #parameterMultipleValues
*/
@Override
public String parameter(String name) {
String s = request.params().get(name);
if (s == null) {
// Check form parameter
if (formData != null) {
List l = formData.get(name);
if (l != null && !l.isEmpty()) {
return l.get(0);
}
}
return null;
} else {
return s;
}
}
/**
* Get the parameter with the given key from the request. The parameter may
* either be a query parameter, or in the case of form submissions, may be a
* form parameter.
*
* The parameter is decoded by default.
*
* @param name The key of the parameter
* @return The values, possibly an empty list.
*/
@Override
public List parameterMultipleValues(String name) {
return request.params().getAll(name);
}
/**
* Same like {@link #parameter(String)}, but returns given defaultValue
* instead of null in case parameter cannot be found.
*
* The parameter is decoded by default.
*
* @param name The name of the post or query parameter
* @param defaultValue A default value if parameter not found.
* @return The value of the parameter of the defaultValue if not found.
*/
@Override
public String parameter(String name, String defaultValue) {
String v = request.params().get(name);
return v != null ? v : defaultValue;
}
/**
* Same like {@link #parameter(String)}, but converts the parameter to
* Integer if found.
*
* The parameter is decoded by default.
*
* @param name The name of the post or query parameter
* @return The value of the parameter or null if not found.
*/
@Override
public Integer parameterAsInteger(String name) {
String parameter = parameter(name);
try {
return Integer.parseInt(parameter);
} catch (Exception e) { //NOSONAR
return null;
}
}
/**
* Like {@link #parameter(String, String)}, but converts the
* parameter to Integer if found.
*
* The parameter is decoded by default.
*
* @param name The name of the post or query parameter
* @param defaultValue A default value if parameter not found.
* @return The value of the parameter of the defaultValue if not found.
*/
@Override
public Integer parameterAsInteger(String name, Integer defaultValue) {
Integer parameter = parameterAsInteger(name);
if (parameter == null) {
return defaultValue;
}
return parameter;
}
/**
* Like {@link #parameter(String)}, but converts the parameter to
* Boolean if found.
*
* The parameter is decoded by default.
*
* @param name The name of the post or query parameter
* @return The value of the parameter or {@literal false} if not found.
*/
@Override
public Boolean parameterAsBoolean(String name) {
String parameter = parameter(name);
try {
return Boolean.parseBoolean(parameter);
} catch (Exception e) { //NOSONAR
return false;
}
}
/**
* Same like {@link #parameter(String)}, but converts the parameter to
* Boolean if found.
*
* The parameter is decoded by default.
*
* @param name The name of the post or query parameter
* @param defaultValue A default value if parameter not found.
* @return The value of the parameter or the defaultValue if not found.
*/
@Override
public Boolean parameterAsBoolean(String name, boolean defaultValue) {
// We have to check if the map contains the key, as the retrieval method returns false on missing key.
if (!request.params().contains(name)) {
return defaultValue;
}
Boolean parameter = parameterAsBoolean(name);
if (parameter == null) {
return defaultValue;
}
return parameter;
}
/**
* Gets all the parameters from the request.
*
* @return The parameters
*/
@Override
public Map> parameters() {
Map> result = new HashMap<>();
for (String key : request.params().names()) {
result.put(key, request.params().getAll(key));
}
return result;
}
/**
* Retrieves the data shared by all the entities participating to the request resolution (i.e. computation of the
* response). This method returns a live map, meaning that modification impacts all other participants. It can be
* used to let filters or interceptors passing objects to action methods or templates.
*
* @return the map storing the data. Unlike session or flash, these data are not stored in cookies,
* and are cleared once the response is sent back to the client.
*/
@Override
public Map data() {
return data;
}
/**
* Gets the underlying Vert.X Request.
*
* @return the request.
*/
public HttpServerRequest getVertxRequest() {
return request;
}
/**
* Gets the form data.
*
* @return the form data
*/
public Map> getFormData() {
return formData;
}
/**
* Gets the 'raw' body.
*
* @return the raw body, {@code null} if there is no body.
*/
public String getRawBodyAsString() {
if (raw == null) {
return null;
}
return raw.toString(Charsets.UTF_8.displayName());
}
/**
* Gets the 'raw' body.
*
* @return the raw body, {@code null} if there is no body.
*/
public byte[] getRawBody() {
return raw.getBytes();
}
/**
* Gets the uploaded files.
*
* @return the list of uploaded files.
*/
public List getFiles() {
return files;
}
/**
* Callbacks invokes when the request has been read completely.
*
* @return a boolean indicating if the request was handled correctly.
*/
public boolean ready() {
for (VertxFileUpload file : files) {
if (file.getErrorIfAny() != null) {
return false;
}
}
String contentType = request.headers().get(HeaderNames.CONTENT_TYPE);
if (contentType != null) {
contentType = HttpUtils.getContentTypeFromContentTypeAndCharacterSetting(contentType);
if ((HttpUtils.isPostOrPut(request))
&&
(contentType.equalsIgnoreCase(MimeTypes.FORM) || contentType.equalsIgnoreCase(MimeTypes.MULTIPART))) {
formData = new HashMap<>();
for (String key : request.formAttributes().names()) {
formData.put(key, request.formAttributes().getAll(key));
}
return true;
}
}
formData = new HashMap<>();
return true;
}
}