Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- package net.bridalapp.server;
- import static net.bridalapp.server.ReactRenderer.RenderResult.ERROR;
- import static net.bridalapp.server.ReactRenderer.RenderResult.MARKUP;
- import static net.bridalapp.server.ReactRenderer.RenderResult.NOTFOUND;
- import static net.bridalapp.server.ReactRenderer.RenderResult.REDIRECT;
- import java.io.File;
- import java.io.FileInputStream;
- import java.io.FileNotFoundException;
- import java.io.IOException;
- import java.io.InputStreamReader;
- import java.io.UnsupportedEncodingException;
- import java.util.ArrayList;
- import java.util.Date;
- import java.util.List;
- import java.util.Map;
- import java.util.logging.Level;
- import java.util.logging.Logger;
- import javax.script.Bindings;
- import javax.script.Compilable;
- import javax.script.CompiledScript;
- import javax.script.ScriptContext;
- import javax.script.ScriptEngine;
- import javax.script.ScriptEngineManager;
- import javax.script.ScriptException;
- import javax.script.SimpleScriptContext;
- import javax.servlet.Filter;
- import javax.servlet.FilterChain;
- import javax.servlet.FilterConfig;
- import javax.servlet.ServletException;
- import javax.servlet.ServletRequest;
- import javax.servlet.ServletResponse;
- import javax.servlet.annotation.WebInitParam;
- import javax.servlet.annotation.WebFilter;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import com.fasterxml.jackson.databind.ObjectMapper;
- import jdk.nashorn.api.scripting.ScriptObjectMirror;
- /**
- * Filters requests, delegating routing and rendering to React + ReactRouter
- * running inside Nashorn.
- */
- @WebFilter (
- filterName="ReactRenderFilter",
- initParams = {
- @WebInitParam(
- name = ReactRenderFilter.PARAM_APP_BUNDLE_PATH, value = "",
- description = "Path, relative from the webroot, to the react application's server bundle."
- ),
- @WebInitParam(
- name = ReactRenderFilter.PARAM_MARKUP_JSP_PATH, value = ReactRenderFilter.DEFAULT_MARKUP_JSP_PATH,
- description = "Path, relative from the webroot, to the JSP that renders the scaffolding HTML."
- ),
- @WebInitParam(
- name = ReactRenderFilter.PARAM_EXTERNAL_LIB_PATHS, value = "",
- 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."
- ),
- }
- )
- @SuppressWarnings("restriction")
- public class ReactRenderFilter implements Filter {
- public static final String PARAM_EXTERNAL_LIB_PATHS = "exernal-library-paths";
- public static final String PARAM_APP_BUNDLE_PATH = "app-bundle-path";
- public static final String PARAM_MARKUP_JSP_PATH = "markup-jsp-path";
- public static final String DEFAULT_MARKUP_JSP_PATH = "/render.jsp";
- private static final Logger LOG = Logger.getLogger(ReactRenderFilter.class.getName());
- private static final Object LOCK = new Object();
- private static final ScriptEngine engine = new ScriptEngineManager().getEngineByMimeType("text/javascript");
- private static final List<CompiledScript> scripts = new ArrayList<>();
- private static final ThreadLocal<RenderEngine> renderEngine = new ThreadLocal<>();
- private FilterConfig config;
- private String bundlePath;
- private String jspPath;
- public static class RenderEngine {
- private final ScriptContext context;
- private final ReactRenderer renderer;
- private final long lastModified;
- public RenderEngine(File bundle) throws ScriptException, UnsupportedEncodingException, FileNotFoundException {
- context = new SimpleScriptContext();
- Bindings global = engine.createBindings();
- context.setBindings(global, ScriptContext.ENGINE_SCOPE);
- global.put("global", global);
- for (CompiledScript script : scripts) {
- script.eval(context);
- }
- engine.eval(new InputStreamReader(new FileInputStream(bundle), "utf-8"), context);
- lastModified = bundle.lastModified();
- String setLevel = "log.level = log.";
- if (LOG.isLoggable(Level.FINE)) {setLevel += "TRACE";}
- else if (LOG.isLoggable(Level.CONFIG)) {setLevel += "DEBUG";}
- else if (LOG.isLoggable(Level.INFO)) {setLevel += "INFO";}
- else if (LOG.isLoggable(Level.WARNING)) {setLevel += "WARN";}
- else if (LOG.isLoggable(Level.SEVERE)) {setLevel += "ERROR";}
- else {setLevel += "NONE";}
- setLevel += ";";
- engine.eval(setLevel, context);
- LOG.finer("Getting renderer");
- renderer = ((ScriptObjectMirror) engine.eval("global.render", context)).to(ReactRenderer.class);
- }
- String render(String path, String initialDataJSON) {
- return renderer.render(path, initialDataJSON);
- }
- boolean isOutdated(File bundle) {
- return lastModified != bundle.lastModified();
- }
- }
- public ReactRenderFilter() {super();}
- @Override public void destroy() {}
- @Override public void init(FilterConfig filterConfig) throws ServletException {
- config = filterConfig;
- try {
- String[] paths = config.getInitParameter(PARAM_EXTERNAL_LIB_PATHS).trim().split("\\s+");
- for (String path : paths) {
- if (path.trim().isEmpty()) {continue;}
- File file = new File(config.getServletContext().getRealPath(path.trim()));
- scripts.add(((Compilable) engine).compile(new InputStreamReader(new FileInputStream(file), "utf-8")));
- }
- bundlePath = config.getServletContext().getRealPath(config.getInitParameter(PARAM_APP_BUNDLE_PATH).trim());
- jspPath = config.getInitParameter(PARAM_MARKUP_JSP_PATH).trim();
- } catch (UnsupportedEncodingException | FileNotFoundException | ScriptException e) {
- throw new ServletException("Unable to initialize ReactRenderServlet.", e);
- }
- }
- @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
- File bundle = new File(bundlePath);
- HttpServletRequest req = (HttpServletRequest) request;
- HttpServletResponse res = (HttpServletResponse) response;
- String path = req.getRequestURI().substring(req.getContextPath().length());
- String initialDataJSON = "{}";
- @SuppressWarnings("unchecked")
- Map<String, Object> initialData = (Map<String, Object>) req.getAttribute("initialData");
- if (initialData != null) {
- ObjectMapper mapper = new ObjectMapper();
- initialDataJSON = mapper.writeValueAsString(initialData);
- req.setAttribute("initialDataJSON", initialDataJSON);
- }
- String renderResult = null;
- try {
- if (renderEngine.get() == null || renderEngine.get().isOutdated(bundle)) {
- // prevent multiple render engines to be instantiated simultaneously
- synchronized (LOCK) {
- renderEngine.set(new RenderEngine(bundle));
- }
- }
- // I sure hope there is a way around this... locking on central object
- // during rendering can't be good for performance... But it beats having
- // only one worker thread
- synchronized (LOCK) {
- renderResult = renderEngine.get().render(path, initialDataJSON);
- }
- if (renderResult.startsWith(MARKUP)) {
- String markup = renderResult.substring(MARKUP.length());
- LOG.finest("GENERATED MARKUP:\n---------------------------------------");
- LOG.finest(markup);
- LOG.finest("---------------------------------------");
- req.setAttribute("markup", markup);
- int maxAge = 60 * 60; // 60 minutes
- res.addHeader("Cache-Control", "public, max-age=" + maxAge);
- res.addDateHeader("Expires", new Date().getTime() + maxAge);
- LOG.finest("Dispatching to [" + jspPath + "]...");
- req.getRequestDispatcher(jspPath).forward(request, response);
- LOG.finer("Dispatched to [" + jspPath + "].");
- }
- else if (renderResult.startsWith(REDIRECT)) {
- String url = renderResult.substring(REDIRECT.length());
- LOG.finer("Redirecting to [" + url + "].");
- res.sendRedirect(url);
- }
- else if (renderResult.startsWith(NOTFOUND)) {
- LOG.finer("Not found.");
- int maxAge = 365 * 24 * 60 * 60; // 365 days
- res.addHeader("Cache-Control", "public, max-age=" + maxAge);
- res.addDateHeader("Expires", new Date().getTime() + maxAge);
- chain.doFilter(request, response);
- }
- else {
- String msg = renderResult.substring(ERROR.length());
- throw new ServletException("Unable to generate response for route [" + path + "]: " + msg);
- }
- } catch (ScriptException e) {
- throw new ServletException(e);
- }
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment