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

play.server.ServletWrapper Maven / Gradle / Ivy

There is a newer version: 1.5.0
Show newest version
package play.server;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import play.Invoker;
import play.Invoker.InvocationContext;
import play.Logger;
import play.Play;
import play.data.binding.CachedBoundActionMethodArgs;
import play.data.validation.Validation;
import play.exceptions.PlayException;
import play.exceptions.UnexpectedException;
import play.libs.MimeTypes;
import play.mvc.ActionInvoker;
import play.mvc.Http;
import play.mvc.Http.Request;
import play.mvc.Http.Response;
import play.mvc.Router;
import play.mvc.Scope;
import play.mvc.results.NotFound;
import play.mvc.results.RenderStatic;
import play.templates.TemplateLoader;
import play.utils.Utils;
import play.vfs.VirtualFile;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URI;
import java.net.URISyntaxException;
import java.text.ParseException;
import java.util.*;

import static org.apache.commons.io.IOUtils.closeQuietly;

/**
 * Servlet implementation.
 * Thanks to Lee Breisacher.
 */
public class ServletWrapper extends HttpServlet implements ServletContextListener {

    public static final String IF_MODIFIED_SINCE = "If-Modified-Since";
    public static final String IF_NONE_MATCH = "If-None-Match";

    /**
     * Constant for accessing the underlying HttpServletRequest from Play's Request
     * in a Servlet based deployment.
     * 

Sample usage:

*

{@code HttpServletRequest req = Request.current().args.get(ServletWrapper.SERVLET_REQ);}

*/ public static final String SERVLET_REQ = "__SERVLET_REQ"; /** * Constant for accessing the underlying HttpServletResponse from Play's Request * in a Servlet based deployment. *

Sample usage:

*

{@code HttpServletResponse res = Request.current().args.get(ServletWrapper.SERVLET_RES);}

*/ public static final String SERVLET_RES = "__SERVLET_RES"; private static boolean routerInitializedWithContext = false; @Override public void contextInitialized(ServletContextEvent e) { Play.standalonePlayServer = false; ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader(); String appDir = e.getServletContext().getRealPath("/WEB-INF/application"); File root = new File(appDir); String playId = System.getProperty("play.id", e.getServletContext().getInitParameter("play.id")); if (StringUtils.isEmpty(playId)) { throw new UnexpectedException("Please define a play.id parameter in your web.xml file. Without that parameter, play! cannot start your application. Please add a context-param into the WEB-INF/web.xml file."); } // This is really important as we know this parameter already (we are running in a servlet container) Play.frameworkPath = root.getParentFile(); Play.usePrecompiled = true; Play.init(root, playId); Play.Mode mode = Play.Mode.valueOf(Play.configuration.getProperty("application.mode", "DEV").toUpperCase()); if (mode.isDev()) { Logger.info("Forcing PROD mode because deploying as a war file."); } // Servlet 2.4 does not allow you to get the context path from the servletcontext... if (isGreaterThan(e.getServletContext(), 2, 4)) { loadRouter(e.getServletContext().getContextPath()); } Thread.currentThread().setContextClassLoader(oldClassLoader); } @Override public void contextDestroyed(ServletContextEvent e) { Play.stop(); } @Override public void destroy() { Logger.trace("ServletWrapper>destroy"); Play.stop(); } private static synchronized void loadRouter(String contextPath) { // Reload the rules, but this time with the context. Not really efficient through... // Servlet 2.4 does not allow you to get the context path from the servletcontext... if (routerInitializedWithContext) return; Play.ctxPath = contextPath; Router.load(contextPath); routerInitializedWithContext = true; } public static boolean isGreaterThan(ServletContext context, int majorVersion, int minorVersion) { int contextMajorVersion = context.getMajorVersion(); int contextMinorVersion = context.getMinorVersion(); return (contextMajorVersion > majorVersion) || (contextMajorVersion == majorVersion && contextMinorVersion > minorVersion); } @Override protected void service(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws ServletException, IOException { if (!routerInitializedWithContext) { loadRouter(httpServletRequest.getContextPath()); } if (Logger.isTraceEnabled()) { Logger.trace("ServletWrapper>service " + httpServletRequest.getRequestURI()); } Request request = null; try { Response response = new Response(); response.out = new ByteArrayOutputStream(); Response.current.set(response); request = parseRequest(httpServletRequest); if (Logger.isTraceEnabled()) { Logger.trace("ServletWrapper>service, request: " + request); } boolean raw = Play.pluginCollection.rawInvocation(request, response); if (raw) { copyResponse(Request.current(), Response.current(), httpServletRequest, httpServletResponse); } else { Invoker.invokeInThread(new ServletInvocation(request, response, httpServletRequest, httpServletResponse)); } } catch (NotFound e) { if (Logger.isTraceEnabled()) { Logger.trace("ServletWrapper>service, NotFound: " + e); } serve404(httpServletRequest, httpServletResponse, e); return; } catch (RenderStatic e) { if (Logger.isTraceEnabled()) { Logger.trace("ServletWrapper>service, RenderStatic: " + e); } serveStatic(httpServletResponse, httpServletRequest, e); return; } catch(URISyntaxException e) { serve404(httpServletRequest, httpServletResponse, new NotFound(e.toString())); return; } catch (Throwable e) { throw new ServletException(e); } finally { Request.current.remove(); Response.current.remove(); Scope.Session.current.remove(); Scope.Params.current.remove(); Scope.Flash.current.remove(); Scope.RenderArgs.current.remove(); Scope.RouteArgs.current.remove(); CachedBoundActionMethodArgs.clear(); } } public void serveStatic(HttpServletResponse servletResponse, HttpServletRequest servletRequest, RenderStatic renderStatic) throws IOException { VirtualFile file = Play.getVirtualFile(renderStatic.file); if (file == null || file.isDirectory() || !file.exists()) { serve404(servletRequest, servletResponse, new NotFound("The file " + renderStatic.file + " does not exist")); } else { servletResponse.setContentType(MimeTypes.getContentType(file.getName())); boolean raw = Play.pluginCollection.serveStatic(file, Request.current(), Response.current()); if (raw) { copyResponse(Request.current(), Response.current(), servletRequest, servletResponse); } else { if (Play.mode == Play.Mode.DEV) { servletResponse.setHeader("Cache-Control", "no-cache"); servletResponse.setHeader("Content-Length", String.valueOf(file.length())); if (!servletRequest.getMethod().equals("HEAD")) { copyStream(servletResponse, file.inputstream()); } else { copyStream(servletResponse, new ByteArrayInputStream(new byte[0])); } } else { long last = file.lastModified(); String etag = "\"" + last + "-" + file.hashCode() + "\""; String lastDate = Utils.getHttpDateFormatter().format(new Date(last)); if (!isModified(etag, lastDate, servletRequest)) { servletResponse.setHeader("Etag", etag); servletResponse.setStatus(304); } else { servletResponse.setHeader("Last-Modified", lastDate); servletResponse.setHeader("Cache-Control", "max-age=" + Play.configuration.getProperty("http.cacheControl", "3600")); servletResponse.setHeader("Etag", etag); copyStream(servletResponse, file.inputstream()); } } } } } public static boolean isModified(String etag, String lastDate, HttpServletRequest request) { // See section 14.26 in rfc 2616 http://www.faqs.org/rfcs/rfc2616.html String browserEtag = request.getHeader(IF_NONE_MATCH); String dateString = request.getHeader(IF_MODIFIED_SINCE); if (browserEtag != null) { boolean etagMatches = browserEtag.equals(etag); if (!etagMatches) { return true; } if (dateString != null) { return !isValidTimeStamp(lastDate, dateString); } return false; } else { if (dateString != null) { return !isValidTimeStamp(lastDate, dateString); } else { return true; } } } private static boolean isValidTimeStamp(String lastDateString, String dateString) { try { long browserDate = Utils.getHttpDateFormatter().parse(dateString).getTime(); long lastDate = Utils.getHttpDateFormatter().parse(lastDateString).getTime(); return browserDate >= lastDate; } catch (ParseException e) { Logger.error("Can't parse date", e); return false; } } public static Request parseRequest(HttpServletRequest httpServletRequest) throws Exception { URI uri = new URI(httpServletRequest.getRequestURI()); String method = httpServletRequest.getMethod().intern(); String path = uri.getPath(); String querystring = httpServletRequest.getQueryString() == null ? "" : httpServletRequest.getQueryString(); if (Logger.isTraceEnabled()) { Logger.trace("httpServletRequest.getContextPath(): " + httpServletRequest.getContextPath()); Logger.trace("request.path: " + path + ", request.querystring: " + querystring); } String contentType = null; if (httpServletRequest.getHeader("Content-Type") != null) { contentType = httpServletRequest.getHeader("Content-Type").split(";")[0].trim().toLowerCase().intern(); } else { contentType = "text/html".intern(); } if (httpServletRequest.getHeader("X-HTTP-Method-Override") != null) { method = httpServletRequest.getHeader("X-HTTP-Method-Override").intern(); } InputStream body = httpServletRequest.getInputStream(); boolean secure = httpServletRequest.isSecure(); String url = uri.toString() + (httpServletRequest.getQueryString() == null ? "" : "?" + httpServletRequest.getQueryString()); String host = httpServletRequest.getHeader("host"); int port = 0; String domain = null; if (host.contains(":")) { port = Integer.parseInt(host.split(":")[1]); domain = host.split(":")[0]; } else { port = 80; domain = host; } String remoteAddress = httpServletRequest.getRemoteAddr(); boolean isLoopback = host.matches("^127\\.0\\.0\\.1:?[0-9]*$"); Request request = Request.createRequest( remoteAddress, method, path, querystring, contentType, body, url, host, isLoopback, port, domain, secure, getHeaders(httpServletRequest), getCookies(httpServletRequest)); Request.current.set(request); Router.routeOnlyStatic(request); return request; } protected static Map getHeaders(HttpServletRequest httpServletRequest) { Map headers = new HashMap<>(16); Enumeration headersNames = httpServletRequest.getHeaderNames(); while (headersNames.hasMoreElements()) { Http.Header hd = new Http.Header(); hd.name = headersNames.nextElement(); hd.values = new ArrayList<>(); Enumeration enumValues = httpServletRequest.getHeaders(hd.name); while (enumValues.hasMoreElements()) { String value = enumValues.nextElement(); hd.values.add(value); } headers.put(hd.name.toLowerCase(), hd); } return headers; } protected static Map getCookies(HttpServletRequest httpServletRequest) { Map cookies = new HashMap<>(16); javax.servlet.http.Cookie[] cookiesViaServlet = httpServletRequest.getCookies(); if (cookiesViaServlet != null) { for (javax.servlet.http.Cookie cookie : cookiesViaServlet) { Http.Cookie playCookie = new Http.Cookie(); playCookie.name = cookie.getName(); playCookie.path = cookie.getPath(); playCookie.domain = cookie.getDomain(); playCookie.secure = cookie.getSecure(); playCookie.value = cookie.getValue(); playCookie.maxAge = cookie.getMaxAge(); cookies.put(playCookie.name, playCookie); } } return cookies; } public void serve404(HttpServletRequest servletRequest, HttpServletResponse servletResponse, NotFound e) { Logger.warn("404 -> %s %s (%s)", servletRequest.getMethod(), servletRequest.getRequestURI(), e.getMessage()); servletResponse.setStatus(404); servletResponse.setContentType("text/html"); Map binding = new HashMap<>(); binding.put("result", e); binding.put("session", Scope.Session.current()); binding.put("request", Http.Request.current()); binding.put("flash", Scope.Flash.current()); binding.put("params", Scope.Params.current()); binding.put("play", new Play()); try { binding.put("errors", Validation.errors()); } catch (Exception ex) { Logger.error(ex, "Failed to bind errors"); } String format = Request.current().format; servletResponse.setStatus(404); // Do we have an ajax request? If we have then we want to display some text even if it is html that is requested if ("XMLHttpRequest".equals(servletRequest.getHeader("X-Requested-With")) && (format == null || format.equals("html"))) { format = "txt"; } if (format == null) { format = "txt"; } servletResponse.setContentType(MimeTypes.getContentType("404." + format, "text/plain")); String errorHtml = TemplateLoader.load("errors/404." + format).render(binding); try { servletResponse.getOutputStream().write(errorHtml.getBytes(Response.current().encoding)); } catch (Exception fex) { Logger.error(fex, "(encoding ?)"); } } public void serve500(Exception e, HttpServletRequest request, HttpServletResponse response) { try { Map binding = new HashMap<>(); if (!(e instanceof PlayException)) { e = new play.exceptions.UnexpectedException(e); } // Flush some cookies try { Map cookies = Response.current().cookies; for (Http.Cookie cookie : cookies.values()) { if (cookie.sendOnError) { Cookie c = new Cookie(cookie.name, cookie.value); c.setSecure(cookie.secure); c.setPath(cookie.path); if (cookie.domain != null) { c.setDomain(cookie.domain); } response.addCookie(c); } } } catch (Exception exx) { Logger.error(exx, "Failed to flush cookies"); } binding.put("exception", e); binding.put("session", Scope.Session.current()); binding.put("request", Http.Request.current()); binding.put("flash", Scope.Flash.current()); binding.put("params", Scope.Params.current()); binding.put("play", new Play()); try { binding.put("errors", Validation.errors()); } catch (Exception ex) { Logger.error(ex, "Failed to bind errors"); } response.setStatus(500); String format = "html"; if (Request.current() != null) { format = Request.current().format; } // Do we have an ajax request? If we have then we want to display some text even if it is html that is requested if ("XMLHttpRequest".equals(request.getHeader("X-Requested-With")) && (format == null || format.equals("html"))) { format = "txt"; } if (format == null) { format = "txt"; } response.setContentType(MimeTypes.getContentType("500." + format, "text/plain")); try { String errorHtml = TemplateLoader.load("errors/500." + format).render(binding); response.getOutputStream().write(errorHtml.getBytes(Response.current().encoding)); Logger.error(e, "Internal Server Error (500)"); } catch (Throwable ex) { Logger.error(e, "Internal Server Error (500)"); Logger.error(ex, "Error during the 500 response generation"); throw ex; } } catch (Throwable exxx) { if (exxx instanceof RuntimeException) { throw (RuntimeException) exxx; } throw new RuntimeException(exxx); } } public void copyResponse(Request request, Response response, HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws IOException { String encoding = Response.current().encoding; if (response.contentType != null) { servletResponse.setHeader("Content-Type", response.contentType + (response.contentType.startsWith("text/") ? "; charset="+encoding : "")); } else { servletResponse.setHeader("Content-Type", "text/plain;charset="+encoding); } servletResponse.setStatus(response.status); if (!response.headers.containsKey("cache-control")) { servletResponse.setHeader("Cache-Control", "no-cache"); } Map headers = response.headers; for (Map.Entry entry : headers.entrySet()) { Http.Header hd = entry.getValue(); String key = entry.getKey(); for (String value : hd.values) { servletResponse.setHeader(key, value); } } Map cookies = response.cookies; for (Http.Cookie cookie : cookies.values()) { Cookie c = new Cookie(cookie.name, cookie.value); c.setSecure(cookie.secure); c.setPath(cookie.path); if (cookie.domain != null) { c.setDomain(cookie.domain); } if (cookie.maxAge != null) { c.setMaxAge(cookie.maxAge); } servletResponse.addCookie(c); } // Content response.out.flush(); if (response.direct != null && response.direct instanceof File) { File file = (File) response.direct; servletResponse.setHeader("Content-Length", String.valueOf(file.length())); if (!request.method.equals("HEAD")) { copyStream(servletResponse, VirtualFile.open(file).inputstream()); } else { copyStream(servletResponse, new ByteArrayInputStream(new byte[0])); } } else if (response.direct != null && response.direct instanceof InputStream) { copyStream(servletResponse, (InputStream) response.direct); } else { byte[] content = response.out.toByteArray(); servletResponse.setHeader("Content-Length", String.valueOf(content.length)); if (!request.method.equals("HEAD")) { servletResponse.getOutputStream().write(content); } else { copyStream(servletResponse, new ByteArrayInputStream(new byte[0])); } } } private void copyStream(HttpServletResponse servletResponse, InputStream is) throws IOException { if (servletResponse != null && is != null) { try { OutputStream os = servletResponse.getOutputStream(); IOUtils.copyLarge(is, os); os.flush(); } finally { closeQuietly(is); } } } public class ServletInvocation extends Invoker.DirectInvocation { private Request request; private Response response; private HttpServletRequest httpServletRequest; private HttpServletResponse httpServletResponse; public ServletInvocation(Request request, Response response, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) { this.httpServletRequest = httpServletRequest; this.httpServletResponse = httpServletResponse; this.request = request; this.response = response; request.args.put(ServletWrapper.SERVLET_REQ, httpServletRequest); request.args.put(ServletWrapper.SERVLET_RES, httpServletResponse); } @Override public boolean init() { try { return super.init(); } catch (NotFound e) { serve404(httpServletRequest, httpServletResponse, e); return false; } catch (RenderStatic r) { try { serveStatic(httpServletResponse, httpServletRequest, r); } catch (IOException e) { throw new UnexpectedException(e); } return false; } } @Override public void run() { try { super.run(); } catch (Exception e) { serve500(e, httpServletRequest, httpServletResponse); return; } } @Override public void execute() throws Exception { ActionInvoker.invoke(request, response); copyResponse(request, response, httpServletRequest, httpServletResponse); } @Override public InvocationContext getInvocationContext() { ActionInvoker.resolve(request, response); return new InvocationContext(Http.invocationType, request.invokedMethod.getAnnotations(), request.invokedMethod.getDeclaringClass().getAnnotations()); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy