All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ro.pippo.core.Response Maven / Gradle / Ivy

There is a newer version: 1.8.0
Show newest version
/*
 * Copyright (C) 2014 the original author or authors.
 *
 * 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.
 */
package ro.pippo.core;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ro.pippo.core.route.RouteContext;
import ro.pippo.core.route.RouteDispatcher;
import ro.pippo.core.util.DateUtils;
import ro.pippo.core.util.IoUtils;
import ro.pippo.core.util.MimeTypes;
import ro.pippo.core.util.StringUtils;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * @author Decebal Suiu
 */
public final class Response {

    private static final Logger log = LoggerFactory.getLogger(Response.class);

    private HttpServletResponse httpServletResponse;
    private ContentTypeEngines contentTypeEngines;
    private TemplateEngine templateEngine;
    private Map locals;
    private Map headers;
    private Map cookies;
    private String contextPath;
    private String applicationPath;
    private ResponseFinalizeListenerList finalizeListeners;
    private MimeTypes mimeTypes;

    private int status;

    public Response(HttpServletResponse httpServletResponse, Application application) {
        this.httpServletResponse = httpServletResponse;
        this.contentTypeEngines = application.getContentTypeEngines();
        this.templateEngine = application.getTemplateEngine();
        this.httpServletResponse.setCharacterEncoding(StandardCharsets.UTF_8.toString());
        this.contextPath = application.getRouter().getContextPath();
        this.applicationPath = StringUtils.removeEnd(application.getRouter().getApplicationPath(), "/");
        this.mimeTypes = application.getMimeTypes();

        this.status = 0;
    }

