/* * Copyright (C) 2009-2015 Typesafe Inc. <http://www.typesafe.com> */ package play.mvc; import static play.libs.Scala.asScala; import java.io.*; import java.net.URISyntaxException; import java.net.URI; import java.net.URLDecoder; import java.util.*; import java.util.Map.Entry; import scala.Predef; import scala.Tuple2; import scala.collection.JavaConversions; import scala.collection.JavaConverters; import scala.collection.Seq; import org.w3c.dom.*; import org.xml.sax.InputSource; import com.fasterxml.jackson.databind.JsonNode; import play.api.libs.json.JsValue; import play.api.libs.json.jackson.JacksonJson; import play.api.mvc.AnyContent; import play.api.mvc.AnyContentAsFormUrlEncoded; import play.api.mvc.AnyContentAsJson; import play.api.mvc.AnyContentAsRaw; import play.api.mvc.AnyContentAsText; import play.api.mvc.AnyContentAsXml; import play.api.mvc.Headers; import play.core.system.RequestIdProvider; import play.Play; import play.i18n.Lang; import play.i18n.Messages; import play.i18n.MessagesApi; /** * Defines HTTP standard objects. */ public class Http { /** * The global HTTP context. */ public static class Context { public static ThreadLocal<Context> current = new ThreadLocal<Context>(); /** * Retrieves the current HTTP context, for the current thread. */ public static Context current() { Context c = current.get(); if(c == null) { throw new RuntimeException("There is no HTTP Context available from here."); } return c; } // private final Long id; private final play.api.mvc.RequestHeader header; private final Request request; private final Response response; private final Session session; private final Flash flash; private Lang lang = null; /** * Creates a new HTTP context. * * @param requestBuilder the HTTP request builder */ public Context(RequestBuilder requestBuilder) { this(requestBuilder.build()); } /** * Creates a new HTTP context. * * @param request the HTTP request */ public Context(Request request) { this.request = request; this.header = request._underlyingHeader(); this.id = header.id(); this.response = new Response(); this.session = new Session(JavaConversions.mapAsJavaMap(header.session().data())); this.flash = new Flash(JavaConversions.mapAsJavaMap(header.flash().data())); this.args = new HashMap<String,Object>(); this.args.putAll(JavaConversions.mapAsJavaMap(header.tags())); } /** * Creates a new HTTP context. * * @param request the HTTP request * @param sessionData the session data extracted from the session cookie * @param flashData the flash data extracted from the flash cookie */ public Context(Long id, play.api.mvc.RequestHeader header, Request request, Map<String,String> sessionData, Map<String,String> flashData, Map<String,Object> args) { this.id = id; this.header = header; this.request = request; this.response = new Response(); this.session = new Session(sessionData); this.flash = new Flash(flashData); this.args = new HashMap<String,Object>(args); } /** * The context id (unique) */ public Long id() { return id; } /** * Returns the current request. */ public Request request() { return request; } /** * Returns the current response. */ public Response response() { return response; } /** * Returns the current session. */ public Session session() { return session; } /** * Returns the current flash scope. */ public Flash flash() { return flash; } /** * The original Play request Header used to create this context. * For internal usage only. */ public play.api.mvc.RequestHeader _requestHeader() { return header; } /** * @return the current lang */ public Lang lang() { if (lang != null) { return lang; } else { return messages().lang(); } } /** * @return the messages for the current lang */ public Messages messages() { return Play.application().injector().instanceOf(MessagesApi.class).preferred(request()); } /** * Change durably the lang for the current user. * @param code New lang code to use (e.g. "fr", "en-US", etc.) * @return true if the requested lang was supported by the application, otherwise false */ public boolean changeLang(String code) { return changeLang(Lang.forCode(code)); } /** * Change durably the lang for the current user. * @param lang New Lang object to use * @return true if the requested lang was supported by the application, otherwise false */ public boolean changeLang(Lang lang) { if (Lang.availables().contains(lang)) { this.lang = lang; scala.Option<String> domain = play.api.mvc.Session.domain(); response.setCookie(Play.langCookieName(), lang.code(), null, play.api.mvc.Session.path(), domain.isDefined() ? domain.get() : null, Play.langCookieSecure(), Play.langCookieHttpOnly()); return true; } else { return false; } } /** * Clear the lang for the current user. */ public void clearLang() { this.lang = null; scala.Option<String> domain = play.api.mvc.Session.domain(); response.discardCookie(Play.langCookieName(), play.api.mvc.Session.path(), domain.isDefined() ? domain.get() : null, Play.langCookieSecure()); } /** * Set the language for the current request, but don't * change the language cookie. This means the language * will be set for this request, but will not change for * future requests. * * @throws IllegalArgumentException If the given language * is not supported by the application. */ public void setTransientLang(String code) { setTransientLang(Lang.forCode(code)); } /** * Set the language for the current request, but don't * change the language cookie. This means the language * will be set for this request, but will not change for * future requests. * * @throws IllegalArgumentException If the given language * is not supported by the application. */ public void setTransientLang(Lang lang) { if (Lang.availables().contains(lang)) { this.lang = lang; } else { throw new IllegalArgumentException("Language not supported in this application: " + lang + " not in Lang.availables()"); } } /** * Clear the language for the current request, but don't * change the language cookie. This means the language * will be cleared for this request (so a default will be * used), but will not change for future requests. */ public void clearTransientLang() { this.lang = null; } /** * Free space to store your request specific data. */ public Map<String, Object> args; /** * Import in templates to get implicit HTTP context. */ public static class Implicit { /** * Returns the current response. */ public static Response response() { return Context.current().response(); } /** * Returns the current request. */ public static Request request() { return Context.current().request(); } /** * Returns the current flash scope. */ public static Flash flash() { return Context.current().flash(); } /** * Returns the current session. */ public static Session session() { return Context.current().session(); } /** * Returns the current lang. */ public static Lang lang() { return Context.current().lang(); } /** * @return the messages for the current lang */ public static Messages messages() { return Context.current().messages(); } /** * Returns the current context. */ public static Context ctx() { return Context.current(); } } /** * @return a String representation */ public String toString() { return "Context attached to (" + request() + ")"; } } /** * A wrapped context. * Use this to modify the context in some way. */ public static abstract class WrappedContext extends Context { private final Context wrapped; /** * @param wrapped */ public WrappedContext(Context wrapped) { super(wrapped.id(), wrapped._requestHeader(), wrapped.request(), wrapped.session(), wrapped.flash(), wrapped.args); this.args = wrapped.args; this.wrapped = wrapped; } @Override public Long id() { return wrapped.id(); } @Override public Request request() { return wrapped.request(); } @Override public Response response() { return wrapped.response(); } @Override public Session session() { return wrapped.session(); } @Override public Flash flash() { return wrapped.flash(); } @Override public play.api.mvc.RequestHeader _requestHeader() { return wrapped._requestHeader(); } @Override public Lang lang() { return wrapped.lang(); } @Override public boolean changeLang(String code) { return wrapped.changeLang(code); } @Override public boolean changeLang(Lang lang) { return wrapped.changeLang(lang); } @Override public void clearLang() { wrapped.clearLang(); } } public static interface RequestHeader { /** * The complete request URI, containing both path and query string. */ String uri(); /** * The HTTP Method. */ String method(); /** * The HTTP version. */ String version(); /** * The client IP address. * * retrieves the last untrusted proxy * from the Forwarded-Headers or the X-Forwarded-*-Headers. */ String remoteAddress(); /** * Is the client using SSL? * */ boolean secure(); /** * The request host. */ String host(); /** * The URI path. */ String path(); /** * The Request Langs extracted from the Accept-Language header and sorted by preference (preferred first). */ List<play.i18n.Lang> acceptLanguages(); /** * @return The media types set in the request Accept header, sorted by preference (preferred first) */ List<play.api.http.MediaRange> acceptedTypes(); /** * Check if this request accepts a given media type. * @return true if <code>mimeType</code> is in the Accept header, otherwise false */ boolean accepts(String mimeType); /** * The query string content. */ Map<String,String[]> queryString(); /** * Helper method to access a queryString parameter. */ String getQueryString(String key); /** * @return the request cookies */ Cookies cookies(); /** * @param name Name of the cookie to retrieve * @return the cookie, if found, otherwise null */ Cookie cookie(String name); /** * Retrieves all headers. * * @return a map of of header name to headers with case-insensitive keys */ Map<String,String[]> headers(); /** * Retrieves a single header. * * @param headerName The name of the header (case-insensitive) */ String getHeader(String headerName); /** * Checks if the request has the header. * * @param headerName The name of the header (case-insensitive) */ boolean hasHeader(String headerName); /** * For internal Play-use only */ play.api.mvc.RequestHeader _underlyingHeader(); } /** * An HTTP request. */ public static interface Request extends RequestHeader { /** * The request body. */ RequestBody body(); /** * The user name for this request, if defined. * This is usually set by annotating your Action with <code>@Authenticated</code>. */ String username(); /** * Defines the user name for this request. * @deprecated As of release 2.4, use {@link #withUsername} */ @Deprecated void setUsername(String username); /** * Returns a request updated with specified user name * @param username the new user name */ Request withUsername(String username); /** * For internal Play-use only */ play.api.mvc.Request<RequestBody> _underlyingRequest(); } /** * An HTTP request. */ public static class RequestImpl extends play.core.j.RequestHeaderImpl implements Request { private final play.api.mvc.Request<RequestBody> underlying; private String username; // Keep it non-final until setUsername is removed /** * Constructor only based on a header. * @param header the header from a request */ public RequestImpl(play.api.mvc.RequestHeader header) { super(header); this.underlying = null; } /** * Constructor with a requestbody. * @param request the body of the request */ public RequestImpl(play.api.mvc.Request<RequestBody> request) { super(request); this.underlying = request; } /** * Constructor with a request and a username. * @param request he body of the request * @param username the user which is making the request */ private RequestImpl(play.api.mvc.Request<RequestBody> request, String username) { super(request); this.underlying = request; this.username = username; } /** * @return the underlying body, if present otherwise null */ public RequestBody body() { return underlying != null ? underlying.body() : null; } /** * @return the username */ public String username() { return username; } /** * Sets the username. * @param username the username of the requester */ public void setUsername(String username) { this.username = username; } /** * This method returns a new request, based on the current underlying with a giving username. * @param username the new user name * @return a new request with a request body based on the current request */ public Request withUsername(String username) { return new RequestImpl(this.underlying, username); } /** * @return the underlying body of the request */ public play.api.mvc.Request<RequestBody> _underlyingRequest() { return underlying; } } /** * The builder for building a request. */ public static class RequestBuilder { protected AnyContent body; protected String username; /** * Returns a simple request builder, based on get and local address. */ public RequestBuilder() { method("GET"); uri("/"); host("localhost"); version("HTTP/1.1"); remoteAddress("127.0.0.1"); body(play.api.mvc.AnyContentAsEmpty$.MODULE$); } /** * @return the request body, if a previously the body has been set */ public RequestBody body() { if (body == null) { return null; } return new play.core.j.JavaParsers.DefaultRequestBody( body.asFormUrlEncoded(), body.asRaw(), body.asText(), body.asJson(), body.asXml(), body.asMultipartFormData()); } /** * @return the body of the request */ public AnyContent bodyAsAnyContent() { return body; } /** * @return the username */ public String username() { return username; } /** * @param username the username for the request * @return the builder */ public RequestBuilder username(String username) { this.username = username; return this; } /** * Set a AnyContent to this request. * @param anyContent the AnyContent * @param contentType Content-Type header value */ protected RequestBuilder body(AnyContent anyContent, String contentType) { header("Content-Type", contentType); body(anyContent); return this; } /** * Set a AnyContent to this request. * @param anyContent the AnyContent */ protected RequestBuilder body(AnyContent anyContent) { body = anyContent; return this; } /** * Set a Binary Data to this request. * The <tt>Content-Type</tt> header of the request is set to <tt>application/octet-stream</tt>. * @param data the Binary Data */ public RequestBuilder bodyRaw(byte[] data) { play.api.mvc.RawBuffer buffer = new play.api.mvc.RawBuffer(data.length, data); return body(new AnyContentAsRaw(buffer), "application/octet-stream"); } /** * Set a Form url encoded body to this request. */ public RequestBuilder bodyFormArrayValues(Map<String,String[]> data) { Map<String,Seq<String>> seqs = new HashMap<>(); for (Entry<String,String[]> entry: data.entrySet()) { seqs.put(entry.getKey(), Predef.genericWrapArray(entry.getValue())); } scala.collection.immutable.Map<String,Seq<String>> map = asScala(seqs); return body(new AnyContentAsFormUrlEncoded(map), "application/x-www-form-urlencoded"); } /** * Set a Form url encoded body to this request. */ public RequestBuilder bodyForm(Map<String,String> data) { Map<String,Seq<String>> seqs = new HashMap<>(); for (Entry<String,String> entry: data.entrySet()) { seqs.put(entry.getKey(), JavaConversions.asScalaBuffer(Arrays.asList(entry.getValue()))); } scala.collection.immutable.Map<String,Seq<String>> map = asScala(seqs); return body(new AnyContentAsFormUrlEncoded(map), "application/x-www-form-urlencoded"); } /** * Set a Json Body to this request. * The <tt>Content-Type</tt> header of the request is set to <tt>application/json</tt>. * @param node the Json Node */ public RequestBuilder bodyJson(JsonNode node) { return bodyJson(JacksonJson.jsonNodeToJsValue(node)); } /** * Set a Json Body to this request. * The <tt>Content-Type</tt> header of the request is set to <tt>application/json</tt>. * @param json the JsValue */ public RequestBuilder bodyJson(JsValue json) { return body(new AnyContentAsJson(json), "application/json"); } /** * Set a XML to this request. * The <tt>Content-Type</tt> header of the request is set to <tt>application/xml</tt>. * @param xml the XML */ public RequestBuilder bodyXml(InputSource xml) { return body(new AnyContentAsXml(scala.xml.XML.load(xml)), "application/xml"); } /** * Set a Text to this request. * The <tt>Content-Type</tt> header of the request is set to <tt>text/plain</tt>. * @param text the text */ public RequestBuilder bodyText(String text) { return body(new AnyContentAsText(text), "text/plain"); } /** * Builds the request. * @return a build of the given parameters */ public RequestImpl build() { return new RequestImpl(new play.api.mvc.RequestImpl( body(), id, asScala(tags()), uri.toString(), uri.getRawPath(), method, version, mapListToScala(splitQuery()), buildHeaders(), remoteAddress, secure)); } // ------------------- // REQUEST HEADER CODE protected Long id = RequestIdProvider.requestIDs().incrementAndGet(); protected Map<String, String> tags = new HashMap<>(); protected String method; protected boolean secure; protected URI uri; protected String version; protected Map<String, String[]> headers = new HashMap<>(); protected String remoteAddress; /** * @return the id of the request */ public Long id() { return id; } /** * @param id the id to be used * @return the builder instance */ public RequestBuilder id(Long id) { this.id = id; return this; } /** * @return the tags for the request */ public Map<String, String> tags() { return tags; } /** * @param tags overwrites the tags for this request * @return the builder instance */ public RequestBuilder tags(Map<String, String> tags) { this.tags = tags; return this; } /** * Puts an extra tag. * @param key the key for the tag * @param value the value for the tag * @return the builder */ public RequestBuilder tag(String key, String value) { tags.put(key, value); return this; } /** * @return the builder instance. */ public String method() { return method; } /** * @param method sets the method * @return the builder instance */ public RequestBuilder method(String method) { this.method = method; return this; } /** * @return gives the uri of the request */ public String uri() { return uri.toString(); } public RequestBuilder uri(URI uri) { if (uri.getScheme() != null) { if (!uri.getScheme().equals("http") && !uri.getScheme().equals("https")) { throw new IllegalArgumentException("URI scheme must be http or https"); } this.secure = uri.getScheme().equals("https"); } this.uri = uri; host(uri.getHost()); return this; } /** * Sets the uri. * @param str the uri * @return the builder instance */ public RequestBuilder uri(String str) { try { uri(new URI(str)); } catch (URISyntaxException e) { throw new IllegalArgumentException("Exception parsing URI", e); } return this; } /** * @param secure true if the request is secure * @return the builder instance */ public RequestBuilder secure(boolean secure) { this.secure = secure; return this; } /** * @return the status if the request is secure */ public boolean secure() { return secure; } /** * @return the host name from the header */ public String host() { return header(HeaderNames.HOST); } /** * @param host sets the host in the header * @return the builder instance */ public RequestBuilder host(String host) { header(HeaderNames.HOST, host); return this; } /** * @return the raw path of the uri */ public String path() { return uri.getRawPath(); } /** * This method sets the path of the uri. * @param path the path after the port and for the query in a uri * @return the builder instance */ public RequestBuilder path(String path) { try { uri = new URI(uri.getScheme(), uri.getUserInfo(), uri.getHost(), uri.getPort(), path, uri.getQuery(), uri.getFragment()); } catch (URISyntaxException e) { throw new IllegalArgumentException(e); } return this; } /** * @return the version */ public String version() { return version; } /** * @param version the version * @return the builder instance */ public RequestBuilder version(String version) { this.version = version; return this; } /** * @param key the key to be used in the header * @return the value associated with the key, if multiple, the first, if none returns null */ public String header(String key) { String[] values = headers.get(key); return values == null || values.length == 0 ? null : values[0]; } /** * @param key the key to be used in the header * @return all values (could be 0) associated with the key */ public String[] headers(String key) { return headers.get(key); } /** * @return the headers */ public Map<String, String[]> headers() { return headers; } /** * @param headers the headers to be replaced * @return the builder instance */ public RequestBuilder headers(Map<String, String[]> headers) { this.headers = headers; return this; } /** * @param key the key for in the header * @param values the values associated with the key * @return the builder instance */ public RequestBuilder header(String key, String[] values) { headers.put(key, values); return this; } /** * @param key the key for in the header * @param value the value (one) associated with the key * @return the builder instance */ public RequestBuilder header(String key, String value) { headers.put(key, new String[] { value }); return this; } /** * @return the cookies in Scala instances */ private play.api.mvc.Cookies scalaCookies() { String cookieHeader = header(HeaderNames.COOKIE); scala.Option<String> cookieHeaderOpt = scala.Option.apply(cookieHeader); return play.api.mvc.Cookies$.MODULE$.fromCookieHeader(cookieHeaderOpt); } /** * @return the cookies in Java instances */ public Cookies cookies() { return play.core.j.JavaHelpers$.MODULE$.cookiesToJavaCookies(scalaCookies()); } /** * Sets the cookies in the header. * @param cookies the cookies in a Scala sequence */ private void cookies(Seq<play.api.mvc.Cookie> cookies) { String cookieHeader = header(HeaderNames.COOKIE); String value = play.api.mvc.Cookies$.MODULE$.mergeCookieHeader(cookieHeader != null ? cookieHeader : "", cookies); header(HeaderNames.COOKIE, value); } /** * Sets one cookie. * @param cookie the cookie to be set * @return the builder instance */ public RequestBuilder cookie(Cookie cookie) { cookies(play.core.j.JavaHelpers$.MODULE$.cookiesToScalaCookies(Arrays.asList(cookie))); return this; } /** * @return the cookies in a Java map */ public Map<String,String> flash() { play.api.mvc.Cookies scalaCookies = scalaCookies(); scala.Option<play.api.mvc.Cookie> cookie = scalaCookies.get(play.api.mvc.Flash$.MODULE$.COOKIE_NAME()); scala.collection.Map<String,String> data = play.api.mvc.Flash$.MODULE$.decodeCookieToMap(cookie); return JavaConversions.mapAsJavaMap(data); } /** * Sets a cookie in the request. * @param key the key for the cookie * @param value the value for the cookie * @return the builder instance */ public RequestBuilder flash(String key, String value) { Map<String,String> data = new HashMap<>(flash()); data.put(key, value); flash(data); return this; } /** * Sets cookies in a request. * @param data a key value mapping of cookies * @return the builder instance */ public RequestBuilder flash(Map<String,String> data) { play.api.mvc.Flash flash = new play.api.mvc.Flash(asScala(data)); cookies(JavaConversions.asScalaBuffer(Arrays.asList(play.api.mvc.Flash$.MODULE$.encodeAsCookie(flash)))); return this; } /** * @return the sessions in the request */ public Map<String,String> session() { play.api.mvc.Cookies scalaCookies = scalaCookies(); scala.Option<play.api.mvc.Cookie> cookie = scalaCookies.get(play.api.mvc.Session$.MODULE$.COOKIE_NAME()); scala.collection.Map<String,String> data = play.api.mvc.Session$.MODULE$.decodeCookieToMap(cookie); return JavaConversions.mapAsJavaMap(data); } /** * Sets a session. * @param key the key for the session * @param value the value associated with the key for the session * @return the builder instance */ public RequestBuilder session(String key, String value) { Map<String,String> data = new HashMap<>(session()); data.put(key, value); session(data); return this; } /** * Sets all parameters for the session. * @param data a key value mapping of the session data * @return the builder instance */ public RequestBuilder session(Map<String,String> data) { play.api.mvc.Session session = new play.api.mvc.Session(asScala(data)); cookies(JavaConversions.asScalaBuffer(Arrays.asList(play.api.mvc.Session$.MODULE$.encodeAsCookie(session)))); return this; } /** * @return the remote address */ public String remoteAddress() { return remoteAddress; } /** * @param remoteAddress sets the remote address * @return the builder instance */ public RequestBuilder remoteAddress(String remoteAddress) { this.remoteAddress = remoteAddress; return this; } protected Map<String, List<String>> splitQuery() { try { Map<String, List<String>> query_pairs = new LinkedHashMap<String, List<String>>(); String query = uri.getQuery(); if (query == null) { return new HashMap<>(); } String[] pairs = query.split("&"); for (String pair : pairs) { int idx = pair.indexOf("="); String key = idx > 0 ? URLDecoder.decode(pair.substring(0, idx), "UTF-8") : pair; if (!query_pairs.containsKey(key)) { query_pairs.put(key, new LinkedList<String>()); } String value = idx > 0 && pair.length() > idx + 1 ? URLDecoder.decode(pair.substring(idx + 1), "UTF-8") : null; query_pairs.get(key).add(value); } return query_pairs; } catch(UnsupportedEncodingException e) { throw new IllegalStateException("This can never happen", e); } } protected static scala.collection.immutable.Map<String,Seq<String>> mapListToScala(Map<String,List<String>> data) { Map<String,Seq<String>> seqs = new HashMap<>(); for (String key: data.keySet()) { seqs.put(key, JavaConversions.asScalaBuffer(data.get(key))); } return asScala(seqs); } protected Headers buildHeaders() { List<Tuple2<String, String>> list = new ArrayList<>(); for (Map.Entry<String,String[]> entry : headers().entrySet()) { for (String value : entry.getValue()) { list.add(new Tuple2<>(entry.getKey(), value)); } } return new Headers(JavaConversions.asScalaBuffer(list)); } } /** * Handle the request body a raw bytes data. */ public abstract static class RawBuffer { /** * Buffer size. */ public abstract Long size(); /** * Returns the buffer content as a bytes array. * * @param maxLength The max length allowed to be stored in memory * @return null if the content is too big to fit in memory */ public abstract byte[] asBytes(int maxLength); /** * Returns the buffer content as a bytes array */ public abstract byte[] asBytes(); /** * Returns the buffer content as File */ public abstract File asFile(); } /** * Multipart form data body. */ public abstract static class MultipartFormData { /** * A file part. */ public static class FilePart { final String key; final String filename; final String contentType; final File file; public FilePart(String key, String filename, String contentType, File file) { this.key = key; this.filename = filename; this.contentType = contentType; this.file = file; } /** * The part name. */ public String getKey() { return key; } /** * The file name. */ public String getFilename() { return filename; } /** * The file Content-Type */ public String getContentType() { return contentType; } /** * The File. */ public File getFile() { return file; } } /** * Extract the data parts as Form url encoded. */ public abstract Map<String,String[]> asFormUrlEncoded(); /** * Retrieves all file parts. */ public abstract List<FilePart> getFiles(); /** * Access a file part. */ public FilePart getFile(String key) { for(FilePart filePart: getFiles()) { if(filePart.getKey().equals(key)) { return filePart; } } return null; } } /** * The request body. */ public static class RequestBody { /** * @deprecated Since Play 2.4, this method always returns false. When the max size is exceeded, a 413 error is * returned */ @Deprecated public boolean isMaxSizeExceeded() { return false; } /** * The request content parsed as multipart form data. */ public MultipartFormData asMultipartFormData() { return null; } /** * The request content parsed as URL form-encoded. */ public Map<String,String[]> asFormUrlEncoded() { return null; } /** * The request content as Array bytes. */ public RawBuffer asRaw() { return null; } /** * The request content as text. */ public String asText() { return null; } /** * The request content as XML. */ public Document asXml() { return null; } /** * The request content as Json. */ public JsonNode asJson() { return null; } /** * Cast this RequestBody as T if possible. */ @SuppressWarnings("unchecked") public <T> T as(Class<T> tType) { if(this.getClass().isAssignableFrom(tType)) { return (T)this; } else { return null; } } } /** * The HTTP response. */ public static class Response implements HeaderNames { private final Map<String, String> headers = new TreeMap<>((Comparator<String>) String::compareToIgnoreCase); private final List<Cookie> cookies = new ArrayList<>(); /** * Adds a new header to the response. * * @param name The name of the header, must not be null * @param value The value of the header, must not be null */ public void setHeader(String name, String value) { this.headers.put(name, value); } /** * Gets the current response headers. */ public Map<String,String> getHeaders() { return headers; } /** * Sets the content-type of the response. * * @param contentType The content type, must not be null */ public void setContentType(String contentType) { setHeader(CONTENT_TYPE, contentType); } /** * Set a new transient cookie with path "/".<br> * For example: * <pre> * response().setCookie("theme", "blue"); * </pre> * @param name Cookie name, must not be null * @param value Cookie value */ public void setCookie(String name, String value) { setCookie(name, value, null); } /** * Set a new cookie with path "/". * @param name Cookie name, must not be null * @param value Cookie value * @param maxAge Cookie duration (null for a transient cookie and 0 or less for a cookie that expires now) */ public void setCookie(String name, String value, Integer maxAge) { setCookie(name, value, maxAge, "/"); } /** * Set a new cookie. * @param name Cookie name, must not be null * @param value Cookie value * @param maxAge Cookie duration (null for a transient cookie and 0 or less for a cookie that expires now) * @param path Cookie path */ public void setCookie(String name, String value, Integer maxAge, String path) { setCookie(name, value, maxAge, path, null); } /** * Set a new cookie. * @param name Cookie name, must not be null * @param value Cookie value * @param maxAge Cookie duration (null for a transient cookie and 0 or less for a cookie that expires now) * @param path Cookie path * @param domain Cookie domain */ public void setCookie(String name, String value, Integer maxAge, String path, String domain) { setCookie(name, value, maxAge, path, domain, false, false); } /** * Set a new cookie. * @param name Cookie name, must not be null * @param value Cookie value * @param maxAge Cookie duration (null for a transient cookie and 0 or less for a cookie that expires now) * @param path Cookie path * @param domain Cookie domain * @param secure Whether the cookie is secured (for HTTPS requests) * @param httpOnly Whether the cookie is HTTP only (i.e. not accessible from client-side JavaScript code) */ public void setCookie(String name, String value, Integer maxAge, String path, String domain, boolean secure, boolean httpOnly) { cookies.add(new Cookie(name, value, maxAge, path, domain, secure, httpOnly)); } /** * Discard a cookie on the default path ("/") with no domain and that's not secure. * * @param name The name of the cookie to discard, must not be null */ public void discardCookie(String name) { discardCookie(name, "/", null, false); } /** * Discard a cookie on the given path with no domain and not that's secure. * * @param name The name of the cookie to discard, must not be null * @param path The path of the cookie te discard, may be null */ public void discardCookie(String name, String path) { discardCookie(name, path, null, false); } /** * Discard a cookie on the given path and domain that's not secure. * * @param name The name of the cookie to discard, must not be null * @param path The path of the cookie te discard, may be null * @param domain The domain of the cookie to discard, may be null */ public void discardCookie(String name, String path, String domain) { discardCookie(name, path, domain, false); } /** * Discard a cookie in this result * * @param name The name of the cookie to discard, must not be null * @param path The path of the cookie te discard, may be null * @param domain The domain of the cookie to discard, may be null * @param secure Whether the cookie to discard is secure */ public void discardCookie(String name, String path, String domain, boolean secure) { cookies.add(new Cookie(name, "", -86400, path, domain, secure, false)); } public Collection<Cookie> cookies() { return cookies; } public Optional<Cookie> cookie(String name) { return cookies.stream().filter(x -> { return x.name().equals(name); }).findFirst(); } } /** * HTTP Session. * <p> * Session data are encoded into an HTTP cookie, and can only contain simple <code>String</code> values. */ public static class Session extends HashMap<String,String>{ public boolean isDirty = false; public Session(Map<String,String> data) { super(data); } /** * Removes the specified value from the session. */ @Override public String remove(Object key) { isDirty = true; return super.remove(key); } /** * Adds the given value to the session. */ @Override public String put(String key, String value) { isDirty = true; return super.put(key, value); } /** * Adds the given values to the session. */ @Override public void putAll(Map<? extends String,? extends String> values) { isDirty = true; super.putAll(values); } /** * Clears the session. */ @Override public void clear() { isDirty = true; super.clear(); } } /** * HTTP Flash. * <p> * Flash data are encoded into an HTTP cookie, and can only contain simple String values. */ public static class Flash extends HashMap<String,String>{ public boolean isDirty = false; public Flash(Map<String,String> data) { super(data); } /** * Removes the specified value from the flash scope. */ @Override public String remove(Object key) { isDirty = true; return super.remove(key); } /** * Adds the given value to the flash scope. */ @Override public String put(String key, String value) { isDirty = true; return super.put(key, value); } /** * Adds the given values to the flash scope. */ @Override public void putAll(Map<? extends String,? extends String> values) { isDirty = true; super.putAll(values); } /** * Clears the flash scope. */ @Override public void clear() { isDirty = true; super.clear(); } } /** * HTTP Cookie */ public static class Cookie { private final String name; private final String value; private final Integer maxAge; private final String path; private final String domain; private final boolean secure; private final boolean httpOnly; public Cookie(String name, String value, Integer maxAge, String path, String domain, boolean secure, boolean httpOnly) { this.name = name; this.value = value; this.maxAge = maxAge; this.path = path; this.domain = domain; this.secure = secure; this.httpOnly = httpOnly; } /** * @return the cookie name */ public String name() { return name; } /** * @return the cookie value */ public String value() { return value; } /** * @return the cookie expiration date in seconds, null for a transient cookie, a value less than zero for a * cookie that expires now */ public Integer maxAge() { return maxAge; } /** * @return the cookie path */ public String path() { return path; } /** * @return the cookie domain, or null if not defined */ public String domain() { return domain; } /** * @return wether the cookie is secured, sent only for HTTPS requests */ public boolean secure() { return secure; } /** * @return wether the cookie is HTTP only, i.e. not accessible from client-side JavaScript code */ public boolean httpOnly() { return httpOnly; } } /** * HTTP Cookies set */ public interface Cookies extends Iterable<Cookie> { /** * @param name Name of the cookie to retrieve * @return the cookie that is associated with the given name, or null if there is no such cookie */ public Cookie get(String name); } /** * Defines all standard HTTP headers. */ public static interface HeaderNames { String ACCEPT = "Accept"; String ACCEPT_CHARSET = "Accept-Charset"; String ACCEPT_ENCODING = "Accept-Encoding"; String ACCEPT_LANGUAGE = "Accept-Language"; String ACCEPT_RANGES = "Accept-Ranges"; String AGE = "Age"; String ALLOW = "Allow"; String AUTHORIZATION = "Authorization"; String CACHE_CONTROL = "Cache-Control"; String CONNECTION = "Connection"; String CONTENT_DISPOSITION = "Content-Disposition"; String CONTENT_ENCODING = "Content-Encoding"; String CONTENT_LANGUAGE = "Content-Language"; String CONTENT_LENGTH = "Content-Length"; String CONTENT_LOCATION = "Content-Location"; String CONTENT_MD5 = "Content-MD5"; String CONTENT_RANGE = "Content-Range"; String CONTENT_TRANSFER_ENCODING = "Content-Transfer-Encoding"; String CONTENT_TYPE = "Content-Type"; String COOKIE = "Cookie"; String DATE = "Date"; String ETAG = "ETag"; String EXPECT = "Expect"; String EXPIRES = "Expires"; String FORWARDED = "Forwarded"; String FROM = "From"; String HOST = "Host"; String IF_MATCH = "If-Match"; String IF_MODIFIED_SINCE = "If-Modified-Since"; String IF_NONE_MATCH = "If-None-Match"; String IF_RANGE = "If-Range"; String IF_UNMODIFIED_SINCE = "If-Unmodified-Since"; String LAST_MODIFIED = "Last-Modified"; String LOCATION = "Location"; String MAX_FORWARDS = "Max-Forwards"; String PRAGMA = "Pragma"; String PROXY_AUTHENTICATE = "Proxy-Authenticate"; String PROXY_AUTHORIZATION = "Proxy-Authorization"; String RANGE = "Range"; String REFERER = "Referer"; String RETRY_AFTER = "Retry-After"; String SERVER = "Server"; String SET_COOKIE = "Set-Cookie"; String SET_COOKIE2 = "Set-Cookie2"; String TE = "Te"; String TRAILER = "Trailer"; String TRANSFER_ENCODING = "Transfer-Encoding"; String UPGRADE = "Upgrade"; String USER_AGENT = "User-Agent"; String VARY = "Vary"; String VIA = "Via"; String WARNING = "Warning"; String WWW_AUTHENTICATE = "WWW-Authenticate"; String ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin"; String ACCESS_CONTROL_EXPOSE_HEADERS = "Access-Control-Expose-Headers"; String ACCESS_CONTROL_MAX_AGE = "Access-Control-Max-Age"; String ACCESS_CONTROL_ALLOW_CREDENTIALS = "Access-Control-Allow-Credentials"; String ACCESS_CONTROL_ALLOW_METHODS = "Access-Control-Allow-Methods"; String ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers"; String ORIGIN = "Origin"; String ACCESS_CONTROL_REQUEST_METHOD = "Access-Control-Request-Method"; String ACCESS_CONTROL_REQUEST_HEADERS = "Access-Control-Request-Headers"; String X_FORWARDED_FOR = "X-Forwarded-For"; String X_FORWARDED_HOST = "X-Forwarded-Host"; String X_FORWARDED_PORT = "X-Forwarded-Port"; String X_FORWARDED_PROTO = "X-Forwarded-Proto"; String X_REQUESTED_WITH = "X-Requested-With"; } /** * Defines all standard HTTP status codes. */ public static interface Status { int CONTINUE = 100; int SWITCHING_PROTOCOLS = 101; int OK = 200; int CREATED = 201; int ACCEPTED = 202; int NON_AUTHORITATIVE_INFORMATION = 203; int NO_CONTENT = 204; int RESET_CONTENT = 205; int PARTIAL_CONTENT = 206; int MULTIPLE_CHOICES = 300; int MOVED_PERMANENTLY = 301; int FOUND = 302; int SEE_OTHER = 303; int NOT_MODIFIED = 304; int USE_PROXY = 305; int TEMPORARY_REDIRECT = 307; int BAD_REQUEST = 400; int UNAUTHORIZED = 401; int PAYMENT_REQUIRED = 402; int FORBIDDEN = 403; int NOT_FOUND = 404; int METHOD_NOT_ALLOWED = 405; int NOT_ACCEPTABLE = 406; int PROXY_AUTHENTICATION_REQUIRED = 407; int REQUEST_TIMEOUT = 408; int CONFLICT = 409; int GONE = 410; int LENGTH_REQUIRED = 411; int PRECONDITION_FAILED = 412; int REQUEST_ENTITY_TOO_LARGE = 413; int REQUEST_URI_TOO_LONG = 414; int UNSUPPORTED_MEDIA_TYPE = 415; int REQUESTED_RANGE_NOT_SATISFIABLE = 416; int EXPECTATION_FAILED = 417; int INTERNAL_SERVER_ERROR = 500; int NOT_IMPLEMENTED = 501; int BAD_GATEWAY = 502; int SERVICE_UNAVAILABLE = 503; int GATEWAY_TIMEOUT = 504; int HTTP_VERSION_NOT_SUPPORTED = 505; } /** Common HTTP MIME types */ public static interface MimeTypes { /** * Content-Type of text. */ String TEXT = "text/plain"; /** * Content-Type of html. */ String HTML = "text/html"; /** * Content-Type of json. */ String JSON = "application/json"; /** * Content-Type of xml. */ String XML = "application/xml"; /** * Content-Type of css. */ String CSS = "text/css"; /** * Content-Type of javascript. */ String JAVASCRIPT = "text/javascript"; /** * Content-Type of form-urlencoded. */ String FORM = "application/x-www-form-urlencoded"; /** * Content-Type of server sent events. */ String EVENT_STREAM = "text/event-stream"; /** * Content-Type of binary data. */ String BINARY = "application/octet-stream"; } }