Skip to content

Instantly share code, notes, and snippets.

@rschildmeijer
Created October 7, 2010 20:26
Show Gist options
  • Save rschildmeijer/615827 to your computer and use it in GitHub Desktop.
Save rschildmeijer/615827 to your computer and use it in GitHub Desktop.
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