Permalink
Browse files

Merge branch 'cache-control' of git://github.com/pk11/webbit into pk1…

…1-cache-control
  • Loading branch information...
2 parents 890593d + cb57219 commit 937e41f9688efef473dc6e1e4416fc5d105e2f14 @aslakhellesoy aslakhellesoy committed Apr 23, 2013
View
@@ -19,6 +19,11 @@ chatroom: test dist/$(LIBRARY)-all-in-one.jar
java -cp $(CLASSPATH):dist/$(LIBRARY)-all-in-one.jar:build/$(LIBRARY)-tests.jar samples.chatroom.Main
.PHONY: chatroom
+# Run sample chatroom
+flashroom: test dist/$(LIBRARY)-all-in-one.jar
+ java -cp $(CLASSPATH):dist/$(LIBRARY)-all-in-one.jar:build/$(LIBRARY)-tests.jar samples.flashchatroom.Main
+.PHONY: chatroom
+
# Run echo server - used by Autobahn
echo: test dist/$(LIBRARY)-all-in-one.jar
java -Xmx256m -cp $(CLASSPATH):dist/$(LIBRARY)-all-in-one.jar:build/$(LIBRARY)-tests.jar samples.echo.Main
@@ -31,7 +36,7 @@ find = $(shell find $(1) -name '*.$(2)')
extracttests = $(shell jar tf $(1) | grep 'Test.class$$' | sed -e 's|/|.|g;s|.class$$||')
# Compile core Jar (just classes, no dependencies)
-dist/$(LIBRARY).jar: $(call find,src/main/java,java)
+dist/$(LIBRARY).jar: $(call find,.,java)
@mkdir -p build/main/classes
@mkdir -p dist
javac -g -cp $(CLASSPATH) -d build/main/classes $(call find,src/main/java,java)
@@ -25,8 +25,7 @@
mimeTypes.put("htm", "text/html");
mimeTypes.put("html", "text/html");
mimeTypes.put("xml", "text/xml");
- mimeTypes.put("js",
- "text/javascript"); // Technically it should be application/javascript (RFC 4329), but IE8 struggles with that
+ mimeTypes.put("js", "text/javascript"); // Technically it should be application/javascript (RFC 4329), but IE8 struggles with that
mimeTypes.put("xhtml", "application/xhtml+xml");
mimeTypes.put("json", "application/json");
mimeTypes.put("pdf", "application/pdf");
@@ -38,6 +37,7 @@
mimeTypes.put("tiff", "image/tiff");
mimeTypes.put("tif", "image/tiff");
mimeTypes.put("png", "image/png");
+ mimeTypes.put("swf", "application/x-shockwave-flash");
mimeTypes.put("svg", "image/svg+xml");
mimeTypes.put("ico", "image/vnd.microsoft.icon");
DEFAULT_MIME_TYPES = Collections.unmodifiableMap(mimeTypes);
@@ -9,16 +9,28 @@
import java.io.FileInputStream;
import java.io.IOException;
import java.util.concurrent.Executor;
+import java.util.Map;
+import java.util.HashMap;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.TimeZone;
+import java.util.Locale;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.math.BigInteger;
import static java.util.concurrent.Executors.newFixedThreadPool;
public class StaticFileHandler extends AbstractResourceHandler {
private final File dir;
+ private final long maxAge;
+
public StaticFileHandler(File dir, Executor ioThread, TemplateEngine templateEngine) {
super(ioThread, templateEngine);
this.dir = dir;
+ this.maxAge = 0;
}
public StaticFileHandler(File dir, Executor ioThread) {
@@ -49,19 +61,85 @@ public StaticFileHandler(String dir) {
this(new File(dir));
}
+ //cache control-aware
+
+ public StaticFileHandler(File dir, Executor ioThread, TemplateEngine templateEngine, long maxAge) {
+ super(ioThread, templateEngine);
+ this.dir = dir;
+ this.maxAge = maxAge;
+ }
+
+ public StaticFileHandler(File dir, Executor ioThread, long maxAge) {
+ this(dir, ioThread, new StaticFile(), maxAge);
+ }
+
+ public StaticFileHandler(String dir, long maxAge) {
+ this(new File(dir), maxAge);
+ }
+
+ public StaticFileHandler(File dir, long maxAge) {
+ this(dir, newFixedThreadPool(4), new StaticFile(), maxAge);
+ }
+
@Override
protected FileWorker createIOWorker(HttpRequest request,
- HttpResponse response,
- HttpControl control) {
- return new FileWorker(request, response, control);
+ HttpResponse response,
+ HttpControl control) {
+ return new FileWorker(request, response, control, maxAge);
}
protected class FileWorker extends IOWorker {
private File file;
- protected FileWorker(HttpRequest request, HttpResponse response, HttpControl control) {
+ private final HttpResponse response;
+
+ private final HttpRequest request;
+
+ private final long maxAge;
+
+ private String mimeType(String uri) {
+ String ext = uri.lastIndexOf(".") != -1 ? uri.substring(uri.lastIndexOf(".")) : null;
+ String currentMimeType = mimeTypes.get(ext);
+ if (currentMimeType == null) currentMimeType = "text/plain";
+ return currentMimeType;
+ }
+ //based on: http://m2tec.be/blog/2010/02/03/java-md5-hex-0093
+ private String MD5(String md5) {
+ try {
+ java.security.MessageDigest md = java.security.MessageDigest.getInstance("MD5");
+ byte[] array = md.digest(md5.getBytes("UTF-8"));
+ StringBuffer sb = new StringBuffer();
+ for (int i = 0; i < array.length; ++i) {
+ sb.append(Integer.toHexString((array[i] & 0xFF) | 0x100).substring(1, 3));
+ }
+ return sb.toString();
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ private String toHeader(Date date) {
+ SimpleDateFormat httpDateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
+ httpDateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
+ return httpDateFormat.format(date);
+ }
+
+ private Date fromHeader(String date) {
+ SimpleDateFormat httpDateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
+ httpDateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
+ try {
+ return httpDateFormat.parse(date);
+ } catch (Exception ex) {
+ return new Date();
+ }
+ }
+
+ protected FileWorker(HttpRequest request, HttpResponse response, HttpControl control, long maxAge) {
super(request.uri(), request, response, control);
+ this.maxAge = maxAge;
+ this.response = response;
+ this.request = request;
}
@Override
@@ -77,7 +155,27 @@ protected boolean isDirectory() throws IOException {
@Override
protected byte[] fileBytes() throws IOException {
- return file.isFile() ? read(file) : null;
+ byte[] raw = file.isFile() ? read(file) : null;
+ //add cache control headers if needed
+ if (raw != null) {
+ Date lastModified = new Date(file.lastModified());
+ String hashtext = MD5(Long.toString(lastModified.getTime()));
+ if (hashtext != null) response.header("ETag", "\"" + hashtext + "\"");
+
+ response.header("Last-Modified", toHeader(lastModified));
+ //is there an incoming If-Modified-Since?
+ if (request.header("If-Modified-Since") != null) {
+ if (fromHeader(request.header("If-Modified-Since")).getTime() >= lastModified.getTime() ) {
+ response.status(304);
+ }
+ }
+ //is setting cache control necessary?
+ if (maxAge != 0) {
+ response.header("Expires", toHeader( new Date(new Date().getTime() + maxAge * 1000)));
+ response.header("Cache-Control", "max-age=" + maxAge+", public");
+ }
+ }
+ return raw;
}
@Override
@@ -23,6 +23,10 @@
import static org.webbitserver.WebServers.createWebServer;
import static org.webbitserver.testutil.HttpClient.contents;
import static org.webbitserver.testutil.HttpClient.httpGet;
+import java.text.SimpleDateFormat;
+import java.util.TimeZone;
+import java.util.Locale;
+import java.util.Date;
public class StaticFileHandlerTest {
@@ -201,11 +205,27 @@ public void prefersWelcomeFileToDirectoryListing() throws Exception {
assertReturnedWithStatusAndContainsContent(200, "hi", handle(request("/")));
}
+ @Test
+ public void shouldHandleCacheHeaders() throws Exception {
+ mkdir("a/b");
+ writeFile("index_cache.html", "Blah");
+ Long aYearAgo = (new Date()).getTime() - (365 * 24 * 60 * 60 * 1000);
+ Long aYearFromNow = (new Date()).getTime() + (365 * 24 * 60 * 60 * 1000);
+ assertEquals(true, handle(request("/index_cache.html")).header("Last-Modified") != null);
+ assertEquals(true, handle(request("/index_cache.html")).header("ETag") != null);
+ assertEquals(true, handle(request("/index_cache.html")).header("Cache-Control") != null);
+ assertEquals(true, handle(request("/index_cache.html")).header("Cache-Control").contains("max-age=3600, public"));
+ assertEquals(true, handle(request("/index_cache.html")).header("Expires") != null);
+ assertEquals(true, handleWithHeader(request("/index_cache.html"), "If-Modified-Since", toDateHeader(new Date(aYearAgo))).status() == 200);
+ assertEquals(true, handleWithHeader(request("/index_cache.html"), "If-Modified-Since", toDateHeader(new Date(aYearFromNow))).status() == 304);
+ }
+
@Test
public void shouldGuessMimeTypeFromExtension() throws Exception {
mkdir("a/b");
writeFile("index.html", "Blah");
writeFile("file.HTML", "Blah");
+ writeFile("file.swf", "Blah");
writeFile("file2.hTM", "Blah");
writeFile("foo.txt", "Blah");
writeFile("foo.png", "Blah");
@@ -217,6 +237,7 @@ public void shouldGuessMimeTypeFromExtension() throws Exception {
writeFile(".x", "Blah");
writeFile("x.", "Blah");
+ assertEquals("application/x-shockwave-flash", handle(request("/file.swf")).header("Content-Type"));
assertEquals("text/html; charset=UTF-8", handle(request("/index.html")).header("Content-Type"));
assertEquals("text/html; charset=UTF-8", handle(request("/file.HTML")).header("Content-Type"));
assertEquals("text/html; charset=UTF-8", handle(request("/file2.hTM")).header("Content-Type"));
@@ -275,7 +296,7 @@ public void execute(Runnable command) {
command.run();
}
};
- handler = new StaticFileHandler(dir, immediateExecutor);
+ handler = new StaticFileHandler(dir, immediateExecutor, 3600);
}
/**
@@ -329,6 +350,17 @@ private StubHttpResponse handle(StubHttpRequest request) throws Exception {
handler.handleHttpRequest(request, response, new StubHttpControl(request, response));
return response;
}
+ private StubHttpResponse handleWithHeader(StubHttpRequest request, String headerName, String headerValue) throws Exception {
+ StubHttpResponse response = new StubHttpResponse();
+ request.header(headerName, headerValue);
+ handler.handleHttpRequest(request, response, new StubHttpControl(request, response));
+ return response;
+ }
+ private String toDateHeader(Date date) {
+ SimpleDateFormat httpDateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
+ httpDateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
+ return httpDateFormat.format(date);
+ }
private void assertReturnedWithStatus(int expectedStatus, StubHttpResponse response) {
assertEquals(expectedStatus, response.status());
Binary file not shown.
@@ -1,8 +1,3 @@
-// web-socket-js
-WEB_SOCKET_SWF_LOCATION = "WebSocketMain.swf";
-WEB_SOCKET_DEBUG = true;
-
-
// Socket reference.
var ws;
@@ -4,11 +4,16 @@
<title>Chat room</title>
<link rel="stylesheet" type="text/css" href="chatroom.css">
<script type="text/javascript" src="swfobject.js"></script>
+ <script type="text/javascript">
+ // web-socket-js
+ window.WEB_SOCKET_SWF_LOCATION = "WebSocketMain.swf";
+ window.WEB_SOCKET_DEBUG = true;
+ </script>
<script type="text/javascript" src="web_socket.js"></script>
<script type="text/javascript" src="chatroom.js"></script>
</head>
<body>
<textarea id="chatlog"></textarea>
<input type="text" id="entry">
</body>
-</html>
+</html>
Oops, something went wrong.

0 comments on commit 937e41f

Please sign in to comment.