/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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. */ package org.jooby; import static java.util.Objects.requireNonNull; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.nio.charset.Charset; import java.util.Optional; import org.jooby.Cookie.Definition; import com.google.common.collect.ImmutableList; /** * Give you access to the actual HTTP response. You can read/write headers and write HTTP body. * * @author edgar * @since 0.1.0 */ public interface Response { /** * A forwarding response. * * @author edgar * @since 0.1.0 */ class Forwarding implements Response { /** The target response. */ protected final Response rsp; /** * Creates a new {@link Forwarding} response. * * @param response A response object. */ public Forwarding(final Response response) { this.rsp = requireNonNull(response, "A response is required."); } @Override public void download(final String filename, final InputStream stream) throws Throwable { rsp.download(filename, stream); } @Override public void download(final File file) throws Throwable { rsp.download(file); } @Override public void download(final String filename, final File file) throws Throwable { rsp.download(filename, file); } @Override public void download(final String filename) throws Throwable { rsp.download(filename); } @Override public void download(final String filename, final String location) throws Throwable { rsp.download(filename, location); } @Override public Response cookie(final String name, final String value) { rsp.cookie(name, value); return this; } @Override public Response cookie(final Cookie cookie) { rsp.cookie(cookie); return this; } @Override public Response cookie(final Definition cookie) { rsp.cookie(cookie); return this; } @Override public Response clearCookie(final String name) { rsp.clearCookie(name); return this; } @Override public Mutant header(final String name) { return rsp.header(name); } @Override public Response header(final String name, final Object value) { rsp.header(name, value); return this; } @Override public Response header(final String name, final Object... values) { rsp.header(name, values); return this; } @Override public Response header(final String name, final Iterable<Object> values) { rsp.header(name, values); return this; } @Override public Charset charset() { return rsp.charset(); } @Override public Response charset(final Charset charset) { rsp.charset(charset); return this; } @Override public Response length(final long length) { rsp.length(length); return this; } @Override public Optional<MediaType> type() { return rsp.type(); } @Override public Response type(final MediaType type) { rsp.type(type); return this; } @Override public Response type(final String type) { rsp.type(type); return this; } @Override public void send(final Object result) throws Throwable { // Special case: let the default response to deal with Object refs. // once resolved it will call the Result version. Response.super.send(result); } @Override public void send(final Result result) throws Throwable { rsp.send(result); } @Override public void end() { rsp.end(); } @Override public void redirect(final String location) throws Throwable { rsp.redirect(location); } @Override public void redirect(final Status status, final String location) throws Throwable { rsp.redirect(status, location); } @Override public Optional<Status> status() { return rsp.status(); } @Override public Response status(final Status status) { rsp.status(status); return this; } @Override public Response status(final int status) { rsp.status(status); return this; } @Override public boolean committed() { return rsp.committed(); } @Override public void after(final Route.After handler) { rsp.after(handler); } @Override public void complete(final Route.Complete handler) { rsp.complete(handler); } @Override public String toString() { return rsp.toString(); } /** * Unwrap a response in order to find out the target instance. * * @param rsp A response. * @return A target instance (not a {@link Response.Forwarding}). */ public static Response unwrap(final Response rsp) { requireNonNull(rsp, "A response is required."); Response root = rsp; while (root instanceof Forwarding) { root = ((Forwarding) root).rsp; } return root; } } /** * Transfer the file at path as an "attachment". Typically, browsers will prompt the user for * download. The <code>Content-Disposition</code> "filename=" parameter (i.e. the one that will * appear in the browser dialog) is set to filename. * * @param filename A file name to use. * @param stream A stream to attach. * @throws Exception If something goes wrong. */ void download(String filename, InputStream stream) throws Throwable; /** * Transfer the file at path as an "attachment". Typically, browsers will prompt the user for * download. The <code>Content-Disposition</code> "filename=" parameter (i.e. the one that will * appear in the browser dialog) is set to filename by default. * * @param location Classpath location of the file. * @throws Exception If something goes wrong. */ default void download(final String location) throws Throwable { download(location, location); } /** * Transfer the file at path as an "attachment". Typically, browsers will prompt the user for * download. The <code>Content-Disposition</code> "filename=" parameter (i.e. the one that will * appear in the browser dialog) is set to filename by default. * * @param filename A file name to use. * @param location classpath location of the file. * @throws Exception If something goes wrong. */ void download(final String filename, final String location) throws Throwable; /** * Transfer the file at path as an "attachment". Typically, browsers will prompt the user for * download. The <code>Content-Disposition</code> "filename=" parameter (i.e. the one that will * appear in the browser dialog) is set to filename by default. * * @param file A file to use. * @throws Exception If something goes wrong. */ default void download(final File file) throws Throwable { download(file.getName(), file); } /** * Transfer the file at path as an "attachment". Typically, browsers will prompt the user for * download. The <code>Content-Disposition</code> "filename=" parameter (i.e. the one that will * appear in the browser dialog) is set to filename. * * @param filename A file name to use. * @param file A file to use. * @throws Exception If something goes wrong. */ default void download(final String filename, final File file) throws Throwable { length(file.length()); download(filename, new FileInputStream(file)); } /** * Adds the specified cookie to the response. * * @param name A cookie's name. * @param value A cookie's value. * @return This response. */ default Response cookie(final String name, final String value) { return cookie(new Cookie.Definition(name, value)); } /** * Adds the specified cookie to the response. * * @param cookie A cookie definition. * @return This response. */ Response cookie(final Cookie.Definition cookie); /** * Adds the specified cookie to the response. * * @param cookie A cookie. * @return This response. */ Response cookie(Cookie cookie); /** * Discard a cookie from response. Discard is done by setting maxAge=0. * * @param name Cookie's name. * @return This response. */ Response clearCookie(String name); /** * Get a header with the given name. * * @param name A name. * @return A HTTP header. */ Mutant header(String name); /** * Sets a response header with the given name and value. If the header had already been set, * the new value overwrites the previous one. * * @param name Header's name. * @param value Header's value. * @return This response. */ Response header(String name, Object value); /** * Sets a response header with the given name and value. If the header had already been set, * the new value overwrites the previous one. * * @param name Header's name. * @param values Header's value. * @return This response. */ default Response header(final String name, final Object... values) { return header(name, ImmutableList.builder().add(values).build()); } /** * Sets a response header with the given name and value. If the header had already been set, * the new value overwrites the previous one. * * @param name Header's name. * @param values Header's value. * @return This response. */ Response header(String name, Iterable<Object> values); /** * If charset is not set this method returns charset defined in the request body. If the request * doesn't specify a character encoding, this method return the global charset: * <code>application.charset</code>. * * @return A current charset. */ Charset charset(); /** * Set the {@link Charset} to use and set the <code>Content-Type</code> header with the current * charset. * * @param charset A charset. * @return This response. */ Response charset(Charset charset); /** * Set the length of the response and set the <code>Content-Length</code> header. * * @param length Length of response. * @return This response. */ Response length(long length); /** * @return Get the response type. */ Optional<MediaType> type(); /** * Set the response media type and set the <code>Content-Type</code> header. * * @param type A media type. * @return This response. */ Response type(MediaType type); /** * Set the response media type and set the <code>Content-Type</code> header. * * @param type A media type. * @return This response. */ default Response type(final String type) { return type(MediaType.valueOf(type)); } /** * Responsible of writing the given body into the HTTP response. * * @param result The HTTP body. * @throws Exception If the response write fails. */ default void send(final Object result) throws Throwable { requireNonNull(result, "A response message is required."); if (result instanceof Result) { send((Result) result); } else { // wrap body Result b = Results.with(result); status().ifPresent(b::status); type().ifPresent(b::type); send(b); } } /** * Responsible of writing the given body into the HTTP response. * * @param result A HTTP response. * @throws Exception If the response write fails. */ void send(Result result) throws Throwable; /** * Redirect to the given url with status code defaulting to {@link Status#FOUND}. * * <pre> * rsp.redirect("/foo/bar"); * rsp.redirect("http://example.com"); * rsp.redirect("http://example.com"); * rsp.redirect("../login"); * </pre> * * Redirects can be a fully qualified URI for redirecting to a different site: * * <pre> * rsp.redirect("http://google.com"); * </pre> * * Redirects can be relative to the root of the host name. For example, if you were * on <code>http://example.com/admin/post/new</code>, the following redirect to /admin would * land you at <code>http://example.com/admin</code>: * * <pre> * rsp.redirect("/admin"); * </pre> * * Redirects can be relative to the current URL. A redirection of post/new, from * <code>http://example.com/blog/admin/</code> (notice the trailing slash), would give you * <code>http://example.com/blog/admin/post/new.</code> * * <pre> * rsp.redirect("post/new"); * </pre> * * Redirecting to post/new from <code>http://example.com/blog/admin</code> (no trailing slash), * will take you to <code>http://example.com/blog/post/new</code>. * * <p> * If you found the above behavior confusing, think of path segments as directories (have trailing * slashes) and files, it will start to make sense. * </p> * * Pathname relative redirects are also possible. If you were on * <code>http://example.com/admin/post/new</code>, the following redirect would land you at * <code>http//example.com/admin</code>: * * <pre> * rsp.redirect(".."); * </pre> * * A back redirection will redirect the request back to the <code>Referer</code>, defaulting to * <code>/</code> when missing. * * <pre> * rsp.redirect("back"); * </pre> * * @param location Either a relative or absolute location. * @throws Throwable If redirection fails. */ default void redirect(final String location) throws Throwable { redirect(Status.FOUND, location); } /** * Redirect to the given url with status code defaulting to {@link Status#FOUND}. * * <pre> * rsp.redirect("/foo/bar"); * rsp.redirect("http://example.com"); * rsp.redirect("http://example.com"); * rsp.redirect("../login"); * </pre> * * Redirects can be a fully qualified URI for redirecting to a different site: * * <pre> * rsp.redirect("http://google.com"); * </pre> * * Redirects can be relative to the root of the host name. For example, if you were * on <code>http://example.com/admin/post/new</code>, the following redirect to /admin would * land you at <code>http://example.com/admin</code>: * * <pre> * rsp.redirect("/admin"); * </pre> * * Redirects can be relative to the current URL. A redirection of post/new, from * <code>http://example.com/blog/admin/</code> (notice the trailing slash), would give you * <code>http://example.com/blog/admin/post/new.</code> * * <pre> * rsp.redirect("post/new"); * </pre> * * Redirecting to post/new from <code>http://example.com/blog/admin</code> (no trailing slash), * will take you to <code>http://example.com/blog/post/new</code>. * * <p> * If you found the above behavior confusing, think of path segments as directories (have trailing * slashes) and files, it will start to make sense. * </p> * * Pathname relative redirects are also possible. If you were on * <code>http://example.com/admin/post/new</code>, the following redirect would land you at * <code>http//example.com/admin</code>: * * <pre> * rsp.redirect(".."); * </pre> * * A back redirection will redirect the request back to the <code>Referer</code>, defaulting to * <code>/</code> when missing. * * <pre> * rsp.redirect("back"); * </pre> * * @param status A redirect status. * @param location Either a relative or absolute location. * @throws Throwable If redirection fails. */ void redirect(Status status, String location) throws Throwable; /** * @return A HTTP status or empty if status was not set yet. */ Optional<Status> status(); /** * Set the HTTP response status. * * @param status A HTTP status. * @return This response. */ Response status(Status status); /** * Set the HTTP response status. * * @param status A HTTP status. * @return This response. */ default Response status(final int status) { return status(Status.valueOf(status)); } /** * Returns a boolean indicating if the response has been committed. A committed response has * already had its status code and headers written. * * @return a boolean indicating if the response has been committed */ boolean committed(); /** * Ends current request/response cycle by releasing any existing resources and committing the * response into the channel. * * This method is automatically call it from a send method, so you are not force to call this * method per each request/response cycle. * * It's recommended for quickly ending the response without any data: * * <pre> * rsp.status(304).end(); * </pre> * * Keep in mind that an explicit call to this method will stop the execution of handlers. So, * any handler further in the chain won't be executed once end has been called. */ void end(); /** * Append an after handler, will be execute before sending response. * * @param handler A handler * @see Route.After */ void after(Route.After handler); /** * Append complete handler, will be execute after sending response. * * @param handler A handler * @see Route.After */ void complete(Route.Complete handler); }