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/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/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/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 new file mode 100644 index 0000000..b045fae --- /dev/null +++ b/src/de/ioexception/www/server/impl/CachingHttpWorker.java @@ -0,0 +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, Cache cache) + { + super(socket, server); + this.cache = cache; + } + + @Override + protected HttpResponse handleRequest(HttpRequest 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; + } + } + +}