package play.modules.grizzly;
import com.sun.grizzly.tcp.http11.GrizzlyAdapter;
import com.sun.grizzly.tcp.http11.GrizzlyRequest;
import com.sun.grizzly.tcp.http11.GrizzlyResponse;
import com.sun.grizzly.util.http.Cookie;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import play.Invoker;
import play.Logger;
import play.Play;
import play.PlayPlugin;
import play.data.validation.Validation;
import play.exceptions.PlayException;
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;
public class PlayGrizzlyAdapter extends GrizzlyAdapter {
public PlayGrizzlyAdapter(File application, String id, String ctx) {
Play.forceProd = true;
Play.ctxPath = ctx;
Play.init(application, id);
}
// ------------
@Override
public void service(GrizzlyRequest grizzlyRequest, GrizzlyResponse grizzlyResponse) {
Request request = null;
try {
Response response = new Response();
response.out = new ByteArrayOutputStream();
Response.current.set(response);
request = parseRequest(grizzlyRequest);
boolean raw = false;
for (PlayPlugin plugin : Play.plugins) {
if (plugin.rawInvocation(request, response)) {
raw = true;
break;
}
}
if (raw) {
copyResponse(Request.current(), Response.current(), grizzlyRequest, grizzlyResponse);
} else {
Invoker.invokeInThread(new GrizzlyInvocation(request, response, grizzlyRequest, grizzlyResponse));
}
} catch (NotFound e) {
serve404(grizzlyRequest, grizzlyResponse, e);
return;
} catch (RenderStatic e) {
serveStatic(grizzlyRequest, grizzlyResponse, e);
return;
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
public void serveStatic(GrizzlyRequest grizzlyRequest, GrizzlyResponse grizzlyResponse, RenderStatic renderStatic) {
VirtualFile file = Play.getVirtualFile(renderStatic.file);
if (file == null || file.isDirectory() || !file.exists()) {
serve404(grizzlyRequest, grizzlyResponse, new NotFound("The file " + renderStatic.file + " does not exist"));
} else {
grizzlyResponse.setContentType(MimeTypes.getContentType(file.getName()));
boolean raw = false;
for (PlayPlugin plugin : Play.plugins) {
if (plugin.serveStatic(file, Request.current(), Response.current())) {
raw = true;
break;
}
}
try {
if (raw) {
copyResponse(Request.current(), Response.current(), grizzlyRequest, grizzlyResponse);
} else {
if (Play.mode == Play.Mode.DEV) {
grizzlyResponse.setHeader("Cache-Control", "no-cache");
grizzlyResponse.setHeader("Content-Length", String.valueOf(file.length()));
if (!grizzlyRequest.getMethod().equals("HEAD")) {
copyStream(grizzlyResponse, file.inputstream());
} else {
copyStream(grizzlyResponse, new ByteArrayInputStream(new byte[0]));
}
} else {
long last = file.lastModified();
String etag = "\"" + last + "-" + file.hashCode() + "\"";
if (!isModified(etag, last, grizzlyRequest)) {
grizzlyResponse.setHeader("Etag", etag);
grizzlyResponse.setStatus(304);
} else {
grizzlyResponse.setHeader("Last-Modified", Utils.getHttpDateFormatter().format(new Date(last)));
grizzlyResponse.setHeader("Cache-Control", "max-age=" + Play.configuration.getProperty("http.cacheControl", "3600"));
grizzlyResponse.setHeader("Etag", etag);
copyStream(grizzlyResponse, file.inputstream());
}
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
public static boolean isModified(String etag, long last, GrizzlyRequest request) {
if (!(request.getHeader("If-None-Match") == null && request.getHeaders("If-Modified-Since") == null)) {
return true;
} else {
String browserEtag = request.getHeader("If-None-Match");
if (!browserEtag.equals(etag)) {
return true;
} else {
try {
Date browserDate = Utils.getHttpDateFormatter().parse(request.getHeader("If-Modified-Since"));
if (browserDate.getTime() >= last) {
return false;
}
} catch (ParseException ex) {
Logger.error("Can't parse date", ex);
}
return true;
}
}
}
public static Request parseRequest(GrizzlyRequest grizzlyRequest) throws Exception {
Request request = new Http.Request();
Request.current.set(request);
URI uri = new URI(grizzlyRequest.getRequestURI());
request.method = grizzlyRequest.getMethod().intern();
request.path = uri.getPath();
request.querystring = grizzlyRequest.getQueryString() == null ? "" : grizzlyRequest.getQueryString();
Router.routeOnlyStatic(request);
if (grizzlyRequest.getHeader("Content-Type") != null) {
request.contentType = grizzlyRequest.getHeader("Content-Type").split(";")[0].trim().toLowerCase().intern();
} else {
request.contentType = "text/html".intern();
}
if (grizzlyRequest.getHeader("X-HTTP-Method-Override") != null) {
request.method = grizzlyRequest.getHeader("X-HTTP-Method-Override").intern();
}
request.body = grizzlyRequest.getInputStream();
request.secure = grizzlyRequest.isSecure();
request.url = uri.toString() + (grizzlyRequest.getQueryString() == null ? "" : "?" + grizzlyRequest.getQueryString());
request.host = grizzlyRequest.getHeader("host");
if (request.host.contains(":")) {
request.port = Integer.parseInt(request.host.split(":")[1]);
request.domain = request.host.split(":")[0];
} else {
request.port = 80;
request.domain = request.host;
}
request.remoteAddress = grizzlyRequest.getRemoteAddr();
if (Play.configuration.containsKey("XForwardedSupport") && grizzlyRequest.getHeader("X-Forwarded-For") != null) {
if (!Arrays.asList(Play.configuration.getProperty("XForwardedSupport", "127.0.0.1").split(",")).contains(request.remoteAddress)) {
throw new RuntimeException("This proxy request is not authorized");
} else {
request.secure = ("https".equals(Play.configuration.get("XForwardedProto")) || "https".equals(grizzlyRequest.getHeader("X-Forwarded-Proto")) || "on".equals(grizzlyRequest.getHeader("X-Forwarded-Ssl")));
if (Play.configuration.containsKey("XForwardedHost")) {
request.host = (String) Play.configuration.get("XForwardedHost");
} else if (grizzlyRequest.getHeader("X-Forwarded-Host") != null) {
request.host = grizzlyRequest.getHeader("X-Forwarded-Host");
}
if (grizzlyRequest.getHeader("X-Forwarded-For") != null) {
request.remoteAddress = grizzlyRequest.getHeader("X-Forwarded-For");
}
}
}
Enumeration headersNames = grizzlyRequest.getHeaderNames();
while (headersNames.hasMoreElements()) {
Http.Header hd = new Http.Header();
hd.name = (String) headersNames.nextElement();
hd.values = new ArrayList<String>();
Enumeration enumValues = grizzlyRequest.getHeaders(hd.name);
while (enumValues.hasMoreElements()) {
String value = (String) enumValues.nextElement();
hd.values.add(value);
}
request.headers.put(hd.name.toLowerCase(), hd);
}
request.resolveFormat();
Cookie[] cookies = grizzlyRequest.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
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();
request.cookies.put(playCookie.name, playCookie);
}
}
request._init();
return request;
}
public void serve404(GrizzlyRequest request, GrizzlyResponse response, NotFound e) {
Logger.warn("404 -> %s %s (%s)", request.getMethod(), request.getRequestURI(), e.getMessage());
response.setStatus(404);
response.setContentType("text/html");
Map<String, Object> binding = new HashMap<String, Object>();
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) {
//
}
String format = Request.current().format;
response.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(request.getHeader("X-Requested-With")) && (format == null || format.equals("html"))) {
format = "txt";
}
if (format == null) {
format = "txt";
}
response.setContentType(MimeTypes.getContentType("404." + format, "text/plain"));
String errorHtml = TemplateLoader.load("errors/404." + format).render(binding);
try {
response.getOutputStream().write(errorHtml.getBytes("utf-8"));
} catch (Exception fex) {
Logger.error(fex, "(utf-8 ?)");
}
}
public void serve500(Exception e, GrizzlyRequest request, GrizzlyResponse response) {
try {
Map<String, Object> binding = new HashMap<String, Object>();
if (!(e instanceof PlayException)) {
e = new play.exceptions.UnexpectedException(e);
}
// Flush some cookies
try {
Map<String, Http.Cookie> 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) {
// humm ?
}
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) {
//
}
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("utf-8"));
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, GrizzlyRequest grizzlyRequest, GrizzlyResponse grizzlyResponse) throws IOException {
if (response.contentType != null) {
grizzlyResponse.setHeader("Content-Type", response.contentType + (response.contentType.startsWith("text/") ? "; charset=utf-8" : ""));
} else {
grizzlyResponse.setHeader("Content-Type", "text/plain;charset=utf-8");
}
grizzlyResponse.setStatus(response.status);
if (!response.headers.containsKey("cache-control")) {
grizzlyResponse.setHeader("Cache-Control", "no-cache");
}
Map<String, Http.Header> headers = response.headers;
for (Map.Entry<String, Http.Header> entry : headers.entrySet()) {
Http.Header hd = entry.getValue();
String key = entry.getKey();
for (String value : hd.values) {
grizzlyResponse.setHeader(key, value);
}
}
Map<String, Http.Cookie> 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);
}
grizzlyResponse.addCookie(c);
}
// Content
response.out.flush();
if (response.direct != null && response.direct instanceof File) {
File file = (File) response.direct;
grizzlyResponse.setHeader("Content-Length", String.valueOf(file.length()));
if (!request.method.equals("HEAD")) {
copyStream(grizzlyResponse, VirtualFile.open(file).inputstream());
} else {
copyStream(grizzlyResponse, new ByteArrayInputStream(new byte[0]));
}
} else if (response.direct != null && response.direct instanceof InputStream) {
copyStream(grizzlyResponse, (InputStream) response.direct);
} else {
byte[] content = response.out.toByteArray();
grizzlyResponse.setHeader("Content-Length", String.valueOf(content.length));
if (!request.method.equals("HEAD")) {
grizzlyResponse.getOutputStream().write(content);
} else {
copyStream(grizzlyResponse, new ByteArrayInputStream(new byte[0]));
}
}
}
private void copyStream(GrizzlyResponse grizzlyResponse, InputStream is) throws IOException {
OutputStream os = grizzlyResponse.getOutputStream();
byte[] buffer = new byte[8096];
int read = 0;
while ((read = is.read(buffer)) > 0) {
os.write(buffer, 0, read);
}
os.flush();
is.close();
}
public class GrizzlyInvocation extends Invoker.DirectInvocation {
private Request request;
private Response response;
private GrizzlyRequest grizzlyRequest;
private GrizzlyResponse grizzlyResponse;
public GrizzlyInvocation(Request request, Response response, GrizzlyRequest grizzlyRequest, GrizzlyResponse grizzlyResponse) {
this.grizzlyRequest = grizzlyRequest;
this.grizzlyResponse = grizzlyResponse;
this.request = request;
this.response = response;
}
@Override
public void run() {
try {
super.run();
} catch (Exception e) {
serve500(e, grizzlyRequest, grizzlyResponse);
return;
}
}
@Override
public void execute() throws Exception {
ActionInvoker.invoke(request, response);
copyResponse(request, response, grizzlyRequest, grizzlyResponse);
}
}
}