Stephen McKamey avatar Stephen McKamey committed 892fd5f

allowing simple config overrides and wildcard routing

Comments (0)

Files changed (4)

duel-staticapps-maven-plugin/pom.xml

 
 	<groupId>org.duelengine</groupId>
 	<artifactId>duel-staticapps-maven-plugin</artifactId>
-	<version>0.9.0</version>
+	<version>0.9.1</version>
 	<packaging>maven-plugin</packaging>
 
 	<name>DUEL Static Apps Maven Plugin</name>

duel-staticapps/pom.xml

 
 	<groupId>org.duelengine</groupId>
 	<artifactId>duel-staticapps</artifactId>
-	<version>0.9.0</version>
+	<version>0.9.1</version>
 	<packaging>jar</packaging>
 
 	<name>DUEL Static Apps</name>

duel-staticapps/src/main/java/org/duelengine/duel/staticapps/NeverExpireFilter.java

+package org.duelengine.duel.staticapps;
+
+import java.io.IOException;
+import java.util.*;
+import javax.servlet.*;
+import javax.servlet.http.*;
+
+/**
+ * Sets cache control to "never" expire & enables cross-origin access.
+ *
+ * Only use for SHA1-named CDN resources which change name as content changes.
+ * 
+ * http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html
+ * 
+ * To mark a response as "never expires," an origin server sends an
+ * Expires date approximately one year from the time the response is sent.
+ * HTTP/1.1 servers SHOULD NOT send Expires dates more than one year in the future.
+ * 
+ * CDN-based resources require permission to be accessed from another domain.
+ * e.g. without, CSS references to other resources like fonts may be blocked
+ *
+ * http://www.w3.org/TR/cors/#access-control-allow-origin-response-hea
+ * https://developer.mozilla.org/En/HTTP_access_control#Access-Control-Allow-Origin
+ */
+public class NeverExpireFilter implements Filter {
+
+	// this just needs to be far out, do not need to worry about leap year
+	private static final long ONE_YEAR_SEC = 365L * 24L * 60L * 60L;
+	private static final long ONE_YEAR_MS = ONE_YEAR_SEC * 1000L;
+
+	public void init(FilterConfig config) {}
+
+	public void destroy() {}
+
+	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
+
+		if (response instanceof HttpServletResponse) {
+			HttpServletResponse httpResponse = (HttpServletResponse)response;
+
+			// expire one year from now
+			long expiryDate = new Date().getTime() + ONE_YEAR_MS;
+
+			// add cache control response headers
+			httpResponse.setDateHeader("Expires", expiryDate);
+			httpResponse.setHeader("Cache-Control", "public, max-age="+ONE_YEAR_SEC);
+
+			// add header to encourage CDN to vary cache on compression
+			httpResponse.setHeader("Vary", "Accept-Encoding");
+
+			// add header to enable CDN cross-origin access
+			// not conditionally sent since CDN will cache
+			httpResponse.setHeader("Access-Control-Allow-Origin", "*");
+		}
+
+		chain.doFilter(request, response);
+	}
+}

duel-staticapps/src/main/java/org/duelengine/duel/staticapps/RoutingServlet.java

 import org.duelengine.duel.DuelView;
 import org.duelengine.duel.FormatPrefs;
 import org.duelengine.duel.LinkInterceptor;
+import org.duelengine.duel.utils.FileUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 		String configPath = null;
 		try {
 			// load from config file
-			configPath = servletConfig.getInitParameter("config-path");
+			configPath = System.getProperty("org.duelengine.duel.staticapps.configPath");
+			if (configPath == null || configPath.isEmpty()) {
+				configPath = servletConfig.getInitParameter("config-path");
+			}
 			if (configPath == null || configPath.isEmpty()) {
 				config = null;
 
 				}
 			}
 
-		} catch (Exception ex) {
+		} catch (Throwable ex) {
 			log.error("Error loading staticapp config from 'config-path' param in WEB-INF/web.xml: "+configPath, ex);
 			config = null;
 		}
 
 		if (config == null) {
 			try {
-				configPath = servletConfig.getInitParameter("config-resource");
+				configPath = System.getProperty("org.duelengine.duel.staticapps.configResource");
+				if (configPath == null || configPath.isEmpty()) {
+					configPath = servletConfig.getInitParameter("config-resource");
+				}
 				log.info("Loading config from resource: "+configPath);
 				InputStream stream = getClass().getResourceAsStream(configPath);
 				config = new ObjectMapper().reader(SiteConfig.class).readValue(stream);
 
-			} catch (Exception ex) {
+			} catch (Throwable ex) {
 				log.error("Error loading staticapp config from 'config-resource' param in WEB-INF/web.xml: "+configPath, ex);
-				config = new SiteConfig();
+				config = null;
 			}
 		}
-
-		String devModeOverride = servletConfig.getInitParameter("dev-mode-override");
-		if (devModeOverride != null && !devModeOverride.isEmpty()) {
-			log.info("dev-mode-override="+devModeOverride);
-			config.isDevMode( Boolean.parseBoolean(devModeOverride) );
+		if (config == null) {
+			// dummy noop config
+			config = new SiteConfig();
 		}
 
 		format = new FormatPrefs()
 			response.setContentType(config.contentType());
 			response.setCharacterEncoding(config.encoding());
 
-			SiteViewPage sitePage = route(request.getServletPath());
+			String servletPath = request.getServletPath();
+			SiteViewPage sitePage = route(servletPath);
 			if (sitePage == null) {
+				log.info("routing: "+servletPath+" (static)");
 				defaultServlet(request, response);
 				return;
 			}
 	 * @return
 	 */
 	private SiteViewPage route(String servletPath) {
-		log.info("routing: "+servletPath);
-
 		if (config.views() == null) {
 			return null;
 		}
 
-		// TODO: expand routing capabilities beyond exact match and default doc
+		// TODO: expand routing capabilities beyond exact match, default-doc and catch-all
 		
 		SiteViewPage page = config.views().get(servletPath.substring(1));
-		if (page == null && servletPath.endsWith("/")) {
-			// continue to attempt to resolve with default document
-			log.info("routing: "+servletPath+DEFAULT_DOC);
-			page = config.views().get(servletPath.substring(1)+DEFAULT_DOC);
+		if (page != null) {
+			log.info("routing: "+servletPath);
+
+		} else {
+			if (servletPath.endsWith("/")) {
+				// continue to attempt to resolve with default document
+				page = config.views().get(servletPath.substring(1)+DEFAULT_DOC);
+			}
+			if (page != null) {
+				log.info("routing: "+servletPath+" (as "+servletPath+DEFAULT_DOC+")");
+
+			} else {
+				// continue to attempt to resolve with catch-all
+				String ext = servletPath.endsWith("/") ? FileUtil.getExtension(DEFAULT_DOC) : FileUtil.getExtension(servletPath);
+				page = config.views().get("*"+ext);
+				if (page != null) {
+					log.info("routing: "+servletPath+" (as *"+ext+")");
+				}
+			}
 		}
 
 		return page;
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.