Commit faa35acb authored by Björn Butzin's avatar Björn Butzin
Browse files

major rework

    - removed unnecessary lib .jars
    - changed package structure
    - moved enumerations in separate package
    - added MultiType resource to handle content types in a resource
    - moved as much fields as possible to private
    - replaced TimeoutHashMap.java by the new version
    - updated maven build configuration
parent 6dea83de
......@@ -16,14 +16,17 @@
* This work has been sponsored by Siemens Corporate Technology.
*
*/
package org.ws4d.coap.proxy;
package org.ws4d.coap.cache;
import java.net.URI;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.ws4d.coap.core.CoapConstants;
import org.ws4d.coap.core.messages.api.CoapResponse;
import org.ws4d.coap.proxy.ProxyMessageContext;
import org.ws4d.coap.proxy.ProxyResource;
import org.ws4d.coap.proxy.ProxyResourceKey;
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
......@@ -31,196 +34,51 @@ import net.sf.ehcache.Element;
import net.sf.ehcache.config.CacheConfiguration;
import net.sf.ehcache.store.MemoryStoreEvictionPolicy;
import org.apache.log4j.Logger;
import org.ws4d.coap.interfaces.CoapResponse;
/**
* @author Christian Lerche <christian.lerche@uni-rostock.de>
* @author Andy Seidel <andy.seidel@uni-rostock.de>
*/
/*
* TODO's:
* - implement Date option as described in "Connecting the Web with the Web of Things: Lessons Learned From Implementing a CoAP-HTTP Proxy"
* - caching of HTTP resources not supported as HTTP servers may have enough resources (in terms of RAM/ROM/batery/computation)
*
* */
* TODO's: - implement Date option as described in
* "Connecting the Web with the Web of Things: Lessons Learned From Implementing a CoAP-HTTP Proxy"
* - caching of HTTP resources not supported as HTTP servers may have enough
* resources (in terms of RAM/ROM/batery/computation)
*/
public class ProxyCache {
static Logger logger = Logger.getLogger(Proxy.class);
public class CoapCache {
private static final Logger logger = LogManager.getLogger();
private static final int MAX_LIFETIME = Integer.MAX_VALUE;
private static Cache cache;
private static CacheManager cacheManager;
private boolean enabled = true;
private static final int defaultMaxAge = org.ws4d.coap.Constants.COAP_DEFAULT_MAX_AGE_S;
private static final int defaultMaxAge = CoapConstants.COAP_DEFAULT_MAX_AGE_S;
private static final ProxyCacheTimePolicy cacheTimePolicy = ProxyCacheTimePolicy.Halftime;
public ProxyCache() {
private static Cache cache;
private static CacheManager cacheManager;
private boolean enabled = true;
public CoapCache() {
cacheManager = CacheManager.create();
cache = new Cache(new CacheConfiguration("proxy", 100)
.memoryStoreEvictionPolicy(MemoryStoreEvictionPolicy.LFU)
.overflowToDisk(true)
.eternal(false)
.diskPersistent(false)
.diskExpiryThreadIntervalSeconds(0));
cacheManager.addCache(cache);
.memoryStoreEvictionPolicy(MemoryStoreEvictionPolicy.LFU)
.overflowToDisk(true)
.eternal(false)
.diskPersistent(false)
.diskExpiryThreadIntervalSeconds(0));
cacheManager.addCache(cache);
}
public void removeKey(URI uri) {
cache.remove(uri);
}
// public void put(ProxyMessageContext context) {
// if (isEnabled() || context == null){
// return;
// }
// //TODO: check for overwrites
//
//
// insertElement(context.getResource().getKey(), context.getResource());
//// URI uri = context.getUri();
//// if (uri.getScheme().equalsIgnoreCase("coap")){
//// putCoapRes(uri, context.getCoapResponse());
//// } else if (uri.getScheme().equalsIgnoreCase("http")){
//// putHttpRes(uri, context.getHttpResponse());
//// }
// }
// private void putHttpRes(URI uri, HttpResponse response){
// if (response == null){
// return;
// }
// logger.info( "Cache HTTP Resource (" + uri.toString() + ")");
// //first determine what to do
// int code = response.getStatusLine().getStatusCode();
//
// //make some garbage collection to avoid a cache overflow caused by many expired elements (when 80% charged as first idea)
// if (cache.getSize() > cache.getCacheConfiguration().getMaxElementsInMemory()*0.8) {
// cache.evictExpiredElements();
// }
//
// //set the max-age of new element
// //use the http-header-options expires and date
// //difference is the same value as the corresponding max-age from coap-response, but at this point we only have a http-response
// int timeToLive = 0;
// Header[] expireHeaders = response.getHeaders("Expires");
// if (expireHeaders.length == 1) {
// String expire = expireHeaders[0].getValue();
// Date expireDate = StringToDate(expire);
//
// Header[] dateHeaders = response.getHeaders("Date");
// if (dateHeaders.length == 1) {
// String dvalue = dateHeaders[0].getValue();
// Date date = StringToDate(dvalue);
//
// timeToLive = (int) ((expireDate.getTime() - date.getTime()) / 1000);
// }
// }
//
// //cache-actions are dependent of response-code, as described in coap-rfc-draft-7
// switch(code) {
// case HttpStatus.SC_CREATED: {
// if (cache.isKeyInCache(uri)) {
// markExpired(uri);
// }
// break;
// }
// case HttpStatus.SC_NO_CONTENT: {
// if (cache.isKeyInCache(uri)) {
// markExpired(uri);
// }
// break;
// }
// case HttpStatus.SC_NOT_MODIFIED: {
// if (cache.isKeyInCache(uri)) {
// insertElement(uri, response, timeToLive); //should update the response if req is already in cache
// }
// break;
// }
// default: {
// insertElement(uri, response, timeToLive);
// break;
// }
// }
// }
// private void putCoapRes(ProxyResourceKey key, CoapResponse response){
// if (response == null){
// return;
// }
// logger.debug( "Cache CoAP Resource (" + uri.toString() + ")");
//
// long timeToLive = response.getMaxAge();
// if (timeToLive < 0){
// timeToLive = defaultTimeToLive;
// }
// insertElement(key, response);
// }
//
// public HttpResponse getHttpRes(URI uri) {
// if (defaultTimeToLive == 0) return null;
// if (cache.getQuiet(uri) != null) {
// Object o = cache.get(uri).getObjectValue();
// logger.debug( "Found in cache (" + uri.toString() + ")");
// return (HttpResponse) o;
// } else {
// logger.debug( "Not in cache (" + uri.toString() + ")");
// return null;
// }
// }
//
// public CoapResponse getCoapRes(URI uri) {
// if (defaultTimeToLive == 0) return null;
//
// if (cache.getQuiet(uri) != null) {
// Object o = cache.get(uri).getObjectValue();
// logger.debug( "Found in cache (" + uri.toString() + ")");
// return (CoapResponse) o;
// }else{
// logger.debug( "Not in cache (" + uri.toString() + ")");
// return null;
// }
// }
public boolean isInCache(ProxyResourceKey key) {
if (!isEnabled()){
return false;
}
if (cache.isKeyInCache(key)) {
if (isEnabled() && cache.isKeyInCache(key)) {
return true;
}
return false;
}
//for some operations it is necessary to build an http-date from string
private static Date StringToDate(String string_date) {
Date date = null;
//this pattern is the official http-date format
final String PATTERN_RFC1123 = "EEE, dd MMM yyyy HH:mm:ss zzz";
SimpleDateFormat formatter = new SimpleDateFormat(PATTERN_RFC1123, Locale.US);
formatter.setTimeZone(TimeZone.getDefault()); //CEST, default is GMT
try {
date = formatter.parse(string_date);
} catch (ParseException e) {
e.printStackTrace();
}
return date;
}
// //mark an element as expired
// private void markExpired(ProxyResourceKey key) {
// if (cache.getQuiet(key) != null) {
// cache.get(key).setTimeToLive(0);
// }
// }
private boolean insertElement(ProxyResourceKey key, ProxyResource resource) {
private static boolean insertElement(ProxyResourceKey key, ProxyResource resource) {
Element elem = new Element(key, resource);
if (resource.expires() == -1) {
/* never expires */
......@@ -234,7 +92,7 @@ public class ProxyCache {
}
elem.setTimeToLive((int) ttl);
cache.put(elem);
logger.debug("cache insert: " + resource.getPath() );
logger.debug("cache insert: " + resource.getPath());
} else {
/* resource is already expired */
......@@ -243,22 +101,6 @@ public class ProxyCache {
}
return true;
}
private void updateTtl(ProxyResourceKey key, long newExpires) {
/*getQuiet is used to not update statistics */
Element elem = cache.getQuiet(key);
if (elem != null) {
long ttl = newExpires - System.currentTimeMillis();
if (ttl > 0 || newExpires == -1 ) {
/* limit the maximum lifetime */
if (ttl > MAX_LIFETIME) {
ttl = MAX_LIFETIME;
}
elem.setTimeToLive((int) ttl);
}
}
}
public boolean isEnabled() {
return this.enabled;
......@@ -269,17 +111,18 @@ public class ProxyCache {
}
public ProxyResource get(ProxyMessageContext context) {
if (!isEnabled()){
if (!isEnabled()) {
return null;
}
String path = context.getUri().getPath();
if(path == null){
if (path == null) {
/* no caching */
return null;
}
Element elem = cache.get(new ProxyResourceKey(context.getServerAddress(), context.getServerPort(), path));
logger.debug("cache get: " + context.getServerAddress().toString() + " " + context.getServerPort() + " " + path);
logger.debug(
"cache get: " + context.getServerAddress().toString() + " " + context.getServerPort() + " " + path);
if (elem != null) {
/* found cached entry */
ProxyResource res = (ProxyResource) elem.getObjectValue();
......@@ -290,62 +133,67 @@ public class ProxyCache {
return null;
}
public void cacheHttpResponse(ProxyMessageContext context) {
if (!isEnabled()){
return;
}
/* TODO caching of HTTP responses currently not supported (not use to unload HTTP servers) */
return;
}
public void cacheCoapResponse(ProxyMessageContext context) {
if (!isEnabled()){
if (!isEnabled()) {
return;
}
CoapResponse response = context.getInCoapResponse();
String path = context.getUri().getPath();
if(path == null){
if (path == null) {
/* no caching */
return;
}
ProxyResourceKey key = new ProxyResourceKey(context.getServerAddress(), context.getServerPort(), path);
/* NOTE:
* - currently caching is only implemented for success error codes (2.xx)
* - not fresh resources are removed (could be used for validation model)*/
/*
* NOTE: - currently caching is only implemented for success error codes
* (2.xx) - not fresh resources are removed (could be used for
* validation model)
*/
switch (context.getInCoapResponse().getResponseCode()) {
case Created_201:
/* A cache SHOULD mark any stored response for the
created resource as not fresh. This response is not cacheable.*/
cache.remove(key);
/*
* A cache SHOULD mark any stored response for the created resource
* as not fresh. This response is not cacheable.
*/
cache.remove(key);
break;
case Deleted_202:
/* This response is not cacheable. However, a cache SHOULD mark any
stored response for the deleted resource as not fresh.*/
cache.remove(key);
/*
* This response is not cacheable. However, a cache SHOULD mark any
* stored response for the deleted resource as not fresh.
*/
cache.remove(key);
break;
case Valid_203:
/* When a cache receives a 2.03 (Valid) response, it needs to update the
stored response with the value of the Max-Age Option included in the
response (see Section 5.6.2). */
//TODO
/*
* When a cache receives a 2.03 (Valid) response, it needs to update
* the stored response with the value of the Max-Age Option included
* in the response (see Section 5.6.2).
*/
// TODO
break;
case Changed_204:
/* This response is not cacheable. However, a cache SHOULD mark any
stored response for the changed resource as not fresh. */
cache.remove(key);
/*
* This response is not cacheable. However, a cache SHOULD mark any
* stored response for the changed resource as not fresh.
*/
cache.remove(key);
break;
case Content_205:
/* This response is cacheable: Caches can use the Max-Age Option to
determine freshness (see Section 5.6.1) and (if present) the ETag
Option for validation (see Section 5.6.2).*/
/*
* This response is cacheable: Caches can use the Max-Age Option to
* determine freshness (see Section 5.6.1) and (if present) the ETag
* Option for validation (see Section 5.6.2).
*/
/* CACHE RESOURCE */
ProxyResource resource = new ProxyResource(path, response.getPayload(), response.getContentType());
resource.setExpires(cacheTimePolicy.calcExpires(context.getRequestTime(), context.getResponseTime(), response.getMaxAge()));
resource.setExpires(cacheTimePolicy.calcExpires(context.getRequestTime(), context.getResponseTime(),
response.getMaxAge()));
insertElement(key, resource);
break;
......@@ -353,31 +201,22 @@ public class ProxyCache {
break;
}
}
public enum ProxyCacheTimePolicy{
Request(0),
Response(1),
Halftime(2);
int state;
private ProxyCacheTimePolicy(int state){
this.state = state;
}
public long calcExpires(long requestTime, long responseTime, long maxAge){
if (maxAge == -1){
maxAge = defaultMaxAge;
}
public enum ProxyCacheTimePolicy {
Request, Response, Halftime;
public long calcExpires(long requestTime, long responseTime, long maxAge) {
long max = maxAge == -1 ? defaultMaxAge : maxAge;
switch (this) {
case Request:
return requestTime + (maxAge * 1000) ;
return requestTime + (max * 1000);
case Response:
return responseTime + (maxAge * 1000);
return responseTime + (max * 1000);
case Halftime:
return requestTime + ((responseTime - requestTime) / 2) + (maxAge * 1000);
return requestTime + ((responseTime - requestTime) / 2) + (max * 1000);
default:
return 0;
}
return 0;
}
}
}
package org.ws4d.coap.udp;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
/**
* @author Christian Lerche <christian.lerche@uni-rostock.de>
*/
public class UDPRelay {
public static final int SERVER_PORT = 6000;
public static final int CLIENT_PORT = 8000;
public static final int UDP_BUFFER_SIZE = 66000; // max UDP size = 65535
public static void main(String[] args) {
if (args.length < 2){
System.out.println("expected parameter: server host and port, e.g. 192.168.1.1 1234");
System.exit(-1);
}
UDPRelay relay = new UDPRelay();
relay.run(new InetSocketAddress(args[0], Integer.parseInt(args[1])));
}
private DatagramChannel serverChannel = null;
private DatagramChannel clientChannel = null;
ByteBuffer serverBuffer = ByteBuffer.allocate(UDP_BUFFER_SIZE);
ByteBuffer clientBuffer = ByteBuffer.allocate(UDP_BUFFER_SIZE);
Selector selector = null;
InetSocketAddress clientAddr = null;
public void run(InetSocketAddress serverAddr) {
try {
this.serverChannel = DatagramChannel.open();
this.serverChannel.socket().bind(new InetSocketAddress(SERVER_PORT));
this.serverChannel.configureBlocking(false);
this.serverChannel.connect(serverAddr);
this.clientChannel = DatagramChannel.open();
this.clientChannel.socket().bind(new InetSocketAddress(CLIENT_PORT));
this.clientChannel.configureBlocking(false);
try {
this.selector = Selector.open();
this.serverChannel.register(this.selector, SelectionKey.OP_READ);
this.clientChannel.register(this.selector, SelectionKey.OP_READ);
} catch (IOException e1) {
e1.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
System.out.println("Initialization failed, Shut down");
System.exit(-1);
}
System.out.println("Start UDP Realy on Server Port " + SERVER_PORT + " and Client Port " + CLIENT_PORT);
int serverLen = 0;
while (true) {
/* Receive Packets */
InetSocketAddress tempClientAddr = null;
try {
this.clientBuffer.clear();
tempClientAddr = (InetSocketAddress) this.clientChannel.receive(this.clientBuffer);
this.clientBuffer.flip();
this.serverBuffer.clear();
serverLen = this.serverChannel.read(this.serverBuffer);
this.serverBuffer.flip();
} catch (IOException e1) {
e1.printStackTrace();
System.out.println("Read failed");
}
/* forward/send packets client -> server*/
if (tempClientAddr != null) {
/* the client address is obtained automatically by the first request of the client
* clientAddr is the last known valid address of the client */
this.clientAddr = tempClientAddr;
try {
this.serverChannel.write(this.clientBuffer);
System.out.println("Forwarded Message client ("+this.clientAddr.getHostName()+" "+this.clientAddr.getPort()
+ ") -> server (" + serverAddr.getHostName()+" " + serverAddr.getPort() + "): "
+ this.clientBuffer.limit() + " bytes");
} catch (IOException e) {
e.printStackTrace();
System.out.println("Send failed");
}
}
/* forward/send packets server -> client*/
if (serverLen > 0) {
try {
this.clientChannel.send(this.serverBuffer, this.clientAddr);
System.out.println("Forwarded Message server ("+serverAddr.getHostName()+" "+serverAddr.getPort()
+ ") -> client (" + this.clientAddr.getHostName()+" " + this.clientAddr.getPort() + "): "
+ this.serverBuffer.limit() + " bytes");
} catch (IOException e) {
e.printStackTrace();
System.out.println("Send failed");
}
}
/* Select */
try {
this.selector.select(2000);
} catch (IOException e) {
e.printStackTrace();
System.out.println("select failed");
}
}
}
}
......@@ -6,7 +6,7 @@ apply plugin: 'java'
apply plugin: 'osgi'
apply plugin: 'maven'
sourceCompatibility = 1.7
sourceCompatibility = 1.6
repositories {
mavenLocal()
......@@ -14,7 +14,7 @@ repositories {
}
dependencies {
compile 'log4j:log4j:1.2.16'
group: 'org.apache.logging.log4j:log4j-core:2.6.1'
testCompile 'junit:junit:4.12'
}
......
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.ws4d.coap</groupId>
<artifactId>jcoap</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>jCoAP</name>
<build>
<sourceDirectory>src</sourceDirectory>
<testSourceDirectory>test</testSourceDirectory>
<plugins>
<plugin>