Created
October 7, 2010 20:26
-
-
Save rschildmeijer/615827 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git src/main/java/org/deftserver/example/AsyncDbApi.java src/main/java/org/deftserver/example/AsyncDbApi.java | |
index 15fe0ed..c1a3c6b 100644 | |
--- src/main/java/org/deftserver/example/AsyncDbApi.java | |
+++ src/main/java/org/deftserver/example/AsyncDbApi.java | |
@@ -14,13 +14,13 @@ | |
private final int DELAY = 100; | |
public void getNameFromId(final String id, final AsyncCallback<String> callback) { | |
- logger.debug("Getting name from database..."); | |
+ //logger.debug("Getting name from database..."); | |
Runnable runnable = new Runnable( ) { | |
@Override | |
public void run() { | |
- logger.debug("sleeping for " + DELAY +" seconds"); | |
+ //logger.debug("sleeping for " + DELAY +" seconds"); | |
try { | |
Thread.sleep(DELAY); | |
String result = "Jim" + id; | |
diff --git src/main/java/org/deftserver/example/AsyncDbHandler.java src/main/java/org/deftserver/example/AsyncDbHandler.java | |
index df83ecd..92d5e43 100644 | |
--- src/main/java/org/deftserver/example/AsyncDbHandler.java | |
+++ src/main/java/org/deftserver/example/AsyncDbHandler.java | |
@@ -14,9 +14,9 @@ | |
@Asynchronous | |
public void get(HttpRequest request, HttpResponse response) { | |
- logger.debug("Entering AsyncDbHandler.get"); | |
+ //logger.debug("Entering AsyncDbHandler.get"); | |
new AsyncDbApi().getNameFromId("123", new MyCallback(request, response)); | |
- logger.debug("Leaving AsyncDbHandler.get"); | |
+ //logger.debug("Leaving AsyncDbHandler.get"); | |
} | |
@@ -38,7 +38,7 @@ | |
@Override | |
public void onSuccess(String result) { | |
response.write("Name: " + result); | |
- logger.debug("MyCallback.onSuccess, retrieved name: " + result); | |
+ //logger.debug("MyCallback.onSuccess, retrieved name: " + result); | |
response.finish(); | |
} | |
diff --git src/main/java/org/deftserver/example/DeftServerExample.java src/main/java/org/deftserver/example/DeftServerExample.java | |
index 800a30d..b4d694e 100644 | |
--- src/main/java/org/deftserver/example/DeftServerExample.java | |
+++ src/main/java/org/deftserver/example/DeftServerExample.java | |
@@ -23,11 +23,11 @@ | |
@Override | |
public void get(HttpRequest request, HttpResponse response) { | |
- response.write("hello "); | |
- response.flush(); | |
- response.write("world"); | |
- response.flush(); | |
- response.write("!"); | |
+ response.write("hello world"); | |
+ //response.flush(); | |
+ //response.write("world"); | |
+ //response.flush(); | |
+ //response.write("!"); | |
} | |
} | |
diff --git src/main/java/org/deftserver/web/IOLoop.java src/main/java/org/deftserver/web/IOLoop.java | |
index 1074913..373aa6e 100644 | |
--- src/main/java/org/deftserver/web/IOLoop.java | |
+++ src/main/java/org/deftserver/web/IOLoop.java | |
@@ -17,7 +17,7 @@ | |
private final static Logger logger = LoggerFactory.getLogger(IOLoop.class); | |
- private static final long TIMEOUT = 3000; //in ms | |
+ private static final long TIMEOUT = 250; //in ms | |
private final Application application; | |
private ServerSocketChannel channel; | |
@@ -43,6 +43,7 @@ | |
while (true) { | |
try { | |
if (selector.select(TIMEOUT) == 0) { | |
+ protocol.handleCallback(); | |
continue; | |
} | |
diff --git src/main/java/org/deftserver/web/protocol/HttpProtocol.java src/main/java/org/deftserver/web/protocol/HttpProtocol.java | |
index 0e3d07d..f4fd80a 100644 | |
--- src/main/java/org/deftserver/web/protocol/HttpProtocol.java | |
+++ src/main/java/org/deftserver/web/protocol/HttpProtocol.java | |
@@ -7,5 +7,6 @@ | |
void handleAccept(SelectionKey key) throws IOException; | |
void handleRead(SelectionKey key) throws IOException; | |
+ void handleCallback(); | |
} | |
diff --git src/main/java/org/deftserver/web/protocol/HttpProtocolImpl.java src/main/java/org/deftserver/web/protocol/HttpProtocolImpl.java | |
index 97ffb87..e4925c0 100644 | |
--- src/main/java/org/deftserver/web/protocol/HttpProtocolImpl.java | |
+++ src/main/java/org/deftserver/web/protocol/HttpProtocolImpl.java | |
@@ -5,15 +5,29 @@ | |
import java.nio.channels.SelectionKey; | |
import java.nio.channels.ServerSocketChannel; | |
import java.nio.channels.SocketChannel; | |
+import java.util.HashMap; | |
+import java.util.Iterator; | |
+import java.util.Map.Entry; | |
import org.deftserver.web.Application; | |
import org.deftserver.web.handler.RequestHandler; | |
import org.slf4j.Logger; | |
import org.slf4j.LoggerFactory; | |
+ | |
+import com.google.common.io.Closeables; | |
public class HttpProtocolImpl implements HttpProtocol { | |
private final static Logger logger = LoggerFactory.getLogger(HttpProtocolImpl.class); | |
+ | |
+ /** The number of seconds Deft will wait for a subsequent request before closing the connection */ | |
+ private final static long keepAliveTimeout = 30 * 1000; // 30s | |
+ | |
+ /** All {@link SocketChannel} connections where request header "Connection: Close" is missing. | |
+ * ("In HTTP 1.1 all connections are considered persistent, unless declared otherwise") | |
+ * The value associated with each {@link SocketChannel} is the connection expiration time in ms. | |
+ */ | |
+ private final HashMap<SocketChannel, Long> aliveConnections = new HashMap<SocketChannel, Long>(); | |
private final int readBufferSize; | |
@@ -26,6 +40,7 @@ | |
@Override | |
public void handleAccept(SelectionKey key) throws IOException { | |
+ //logger.debug("accept"); | |
SocketChannel clientChannel = ((ServerSocketChannel) key.channel()).accept(); | |
clientChannel.configureBlocking(false); | |
clientChannel.register(key.selector(), SelectionKey.OP_READ, ByteBuffer.allocate(readBufferSize)); | |
@@ -33,10 +48,19 @@ | |
@Override | |
public void handleRead(SelectionKey key) throws IOException { | |
+ //logger.debug("read"); | |
SocketChannel clientChannel = (SocketChannel) key.channel(); | |
HttpRequest request = getHttpRequest(key, clientChannel); | |
- HttpResponse response = new HttpResponse(clientChannel); | |
- | |
+ String connection = request.getHeader("Connection"); | |
+ boolean keepAlive = true; | |
+ if (connection != null && connection.toLowerCase().equals("close")) { | |
+ keepAlive = false; | |
+ } else { | |
+ logger.debug("adding alive connection for path: " + request.getRequestedPath()); | |
+ aliveConnections.put(clientChannel, System.currentTimeMillis() + keepAliveTimeout); | |
+ } | |
+ HttpResponse response = new HttpResponse(clientChannel, keepAlive); | |
+ | |
RequestHandler rh = application.getHandler(request); | |
HttpRequestDispatcher.dispatch(rh, request, response); | |
@@ -51,11 +75,26 @@ | |
try { | |
clientChannel.read(buffer); | |
} catch (IOException e) { | |
- logger.error("Could not read buffer: {}", e); | |
+ logger.error("Could not read buffer: {}", e.getMessage()); | |
+ Closeables.closeQuietly(clientChannel); | |
} | |
+ buffer.clear(); // reuse the read buffer, (hint: "Connection: Keep-Alive" header) | |
return HttpRequest.of(buffer); | |
} | |
- | |
+ public void handleCallback() { | |
+ //logger.debug("handle callback"); | |
+ long now = System.currentTimeMillis(); | |
+ Iterator<Entry<SocketChannel, Long>> iter = aliveConnections.entrySet().iterator(); | |
+ while (iter.hasNext()) { | |
+ Entry<SocketChannel, Long> candidate = iter.next(); | |
+ long expires = candidate.getValue(); | |
+ if (now >= expires) { | |
+ logger.debug("Closing expired keep-alive connection"); | |
+ Closeables.closeQuietly(candidate.getKey());; | |
+ iter.remove(); | |
+ } | |
+ } | |
+ } | |
} | |
diff --git src/main/java/org/deftserver/web/protocol/HttpResponse.java src/main/java/org/deftserver/web/protocol/HttpResponse.java | |
index 60c2bd1..2fcfb84 100644 | |
--- src/main/java/org/deftserver/web/protocol/HttpResponse.java | |
+++ src/main/java/org/deftserver/web/protocol/HttpResponse.java | |
@@ -12,6 +12,8 @@ | |
import org.slf4j.Logger; | |
import org.slf4j.LoggerFactory; | |
+import com.google.common.io.Closeables; | |
+ | |
public class HttpResponse { | |
private final static Logger logger = LoggerFactory.getLogger(HttpProtocolImpl.class); | |
@@ -29,15 +31,20 @@ | |
private final Map<String, String> headers = new HashMap<String, String>(); | |
private boolean headersCreated = false; | |
private String responseData = ""; | |
+ private final boolean keepAlive; | |
- public HttpResponse(SocketChannel sc) { | |
+ public HttpResponse(SocketChannel sc, boolean keepAlive) { | |
clientChannel = sc; | |
headers.put("Server", "DeftServer/0.0.1"); | |
headers.put("Date", DateUtil.getCurrentAsString()); | |
- /* We should consider setting these when response includes a body. | |
- * The 'Content-Type' header gives the MIME-type of the data in the body, such as text/html or image/gif. | |
- * The 'Content-Length' header gives the number of bytes in the body. | |
- */ | |
+ | |
+ if (keepAlive) { | |
+ this.keepAlive = true; | |
+ headers.put("Connection", "Keep-Alive"); | |
+ } else { | |
+ this.keepAlive = false; | |
+ //headers.put("Connection", "Close"); // TODO RS should we add connection: close header? | |
+ } | |
} | |
public void setStatusCode(int sc) { | |
@@ -80,10 +87,8 @@ | |
} | |
bytesWritten = flush(); | |
} | |
- try { | |
- clientChannel.close(); | |
- } catch (IOException ioe) { | |
- logger.error("Could not close client (SocketChannel) connection. {}", ioe); | |
+ if (!keepAlive) { | |
+ Closeables.closeQuietly(clientChannel); | |
} | |
return bytesWritten; | |
} | |
diff --git src/test/java/org/deftserver/web/DeftSystemTest.java src/test/java/org/deftserver/web/DeftSystemTest.java | |
index 638498c..36ea26a 100644 | |
--- src/test/java/org/deftserver/web/DeftSystemTest.java | |
+++ src/test/java/org/deftserver/web/DeftSystemTest.java | |
@@ -10,6 +10,7 @@ | |
import java.io.InputStreamReader; | |
import java.util.Arrays; | |
import java.util.HashMap; | |
+import java.util.LinkedList; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.concurrent.CountDownLatch; | |
@@ -17,6 +18,7 @@ | |
import java.util.concurrent.Executors; | |
import java.util.concurrent.TimeUnit; | |
+import org.apache.http.Header; | |
import org.apache.http.HttpResponse; | |
import org.apache.http.ProtocolVersion; | |
import org.apache.http.client.ClientProtocolException; | |
@@ -26,6 +28,7 @@ | |
import org.apache.http.client.methods.HttpPost; | |
import org.apache.http.client.methods.HttpPut; | |
import org.apache.http.impl.client.DefaultHttpClient; | |
+import org.apache.http.message.BasicHeader; | |
import org.apache.http.params.BasicHttpParams; | |
import org.apache.http.params.HttpConnectionParams; | |
import org.apache.http.params.HttpParams; | |
@@ -149,8 +152,11 @@ | |
} | |
private void doSimpleGetRequest() throws ClientProtocolException, IOException { | |
+ List<Header> headers = new LinkedList<Header>(); | |
+ headers.add(new BasicHeader("Connection", "Close")); | |
HttpParams params = new BasicHttpParams(); | |
- params.setParameter(" Connection", "Close"); | |
+ params.setParameter("http.default-headers", headers); | |
+ | |
HttpClient httpclient = new DefaultHttpClient(params); | |
HttpGet httpget = new HttpGet("http://localhost:" + PORT + "/"); | |
HttpResponse response = httpclient.execute(httpget); | |
@@ -177,8 +183,11 @@ | |
*/ | |
@Test | |
public void wTest() throws ClientProtocolException, IOException { | |
+ List<Header> headers = new LinkedList<Header>(); | |
+ headers.add(new BasicHeader("Connection", "Close")); | |
HttpParams params = new BasicHttpParams(); | |
- params.setParameter(" Connection", "Close"); | |
+ params.setParameter("http.default-headers", headers); | |
+ | |
HttpClient httpclient = new DefaultHttpClient(params); | |
HttpGet httpget = new HttpGet("http://localhost:" + PORT + "/w"); | |
HttpResponse response = httpclient.execute(httpget); | |
@@ -196,8 +205,11 @@ | |
@Test | |
public void wwTest() throws ClientProtocolException, IOException { | |
+ List<Header> headers = new LinkedList<Header>(); | |
+ headers.add(new BasicHeader("Connection", "Close")); | |
HttpParams params = new BasicHttpParams(); | |
- params.setParameter(" Connection", "Close"); | |
+ params.setParameter("http.default-headers", headers); | |
+ | |
HttpClient httpclient = new DefaultHttpClient(params); | |
HttpGet httpget = new HttpGet("http://localhost:" + PORT + "/ww"); | |
HttpResponse response = httpclient.execute(httpget); | |
@@ -214,8 +226,11 @@ | |
@Test | |
public void wwfwTest() throws ClientProtocolException, IOException { | |
+ List<Header> headers = new LinkedList<Header>(); | |
+ headers.add(new BasicHeader("Connection", "Close")); | |
HttpParams params = new BasicHttpParams(); | |
- params.setParameter(" Connection", "Close"); | |
+ params.setParameter("http.default-headers", headers); | |
+ | |
HttpClient httpclient = new DefaultHttpClient(params); | |
HttpGet httpget = new HttpGet("http://localhost:" + PORT + "/wwfw"); | |
HttpResponse response = httpclient.execute(httpget); | |
@@ -231,8 +246,11 @@ | |
@Test | |
public void wfwfTest() throws ClientProtocolException, IOException { | |
+ List<Header> headers = new LinkedList<Header>(); | |
+ headers.add(new BasicHeader("Connection", "Close")); | |
HttpParams params = new BasicHttpParams(); | |
- params.setParameter(" Connection", "Close"); | |
+ params.setParameter("http.default-headers", headers); | |
+ | |
HttpClient httpclient = new DefaultHttpClient(params); | |
HttpGet httpget = new HttpGet("http://localhost:" + PORT + "/wfwf"); | |
HttpResponse response = httpclient.execute(httpget); | |
@@ -248,8 +266,11 @@ | |
@Test | |
public void deleteTest() throws ClientProtocolException, IOException { | |
+ List<Header> headers = new LinkedList<Header>(); | |
+ headers.add(new BasicHeader("Connection", "Close")); | |
HttpParams params = new BasicHttpParams(); | |
- params.setParameter(" Connection", "Close"); | |
+ params.setParameter("http.default-headers", headers); | |
+ | |
HttpClient httpclient = new DefaultHttpClient(params); | |
HttpDelete httpdelete = new HttpDelete("http://localhost:" + PORT + "/delete"); | |
HttpResponse response = httpclient.execute(httpdelete); | |
@@ -264,8 +285,11 @@ | |
@Test | |
public void PostTest() throws ClientProtocolException, IOException { | |
+ List<Header> headers = new LinkedList<Header>(); | |
+ headers.add(new BasicHeader("Connection", "Close")); | |
HttpParams params = new BasicHttpParams(); | |
- params.setParameter(" Connection", "Close"); | |
+ params.setParameter("http.default-headers", headers); | |
+ | |
HttpClient httpclient = new DefaultHttpClient(params); | |
HttpPost httppost = new HttpPost("http://localhost:" + PORT + "/post"); | |
HttpResponse response = httpclient.execute(httppost); | |
@@ -280,8 +304,11 @@ | |
@Test | |
public void putTest() throws ClientProtocolException, IOException { | |
+ List<Header> headers = new LinkedList<Header>(); | |
+ headers.add(new BasicHeader("Connection", "Close")); | |
HttpParams params = new BasicHttpParams(); | |
- params.setParameter(" Connection", "Close"); | |
+ params.setParameter("http.default-headers", headers); | |
+ | |
HttpClient httpclient = new DefaultHttpClient(params); | |
HttpPut httpput = new HttpPut("http://localhost:" + PORT + "/put"); | |
HttpResponse response = httpclient.execute(httpput); | |
@@ -296,8 +323,11 @@ | |
@Test | |
public void capturingTest() throws ClientProtocolException, IOException { | |
+ List<Header> headers = new LinkedList<Header>(); | |
+ headers.add(new BasicHeader("Connection", "Close")); | |
HttpParams params = new BasicHttpParams(); | |
- params.setParameter(" Connection", "Close"); | |
+ params.setParameter("http.default-headers", headers); | |
+ | |
HttpClient httpclient = new DefaultHttpClient(params); | |
HttpGet httpget = new HttpGet("http://localhost:" + PORT + "/capturing/1911"); | |
HttpResponse response = httpclient.execute(httpget); | |
@@ -312,8 +342,11 @@ | |
@Test | |
public void erroneousCapturingTest() throws ClientProtocolException, IOException { | |
+ List<Header> headers = new LinkedList<Header>(); | |
+ headers.add(new BasicHeader("Connection", "Close")); | |
HttpParams params = new BasicHttpParams(); | |
- params.setParameter(" Connection", "Close"); | |
+ params.setParameter("http.default-headers", headers); | |
+ | |
HttpClient httpclient = new DefaultHttpClient(params); | |
HttpGet httpget = new HttpGet("http://localhost:" + PORT + "/capturing/r1911"); | |
HttpResponse response = httpclient.execute(httpget); | |
@@ -360,17 +393,20 @@ | |
@Test | |
public void asynchronousRequestTest() throws ClientProtocolException, IllegalStateException, IOException { | |
- for (int i = 1; i < 50; i++) { | |
+ for (int i = 1; i <= 40; i++) { | |
doAsynchronousRequestTest(); | |
} | |
} | |
private void doAsynchronousRequestTest() throws ClientProtocolException, IOException, IllegalStateException { | |
- HttpClient httpclient = new DefaultHttpClient(); | |
- HttpParams params = httpclient.getParams(); | |
- params.setParameter(" Connection", "Close"); | |
- HttpConnectionParams.setConnectionTimeout(params, 4 * 1000); | |
- HttpConnectionParams.setSoTimeout(params, 10 * 1000); | |
+ List<Header> headers = new LinkedList<Header>(); | |
+ headers.add(new BasicHeader("Connection", "Close")); | |
+ HttpParams params = new BasicHttpParams(); | |
+ params.setParameter("http.default-headers", headers); | |
+ | |
+ DefaultHttpClient httpclient = new DefaultHttpClient(params); | |
+ HttpConnectionParams.setConnectionTimeout(params, 40 * 1000); | |
+ HttpConnectionParams.setSoTimeout(params, 100 * 1000); | |
HttpGet httpget = new HttpGet("http://localhost:" + PORT + "/mySql"); | |
HttpResponse response = httpclient.execute(httpget); | |
@@ -381,6 +417,34 @@ | |
assertEquals("OK", response.getStatusLine().getReasonPhrase()); | |
String payLoad = convertStreamToString(response.getEntity().getContent()).trim(); | |
assertEquals("Name: Jim123", payLoad); | |
+ } | |
+ | |
+ @Test | |
+ public void keepAliveRequestTest() throws ClientProtocolException, IOException { | |
+ List<Header> headers = new LinkedList<Header>(); | |
+ headers.add(new BasicHeader("Connection", "Keep-Alive")); | |
+ HttpParams params = new BasicHttpParams(); | |
+ params.setParameter("http.default-headers", headers); | |
+ | |
+ DefaultHttpClient httpclient = new DefaultHttpClient(params); | |
+ | |
+ for (int i = 1; i <= 25; i++) { | |
+ doKeepAliveRequestTest(httpclient); | |
+ } | |
+ } | |
+ | |
+ private void doKeepAliveRequestTest(DefaultHttpClient httpclient) | |
+ throws IOException, ClientProtocolException { | |
+ HttpGet httpget = new HttpGet("http://localhost:" + PORT + "/"); | |
+ HttpResponse response = httpclient.execute(httpget); | |
+ | |
+ assertNotNull(response); | |
+ assertEquals(200, response.getStatusLine().getStatusCode()); | |
+ assertEquals(new ProtocolVersion("HTTP", 1, 1), response.getStatusLine().getProtocolVersion()); | |
+ assertEquals("OK", response.getStatusLine().getReasonPhrase()); | |
+ assertEquals(5, response.getAllHeaders().length); | |
+ String payLoad = convertStreamToString(response.getEntity().getContent()).trim(); | |
+ assertEquals(expectedPayload, payLoad); | |
} | |
public String convertStreamToString(InputStream is) throws IOException { |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment