Guest User

Untitled

a guest
Dec 14th, 2015
190
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 8.23 KB | None | 0 0
  1. package net.bridalapp.server;
  2.  
  3. import static net.bridalapp.server.ReactRenderer.RenderResult.ERROR;
  4. import static net.bridalapp.server.ReactRenderer.RenderResult.MARKUP;
  5. import static net.bridalapp.server.ReactRenderer.RenderResult.NOTFOUND;
  6. import static net.bridalapp.server.ReactRenderer.RenderResult.REDIRECT;
  7.  
  8. import java.io.File;
  9. import java.io.FileInputStream;
  10. import java.io.FileNotFoundException;
  11. import java.io.IOException;
  12. import java.io.InputStreamReader;
  13. import java.io.UnsupportedEncodingException;
  14. import java.util.ArrayList;
  15. import java.util.Date;
  16. import java.util.List;
  17. import java.util.Map;
  18. import java.util.logging.Level;
  19. import java.util.logging.Logger;
  20.  
  21. import javax.script.Bindings;
  22. import javax.script.Compilable;
  23. import javax.script.CompiledScript;
  24. import javax.script.ScriptContext;
  25. import javax.script.ScriptEngine;
  26. import javax.script.ScriptEngineManager;
  27. import javax.script.ScriptException;
  28. import javax.script.SimpleScriptContext;
  29. import javax.servlet.Filter;
  30. import javax.servlet.FilterChain;
  31. import javax.servlet.FilterConfig;
  32. import javax.servlet.ServletException;
  33. import javax.servlet.ServletRequest;
  34. import javax.servlet.ServletResponse;
  35. import javax.servlet.annotation.WebInitParam;
  36. import javax.servlet.annotation.WebFilter;
  37. import javax.servlet.http.HttpServletRequest;
  38. import javax.servlet.http.HttpServletResponse;
  39.  
  40. import com.fasterxml.jackson.databind.ObjectMapper;
  41.  
  42. import jdk.nashorn.api.scripting.ScriptObjectMirror;
  43.  
  44. /**
  45. * Filters requests, delegating routing and rendering to React + ReactRouter
  46. * running inside Nashorn.
  47. */
  48. @WebFilter (
  49. filterName="ReactRenderFilter",
  50. initParams = {
  51. @WebInitParam(
  52. name = ReactRenderFilter.PARAM_APP_BUNDLE_PATH, value = "",
  53. description = "Path, relative from the webroot, to the react application's server bundle."
  54. ),
  55. @WebInitParam(
  56. name = ReactRenderFilter.PARAM_MARKUP_JSP_PATH, value = ReactRenderFilter.DEFAULT_MARKUP_JSP_PATH,
  57. description = "Path, relative from the webroot, to the JSP that renders the scaffolding HTML."
  58. ),
  59. @WebInitParam(
  60. name = ReactRenderFilter.PARAM_EXTERNAL_LIB_PATHS, value = "",
  61. description = "Whitespace-separated list of paths, relative from the webroot, to any external libraries to be included. Use this for dependecies that are not included in your application bundle."
  62. ),
  63. }
  64. )
  65. @SuppressWarnings("restriction")
  66. public class ReactRenderFilter implements Filter {
  67. public static final String PARAM_EXTERNAL_LIB_PATHS = "exernal-library-paths";
  68. public static final String PARAM_APP_BUNDLE_PATH = "app-bundle-path";
  69. public static final String PARAM_MARKUP_JSP_PATH = "markup-jsp-path";
  70. public static final String DEFAULT_MARKUP_JSP_PATH = "/render.jsp";
  71.  
  72. private static final Logger LOG = Logger.getLogger(ReactRenderFilter.class.getName());
  73. private static final Object LOCK = new Object();
  74.  
  75. private static final ScriptEngine engine = new ScriptEngineManager().getEngineByMimeType("text/javascript");
  76. private static final List<CompiledScript> scripts = new ArrayList<>();
  77. private static final ThreadLocal<RenderEngine> renderEngine = new ThreadLocal<>();
  78.  
  79. private FilterConfig config;
  80. private String bundlePath;
  81. private String jspPath;
  82.  
  83. public static class RenderEngine {
  84. private final ScriptContext context;
  85. private final ReactRenderer renderer;
  86. private final long lastModified;
  87.  
  88. public RenderEngine(File bundle) throws ScriptException, UnsupportedEncodingException, FileNotFoundException {
  89. context = new SimpleScriptContext();
  90. Bindings global = engine.createBindings();
  91. context.setBindings(global, ScriptContext.ENGINE_SCOPE);
  92. global.put("global", global);
  93. for (CompiledScript script : scripts) {
  94. script.eval(context);
  95. }
  96. engine.eval(new InputStreamReader(new FileInputStream(bundle), "utf-8"), context);
  97. lastModified = bundle.lastModified();
  98. String setLevel = "log.level = log.";
  99. if (LOG.isLoggable(Level.FINE)) {setLevel += "TRACE";}
  100. else if (LOG.isLoggable(Level.CONFIG)) {setLevel += "DEBUG";}
  101. else if (LOG.isLoggable(Level.INFO)) {setLevel += "INFO";}
  102. else if (LOG.isLoggable(Level.WARNING)) {setLevel += "WARN";}
  103. else if (LOG.isLoggable(Level.SEVERE)) {setLevel += "ERROR";}
  104. else {setLevel += "NONE";}
  105. setLevel += ";";
  106. engine.eval(setLevel, context);
  107. LOG.finer("Getting renderer");
  108. renderer = ((ScriptObjectMirror) engine.eval("global.render", context)).to(ReactRenderer.class);
  109. }
  110.  
  111. String render(String path, String initialDataJSON) {
  112. return renderer.render(path, initialDataJSON);
  113. }
  114.  
  115. boolean isOutdated(File bundle) {
  116. return lastModified != bundle.lastModified();
  117. }
  118. }
  119.  
  120.  
  121. public ReactRenderFilter() {super();}
  122. @Override public void destroy() {}
  123.  
  124. @Override public void init(FilterConfig filterConfig) throws ServletException {
  125. config = filterConfig;
  126. try {
  127. String[] paths = config.getInitParameter(PARAM_EXTERNAL_LIB_PATHS).trim().split("\\s+");
  128. for (String path : paths) {
  129. if (path.trim().isEmpty()) {continue;}
  130. File file = new File(config.getServletContext().getRealPath(path.trim()));
  131. scripts.add(((Compilable) engine).compile(new InputStreamReader(new FileInputStream(file), "utf-8")));
  132. }
  133. bundlePath = config.getServletContext().getRealPath(config.getInitParameter(PARAM_APP_BUNDLE_PATH).trim());
  134. jspPath = config.getInitParameter(PARAM_MARKUP_JSP_PATH).trim();
  135. } catch (UnsupportedEncodingException | FileNotFoundException | ScriptException e) {
  136. throw new ServletException("Unable to initialize ReactRenderServlet.", e);
  137. }
  138. }
  139.  
  140. @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
  141. File bundle = new File(bundlePath);
  142. HttpServletRequest req = (HttpServletRequest) request;
  143. HttpServletResponse res = (HttpServletResponse) response;
  144. String path = req.getRequestURI().substring(req.getContextPath().length());
  145. String initialDataJSON = "{}";
  146. @SuppressWarnings("unchecked")
  147. Map<String, Object> initialData = (Map<String, Object>) req.getAttribute("initialData");
  148. if (initialData != null) {
  149. ObjectMapper mapper = new ObjectMapper();
  150. initialDataJSON = mapper.writeValueAsString(initialData);
  151. req.setAttribute("initialDataJSON", initialDataJSON);
  152. }
  153. String renderResult = null;
  154. try {
  155. if (renderEngine.get() == null || renderEngine.get().isOutdated(bundle)) {
  156. // prevent multiple render engines to be instantiated simultaneously
  157. synchronized (LOCK) {
  158. renderEngine.set(new RenderEngine(bundle));
  159. }
  160. }
  161.  
  162. // I sure hope there is a way around this... locking on central object
  163. // during rendering can't be good for performance... But it beats having
  164. // only one worker thread
  165. synchronized (LOCK) {
  166. renderResult = renderEngine.get().render(path, initialDataJSON);
  167. }
  168.  
  169. if (renderResult.startsWith(MARKUP)) {
  170. String markup = renderResult.substring(MARKUP.length());
  171. LOG.finest("GENERATED MARKUP:\n---------------------------------------");
  172. LOG.finest(markup);
  173. LOG.finest("---------------------------------------");
  174. req.setAttribute("markup", markup);
  175. int maxAge = 60 * 60; // 60 minutes
  176. res.addHeader("Cache-Control", "public, max-age=" + maxAge);
  177. res.addDateHeader("Expires", new Date().getTime() + maxAge);
  178. LOG.finest("Dispatching to [" + jspPath + "]...");
  179. req.getRequestDispatcher(jspPath).forward(request, response);
  180. LOG.finer("Dispatched to [" + jspPath + "].");
  181. }
  182. else if (renderResult.startsWith(REDIRECT)) {
  183. String url = renderResult.substring(REDIRECT.length());
  184. LOG.finer("Redirecting to [" + url + "].");
  185. res.sendRedirect(url);
  186. }
  187. else if (renderResult.startsWith(NOTFOUND)) {
  188. LOG.finer("Not found.");
  189. int maxAge = 365 * 24 * 60 * 60; // 365 days
  190. res.addHeader("Cache-Control", "public, max-age=" + maxAge);
  191. res.addDateHeader("Expires", new Date().getTime() + maxAge);
  192. chain.doFilter(request, response);
  193. }
  194. else {
  195. String msg = renderResult.substring(ERROR.length());
  196. throw new ServletException("Unable to generate response for route [" + path + "]: " + msg);
  197. }
  198. } catch (ScriptException e) {
  199. throw new ServletException(e);
  200. }
  201. }
  202. }
Advertisement
Add Comment
Please, Sign In to add comment