From 96bb3072b02e9e8eaaec37e10db578552fcfb2c2 Mon Sep 17 00:00:00 2001 From: Benjamin Erb Date: Tue, 21 Dec 2010 13:13:09 +0100 Subject: [PATCH 1/9] added Basic Authentication --- .../www/server/impl/BasicAuthHttpWorker.java | 64 +++++++++++++++++++ .../www/server/impl/BasicHttpServer.java | 2 +- 2 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 src/de/ioexception/www/server/impl/BasicAuthHttpWorker.java diff --git a/src/de/ioexception/www/server/impl/BasicAuthHttpWorker.java b/src/de/ioexception/www/server/impl/BasicAuthHttpWorker.java new file mode 100644 index 0000000..2d2c110 --- /dev/null +++ b/src/de/ioexception/www/server/impl/BasicAuthHttpWorker.java @@ -0,0 +1,64 @@ +package de.ioexception.www.server.impl; + +import java.net.Socket; +import java.util.HashMap; +import java.util.Map; + +import util.Base64; +import de.ioexception.www.http.HttpRequest; +import de.ioexception.www.http.HttpResponse; +import de.ioexception.www.http.HttpStatusCode; +import de.ioexception.www.http.HttpVersion; +import de.ioexception.www.http.impl.BasicHttpResponse; + +/** + * @author Benjamin Erb + * + */ +public class BasicAuthHttpWorker extends BasicHttpWorker +{ + private static final Map authentications; + private static final String realm = "Protected Area"; + + static + { + authentications = new HashMap(); + authentications.put("test", "secret"); + authentications.put("user", "1234"); + }; + + public BasicAuthHttpWorker(Socket socket, BasicHttpServer server) + { + super(socket, server); + } + + @Override + protected HttpResponse handleRequest(HttpRequest request) + { + if (request.getHeaders().containsKey("Authorization")) + { + String authValue = request.getHeaders().get("Authorization"); + String[] authValues = authValue.split(" ", 2); + String type = authValues[0]; + String values = authValues[1]; + if (type.equalsIgnoreCase("Basic")) + { + String auth = new String(Base64.decode(values)); + String[] authentication = auth.split(":", 2); + if (authentications.containsKey(authentication[0]) && authentications.get(authentication[0]).equals(authentication[1])) + { + return super.handleRequest(request); + } + } + } + BasicHttpResponse response = new BasicHttpResponse(); + response.setStatusCode(HttpStatusCode.UNAUTHORIZED); + Map headers = new HashMap(); + headers.put("WWW-Authenticate", "Basic realm=\"" + realm + "\""); + headers.put("Content-Length", "0"); + response.setVersion(HttpVersion.VERSION_1_1); + response.setHeaders(headers); + response.setEntity(null); + return response; + } +} \ No newline at end of file diff --git a/src/de/ioexception/www/server/impl/BasicHttpServer.java b/src/de/ioexception/www/server/impl/BasicHttpServer.java index ddca550..eb69e47 100644 --- a/src/de/ioexception/www/server/impl/BasicHttpServer.java +++ b/src/de/ioexception/www/server/impl/BasicHttpServer.java @@ -62,7 +62,7 @@ public BasicHttpServer(int port) @Override public void dispatchRequest(Socket socket) { - workerPool.submit(new BasicHttpWorker(socket, this)); + workerPool.submit(new BasicAuthHttpWorker(socket, this)); } @Override From e4c0742e2d39d3b32dce3568aed235d4910ea52b Mon Sep 17 00:00:00 2001 From: Benjamin Erb Date: Tue, 21 Dec 2010 18:40:45 +0100 Subject: [PATCH 2/9] added access logging using the "Combined Log Format" --- .../ioexception/www/server/AccessLogger.java | 12 +++ src/de/ioexception/www/server/HttpServer.java | 7 ++ src/de/ioexception/www/server/HttpWorker.java | 2 + .../www/server/impl/BasicAccessLogger.java | 101 ++++++++++++++++++ .../www/server/impl/BasicHttpServer.java | 13 +++ 5 files changed, 135 insertions(+) create mode 100644 src/de/ioexception/www/server/AccessLogger.java create mode 100644 src/de/ioexception/www/server/impl/BasicAccessLogger.java diff --git a/src/de/ioexception/www/server/AccessLogger.java b/src/de/ioexception/www/server/AccessLogger.java new file mode 100644 index 0000000..3ae0af8 --- /dev/null +++ b/src/de/ioexception/www/server/AccessLogger.java @@ -0,0 +1,12 @@ +package de.ioexception.www.server; + +import java.util.concurrent.Callable; + +import de.ioexception.www.http.HttpRequest; +import de.ioexception.www.http.HttpResponse; + +public interface AccessLogger extends Callable +{ + public void log(String clientHost, HttpRequest request, HttpResponse response); + public void log(String s); +} diff --git a/src/de/ioexception/www/server/HttpServer.java b/src/de/ioexception/www/server/HttpServer.java index 55e1fda..c801c63 100644 --- a/src/de/ioexception/www/server/HttpServer.java +++ b/src/de/ioexception/www/server/HttpServer.java @@ -33,5 +33,12 @@ public interface HttpServer * @return */ public String getServerSignature(); + + /** + * Returns the signature of the webserver. + * + * @return + */ + public AccessLogger getAccessLogger(); } diff --git a/src/de/ioexception/www/server/HttpWorker.java b/src/de/ioexception/www/server/HttpWorker.java index bbc7f2c..e779c0e 100644 --- a/src/de/ioexception/www/server/HttpWorker.java +++ b/src/de/ioexception/www/server/HttpWorker.java @@ -58,6 +58,8 @@ public Void call() throws Exception sendResponse(response, socket.getOutputStream()); socket.close(); } + //Log + server.getAccessLogger().log(socket.getInetAddress().getHostAddress(), request, response); // We do not return anything here. return null; diff --git a/src/de/ioexception/www/server/impl/BasicAccessLogger.java b/src/de/ioexception/www/server/impl/BasicAccessLogger.java new file mode 100644 index 0000000..fa75b38 --- /dev/null +++ b/src/de/ioexception/www/server/impl/BasicAccessLogger.java @@ -0,0 +1,101 @@ +package de.ioexception.www.server.impl; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.concurrent.LinkedBlockingQueue; + +import de.ioexception.www.http.HttpRequest; +import de.ioexception.www.http.HttpResponse; +import de.ioexception.www.server.AccessLogger; + +public class BasicAccessLogger implements AccessLogger +{ + private final BufferedWriter log; + private volatile boolean running = true; + private final LinkedBlockingQueue logQueue = new LinkedBlockingQueue(); + + private final SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MMM/yyyy:HH:mm:ss Z", Locale.ENGLISH); + + /* + * [10/Oct/2000:13:55:36 -0700] (%t) + The time that the request was received. The format is: + + [day/month/year:hour:minute:second zone] + day = 2*digit + month = 3*letter + year = 4*digit + hour = 2*digit + minute = 2*digit + second = 2*digit + zone = (`+' | `-') 4*digit + */ + + public BasicAccessLogger(File logFile) throws IOException + { + log = new BufferedWriter(new FileWriter(logFile, true)); + } + + + @Override + public void log(String logline) + { + try + { + logQueue.put(logline); + } + catch (InterruptedException e) + { + e.printStackTrace(); + } + } + + + @Override + public Void call() throws Exception + { + try + { + while(running || logQueue.size() > 0) + { + log.write(logQueue.take()); + log.flush(); + } + log.flush(); + return null; + } + finally + { + System.err.println("closing.."); + log.close(); + } + } + + public void shutdown() + { + running = false; + } + + + @Override + public void log(String clientHost, HttpRequest request, HttpResponse response) + { + StringBuilder s = new StringBuilder(); + s.append(clientHost+" "); + s.append("- "); + s.append("- "); + s.append("["+dateFormat.format(new Date())+"] "); + s.append("\""+request.getHttpMethod().toString()+" "+request.getRequestUri()+" "+request.getHttpVersion().toString()+"\" "); + s.append(response.getStatusCode().getCode()+" "); + s.append((response.getEntity() != null ? response.getEntity().length : 0)+" "); + s.append("\""+(request.getHeaders().containsKey("Referer") ? request.getHeaders().get("Referer") : "")+"\" " ); + s.append("\""+(request.getHeaders().containsKey("User-Agent") ? request.getHeaders().get("User-Agent") : "")+"\""); + s.append("\r\n"); + log(s.toString()); + } + +} diff --git a/src/de/ioexception/www/server/impl/BasicHttpServer.java b/src/de/ioexception/www/server/impl/BasicHttpServer.java index ddca550..6ea16c4 100644 --- a/src/de/ioexception/www/server/impl/BasicHttpServer.java +++ b/src/de/ioexception/www/server/impl/BasicHttpServer.java @@ -1,5 +1,6 @@ package de.ioexception.www.server.impl; +import java.io.File; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; @@ -7,6 +8,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import de.ioexception.www.server.AccessLogger; import de.ioexception.www.server.HttpServer; /** @@ -26,7 +28,9 @@ public class BasicHttpServer implements HttpServer private final ExecutorService workerPool; private final ExecutorService dispatcherService; + private final ExecutorService loggingService; private final ServerSocket serverSocket; + private final AccessLogger accessLogger; /** @@ -51,6 +55,8 @@ public BasicHttpServer(int port) serverSocket = new ServerSocket(port); workerPool = Executors.newFixedThreadPool(16); dispatcherService = Executors.newSingleThreadExecutor(); + loggingService = Executors.newSingleThreadExecutor(); + accessLogger = new BasicAccessLogger(new File("log/access.log")); } catch (IOException e) { @@ -69,6 +75,7 @@ public void dispatchRequest(Socket socket) public void start() { running = true; + loggingService.submit(accessLogger); // Initiate the main server loop accepting incoming connections. dispatcherService.submit(new Runnable() { @@ -123,4 +130,10 @@ public String getServerSignature() return BasicHttpServer.SERVER_SIGNATURE; } + @Override + public AccessLogger getAccessLogger() + { + return accessLogger; + } + } From a0da5869578628ecca4bfa14d924dccfb3f98c40 Mon Sep 17 00:00:00 2001 From: Benjamin Erb Date: Wed, 22 Dec 2010 23:45:27 +0100 Subject: [PATCH 3/9] added more loggers and buildfile --- build.xml | 38 +++++++ src/de/ioexception/www/server/HttpServer.java | 2 + .../www/server/impl/BasicAccessLogger.java | 101 ----------------- .../www/server/impl/BasicHttpServer.java | 7 +- .../www/server/{ => log}/AccessLogger.java | 2 +- .../log/impl/BufferedFileAccessLogger.java | 25 ++++ .../server/log/impl/ConsoleAccessLogger.java | 32 ++++++ .../www/server/log/impl/FileAccessLogger.java | 23 ++++ .../server/log/impl/GenericAccessLogger.java | 107 ++++++++++++++++++ 9 files changed, 233 insertions(+), 104 deletions(-) create mode 100644 build.xml delete mode 100644 src/de/ioexception/www/server/impl/BasicAccessLogger.java rename src/de/ioexception/www/server/{ => log}/AccessLogger.java (88%) create mode 100644 src/de/ioexception/www/server/log/impl/BufferedFileAccessLogger.java create mode 100644 src/de/ioexception/www/server/log/impl/ConsoleAccessLogger.java create mode 100644 src/de/ioexception/www/server/log/impl/FileAccessLogger.java create mode 100644 src/de/ioexception/www/server/log/impl/GenericAccessLogger.java diff --git a/build.xml b/build.xml new file mode 100644 index 0000000..f201982 --- /dev/null +++ b/build.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/de/ioexception/www/server/HttpServer.java b/src/de/ioexception/www/server/HttpServer.java index c801c63..ab1c239 100644 --- a/src/de/ioexception/www/server/HttpServer.java +++ b/src/de/ioexception/www/server/HttpServer.java @@ -2,6 +2,8 @@ import java.net.Socket; +import de.ioexception.www.server.log.AccessLogger; + /** * A basic HTTP server interface. * diff --git a/src/de/ioexception/www/server/impl/BasicAccessLogger.java b/src/de/ioexception/www/server/impl/BasicAccessLogger.java deleted file mode 100644 index fa75b38..0000000 --- a/src/de/ioexception/www/server/impl/BasicAccessLogger.java +++ /dev/null @@ -1,101 +0,0 @@ -package de.ioexception.www.server.impl; - -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Locale; -import java.util.concurrent.LinkedBlockingQueue; - -import de.ioexception.www.http.HttpRequest; -import de.ioexception.www.http.HttpResponse; -import de.ioexception.www.server.AccessLogger; - -public class BasicAccessLogger implements AccessLogger -{ - private final BufferedWriter log; - private volatile boolean running = true; - private final LinkedBlockingQueue logQueue = new LinkedBlockingQueue(); - - private final SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MMM/yyyy:HH:mm:ss Z", Locale.ENGLISH); - - /* - * [10/Oct/2000:13:55:36 -0700] (%t) - The time that the request was received. The format is: - - [day/month/year:hour:minute:second zone] - day = 2*digit - month = 3*letter - year = 4*digit - hour = 2*digit - minute = 2*digit - second = 2*digit - zone = (`+' | `-') 4*digit - */ - - public BasicAccessLogger(File logFile) throws IOException - { - log = new BufferedWriter(new FileWriter(logFile, true)); - } - - - @Override - public void log(String logline) - { - try - { - logQueue.put(logline); - } - catch (InterruptedException e) - { - e.printStackTrace(); - } - } - - - @Override - public Void call() throws Exception - { - try - { - while(running || logQueue.size() > 0) - { - log.write(logQueue.take()); - log.flush(); - } - log.flush(); - return null; - } - finally - { - System.err.println("closing.."); - log.close(); - } - } - - public void shutdown() - { - running = false; - } - - - @Override - public void log(String clientHost, HttpRequest request, HttpResponse response) - { - StringBuilder s = new StringBuilder(); - s.append(clientHost+" "); - s.append("- "); - s.append("- "); - s.append("["+dateFormat.format(new Date())+"] "); - s.append("\""+request.getHttpMethod().toString()+" "+request.getRequestUri()+" "+request.getHttpVersion().toString()+"\" "); - s.append(response.getStatusCode().getCode()+" "); - s.append((response.getEntity() != null ? response.getEntity().length : 0)+" "); - s.append("\""+(request.getHeaders().containsKey("Referer") ? request.getHeaders().get("Referer") : "")+"\" " ); - s.append("\""+(request.getHeaders().containsKey("User-Agent") ? request.getHeaders().get("User-Agent") : "")+"\""); - s.append("\r\n"); - log(s.toString()); - } - -} diff --git a/src/de/ioexception/www/server/impl/BasicHttpServer.java b/src/de/ioexception/www/server/impl/BasicHttpServer.java index 6ea16c4..f111acf 100644 --- a/src/de/ioexception/www/server/impl/BasicHttpServer.java +++ b/src/de/ioexception/www/server/impl/BasicHttpServer.java @@ -8,8 +8,10 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import de.ioexception.www.server.AccessLogger; import de.ioexception.www.server.HttpServer; +import de.ioexception.www.server.log.AccessLogger; +import de.ioexception.www.server.log.impl.BufferedFileAccessLogger; +import de.ioexception.www.server.log.impl.ConsoleAccessLogger; /** * A simple HTTP server implementation. @@ -56,7 +58,8 @@ public BasicHttpServer(int port) workerPool = Executors.newFixedThreadPool(16); dispatcherService = Executors.newSingleThreadExecutor(); loggingService = Executors.newSingleThreadExecutor(); - accessLogger = new BasicAccessLogger(new File("log/access.log")); +// accessLogger = new BufferedFileAccessLogger(new File("log/access.log")); + accessLogger = new ConsoleAccessLogger(); } catch (IOException e) { diff --git a/src/de/ioexception/www/server/AccessLogger.java b/src/de/ioexception/www/server/log/AccessLogger.java similarity index 88% rename from src/de/ioexception/www/server/AccessLogger.java rename to src/de/ioexception/www/server/log/AccessLogger.java index 3ae0af8..662389b 100644 --- a/src/de/ioexception/www/server/AccessLogger.java +++ b/src/de/ioexception/www/server/log/AccessLogger.java @@ -1,4 +1,4 @@ -package de.ioexception.www.server; +package de.ioexception.www.server.log; import java.util.concurrent.Callable; diff --git a/src/de/ioexception/www/server/log/impl/BufferedFileAccessLogger.java b/src/de/ioexception/www/server/log/impl/BufferedFileAccessLogger.java new file mode 100644 index 0000000..c5f4cd0 --- /dev/null +++ b/src/de/ioexception/www/server/log/impl/BufferedFileAccessLogger.java @@ -0,0 +1,25 @@ +package de.ioexception.www.server.log.impl; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; + +/** + * A File-based Logger using the "Combined Log Format". It buffers log entries + * and writes them into the logfile when a certain amount of chunks is + * available. This decreases the overhead of i/o operations, but will cause loss + * of entries in case of ungrateful server shutdowns. + * + * @author Benjamin Erb + * + */ +public class BufferedFileAccessLogger extends GenericAccessLogger +{ + + public BufferedFileAccessLogger(File logFile) throws IOException + { + super(new BufferedWriter(new FileWriter(logFile, true))); + } + +} diff --git a/src/de/ioexception/www/server/log/impl/ConsoleAccessLogger.java b/src/de/ioexception/www/server/log/impl/ConsoleAccessLogger.java new file mode 100644 index 0000000..9091b42 --- /dev/null +++ b/src/de/ioexception/www/server/log/impl/ConsoleAccessLogger.java @@ -0,0 +1,32 @@ +package de.ioexception.www.server.log.impl; + +import java.io.IOException; +import java.io.PrintWriter; + +/** + * A logger for console output. Flushes every entry immediately. + * + * @author Benjamin Erb + * + */ +public class ConsoleAccessLogger extends GenericAccessLogger +{ + public ConsoleAccessLogger() + { + super(new PrintWriter(System.out)); + } + + @Override + public void log(String logline) + { + super.log(logline); + try + { + super.flush(); + } + catch (IOException e) + { + e.printStackTrace(); + } + } +} diff --git a/src/de/ioexception/www/server/log/impl/FileAccessLogger.java b/src/de/ioexception/www/server/log/impl/FileAccessLogger.java new file mode 100644 index 0000000..7ff433f --- /dev/null +++ b/src/de/ioexception/www/server/log/impl/FileAccessLogger.java @@ -0,0 +1,23 @@ +package de.ioexception.www.server.log.impl; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; + +/** + * A File-based Logger using the "Combined Log Format". It directly writes each + * log entry into file and thus leads to poor i/o efficiency under heavy server + * load. + * + * @author Benjamin Erb + * + */ +public class FileAccessLogger extends GenericAccessLogger +{ + + public FileAccessLogger(File logFile) throws IOException + { + super(new FileWriter(logFile, true)); + } + +} diff --git a/src/de/ioexception/www/server/log/impl/GenericAccessLogger.java b/src/de/ioexception/www/server/log/impl/GenericAccessLogger.java new file mode 100644 index 0000000..e29dad3 --- /dev/null +++ b/src/de/ioexception/www/server/log/impl/GenericAccessLogger.java @@ -0,0 +1,107 @@ +package de.ioexception.www.server.log.impl; + +import java.io.Flushable; +import java.io.IOException; +import java.io.Writer; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.concurrent.LinkedBlockingQueue; + +import de.ioexception.www.http.HttpRequest; +import de.ioexception.www.http.HttpResponse; +import de.ioexception.www.server.log.AccessLogger; + +/** + * An abstract logger class with provides basic functionalities for logging + * access, using the "Combined Log Format". + * + * @author Benjamin Erb + */ +public abstract class GenericAccessLogger implements AccessLogger, Flushable +{ + private final SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MMM/yyyy:HH:mm:ss Z", Locale.ENGLISH); + + private volatile boolean running = true; + private final LinkedBlockingQueue logQueue = new LinkedBlockingQueue(); + private final Writer writer; + + /** + * The writer instance to be used. + * + * @param writer + */ + public GenericAccessLogger(Writer writer) + { + this.writer = writer; + } + + @Override + public void log(String logline) + { + try + { + logQueue.put(logline); + } + catch (InterruptedException e) + { + e.printStackTrace(); + } + } + + @Override + public Void call() throws Exception + { + try + { + while (running || logQueue.size() > 0) + { + writer.write(logQueue.take()); + } + writer.flush(); + return null; + } + finally + { + writer.close(); + } + } + + public void shutdown() + { + try + { + writer.flush(); + } + catch (IOException e) + { + e.printStackTrace(); + } + running = false; + } + + + @Override + public void log(String clientHost, HttpRequest request, HttpResponse response) + { + StringBuilder s = new StringBuilder(); + s.append(clientHost + " "); + s.append("- "); + s.append("- "); + s.append("[" + dateFormat.format(new Date()) + "] "); + s.append("\"" + request.getHttpMethod().toString() + " " + request.getRequestUri() + " " + request.getHttpVersion().toString() + "\" "); + s.append(response.getStatusCode().getCode() + " "); + s.append((response.getEntity() != null ? response.getEntity().length : 0) + " "); + s.append("\"" + (request.getHeaders().containsKey("Referer") ? request.getHeaders().get("Referer") : "") + "\" "); + s.append("\"" + (request.getHeaders().containsKey("User-Agent") ? request.getHeaders().get("User-Agent") : "") + "\""); + s.append("\n"); + log(s.toString()); + } + + @Override + public void flush() throws IOException + { + writer.flush(); + } + +} From f321d56283005f071a1917e7dd377de074692a8c Mon Sep 17 00:00:00 2001 From: Benjamin Erb Date: Thu, 23 Dec 2010 00:00:49 +0100 Subject: [PATCH 4/9] * added caching skeleton --- .../ioexception/www/server/cache/Cache.java | 7 +++ .../www/server/cache/impl/LRUCache.java | 54 +++++++++++++++++++ .../www/server/impl/CachingHttpWorker.java | 23 ++++++++ 3 files changed, 84 insertions(+) create mode 100644 src/de/ioexception/www/server/cache/Cache.java create mode 100644 src/de/ioexception/www/server/cache/impl/LRUCache.java create mode 100644 src/de/ioexception/www/server/impl/CachingHttpWorker.java diff --git a/src/de/ioexception/www/server/cache/Cache.java b/src/de/ioexception/www/server/cache/Cache.java new file mode 100644 index 0000000..94cbc02 --- /dev/null +++ b/src/de/ioexception/www/server/cache/Cache.java @@ -0,0 +1,7 @@ +package de.ioexception.www.server.cache; + +public interface Cache +{ + public V put(K key, V value); + public V get(K key); +} diff --git a/src/de/ioexception/www/server/cache/impl/LRUCache.java b/src/de/ioexception/www/server/cache/impl/LRUCache.java new file mode 100644 index 0000000..371394b --- /dev/null +++ b/src/de/ioexception/www/server/cache/impl/LRUCache.java @@ -0,0 +1,54 @@ +package de.ioexception.www.server.cache.impl; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +import de.ioexception.www.server.cache.Cache; + +/** + * A thread-safe LRU cache implementation based on internal LinkedHashMap. + * + * @author Benjamin Erb + * + * @param + * Entry Key Type + * @param + * Entry Value Type + */ +public class LRUCache implements Cache +{ + public static final int DEFAULT_MAX_SIZE = 1000; + + private final Map internalMap; + + public LRUCache() + { + this(DEFAULT_MAX_SIZE); + } + + public LRUCache(final int maxSize) + { + this.internalMap = (Map) Collections.synchronizedMap(new LinkedHashMap(maxSize + 1, .75F, true) + { + private static final long serialVersionUID = 5369285290965670135L; + + @Override + protected boolean removeEldestEntry(Map.Entry eldest) + { + return size() > maxSize; + } + }); + } + + public V put(K key, V value) + { + return internalMap.put(key, value); + } + + public V get(K key) + { + return internalMap.get(key); + } + +} \ No newline at end of file diff --git a/src/de/ioexception/www/server/impl/CachingHttpWorker.java b/src/de/ioexception/www/server/impl/CachingHttpWorker.java new file mode 100644 index 0000000..6e0b7a9 --- /dev/null +++ b/src/de/ioexception/www/server/impl/CachingHttpWorker.java @@ -0,0 +1,23 @@ +package de.ioexception.www.server.impl; + +import java.net.Socket; + +import de.ioexception.www.http.HttpRequest; +import de.ioexception.www.http.HttpResponse; + +public class CachingHttpWorker extends BasicHttpWorker +{ + + public CachingHttpWorker(Socket socket, BasicHttpServer server) + { + super(socket, server); + } + + @Override + protected HttpResponse handleRequest(HttpRequest request) + { + // TODO: add caching support + return super.handleRequest(request); + } + +} From 7a6af4e4248b88a67fc44a5e2851a11d6eb15923 Mon Sep 17 00:00:00 2001 From: Benjamin Erb Date: Thu, 23 Dec 2010 17:41:52 +0100 Subject: [PATCH 5/9] added simple LRU-based in memory cache --- .../www/server/cache/EntityCacheEntry.java | 8 ++++ .../cache/impl/EntityCacheEntryImpl.java | 37 +++++++++++++++ .../www/server/impl/BasicHttpServer.java | 8 +++- .../www/server/impl/CachingHttpWorker.java | 47 +++++++++++++++++-- 4 files changed, 95 insertions(+), 5 deletions(-) create mode 100644 src/de/ioexception/www/server/cache/EntityCacheEntry.java create mode 100644 src/de/ioexception/www/server/cache/impl/EntityCacheEntryImpl.java diff --git a/src/de/ioexception/www/server/cache/EntityCacheEntry.java b/src/de/ioexception/www/server/cache/EntityCacheEntry.java new file mode 100644 index 0000000..f04a2d7 --- /dev/null +++ b/src/de/ioexception/www/server/cache/EntityCacheEntry.java @@ -0,0 +1,8 @@ +package de.ioexception.www.server.cache; + +public interface EntityCacheEntry +{ + byte[] getEntity(); + String getETag(); + String getContentType(); +} diff --git a/src/de/ioexception/www/server/cache/impl/EntityCacheEntryImpl.java b/src/de/ioexception/www/server/cache/impl/EntityCacheEntryImpl.java new file mode 100644 index 0000000..d895d39 --- /dev/null +++ b/src/de/ioexception/www/server/cache/impl/EntityCacheEntryImpl.java @@ -0,0 +1,37 @@ +package de.ioexception.www.server.cache.impl; + +import de.ioexception.www.server.cache.EntityCacheEntry; + +public class EntityCacheEntryImpl implements EntityCacheEntry +{ + private final byte[] entity; + private final String eTag; + private final String contentType; + + public EntityCacheEntryImpl(byte[] entity, String eTag, String contentType) + { + super(); + this.entity = entity; + this.eTag = eTag; + this.contentType = contentType; + } + + @Override + public byte[] getEntity() + { + return entity; + } + + @Override + public String getETag() + { + return eTag; + } + + @Override + public String getContentType() + { + return contentType; + } + +} diff --git a/src/de/ioexception/www/server/impl/BasicHttpServer.java b/src/de/ioexception/www/server/impl/BasicHttpServer.java index ddca550..8ccf297 100644 --- a/src/de/ioexception/www/server/impl/BasicHttpServer.java +++ b/src/de/ioexception/www/server/impl/BasicHttpServer.java @@ -8,6 +8,9 @@ import java.util.concurrent.Executors; import de.ioexception.www.server.HttpServer; +import de.ioexception.www.server.cache.Cache; +import de.ioexception.www.server.cache.EntityCacheEntry; +import de.ioexception.www.server.cache.impl.LRUCache; /** * A simple HTTP server implementation. @@ -27,6 +30,8 @@ public class BasicHttpServer implements HttpServer private final ExecutorService workerPool; private final ExecutorService dispatcherService; private final ServerSocket serverSocket; + + private final Cache cache = new LRUCache(100); /** @@ -62,7 +67,8 @@ public BasicHttpServer(int port) @Override public void dispatchRequest(Socket socket) { - workerPool.submit(new BasicHttpWorker(socket, this)); +// workerPool.submit(new BasicHttpWorker(socket, this)); + workerPool.submit(new CachingHttpWorker(socket, this,cache)); } @Override diff --git a/src/de/ioexception/www/server/impl/CachingHttpWorker.java b/src/de/ioexception/www/server/impl/CachingHttpWorker.java index 6e0b7a9..b045fae 100644 --- a/src/de/ioexception/www/server/impl/CachingHttpWorker.java +++ b/src/de/ioexception/www/server/impl/CachingHttpWorker.java @@ -1,23 +1,62 @@ package de.ioexception.www.server.impl; import java.net.Socket; +import java.util.HashMap; +import de.ioexception.www.Http; import de.ioexception.www.http.HttpRequest; import de.ioexception.www.http.HttpResponse; +import de.ioexception.www.http.HttpStatusCode; +import de.ioexception.www.http.impl.BasicHttpResponse; +import de.ioexception.www.server.cache.Cache; +import de.ioexception.www.server.cache.EntityCacheEntry; +import de.ioexception.www.server.cache.impl.EntityCacheEntryImpl; public class CachingHttpWorker extends BasicHttpWorker { + private final Cache cache; - public CachingHttpWorker(Socket socket, BasicHttpServer server) + public CachingHttpWorker(Socket socket, BasicHttpServer server, Cache cache) { super(socket, server); + this.cache = cache; } - + @Override protected HttpResponse handleRequest(HttpRequest request) { - // TODO: add caching support - return super.handleRequest(request); + EntityCacheEntry cacheEntry = cache.get(request.getRequestUri()); + + if(null == cacheEntry) + { + HttpResponse response = super.handleRequest(request); + if(response.getStatusCode().equals(HttpStatusCode.OK) && response.getEntity() != null && response.getEntity().length > 0) + { + EntityCacheEntry entry = new EntityCacheEntryImpl(response.getEntity(), response.getHeaders().get(Http.ETAG), response.getHeaders().get(Http.CONTENT_TYPE)); + cache.put(request.getRequestUri(), entry); + } + return response; + } + else + { + //TODO check for conditional request + BasicHttpResponse response = new BasicHttpResponse(); + response.setHeaders(new HashMap()); + response.getHeaders().put(Http.SERVER, server.getServerSignature()); + response.setVersion(request.getHttpVersion()); + response.getHeaders().put(Http.CONTENT_LENGTH, ""+cacheEntry.getEntity().length); + if(null != cacheEntry.getETag()) + { + response.getHeaders().put(Http.ETAG, cacheEntry.getETag()); + } + if(null != cacheEntry.getContentType()) + { + response.getHeaders().put(Http.CONTENT_TYPE, cacheEntry.getContentType()); + } + response.setEntity(cacheEntry.getEntity()); + response.setStatusCode(HttpStatusCode.OK); + return response; + } } } From 5e7382d6fc2e205db53705d1668bc6325689688b Mon Sep 17 00:00:00 2001 From: Scott Wakeling Date: Sat, 5 Mar 2011 20:50:26 +0000 Subject: [PATCH 6/9] merged in 'logging' branch and added tasks to build.xml --- build.xml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/build.xml b/build.xml index f201982..5819449 100644 --- a/build.xml +++ b/build.xml @@ -30,9 +30,21 @@ + + + + + + + + + + + + From 6dc5fb07d0e51d1dbf8347649f17c79344ef3d3e Mon Sep 17 00:00:00 2001 From: Scott Wakeling Date: Sun, 6 Mar 2011 18:42:11 +0000 Subject: [PATCH 7/9] use the hashcode of cached entities as an ETag for conditional responses --- .../www/server/impl/CachingHttpWorker.java | 50 ++++++++++++------- 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/src/de/ioexception/www/server/impl/CachingHttpWorker.java b/src/de/ioexception/www/server/impl/CachingHttpWorker.java index b045fae..023234f 100644 --- a/src/de/ioexception/www/server/impl/CachingHttpWorker.java +++ b/src/de/ioexception/www/server/impl/CachingHttpWorker.java @@ -25,8 +25,10 @@ public CachingHttpWorker(Socket socket, BasicHttpServer server, Cache()); - response.getHeaders().put(Http.SERVER, server.getServerSignature()); - response.setVersion(request.getHttpVersion()); - response.getHeaders().put(Http.CONTENT_LENGTH, ""+cacheEntry.getEntity().length); - if(null != cacheEntry.getETag()) - { - response.getHeaders().put(Http.ETAG, cacheEntry.getETag()); - } - if(null != cacheEntry.getContentType()) - { - response.getHeaders().put(Http.CONTENT_TYPE, cacheEntry.getContentType()); - } - response.setEntity(cacheEntry.getEntity()); - response.setStatusCode(HttpStatusCode.OK); - return response; + if (request.getHeaders().containsKey(Http.IF_NONE_MATCH)) + { + if (Integer.parseInt(request.getHeaders().get(Http.IF_NONE_MATCH)) == cacheEntry.hashCode()) + { + BasicHttpResponse response = new BasicHttpResponse(); + response.setHeaders(new HashMap()); + response.getHeaders().put(Http.SERVER, server.getServerSignature()); + response.getHeaders().put("Content-Length", "0"); + response.setVersion(request.getHttpVersion()); + response.setStatusCode(HttpStatusCode.NOT_MODIFIED); + response.setEntity(null); + return response; + } + } + + BasicHttpResponse response = new BasicHttpResponse(); + response.setHeaders(new HashMap()); + response.getHeaders().put(Http.SERVER, server.getServerSignature()); + response.setVersion(request.getHttpVersion()); + response.getHeaders().put(Http.CONTENT_LENGTH, ""+cacheEntry.getEntity().length); + response.getHeaders().put(Http.ETAG, new Integer(cacheEntry.hashCode()).toString()); + if(null != cacheEntry.getContentType()) + { + response.getHeaders().put(Http.CONTENT_TYPE, cacheEntry.getContentType()); + } + response.setEntity(cacheEntry.getEntity()); + response.setStatusCode(HttpStatusCode.OK); + return response; } } From 63a5ce52c8ce9fbaa657944a90e7c4df08d4225e Mon Sep 17 00:00:00 2001 From: Scott Wakeling Date: Sun, 6 Mar 2011 19:09:20 +0000 Subject: [PATCH 8/9] set sensible defaults in BasicHTTPResponse() and use Http statics instead of local strings --- .../www/http/impl/BasicHttpResponse.java | 11 +++++++++++ src/de/ioexception/www/server/HttpWorker.java | 3 ++- .../www/server/impl/BasicAuthHttpWorker.java | 13 +++++-------- .../www/server/impl/BasicHttpWorker.java | 4 +--- .../www/server/impl/CachingHttpWorker.java | 4 ---- 5 files changed, 19 insertions(+), 16 deletions(-) diff --git a/src/de/ioexception/www/http/impl/BasicHttpResponse.java b/src/de/ioexception/www/http/impl/BasicHttpResponse.java index 00526d0..3692d31 100644 --- a/src/de/ioexception/www/http/impl/BasicHttpResponse.java +++ b/src/de/ioexception/www/http/impl/BasicHttpResponse.java @@ -1,12 +1,23 @@ package de.ioexception.www.http.impl; +import de.ioexception.www.Http; import de.ioexception.www.http.HttpResponse; import de.ioexception.www.http.HttpStatusCode; +import de.ioexception.www.http.HttpVersion; +import java.util.HashMap; public class BasicHttpResponse extends BasicHttpMessage implements HttpResponse { HttpStatusCode statusCode; + public BasicHttpResponse() + { + setHeaders(new HashMap()); + getHeaders().put(Http.CONTENT_LENGTH, "0"); + setEntity(null); + setVersion(HttpVersion.VERSION_1_1); + } + @Override public HttpStatusCode getStatusCode() { diff --git a/src/de/ioexception/www/server/HttpWorker.java b/src/de/ioexception/www/server/HttpWorker.java index e779c0e..2d992ba 100644 --- a/src/de/ioexception/www/server/HttpWorker.java +++ b/src/de/ioexception/www/server/HttpWorker.java @@ -1,5 +1,6 @@ package de.ioexception.www.server; +import de.ioexception.www.Http; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -54,7 +55,7 @@ public Void call() throws Exception } else { - response.getHeaders().put("Connection", "close"); + response.getHeaders().put(Http.CONNECTION, "close"); sendResponse(response, socket.getOutputStream()); socket.close(); } diff --git a/src/de/ioexception/www/server/impl/BasicAuthHttpWorker.java b/src/de/ioexception/www/server/impl/BasicAuthHttpWorker.java index 2d2c110..da658e6 100644 --- a/src/de/ioexception/www/server/impl/BasicAuthHttpWorker.java +++ b/src/de/ioexception/www/server/impl/BasicAuthHttpWorker.java @@ -1,5 +1,6 @@ package de.ioexception.www.server.impl; +import de.ioexception.www.Http; import java.net.Socket; import java.util.HashMap; import java.util.Map; @@ -35,9 +36,9 @@ public BasicAuthHttpWorker(Socket socket, BasicHttpServer server) @Override protected HttpResponse handleRequest(HttpRequest request) { - if (request.getHeaders().containsKey("Authorization")) + if (request.getHeaders().containsKey(Http.AUTHORIZATION)) { - String authValue = request.getHeaders().get("Authorization"); + String authValue = request.getHeaders().get(Http.AUTHORIZATION); String[] authValues = authValue.split(" ", 2); String type = authValues[0]; String values = authValues[1]; @@ -53,12 +54,8 @@ protected HttpResponse handleRequest(HttpRequest request) } BasicHttpResponse response = new BasicHttpResponse(); response.setStatusCode(HttpStatusCode.UNAUTHORIZED); - Map headers = new HashMap(); - headers.put("WWW-Authenticate", "Basic realm=\"" + realm + "\""); - headers.put("Content-Length", "0"); - response.setVersion(HttpVersion.VERSION_1_1); - response.setHeaders(headers); - response.setEntity(null); + response.getHeaders().put(Http.WWW_AUTHENTICATE, "Basic realm=\"" + realm + "\""); + response.getHeaders().put(Http.CONTENT_LENGTH, "0"); return response; } } \ No newline at end of file diff --git a/src/de/ioexception/www/server/impl/BasicHttpWorker.java b/src/de/ioexception/www/server/impl/BasicHttpWorker.java index 519e03b..dc56a52 100644 --- a/src/de/ioexception/www/server/impl/BasicHttpWorker.java +++ b/src/de/ioexception/www/server/impl/BasicHttpWorker.java @@ -80,10 +80,8 @@ protected HttpResponse handleRequest(HttpRequest request) BasicHttpResponse response = new BasicHttpResponse(); - response.setHeaders(new HashMap()); response.getHeaders().put(Http.SERVER, server.getServerSignature()); - response.setVersion(request.getHttpVersion()); - + String requestUri = request.getRequestUri(); if (requestUri.equals("/")) { diff --git a/src/de/ioexception/www/server/impl/CachingHttpWorker.java b/src/de/ioexception/www/server/impl/CachingHttpWorker.java index 023234f..79b527a 100644 --- a/src/de/ioexception/www/server/impl/CachingHttpWorker.java +++ b/src/de/ioexception/www/server/impl/CachingHttpWorker.java @@ -47,18 +47,14 @@ protected HttpResponse handleRequest(HttpRequest request) if (Integer.parseInt(request.getHeaders().get(Http.IF_NONE_MATCH)) == cacheEntry.hashCode()) { BasicHttpResponse response = new BasicHttpResponse(); - response.setHeaders(new HashMap()); response.getHeaders().put(Http.SERVER, server.getServerSignature()); - response.getHeaders().put("Content-Length", "0"); response.setVersion(request.getHttpVersion()); response.setStatusCode(HttpStatusCode.NOT_MODIFIED); - response.setEntity(null); return response; } } BasicHttpResponse response = new BasicHttpResponse(); - response.setHeaders(new HashMap()); response.getHeaders().put(Http.SERVER, server.getServerSignature()); response.setVersion(request.getHttpVersion()); response.getHeaders().put(Http.CONTENT_LENGTH, ""+cacheEntry.getEntity().length); From e2b1449a9129060c5fd9b6b1aa7dcc0f94e2d2f4 Mon Sep 17 00:00:00 2001 From: Scott Wakeling Date: Sun, 6 Mar 2011 19:27:34 +0000 Subject: [PATCH 9/9] reinstated http version in a basic response, use request's version no need to set zero content length, it's in the default ctor now --- src/de/ioexception/www/server/impl/BasicAuthHttpWorker.java | 1 - src/de/ioexception/www/server/impl/BasicHttpWorker.java | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/de/ioexception/www/server/impl/BasicAuthHttpWorker.java b/src/de/ioexception/www/server/impl/BasicAuthHttpWorker.java index da658e6..1b0bc82 100644 --- a/src/de/ioexception/www/server/impl/BasicAuthHttpWorker.java +++ b/src/de/ioexception/www/server/impl/BasicAuthHttpWorker.java @@ -55,7 +55,6 @@ protected HttpResponse handleRequest(HttpRequest request) BasicHttpResponse response = new BasicHttpResponse(); response.setStatusCode(HttpStatusCode.UNAUTHORIZED); response.getHeaders().put(Http.WWW_AUTHENTICATE, "Basic realm=\"" + realm + "\""); - response.getHeaders().put(Http.CONTENT_LENGTH, "0"); return response; } } \ No newline at end of file diff --git a/src/de/ioexception/www/server/impl/BasicHttpWorker.java b/src/de/ioexception/www/server/impl/BasicHttpWorker.java index dc56a52..9763401 100644 --- a/src/de/ioexception/www/server/impl/BasicHttpWorker.java +++ b/src/de/ioexception/www/server/impl/BasicHttpWorker.java @@ -81,6 +81,7 @@ protected HttpResponse handleRequest(HttpRequest request) BasicHttpResponse response = new BasicHttpResponse(); response.getHeaders().put(Http.SERVER, server.getServerSignature()); + response.setVersion(request.getHttpVersion()); String requestUri = request.getRequestUri(); if (requestUri.equals("/"))