package peergos.server.net; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import peergos.shared.crypto.hash.Hash; import peergos.shared.util.ArrayOps; import java.io.*; import java.util.*; import java.util.concurrent.*; import java.util.zip.GZIPOutputStream; public abstract class StaticHandler implements HttpHandler { private final boolean isGzip; public StaticHandler(boolean isGzip) { this.isGzip = isGzip; } public abstract Asset getAsset(String resourcePath) throws IOException; public static class Asset { public final byte[] data; public final String hash; public Asset(byte[] data) { this.data = data; byte[] digest = Hash.sha256(data); this.hash = ArrayOps.bytesToHex(Arrays.copyOfRange(digest, 0, 4)); } } protected boolean isGzip() { return isGzip; } @Override public void handle(HttpExchange httpExchange) throws IOException { String path = httpExchange.getRequestURI().getPath(); try { path = path.substring(1); path = path.replaceAll("//", "/"); if (path.length() == 0) path = "index.html"; Asset res = getAsset(path); if (isGzip) httpExchange.getResponseHeaders().set("Content-Encoding", "gzip"); if (path.endsWith(".js")) httpExchange.getResponseHeaders().set("Content-Type", "text/javascript"); else if (path.endsWith(".html")) httpExchange.getResponseHeaders().set("Content-Type", "text/html"); else if (path.endsWith(".css")) httpExchange.getResponseHeaders().set("Content-Type", "text/css"); else if (path.endsWith(".json")) httpExchange.getResponseHeaders().set("Content-Type", "application/json"); if (httpExchange.getRequestMethod().equals("HEAD")) { httpExchange.getResponseHeaders().set("Content-Length", "" + res.data.length); httpExchange.sendResponseHeaders(200, -1); return; } if (res.data.length > 100 * 1024) { httpExchange.getResponseHeaders().set("Cache-Control", "public, max-age=3600"); httpExchange.getResponseHeaders().set("ETag", res.hash); } httpExchange.sendResponseHeaders(200, res.data.length); httpExchange.getResponseBody().write(res.data); httpExchange.getResponseBody().close(); } catch (NullPointerException t) { System.err.println("Error retrieving: " + path); } catch (Throwable t) { System.err.println("Error retrieving: " + path); httpExchange.sendResponseHeaders(404, 0); httpExchange.getResponseBody().close(); } } protected static byte[] readResource(InputStream in, boolean gzip) throws IOException { ByteArrayOutputStream bout = new ByteArrayOutputStream(); OutputStream gout = gzip ? new GZIPOutputStream(bout) : new DataOutputStream(bout); byte[] tmp = new byte[4096]; int r; while ((r=in.read(tmp)) >= 0) gout.write(tmp, 0, r); gout.flush(); gout.close(); in.close(); return bout.toByteArray(); } public StaticHandler withCache() { Map<String, Asset> cache = new ConcurrentHashMap<>(); StaticHandler that = this; return new StaticHandler(isGzip) { @Override public Asset getAsset(String resourcePath) throws IOException { if (! cache.containsKey(resourcePath)) cache.put(resourcePath, that.getAsset(resourcePath)); return cache.get(resourcePath); } }; } }