    /**
     * Map of bound objects which can be stored and shared between all handlers
     * for the current request/response cycle.
     * 

* All bound objects are made available to the template engine during parsing. *

* * @return the bound objects map */ public Map getLocals() { if (locals == null) { locals = new HashMap<>(); } return locals; } /** * Shortcut for getLocals().get("flash"). * @return */ public Flash getFlash() { return (Flash) getLocals().get("flash"); } /** * Binds an object to the response. * * @param name * @param model * @return the response */ public Response bind(String name, Object model) { getLocals().put(name, model); return this; } /** * Returns the servlet response. * * @return the servlet response */ public HttpServletResponse getHttpServletResponse() { return httpServletResponse; } /** * Gets the character encoding of the response. * * @return the character encoding */ public String getCharacterEncoding() { return getHttpServletResponse().getCharacterEncoding(); } /** * Sets the character encoding of the response. * * @param charset * @return the response */ public Response characterEncoding(String charset) { checkCommitted(); getHttpServletResponse().setCharacterEncoding(charset); return this; } private void addCookie(Cookie cookie) { checkCommitted(); if (StringUtils.isNullOrEmpty(cookie.getPath())) { cookie.setPath(StringUtils.addStart(contextPath, "/")); } getCookieMap().put(cookie.getName(), cookie); } /** * Adds a cookie to the response. * * @param cookie * @return the response */ public Response cookie(Cookie cookie) { addCookie(cookie); return this; } /** * Adds a cookie to the response. * * @param name * @param value * @return the response */ public Response cookie(String name, String value) { Cookie cookie = new Cookie(name, value); addCookie(cookie); return this; } /** * Adds a cookie to the response. * * @param name * @param value * @param maxAge * @return the response */ public Response cookie(String name, String value, int maxAge) { Cookie cookie = new Cookie(name, value); cookie.setMaxAge(maxAge); addCookie(cookie); return this; } /** * Adds a cookie to the response. * * @param path * @param domain * @param name * @param value * @param maxAge * @param secure * @return the response */ public Response cookie(String path, String domain, String name, String value, int maxAge, boolean secure) { Cookie cookie = new Cookie(name, value); cookie.setPath(path); cookie.setDomain(domain); cookie.setMaxAge(maxAge); cookie.setSecure(secure); addCookie(cookie); return this; } /** * Returns all cookies added to the response. * * @return the cookies added to the response */ public Collection getCookies() { return getCookieMap().values(); } /** * Gets the specified cookie by name. * * @param name * @return the cookie or null */ public Cookie getCookie(String name) { return getCookieMap().get(name); } /** * Removes the specified cookie by name. * * @param name * @return the response */ public Response removeCookie(String name) { Cookie cookie = new Cookie(name, ""); cookie.setMaxAge(0); addCookie(cookie); return this; } private Map getCookieMap() { if (cookies == null) { cookies = new HashMap<>(); } return cookies; } private boolean isHeaderEmpty(String name) { return StringUtils.isNullOrEmpty(getHeaderMap().get(name)); } /** * Sets a header. * * @param name * @param value * @return the response */ public Response header(String name, String value) { checkCommitted(); getHeaderMap().put(name, value); return this; } /** * Sets a header. * * @param name * @param value * @return the response */ public Response header(String name, Date value) { checkCommitted(); getHeaderMap().put(name, DateUtils.formatForHttpHeader(value)); return this; } /** * Returns a header value, if set in the Response. * * @param name * @return the header value or null */ public String getHeader(String name) { return getHeaderMap().get(name); } private Map getHeaderMap() { if (headers == null) { headers = new HashMap<>(); } return headers; } /** * Sets this response as not cacheable. * * @return the response */ public Response noCache() { checkCommitted(); // no-cache headers for HTTP/1.1 header(HttpConstants.Header.CACHE_CONTROL, "no-store, no-cache, must-revalidate"); // no-cache headers for HTTP/1.1 (IE) header(HttpConstants.Header.CACHE_CONTROL, "post-check=0, pre-check=0"); // no-cache headers for HTTP/1.0 header(HttpConstants.Header.PRAGMA, "no-cache"); // set the expires to past httpServletResponse.setDateHeader("Expires", 0); return this; } /** * Gets the status code of the response. * * @return the status code */ public int getStatus() { return status; } /** * Sets the status code of the response. * * @param status * @return the response */ public Response status(int status) { checkCommitted(); httpServletResponse.setStatus(status); this.status = status; return this; } /** * Redirect the browser to a location which may be... *
    *
  • relative to the current request *
  • relative to the servlet container root (if location starts with '/') *
  • an absolute url *
* If you want a context-relative redirect, use the * {@link ro.pippo.core.Response#redirectToContextPath(String)} method. * If you want an application-relative redirect, use the * {@link ro.pippo.core.Response#redirectToApplicationPath(String)} method. *

This method commits the response.

* * @param location * Where to redirect */ public void redirect(String location) { checkCommitted(); finalizeResponse(); try { httpServletResponse.sendRedirect(location); } catch (IOException e) { throw new PippoRuntimeException(e); } } /** * Redirects the browser to a path relative to the application context. For * example, redirectToContextPath("/contacts") might redirect the browser to * http://localhost/myContext/contacts *

This method commits the response.

* * @param path */ public void redirectToContextPath(String path) { if ("".equals(contextPath)) { // context path is the root redirect(path); } else { redirect(contextPath + StringUtils.addStart(path, "/")); } } /** * Redirects the browser to a path relative to the Pippo application root. For * example, redirectToApplicationPath("/contacts") might redirect the browser to * http://localhost/myContext/myApp/contacts *

This method commits the response.

* * @param path */ public void redirectToApplicationPath(String path) { if ("".equals(applicationPath)) { // application path is the root redirect(path); } else { redirect(applicationPath + StringUtils.addStart(path, "/")); } } /** * A permanent (3XX status code) redirect. *

This method commits the response.

* * @param location * @param statusCode */ public void redirect(String location, int statusCode) { checkCommitted(); finalizeResponse(); status(statusCode); header(HttpConstants.Header.LOCATION, location); header(HttpConstants.Header.CONNECTION, "close"); try { httpServletResponse.sendError(statusCode); } catch (IOException e) { throw new PippoRuntimeException(e); } } /** * Set the response status to OK (200). *

* Standard response for successful HTTP requests. The actual response will * depend on the request method used. In a GET request, the response will * contain an entity corresponding to the requested resource. In a POST * request the response will contain an entity describing or containing the * result of the action. *

* */ public Response ok() { status(HttpConstants.StatusCode.OK); return this; } /** * Set the response status to CREATED (201). *

* The request has been fulfilled and resulted in a new resource being created. *

* */ public Response created() { status(HttpConstants.StatusCode.CREATED); return this; } /** * Set the response status to ACCEPTED (202). *

* The request has been accepted for processing, but the processing has not * been completed. The request might or might not eventually be acted upon, * as it might be disallowed when processing actually takes place. *

* */ public Response accepted() { status(HttpConstants.StatusCode.ACCEPTED); return this; } /** * Set the response status to BAD REQUEST (400). *

* The server cannot or will not process the request due to something that * is perceived to be a client error. *

* */ public Response badRequest() { status(HttpConstants.StatusCode.BAD_REQUEST); return this; } /** * Set the response status to UNAUTHORIZED (401). *

* Similar to 403 Forbidden, but specifically for use when authentication is * required and has failed or has not yet been provided. The response must * include a WWW-Authenticate header field containing a challenge applicable * to the requested resource. *

*/ public Response unauthorized() { status(HttpConstants.StatusCode.UNAUTHORIZED); return this; } /** * Set the response status to PAYMENT REQUIRED (402). *

* Reserved for future use. The original intention was that this code might * be used as part of some form of digital cash or micropayment scheme, but * that has not happened, and this code is not usually used. *

*/ public Response paymentRequired() { status(HttpConstants.StatusCode.PAYMENT_REQUIRED); return this; } /** * Set the response status to FORBIDDEN (403). *

* The request was a valid request, but the server is refusing to respond to * it. Unlike a 401 Unauthorized response, authenticating will make no * difference. *

* */ public Response forbidden() { status(HttpConstants.StatusCode.FORBIDDEN); return this; } /** * Set the response status to NOT FOUND (404). *

* The requested resource could not be found but may be available again in * the future. Subsequent requests by the client are permissible. *

* */ public Response notFound() { status(HttpConstants.StatusCode.NOT_FOUND); return this; } /** * Set the response status to METHOD NOT ALLOWED (405). *

* A request was made of a resource using a request method not supported * by that resource; for example, using GET on a form which requires data * to be presented via POST, or using PUT on a read-only resource. *

* */ public Response methodNotAllowed() { status(HttpConstants.StatusCode.METHOD_NOT_ALLOWED); return this; } /** * Set the response status to CONFLICT (409). *

* Indicates that the request could not be processed because of conflict in * the request, such as an edit conflict in the case of multiple updates. *

* */ public Response conflict() { status(HttpConstants.StatusCode.CONFLICT); return this; } /** * Set the response status to GONE (410). *

* Indicates that the resource requested is no longer available and will not * be available again. This should be used when a resource has been * intentionally removed and the resource should be purged. Upon receiving a * 410 status code, the client should not request the resource again in the * future. *

* */ public Response gone() { status(HttpConstants.StatusCode.GONE); return this; } /** * Set the response status to INTERNAL ERROR (500). *

* A generic error message, given when an unexpected condition was * encountered and no more specific message is suitable. *

* */ public Response internalError() { status(HttpConstants.StatusCode.INTERNAL_ERROR); return this; } /** * Set the response status to NOT IMPLEMENTED (501). *

* The server either does not recognize the request method, or it lacks the * ability to fulfil the request. Usually this implies future availability * (e.g., a new feature of a web-service API). *

* */ public Response notImplemented() { status(HttpConstants.StatusCode.NOT_IMPLEMENTED); return this; } /** * Set the response status to OVERLOADED (502). *

* The server is currently unavailable (because it is overloaded or down * for maintenance). Generally, this is a temporary state. *

*/ public Response overloaded() { status(HttpConstants.StatusCode.OVERLOADED); return this; } /** * Set the response status to SERVICE UNAVAILABLE (503). *

* The server is currently unavailable (because it is overloaded or down * for maintenance). Generally, this is a temporary state. *

*/ public Response serviceUnavailable() { status(HttpConstants.StatusCode.SERVICE_UNAVAILABLE); return this; } /** * Sets the content length of the response. * * @param length * @return the response */ public Response contentLength(long length) { checkCommitted(); httpServletResponse.setContentLength((int) length); return this; } /** * Returns the content type of the response. * * @return the content type */ public String getContentType() { return httpServletResponse.getContentType(); } /** * Sets the content type of the response. * * @param contentType * @return the response */ public Response contentType(String contentType) { checkCommitted(); httpServletResponse.setContentType(contentType); return this; } /** * Attempts to set the Content-Type of the Response based on Request * headers. *

* The Accept header is preferred for negotiation but the Content-Type * header may also be used if an agreeable engine can not be determined. *

*

* If no Content-Type can not be negotiated then the response will not be * modified. This behavior allows specification of a default Content-Type * using one of the methods such as xml() or json(). *

* For example, response.xml().contentType(request).send(myObject); * would set the default Content-Type as application/xml and * then attempt to negotiate the client's preferred type. If negotiation failed, * then the default application/xml would be sent and used to * serialize the outgoing object. * * @param request * @return the response */ public Response contentType(Request request) { // prefer the Accept header if ("*/*".equals(request.getAcceptType())) { // client accepts all types return this; } ContentTypeEngine engine = contentTypeEngines.getContentTypeEngine(request.getAcceptType()); if (engine != null) { log.debug("Negotiated '{}' from request Accept header", engine.getContentType()); } else if (!StringUtils.isNullOrEmpty(request.getContentType())) { // try to match the Request content-type engine = contentTypeEngines.getContentTypeEngine(request.getContentType()); if (engine != null) { log.debug("Negotiated '{}' from request Content-Type header", engine.getContentType()); } } if (engine == null) { log.debug("Failed to negotiate a content type for Accept='{}' and Content-Type='{}'", request.getAcceptType(), request.getContentType()); return this; } return contentType(engine.getContentType()); } /** * Sets the Response content-type to text/plain. */ public Response text() { return contentType(HttpConstants.ContentType.TEXT_PLAIN); } /** * Sets the Response content-type to text/html. */ public Response html() { return contentType(HttpConstants.ContentType.TEXT_HTML); } /** * Sets the Response content-type to application/json. */ public Response json() { return contentType(HttpConstants.ContentType.APPLICATION_JSON); } /** * Sets the Response content-type to application/xml. */ public Response xml() { return contentType(HttpConstants.ContentType.APPLICATION_XML); } /** * Sets the Response content-type to application/x-yaml. */ public Response yaml() { return contentType(HttpConstants.ContentType.APPLICATION_X_YAML); } /** * Writes the string content directly to the response. *

This method commits the response.

* * @param content */ public void send(CharSequence content) { checkCommitted(); commit(content); } /** * Replaces '{}' in the format string with the supplied arguments and * writes the string content directly to the response. *

This method commits the response.

* * @param format * @param args */ public void send(String format, Object... args) { checkCommitted(); commit(StringUtils.format(format, args)); } /** * Serializes the object as JSON using the registered application/json * ContentTypeEngine and writes it to the response. *

This method commits the response.

* * @param object */ public void json(Object object) { send(object, HttpConstants.ContentType.APPLICATION_JSON); } /** * Serializes the object as XML using the registered application/xml * ContentTypeEngine and writes it to the response. *

This method commits the response.

* * @param object */ public void xml(Object object) { send(object, HttpConstants.ContentType.APPLICATION_XML); } /** * Serializes the object as YAML using the registered application/x-yaml * ContentTypeEngine and writes it to the response. *

This method commits the response.

* * @param object */ public void yaml(Object object) { send(object, HttpConstants.ContentType.APPLICATION_X_YAML); } /** * Serializes the object as plain text using the registered text/plain * ContentTypeEngine and writes it to the response. *

This method commits the response.

* * @param object */ public void text(Object object) { send(object, HttpConstants.ContentType.TEXT_PLAIN); } /** * Serializes the object using the registered ContentTypeEngine matching * the pre-specified content-type. *

This method commits the response.

* * @param object */ public void send(Object object) { send(object, getContentType()); } private void send(Object object, String contentType) { if (StringUtils.isNullOrEmpty(contentType)) { throw new PippoRuntimeException("You must specify a content type!"); } ContentTypeEngine contentTypeEngine = contentTypeEngines.getContentTypeEngine(contentType); if (contentTypeEngine == null) { throw new PippoRuntimeException("You must set a content type engine for '{}'", contentType); } header(HttpConstants.Header.CONTENT_TYPE, contentTypeEngine.getContentType()); send(contentTypeEngine.toString(object)); } /** * Copies the input stream to the response output stream and closes the input stream upon completion. *

This method commits the response.

* * @param input */ public void resource(InputStream input) { checkCommitted(); finalizeResponse(); // content type to OCTET_STREAM if it's not set if (getContentType() == null) { contentType(HttpConstants.ContentType.APPLICATION_OCTET_STREAM); } try { // by calling httpServletResponse.getOutputStream() we are committing the response IoUtils.copy(input, httpServletResponse.getOutputStream()); // flushing the buffer forces chunked-encoding httpServletResponse.flushBuffer(); } catch (Exception e) { throw new PippoRuntimeException(e); } finally { IoUtils.close(input); } } /** * Writes the specified file directly to the response. *

This method commits the response.

* * @param file */ public void file(File file) { try { file(file.getName(), new FileInputStream(file)); } catch (FileNotFoundException e) { throw new PippoRuntimeException(e); } } /** * Copies the input stream to the response output stream as a download and closes the input stream upon completion. *

This method commits the response.

* * @param filename * @param input */ public void file(String filename, InputStream input) { checkCommitted(); // content type to OCTET_STREAM if it's not set if (getContentType() == null) { contentType(mimeTypes.getContentType(filename, HttpConstants.ContentType.APPLICATION_OCTET_STREAM)); } if (isHeaderEmpty(HttpConstants.Header.CONTENT_DISPOSITION)) { if (filename != null && !filename.isEmpty()) { header(HttpConstants.Header.CONTENT_DISPOSITION, "attachment; filename=\"" + filename + "\""); } else { header(HttpConstants.Header.CONTENT_DISPOSITION, "attachment; filename=\"\""); } } finalizeResponse(); try { // by calling httpServletResponse.getOutputStream() we are committing the response IoUtils.copy(input, httpServletResponse.getOutputStream()); // flushing the buffer forces chunked-encoding httpServletResponse.flushBuffer(); } catch (Exception e) { throw new PippoRuntimeException(e); } finally { IoUtils.close(input); } } /** * Renders a template and writes the output directly to the response. *

This method commits the response.

* * @param templateName */ public void render(String templateName) { render(templateName, new HashMap()); } /** * Renders a template and writes the output directly to the response. *

This method commits the response.

* * @param templateName * @param model */ public void render(String templateName, Map model) { if (templateEngine == null) { log.error("You must set a template engine first"); return; } // merge the model passed with the locals data model.putAll(getLocals()); // add session (if exists) to model Session session = Session.get(); if (session != null) { // model.put("session", session.getAll()); model.put("session", session); } // render the template using the merged model StringWriter stringWriter = new StringWriter(); templateEngine.renderResource(templateName, model, stringWriter); send(stringWriter.toString()); } private void checkCommitted() { if (isCommitted()) { throw new PippoRuntimeException("The response has already been committed"); } } /** * Returns true if this response has already been committed. * * @return true if the response has been committed */ public boolean isCommitted() { return httpServletResponse.isCommitted(); } /** * This method commits the response. */ public void commit() { commit(null); } private void commit(CharSequence content) { checkCommitted(); finalizeResponse(); // content type to TEXT_HTML if it's not set if (getContentType() == null) { contentType(HttpConstants.ContentType.TEXT_HTML); } try { if (content != null) { httpServletResponse.getWriter().append(content); } log.trace("Response committed"); httpServletResponse.flushBuffer(); } catch (IOException e) { throw new PippoRuntimeException(e); } } private void finalizeResponse() { // add headers for (Map.Entry header : getHeaderMap().entrySet()) { httpServletResponse.setHeader(header.getKey(), header.getValue()); } // add cookies for (Cookie cookie : getCookies()) { httpServletResponse.addCookie(cookie); } // set status to OK if it's not set if (getStatus() == 0 || getStatus() == Integer.MAX_VALUE) { ok(); } // call finalize listeners if ((finalizeListeners != null) && !finalizeListeners.isEmpty()) { finalizeListeners.onFinalize(this); } } public ResponseFinalizeListenerList getFinalizeListeners() { if (finalizeListeners == null) { finalizeListeners = new ResponseFinalizeListenerList(); } return finalizeListeners; } public OutputStream getOutputStream() { checkCommitted(); finalizeResponse(); // content type to OCTET_STREAM if it's not set if (getContentType() == null) { contentType(HttpConstants.ContentType.APPLICATION_OCTET_STREAM); } try { return httpServletResponse.getOutputStream(); } catch (IOException e) { throw new PippoRuntimeException(e); } } public static Response get() { RouteContext routeContext = RouteDispatcher.getRouteContext(); return (routeContext != null) ? routeContext.getResponse() : null; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy