Browse files

v. 0.7.3 Added Endpoint.fillMessage() to read in length prefixed mess…

…ages.

Fixed bugs in scheduler, http and nio.
Specifically,
1. scheduler shutdown was not waking up worker threads because the latter used a different monitor
2. nio scheduler shutdown was not releasing listen sockets
3. off-by-n errors in buffer resizing and in decoding chunked http requets

	modified:   docs/manual.html
	new file:   examples/kilim/examples/SimpleHttpServer.java
	modified:   src/kilim/Constants.java
	modified:   src/kilim/Scheduler.java
	modified:   src/kilim/WorkerThread.java
	modified:   src/kilim/http/HttpRequest.java
	modified:   src/kilim/http/HttpRequestParser.java
	modified:   src/kilim/http/HttpRequestParser.rl
	modified:   src/kilim/nio/EndPoint.java
	modified:   src/kilim/nio/ExposedBais.java
	modified:   src/kilim/nio/NioSelectorScheduler.java
	deleted:    test/kilim/test/All.java
	modified:   test/kilim/test/AllWoven.java
	new file:   test/kilim/test/TestHTTP.java
	new file:   test/kilim/test/TestIO.java
	modified:   test/kilim/test/TestYield.java
  • Loading branch information...
1 parent 4ed56c8 commit 886fd165f15c292dca9d0d26ee4cb33b35d538fa @kilim kilim committed Jun 21, 2010
View
4 docs/manual.html
@@ -216,7 +216,7 @@
<h1>Release notes</h1>
-v.0.7 (April 2010): Added support for reflection, NIO and HTTP, and fixed a data race in the scheduler.
+v.0.7 (April 2010): Added support for reflection, NIO and HTTP, and fixed data races in the scheduler.
<h1>Acknowledgments</h1>
Adrian Quark
@@ -226,7 +226,7 @@
Kresten Krab Thorup
Jan Schäfer
Andrey Tarantsov
-
+Jason Pell
</body> </html>
View
85 examples/kilim/examples/SimpleHttpServer.java
@@ -0,0 +1,85 @@
+/* Copyright (c) 2006, Sriram Srinivasan
+ *
+ * You may distribute this software under the terms of the license
+ * specified in the file "License"
+ */
+
+package kilim.examples;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import kilim.Pausable;
+import kilim.http.HttpRequest;
+import kilim.http.HttpResponse;
+import kilim.http.HttpServer;
+import kilim.http.HttpSession;
+import kilim.http.KeyValues;
+
+/**
+ * A basic HTTP server that merely echoes the path and the query string supplied to it in a GET request
+ *
+ * Usage: Run java kilim.examples.HttpFileServer [base directory name]
+ *
+ * From a browser, try "http://localhost:7262/hello", "http://localhost:7262/buy?code=200&desc=Rolls%20Royce">"
+ *
+ * SimpleHttpSession is an HTTPSession task, itself a thin wrapper over the socket connection. An instance of this
+ * task is launched for each new connection, and its execute method is called when the task is scheduled.
+ *
+ * <p>
+ * The HttpRequest and HttpResponse objects are wrappers over a bytebuffer,
+ * and unrelated to the socket. The request object is "filled in" by HttpSession.readRequest() and the response object
+ * is sent by HttpSession.sendResponse().
+ */
+
+public class SimpleHttpServer {
+
+ public static void main(String[] args) throws IOException {
+ new HttpServer(7262, SimpleHttpSession.class);
+ System.out.println("SimpleHttpServer listening on http://localhost:7262");
+ System.out.println("From a browser, try http://localhost:7262/hello\n or http://localhost:7262/buy?code=200&desc=Rolls%20Royce");
+ }
+
+ public static class SimpleHttpSession extends HttpSession {
+
+ @Override
+ public void execute() throws Pausable, Exception {
+ try {
+ // We will reuse the req and resp objects
+ HttpRequest req = new HttpRequest();
+ HttpResponse resp = new HttpResponse();
+ while (true) {
+ super.readRequest(req);
+ System.out.println("Received: " + req);
+ if (req.method.equals("GET") || req.method.equals("HEAD")) {
+ resp.setContentType("text/html");
+ PrintWriter pw = new PrintWriter(resp.getOutputStream());
+ // Note that resp.getOutputStream() does not write to the socket; it merely buffers the entire
+ // output.
+ pw.append("<html><body>path = ");
+ pw.append(req.uriPath).append("<p>");
+ KeyValues kvs = req.getQueryComponents();
+ for (int i = 0; i < kvs.count; i++) {
+ pw.append(kvs.keys[i]).append(" = ").append(kvs.values[i]).append("<br>");
+ }
+ pw.append("</body></html>");
+ pw.flush();
+ sendResponse(resp);
+ } else {
+ super.problem(resp, HttpResponse.ST_FORBIDDEN, "Only GET and HEAD accepted");
+ }
+
+ if (!req.keepAlive())
+ break;
+ break;
+ }
+ } catch (EOFException e) {
+ System.out.println("[" + this.id + "] Connection Terminated");
+ } catch (IOException ioe) {
+ System.out.println("[" + this.id + "] IO Exception:" + ioe.getMessage());
+ }
+ super.close();
+ }
+ }
+}
View
2 src/kilim/Constants.java
@@ -9,7 +9,7 @@
public interface Constants extends Opcodes {
- String KILIM_VERSION = "0.7.2";
+ String KILIM_VERSION = "0.7.3";
// Type descriptors
String D_BOOLEAN = "Z";
View
14 src/kilim/Scheduler.java
@@ -18,8 +18,9 @@
*
*/
public class Scheduler {
- public static Scheduler defaultScheduler = null;
+ public static volatile Scheduler defaultScheduler = null;
public static int defaultNumberThreads;
+
public LinkedList<WorkerThread> allThreads = new LinkedList<WorkerThread>();
public RingQueue<WorkerThread> waitingThreads = new RingQueue<WorkerThread>(10);
protected volatile boolean shutdown = false;
@@ -79,9 +80,14 @@ public void schedule(Task t) {
}
public void shutdown() {
- synchronized(this) {
- shutdown = true;
- notifyAll();
+ shutdown = true;
+ if (defaultScheduler == this) {
+ defaultScheduler = null;
+ }
+ for (WorkerThread wt: allThreads) {
+ synchronized(wt) {
+ wt.notify();
+ }
}
}
View
153 src/kilim/WorkerThread.java
@@ -9,93 +9,94 @@
import java.util.concurrent.atomic.AtomicInteger;
public class WorkerThread extends Thread {
- volatile Task runningTask;
- /**
- * A list of tasks that prefer to run only on this thread. This is used by kilim.ReentrantLock and Task to ensure that
- * lock.release() is done on the same thread as lock.acquire()
- */
- RingQueue<Task> tasks = new RingQueue<Task>(10);
- Scheduler scheduler;
- static AtomicInteger gid = new AtomicInteger();
- public int numResumes = 0;
+ volatile Task runningTask;
+ /**
+ * A list of tasks that prefer to run only on this thread. This is used by kilim.ReentrantLock and Task to ensure
+ * that lock.release() is done on the same thread as lock.acquire()
+ */
+ RingQueue<Task> tasks = new RingQueue<Task>(10);
+ Scheduler scheduler;
+ static AtomicInteger gid = new AtomicInteger();
+ public int numResumes = 0;
- WorkerThread(Scheduler ascheduler) {
- super("KilimWorker-" + gid.incrementAndGet());
- scheduler = ascheduler;
- }
+ WorkerThread(Scheduler ascheduler) {
+ super("KilimWorker-" + gid.incrementAndGet());
+ scheduler = ascheduler;
+ }
- public void run() {
- try {
- while (true) {
- Task t = getNextTask(this); // blocks until task available
- runningTask = t;
- t._runExecute(this);
+ public void run() {
+ try {
+ while (true) {
+ Task t = getNextTask(this); // blocks until task available
+ runningTask = t;
+ t._runExecute(this);
+ runningTask = null;
+ }
+ } catch (ShutdownException se) {
+ // nothing to do.
+ } catch (OutOfMemoryError ex) {
+ System.err.println("Out of memory");
+ System.exit(1);
+ } catch (Throwable ex) {
+ ex.printStackTrace();
+ System.err.println(runningTask);
+ }
runningTask = null;
- }
- } catch (ShutdownException se) {
- // nothing to do.
- } catch (OutOfMemoryError ex) {
- System.err.println("Out of memory");
- System.exit(1);
- } catch (Throwable ex) {
- ex.printStackTrace();
- System.err.println(runningTask);
}
- runningTask = null;
- }
- protected Task getNextTask(WorkerThread workerThread) throws ShutdownException {
- Task t = null;
- while (true) {
- if (scheduler.isShutdown())
- throw new ShutdownException();
+ protected Task getNextTask(WorkerThread workerThread) throws ShutdownException {
+ Task t = null;
+ while (true) {
+ if (scheduler.isShutdown())
+ throw new ShutdownException();
- t = getNextTask();
- if (t != null)
- break;
-
- // try loading from scheduler
- scheduler.loadNextTask(this);
- synchronized(this) { /////////////////////////////////////////
- // Wait if still no task to execute.
- t = tasks.get();
- if (t != null)
- break;
-
- scheduler.addWaitingThread(this);
- try {
- wait();
- } catch (InterruptedException ignore) {} // shutdown indicator checked above
- } ////////////////////////////////////////////////////////////
+ t = getNextTask();
+ if (t != null)
+ break;
+
+ // try loading from scheduler
+ scheduler.loadNextTask(this);
+ synchronized (this) { // ///////////////////////////////////////
+ // Wait if still no task to execute.
+ t = tasks.get();
+ if (t != null)
+ break;
+
+ scheduler.addWaitingThread(this);
+ try {
+ wait();
+ } catch (InterruptedException ignore) {
+ } // shutdown indicator checked above
+ } // //////////////////////////////////////////////////////////
+ }
+ assert t != null : "Returning null task";
+ return t;
}
- assert t != null: "Returning null task";
- return t;
- }
- public Task getCurrentTask() {
- return runningTask;
- }
+ public Task getCurrentTask() {
+ return runningTask;
+ }
- public synchronized void addRunnableTask(Task t) {
- assert t.preferredResumeThread == null || t.preferredResumeThread == this : "Task given to wrong thread";
- tasks.put(t);
- notify();
- }
+ public synchronized void addRunnableTask(Task t) {
+ assert t.preferredResumeThread == null || t.preferredResumeThread == this : "Task given to wrong thread";
+ tasks.put(t);
+ notify();
+ }
- public synchronized boolean hasTasks() {
- return tasks.size() > 0;
- }
+ public synchronized boolean hasTasks() {
+ return tasks.size() > 0;
+ }
- public synchronized Task getNextTask() {
- return tasks.get();
- }
+ public synchronized Task getNextTask() {
+ return tasks.get();
+ }
- public synchronized void waitForMsgOrSignal() {
- try {
- if (tasks.size() == 0) {
- wait();
- }
- } catch (InterruptedException ignore) {
+ public synchronized void waitForMsgOrSignal() {
+ try {
+ if (tasks.size() == 0) {
+ wait();
+ }
+ } catch (InterruptedException ignore) {
+ }
}
- }
}
View
34 src/kilim/http/HttpRequest.java
@@ -262,17 +262,25 @@ public void readBody(EndPoint endpoint) throws Pausable, IOException {
public void readTrailers(EndPoint endpoint) {
}
+ /*
+ * Read all chunks until a chunksize of 0 is received, then consolidate the chunks into a single contiguous chunk.
+ * At the end of this method, the entire content is available in the requests buffer, starting at contentOffset and
+ * of length contentLength.
+ */
public void readAllChunks(EndPoint endpoint) throws IOException, Pausable {
- IntList chunkRanges = new IntList();
+ IntList chunkRanges = new IntList(); // alternate numbers in this list refer to the start and end offsets of chunks.
do {
- int n = readLine(endpoint);
+ int n = readLine(endpoint); // read chunk size text into buffer
int beg = iread;
- int size = parseHex(buffer, iread - n, iread);
+ int size = parseChunkSize(buffer, iread - n, iread); // Parse size in hex, ignore extension
if (size == 0)
break;
- fill(endpoint, iread, size);
- chunkRanges.add(beg);
- chunkRanges.add(beg + size);
+ // If the chunk has not already been read in, do so
+ fill(endpoint, iread, size+2 /*chunksize + CRLF*/);
+ // record chunk start and end
+ chunkRanges.add(beg);
+ chunkRanges.add(beg + size); // without the CRLF
+ iread += size + 2; // for the next round.
} while (true);
// / consolidate all chunkRanges
@@ -292,15 +300,19 @@ public void readAllChunks(EndPoint endpoint) throws IOException, Pausable {
}
// TODO move all trailer stuff up
contentLength = endOfLastChunk - contentOffset;
+
+ // At this point, the contentOffset and contentLen give the entire content
}
+
public static byte CR = (byte) '\r';
public static byte LF = (byte) '\n';
static final byte b0 = (byte) '0', b9 = (byte) '9';
static final byte ba = (byte) 'a', bf = (byte) 'f';
static final byte bA = (byte) 'A', bF = (byte) 'F';
+ static final byte SEMI = (byte)';';
- public static int parseHex(ByteBuffer buffer, int start, int end) throws IOException {
+ public static int parseChunkSize(ByteBuffer buffer, int start, int end) throws IOException {
byte[] bufa = buffer.array();
int size = 0;
for (int i = start; i < end; i++) {
@@ -311,8 +323,11 @@ public static int parseHex(ByteBuffer buffer, int start, int end) throws IOExcep
size = size * 16 + ((b - ba) + 10);
} else if (b >= bA && b <= bF) {
size = size * 16 + ((b - bA) + 10);
+ } else if (b == CR || b == SEMI) {
+ // SEMI-colon starts a chunk extension. We ignore extensions currently.
+ break;
} else {
- throw new IOException("Expected hex digit at " + i);
+ throw new IOException("Error parsing chunk size; unexpected char " + b + " at offset " + i);
}
}
return size;
@@ -336,14 +351,15 @@ public int readLine(EndPoint endpoint) throws IOException, Pausable {
for (; i < end; i++) {
if (bufa[i] == CR) {
++i;
- if (i == end + 1) {
+ if (i >= end) {
buffer = endpoint.fill(buffer, 1);
bufa = buffer.array(); // fill could have changed the buffer.
end = buffer.position();
}
if (bufa[i] != LF) {
throw new IOException("Expected LF at " + i);
}
+ ++i;
int lineLength = i - ireadSave;
iread = i;
return lineLength;
View
345 src/kilim/http/HttpRequestParser.java
@@ -26,7 +26,7 @@
public static final Charset UTF8 = Charset.forName("UTF-8");
-// line 127 "HttpRequestParser.rl"
+// line 134 "HttpRequestParser.rl"
@@ -36,8 +36,7 @@
return new byte [] {
0, 1, 0, 1, 1, 1, 2, 1, 3, 1, 4, 1,
5, 1, 6, 1, 7, 1, 8, 1, 9, 1, 10, 1,
- 11, 1, 12, 1, 13, 2, 0, 6, 2, 0, 7, 2,
- 1, 4, 2, 1, 7, 2, 2, 6
+ 11, 1, 12, 2, 0, 3, 2, 0, 6, 2, 1, 5
};
}
@@ -49,9 +48,9 @@
return new short [] {
0, 0, 8, 10, 12, 14, 16, 18, 19, 29, 39, 48,
50, 51, 52, 53, 54, 56, 59, 61, 64, 65, 67, 68,
- 70, 71, 73, 82, 91, 97, 103, 112, 118, 124, 133, 137,
- 141, 151, 157, 163, 173, 182, 191, 197, 203, 212, 214, 216,
- 218, 220, 222, 226, 228, 230, 232
+ 70, 71, 73, 82, 91, 97, 103, 109, 115, 119, 123, 133,
+ 139, 145, 154, 163, 169, 175, 177, 179, 181, 183, 185, 189,
+ 191, 193, 195
};
}
@@ -69,18 +68,15 @@
57, 13, 48, 57, 10, 13, 58, 58, 13, 32, 13, 10,
58, 32, 37, 60, 62, 127, 0, 31, 34, 35, 32, 37,
60, 62, 127, 0, 31, 34, 35, 48, 57, 65, 70, 97,
- 102, 48, 57, 65, 70, 97, 102, 32, 37, 60, 62, 127,
- 0, 31, 34, 35, 48, 57, 65, 70, 97, 102, 48, 57,
- 65, 70, 97, 102, 32, 34, 35, 37, 60, 62, 127, 0,
- 31, 32, 35, 59, 63, 32, 35, 59, 63, 32, 34, 35,
- 37, 60, 62, 63, 127, 0, 31, 48, 57, 65, 70, 97,
- 102, 48, 57, 65, 70, 97, 102, 32, 34, 35, 37, 60,
- 62, 63, 127, 0, 31, 32, 34, 35, 37, 60, 62, 127,
- 0, 31, 32, 34, 35, 37, 60, 62, 127, 0, 31, 48,
- 57, 65, 70, 97, 102, 48, 57, 65, 70, 97, 102, 32,
- 34, 35, 37, 60, 62, 127, 0, 31, 69, 101, 84, 116,
- 69, 101, 65, 97, 68, 100, 79, 85, 111, 117, 83, 115,
- 84, 116, 84, 116, 58, 0
+ 102, 48, 57, 65, 70, 97, 102, 48, 57, 65, 70, 97,
+ 102, 48, 57, 65, 70, 97, 102, 32, 35, 59, 63, 32,
+ 35, 59, 63, 32, 34, 35, 37, 60, 62, 63, 127, 0,
+ 31, 48, 57, 65, 70, 97, 102, 48, 57, 65, 70, 97,
+ 102, 32, 34, 35, 37, 60, 62, 127, 0, 31, 32, 34,
+ 35, 37, 60, 62, 127, 0, 31, 48, 57, 65, 70, 97,
+ 102, 48, 57, 65, 70, 97, 102, 69, 101, 84, 116, 69,
+ 101, 65, 97, 68, 100, 79, 85, 111, 117, 83, 115, 84,
+ 116, 84, 116, 58, 0
};
}
@@ -92,9 +88,9 @@
return new byte [] {
0, 8, 2, 2, 2, 2, 2, 1, 4, 2, 7, 2,
1, 1, 1, 1, 0, 1, 0, 1, 1, 2, 1, 2,
- 1, 2, 5, 5, 0, 0, 5, 0, 0, 7, 4, 4,
- 8, 0, 0, 8, 7, 7, 0, 0, 7, 2, 2, 2,
- 2, 2, 4, 2, 2, 2, 1
+ 1, 2, 5, 5, 0, 0, 0, 0, 4, 4, 8, 0,
+ 0, 7, 7, 0, 0, 2, 2, 2, 2, 2, 4, 2,
+ 2, 2, 1
};
}
@@ -106,9 +102,9 @@
return new byte [] {
0, 0, 0, 0, 0, 0, 0, 0, 3, 4, 1, 0,
0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0,
- 0, 0, 2, 2, 3, 3, 2, 3, 3, 1, 0, 0,
- 1, 3, 3, 1, 1, 1, 3, 3, 1, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 2, 2, 3, 3, 3, 3, 0, 0, 1, 3,
+ 3, 1, 1, 3, 3, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0
};
}
@@ -120,9 +116,9 @@
return new short [] {
0, 0, 9, 12, 15, 18, 21, 24, 26, 34, 41, 50,
53, 55, 57, 59, 61, 63, 66, 68, 71, 73, 76, 78,
- 81, 83, 86, 94, 102, 106, 110, 118, 122, 126, 135, 140,
- 145, 155, 159, 163, 173, 182, 191, 195, 199, 208, 211, 214,
- 217, 220, 223, 228, 231, 234, 237
+ 81, 83, 86, 94, 102, 106, 110, 114, 118, 123, 128, 138,
+ 142, 146, 155, 164, 168, 172, 175, 178, 181, 184, 187, 192,
+ 195, 198, 201
};
}
@@ -140,18 +136,15 @@
1, 24, 1, 25, 26, 1, 27, 1, 28, 27, 1, 29,
1, 31, 1, 30, 33, 32, 35, 36, 34, 38, 37, 39,
33, 32, 40, 42, 1, 1, 1, 1, 1, 41, 43, 45,
- 1, 1, 1, 1, 1, 44, 46, 46, 46, 1, 47, 47,
- 47, 1, 48, 50, 1, 1, 1, 1, 1, 49, 51, 51,
- 51, 1, 52, 52, 52, 1, 53, 1, 55, 56, 1, 1,
- 1, 1, 54, 16, 17, 58, 59, 57, 60, 61, 62, 63,
- 57, 16, 1, 17, 64, 1, 1, 59, 1, 1, 58, 65,
- 65, 65, 1, 66, 66, 66, 1, 53, 1, 55, 68, 1,
- 1, 69, 1, 1, 67, 70, 1, 72, 73, 1, 1, 1,
- 1, 71, 74, 1, 76, 77, 1, 1, 1, 1, 75, 78,
- 78, 78, 1, 79, 79, 79, 1, 80, 1, 82, 83, 1,
- 1, 1, 1, 81, 84, 84, 1, 85, 85, 1, 86, 86,
- 1, 87, 87, 1, 88, 88, 1, 89, 90, 89, 90, 1,
- 91, 91, 1, 92, 92, 1, 93, 93, 1, 33, 32, 0
+ 1, 1, 1, 1, 1, 44, 46, 46, 46, 1, 44, 44,
+ 44, 1, 47, 47, 47, 1, 15, 15, 15, 1, 16, 17,
+ 49, 50, 48, 51, 52, 53, 54, 48, 16, 1, 17, 55,
+ 1, 1, 50, 1, 1, 49, 56, 56, 56, 1, 49, 49,
+ 49, 1, 57, 1, 59, 60, 1, 1, 1, 1, 58, 61,
+ 1, 63, 64, 1, 1, 1, 1, 62, 65, 65, 65, 1,
+ 62, 62, 62, 1, 66, 66, 1, 67, 67, 1, 68, 68,
+ 1, 69, 69, 1, 70, 70, 1, 71, 72, 71, 72, 1,
+ 73, 73, 1, 74, 74, 1, 75, 75, 1, 33, 32, 0
};
}
@@ -161,14 +154,13 @@
private static byte[] init__http_parser_trans_targs_0()
{
return new byte [] {
- 2, 0, 45, 47, 50, 3, 4, 5, 6, 7, 8, 9,
- 34, 10, 9, 10, 11, 26, 31, 12, 13, 14, 15, 16,
+ 2, 0, 41, 43, 46, 3, 4, 5, 6, 7, 8, 9,
+ 32, 10, 9, 10, 11, 26, 30, 12, 13, 14, 15, 16,
17, 18, 17, 19, 20, 21, 22, 25, 22, 23, 24, 20,
- 23, 24, 20, 54, 11, 27, 28, 11, 27, 28, 29, 30,
- 11, 27, 28, 32, 33, 11, 10, 26, 31, 35, 36, 40,
- 11, 26, 36, 40, 37, 38, 39, 36, 37, 40, 11, 41,
- 26, 42, 11, 41, 26, 42, 43, 44, 11, 41, 26, 42,
- 46, 7, 48, 49, 7, 51, 53, 52, 7, 7
+ 23, 24, 20, 50, 11, 27, 28, 11, 27, 28, 29, 31,
+ 33, 34, 37, 11, 26, 34, 37, 35, 36, 11, 38, 26,
+ 39, 11, 38, 26, 39, 40, 42, 7, 44, 45, 7, 47,
+ 49, 48, 7, 7
};
}
@@ -178,28 +170,27 @@
private static byte[] init__http_parser_trans_actions_0()
{
return new byte [] {
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 23, 0, 3,
- 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 3, 0, 0, 0, 17, 0, 3, 3, 0, 7, 3, 35,
- 3, 0, 9, 0, 38, 3, 3, 15, 0, 0, 0, 0,
- 32, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0,
- 11, 11, 11, 11, 0, 0, 0, 1, 1, 1, 41, 5,
- 41, 5, 13, 0, 13, 0, 0, 0, 29, 1, 29, 1,
- 0, 19, 0, 0, 25, 0, 0, 0, 21, 27
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 21, 0, 1,
+ 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0, 15, 0, 1, 1, 0, 5, 1, 27,
+ 1, 0, 7, 0, 30, 1, 1, 13, 0, 0, 0, 0,
+ 0, 0, 0, 9, 9, 9, 9, 0, 0, 33, 3, 33,
+ 3, 11, 0, 11, 0, 0, 0, 17, 0, 0, 23, 0,
+ 0, 0, 19, 25
};
}
private static final byte _http_parser_trans_actions[] = init__http_parser_trans_actions_0();
static final int http_parser_start = 1;
-static final int http_parser_first_final = 54;
+static final int http_parser_first_final = 50;
static final int http_parser_error = 0;
static final int http_parser_en_main = 1;
-// line 130 "HttpRequestParser.rl"
+// line 137 "HttpRequestParser.rl"
public static void err(String msg) throws IOException{
throw new IOException(msg);
@@ -218,17 +209,16 @@ public static void initHeader(HttpRequest req, int headerLength) throws IOExcept
int query_start = 0;
int mark = 0;
String field_name = "";
- boolean need_decode = false;
-// line 225 "HttpRequestParser.java"
+// line 215 "HttpRequestParser.java"
{
cs = http_parser_start;
}
-// line 151 "HttpRequestParser.rl"
+// line 157 "HttpRequestParser.rl"
-// line 232 "HttpRequestParser.java"
+// line 222 "HttpRequestParser.java"
{
int _klen;
int _trans = 0;
@@ -310,84 +300,89 @@ else if ( data[p] > _http_parser_trans_keys[_mid+1] )
{
case 0:
// line 31 "HttpRequestParser.rl"
- {need_decode = true;}
+ {mark = p; }
break;
case 1:
// line 33 "HttpRequestParser.rl"
- {mark = p; }
+ {query_start = p; }
break;
case 2:
// line 35 "HttpRequestParser.rl"
- {query_start = p; }
- break;
- case 3:
-// line 37 "HttpRequestParser.rl"
{
field_name = kw_lookup(data, mark, p);
if (field_name == null) {// not a known keyword
field_name = req.extractRange(mark, p);
}
}
break;
- case 4:
-// line 44 "HttpRequestParser.rl"
+ case 3:
+// line 42 "HttpRequestParser.rl"
{
int value = encodeRange(mark, p);
req.addField(field_name, value);
}
break;
- case 5:
-// line 49 "HttpRequestParser.rl"
+ case 4:
+// line 47 "HttpRequestParser.rl"
{
req.uriPath = req.extractRange(mark, p);
- if (need_decode) {
- // TODO: Correct this. URLDecoder is broken for path (upto JDK1.6): it converts '+' to ' ', which should
- // be done only for the query part of the url.
- try {
- req.uriPath = URLDecoder.decode(req.uriPath, "UTF-8");
- } catch (UnsupportedEncodingException ignore){}
+ String s = req.uriPath;
+ int len = s.length();
+ boolean need_decode;
+ // Scan the string to see if the string requires any conversion.
+ for (int i = 0; i < len; i++) {
+ char c = s.charAt(i);
+ if (c == '%' || c > 0x7F) {
+ try {
+ // TODO: Correct this. URLDecoder is broken for path (upto
+ // JDK1.6): it converts'+' to ' ', which should
+ // be done only for the query part of the url.
+ req.uriPath = URLDecoder.decode(req.uriPath, "UTF-8");
+ break;
+ } catch (UnsupportedEncodingException ignore){}
+ }
}
}
break;
- case 6:
-// line 65 "HttpRequestParser.rl"
+ case 5:
+// line 72 "HttpRequestParser.rl"
{
req.queryStringRange = encodeRange(query_start, p);
}
break;
- case 7:
-// line 69 "HttpRequestParser.rl"
+ case 6:
+// line 76 "HttpRequestParser.rl"
{
req.uriFragmentRange = encodeRange(mark, p);
}
break;
- case 8:
-// line 73 "HttpRequestParser.rl"
+ case 7:
+// line 80 "HttpRequestParser.rl"
{
req.versionRange = encodeRange(mark, p);
}
break;
- case 9:
-// line 112 "HttpRequestParser.rl"
+ case 8:
+// line 119 "HttpRequestParser.rl"
{req.method = "GET";}
break;
- case 10:
-// line 113 "HttpRequestParser.rl"
+ case 9:
+// line 120 "HttpRequestParser.rl"
{req.method = "POST";}
break;
- case 11:
-// line 114 "HttpRequestParser.rl"
+ case 10:
+// line 121 "HttpRequestParser.rl"
{req.method = "DELETE";}
break;
- case 12:
-// line 115 "HttpRequestParser.rl"
+ case 11:
+// line 122 "HttpRequestParser.rl"
{req.method = "HEAD";}
break;
- case 13:
-// line 116 "HttpRequestParser.rl"
+ case 12:
+// line 123 "HttpRequestParser.rl"
{req.method = "PUT";}
break;
-// line 391 "HttpRequestParser.java"
+// line 386 "HttpRequestParser.java"
}
}
}
@@ -407,7 +402,7 @@ else if ( data[p] > _http_parser_trans_keys[_mid+1] )
break; }
}
-// line 152 "HttpRequestParser.rl"
+// line 158 "HttpRequestParser.rl"
if (cs == http_parser_error) {
throw new IOException("Malformed HTTP Header. p = " + p +", cs = " + cs);
@@ -422,7 +417,7 @@ public static int encodeRange(int start, int end) {
}
-// line 426 "HttpRequestParser.java"
+// line 421 "HttpRequestParser.java"
private static byte[] init__http_keywords_actions_0()
{
return new byte [] {
@@ -950,7 +945,7 @@ public static int encodeRange(int start, int end) {
static final int http_keywords_en_main = 307;
-// line 219 "HttpRequestParser.rl"
+// line 225 "HttpRequestParser.rl"
@SuppressWarnings("unused")
@@ -965,17 +960,17 @@ public static String kw_lookup(byte[] data, int start, int len) {
int cs;
String kw = null;
-// line 969 "HttpRequestParser.java"
+// line 964 "HttpRequestParser.java"
{
cs = http_keywords_start;
ts = -1;
te = -1;
act = 0;
}
-// line 233 "HttpRequestParser.rl"
+// line 239 "HttpRequestParser.rl"
-// line 979 "HttpRequestParser.java"
+// line 974 "HttpRequestParser.java"
{
int _klen;
int _trans = 0;
@@ -1004,7 +999,7 @@ public static String kw_lookup(byte[] data, int start, int len) {
// line 1 "HttpRequestParser.rl"
{ts = p;}
break;
-// line 1008 "HttpRequestParser.java"
+// line 1003 "HttpRequestParser.java"
}
}
@@ -1072,198 +1067,198 @@ else if ( data[p] > _http_keywords_trans_keys[_mid+1] )
{te = p+1;}
break;
case 3:
-// line 170 "HttpRequestParser.rl"
+// line 176 "HttpRequestParser.rl"
{te = p+1;{ kw = "Accept-Charset";}}
break;
case 4:
-// line 171 "HttpRequestParser.rl"
+// line 177 "HttpRequestParser.rl"
{te = p+1;{ kw = "Accept-Encoding";}}
break;
case 5:
-// line 172 "HttpRequestParser.rl"
+// line 178 "HttpRequestParser.rl"
{te = p+1;{ kw = "Accept-Language";}}
break;
case 6:
-// line 173 "HttpRequestParser.rl"
+// line 179 "HttpRequestParser.rl"
{te = p+1;{ kw = "Accept-Ranges";}}
break;
case 7:
-// line 174 "HttpRequestParser.rl"
+// line 180 "HttpRequestParser.rl"
{te = p+1;{ kw = "Age";}}
break;
case 8:
-// line 175 "HttpRequestParser.rl"
+// line 181 "HttpRequestParser.rl"
{te = p+1;{ kw = "Allow";}}
break;
case 9:
-// line 176 "HttpRequestParser.rl"
+// line 182 "HttpRequestParser.rl"
{te = p+1;{ kw = "Authorization";}}
break;
case 10:
-// line 177 "HttpRequestParser.rl"
+// line 183 "HttpRequestParser.rl"
{te = p+1;{ kw = "Cache-Control";}}
break;
case 11:
-// line 178 "HttpRequestParser.rl"
+// line 184 "HttpRequestParser.rl"
{te = p+1;{ kw = "Connection";}}
break;
case 12:
-// line 179 "HttpRequestParser.rl"
+// line 185 "HttpRequestParser.rl"
{te = p+1;{ kw = "Content-Encoding";}}
break;
case 13:
-// line 180 "HttpRequestParser.rl"
+// line 186 "HttpRequestParser.rl"
{te = p+1;{ kw = "Content-Language";}}
break;
case 14:
-// line 181 "HttpRequestParser.rl"
+// line 187 "HttpRequestParser.rl"
{te = p+1;{ kw = "Content-Length";}}
break;
case 15:
-// line 182 "HttpRequestParser.rl"
+// line 188 "HttpRequestParser.rl"
{te = p+1;{ kw = "Content-Location";}}
break;
case 16:
-// line 183 "HttpRequestParser.rl"
+// line 189 "HttpRequestParser.rl"
{te = p+1;{ kw = "Content-MD5";}}
break;
case 17:
-// line 184 "HttpRequestParser.rl"
+// line 190 "HttpRequestParser.rl"
{te = p+1;{ kw = "Content-Range";}}
break;
case 18:
-// line 185 "HttpRequestParser.rl"
+// line 191 "HttpRequestParser.rl"
{te = p+1;{ kw = "Content-Type";}}
break;
case 19:
-// line 186 "HttpRequestParser.rl"
+// line 192 "HttpRequestParser.rl"
{te = p+1;{ kw = "Date";}}
break;
case 20:
-// line 187 "HttpRequestParser.rl"
+// line 193 "HttpRequestParser.rl"
{te = p+1;{ kw = "ETag";}}
break;
case 21:
-// line 188 "HttpRequestParser.rl"
+// line 194 "HttpRequestParser.rl"
{te = p+1;{ kw = "Expect";}}
break;
case 22:
-// line 189 "HttpRequestParser.rl"
+// line 195 "HttpRequestParser.rl"
{te = p+1;{ kw = "Expires";}}
break;
case 23:
-// line 190 "HttpRequestParser.rl"
+// line 196 "HttpRequestParser.rl"
{te = p+1;{ kw = "From";}}
break;
case 24:
-// line 191 "HttpRequestParser.rl"
+// line 197 "HttpRequestParser.rl"
{te = p+1;{ kw = "Host";}}
break;
case 25:
-// line 192 "HttpRequestParser.rl"
+// line 198 "HttpRequestParser.rl"
{te = p+1;{ kw = "If-Match";}}
break;
case 26:
-// line 193 "HttpRequestParser.rl"
+// line 199 "HttpRequestParser.rl"
{te = p+1;{ kw = "If-Modified-Since";}}
break;
case 27:
-// line 194 "HttpRequestParser.rl"
+// line 200 "HttpRequestParser.rl"
{te = p+1;{ kw = "If-None-Match";}}
break;
case 28:
-// line 195 "HttpRequestParser.rl"
+// line 201 "HttpRequestParser.rl"
{te = p+1;{ kw = "If-Range";}}
break;
case 29:
-// line 196 "HttpRequestParser.rl"
+// line 202 "HttpRequestParser.rl"
{te = p+1;{ kw = "If-Unmodified-Since";}}
break;
case 30:
-// line 197 "HttpRequestParser.rl"
+// line 203 "HttpRequestParser.rl"
{te = p+1;{ kw = "Last-Modified";}}
break;
case 31:
-// line 198 "HttpRequestParser.rl"
+// line 204 "HttpRequestParser.rl"
{te = p+1;{ kw = "Location";}}
break;
case 32:
-// line 199 "HttpRequestParser.rl"
+// line 205 "HttpRequestParser.rl"
{te = p+1;{ kw = "Max-Forwards";}}
break;
case 33:
-// line 200 "HttpRequestParser.rl"
+// line 206 "HttpRequestParser.rl"
{te = p+1;{ kw = "Pragma";}}
break;
case 34:
-// line 201 "HttpRequestParser.rl"
+// line 207 "HttpRequestParser.rl"
{te = p+1;{ kw = "Proxy-Authenticate";}}
break;
case 35:
-// line 202 "HttpRequestParser.rl"
+// line 208 "HttpRequestParser.rl"
{te = p+1;{ kw = "Proxy-Authorization";}}
break;
case 36:
-// line 203 "HttpRequestParser.rl"
+// line 209 "HttpRequestParser.rl"
{te = p+1;{ kw = "Range";}}
break;
case 37:
-// line 204 "HttpRequestParser.rl"
+// line 210 "HttpRequestParser.rl"
{te = p+1;{ kw = "Referer";}}
break;
case 38:
-// line 205 "HttpRequestParser.rl"
+// line 211 "HttpRequestParser.rl"
{te = p+1;{ kw = "Retry-After";}}
break;
case 39:
-// line 206 "HttpRequestParser.rl"
+// line 212 "HttpRequestParser.rl"
{te = p+1;{ kw = "Server";}}
break;
case 40:
-// line 207 "HttpRequestParser.rl"
+// line 213 "HttpRequestParser.rl"
{te = p+1;{ kw = "TE";}}
break;
case 41:
-// line 208 "HttpRequestParser.rl"
+// line 214 "HttpRequestParser.rl"
{te = p+1;{ kw = "Trailer";}}
break;
case 42:
-// line 209 "HttpRequestParser.rl"
+// line 215 "HttpRequestParser.rl"
{te = p+1;{ kw = "Transfer-Encoding";}}
break;
case 43:
-// line 210 "HttpRequestParser.rl"
+// line 216 "HttpRequestParser.rl"
{te = p+1;{ kw = "Upgrade";}}
break;
case 44:
-// line 211 "HttpRequestParser.rl"
+// line 217 "HttpRequestParser.rl"
{te = p+1;{ kw = "User-Agent";}}
break;
case 45:
-// line 212 "HttpRequestParser.rl"
+// line 218 "HttpRequestParser.rl"
{te = p+1;{ kw = "Vary";}}
break;
case 46:
-// line 213 "HttpRequestParser.rl"
+// line 219 "HttpRequestParser.rl"
{te = p+1;{ kw = "Via";}}
break;
case 47:
-// line 214 "HttpRequestParser.rl"
+// line 220 "HttpRequestParser.rl"
{te = p+1;{ kw = "Warning";}}
break;
case 48:
-// line 215 "HttpRequestParser.rl"
+// line 221 "HttpRequestParser.rl"
{te = p+1;{ kw = "WWW-Authenticate";}}
break;
case 49:
-// line 169 "HttpRequestParser.rl"
+// line 175 "HttpRequestParser.rl"
{te = p;p--;{ kw = "Accept";}}
break;
case 50:
-// line 169 "HttpRequestParser.rl"
+// line 175 "HttpRequestParser.rl"
{{p = ((te))-1;}{ kw = "Accept";}}
break;
-// line 1267 "HttpRequestParser.java"
+// line 1262 "HttpRequestParser.java"
}
}
}
@@ -1277,7 +1272,7 @@ else if ( data[p] > _http_keywords_trans_keys[_mid+1] )
// line 1 "HttpRequestParser.rl"
{ts = -1;}
break;
-// line 1281 "HttpRequestParser.java"
+// line 1276 "HttpRequestParser.java"
}
}
@@ -1304,13 +1299,13 @@ else if ( data[p] > _http_keywords_trans_keys[_mid+1] )
break; }
}
-// line 234 "HttpRequestParser.rl"
+// line 240 "HttpRequestParser.rl"
return kw;
}
-// line 1314 "HttpRequestParser.java"
+// line 1309 "HttpRequestParser.java"
private static byte[] init__http_date_actions_0()
{
return new byte [] {
@@ -1488,7 +1483,7 @@ else if ( data[p] > _http_keywords_trans_keys[_mid+1] )
static final int http_date_en_main = 1;
-// line 278 "HttpRequestParser.rl"
+// line 284 "HttpRequestParser.rl"
public static TimeZone GMT = TimeZone.getTimeZone("GMT");
@@ -1503,14 +1498,14 @@ public static long parseDate(byte[] data, int pos, int len) {
int hh = 0, mm = 0, ss = 0;
-// line 1507 "HttpRequestParser.java"
+// line 1502 "HttpRequestParser.java"
{
cs = http_date_start;
}
-// line 292 "HttpRequestParser.rl"
+// line 298 "HttpRequestParser.rl"
-// line 1514 "HttpRequestParser.java"
+// line 1509 "HttpRequestParser.java"
{
int _klen;
int _trans = 0;
@@ -1590,74 +1585,74 @@ else if ( data[p] > _http_date_trans_keys[_mid+1] )
switch ( _http_date_actions[_acts++] )
{
case 0:
-// line 247 "HttpRequestParser.rl"
+// line 253 "HttpRequestParser.rl"
{day = day * 10 + (data[p] - 48);}
break;
case 1:
-// line 248 "HttpRequestParser.rl"
+// line 254 "HttpRequestParser.rl"
{year = year * 10 + (data[p] - 48);}
break;
case 2:
-// line 249 "HttpRequestParser.rl"
+// line 255 "HttpRequestParser.rl"
{hh = hh * 10 + (data[p] - 48) ;}
break;
case 3:
-// line 250 "HttpRequestParser.rl"
+// line 256 "HttpRequestParser.rl"
{mm = mm * 10 + (data[p] - 48) ;}
break;
case 4:
-// line 251 "HttpRequestParser.rl"
+// line 257 "HttpRequestParser.rl"
{ss = ss * 10 + (data[p] - 48) ;}
break;
case 5:
-// line 255 "HttpRequestParser.rl"
+// line 261 "HttpRequestParser.rl"
{ month = 0;}
break;
case 6:
-// line 256 "HttpRequestParser.rl"
+// line 262 "HttpRequestParser.rl"
{ month = 1;}
break;
case 7:
-// line 257 "HttpRequestParser.rl"
+// line 263 "HttpRequestParser.rl"
{ month = 2;}
break;
case 8:
-// line 258 "HttpRequestParser.rl"
+// line 264 "HttpRequestParser.rl"
{ month = 3;}
break;
case 9:
-// line 259 "HttpRequestParser.rl"
+// line 265 "HttpRequestParser.rl"
{ month = 4;}
break;
case 10:
-// line 260 "HttpRequestParser.rl"
+// line 266 "HttpRequestParser.rl"
{ month = 5;}
break;
case 11:
-// line 261 "HttpRequestParser.rl"
+// line 267 "HttpRequestParser.rl"
{ month = 6;}
break;
case 12:
-// line 262 "HttpRequestParser.rl"
+// line 268 "HttpRequestParser.rl"
{ month = 7;}
break;
case 13:
-// line 263 "HttpRequestParser.rl"
+// line 269 "HttpRequestParser.rl"
{ month = 8;}
break;
case 14:
-// line 264 "HttpRequestParser.rl"
+// line 270 "HttpRequestParser.rl"
{ month = 90;}
break;
case 15:
-// line 265 "HttpRequestParser.rl"
+// line 271 "HttpRequestParser.rl"
{ month = 10;}
break;
case 16:
-// line 266 "HttpRequestParser.rl"
+// line 272 "HttpRequestParser.rl"
{ month = 11;}
break;
-// line 1661 "HttpRequestParser.java"
+// line 1656 "HttpRequestParser.java"
}
}
}
@@ -1677,7 +1672,7 @@ else if ( data[p] > _http_date_trans_keys[_mid+1] )
break; }
}
-// line 293 "HttpRequestParser.rl"
+// line 299 "HttpRequestParser.rl"
if (year < 100) {year += 1900;}
View
26 src/kilim/http/HttpRequestParser.rl
@@ -28,8 +28,6 @@ public class HttpRequestParser {
machine http_parser;
- action need_urldecode{need_decode = true;}
-
action mark {mark = fpc; }
action start_query {query_start = fpc; }
@@ -48,12 +46,21 @@ public class HttpRequestParser {
action request_path {
req.uriPath = req.extractRange(mark, fpc);
- if (need_decode) {
- // TODO: Correct this. URLDecoder is broken for path (upto JDK1.6): it converts '+' to ' ', which should
- // be done only for the query part of the url.
- try {
- req.uriPath = URLDecoder.decode(req.uriPath, "UTF-8");
- } catch (UnsupportedEncodingException ignore){}
+ String s = req.uriPath;
+ int len = s.length();
+ boolean need_decode;
+ // Scan the string to see if the string requires any conversion.
+ for (int i = 0; i < len; i++) {
+ char c = s.charAt(i);
+ if (c == '%' || c > 0x7F) {
+ try {
+ // TODO: Correct this. URLDecoder is broken for path (upto
+ // JDK1.6): it converts'+' to ' ', which should
+ // be done only for the query part of the url.
+ req.uriPath = URLDecoder.decode(req.uriPath, "UTF-8");
+ break;
+ } catch (UnsupportedEncodingException ignore){}
+ }
}
}
@@ -84,7 +91,7 @@ public class HttpRequestParser {
unsafe = (CTL | " " | "\"" | "#" | "%" | "<" | ">");
national = any -- (alpha | digit | reserved | extra | safe | unsafe);
unreserved = (alpha | digit | safe | extra | national);
- escape = ("%" xdigit xdigit) %need_urldecode;
+ escape = ("%" xdigit xdigit);
uchar = (unreserved | escape);
pchar = (uchar | ":" | "@" | "&" | "=" | "+");
tspecials = ("(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\\" | "\"" | "/" | "[" | "]" | "?" | "=" | "{" | "}" | " " | "\t");
@@ -145,7 +152,6 @@ public class HttpRequestParser {
int query_start = 0;
int mark = 0;
String field_name = "";
- boolean need_decode = false;
%% write init;
%% write exec;
View
49 src/kilim/nio/EndPoint.java
@@ -91,7 +91,8 @@ public void write(ByteBuffer buf) throws IOException, Pausable {
/**
- * Read into buffer buf and ensure that buf position > atLeastN before returning.
+ * Read <code>atleastN</code> bytes more into the buffer if there's space. Otherwise, allocate a bigger
+ * buffer that'll accomodate the earlier contents and atleastN more bytes.
*
* @param buf
* ByteBuffer to be filled
@@ -101,7 +102,7 @@ public void write(ByteBuffer buf) throws IOException, Pausable {
*/
public ByteBuffer fill(ByteBuffer buf, int atleastN) throws IOException, Pausable {
if (buf.remaining() < atleastN) {
- ByteBuffer newbb = ByteBuffer.allocate(Math.max(buf.capacity() * 3 / 2, atleastN));
+ ByteBuffer newbb = ByteBuffer.allocate(Math.max(buf.capacity() * 3 / 2, buf.position() + atleastN));
buf.rewind();
newbb.put(buf);
buf = newbb;
@@ -117,7 +118,7 @@ public ByteBuffer fill(ByteBuffer buf, int atleastN) throws IOException, Pausabl
// System.out.println(buf);
if (n == -1) {
close();
- return buf;
+ throw new EOFException();
}
if (n == 0) {
yieldCount++;
@@ -132,10 +133,50 @@ public ByteBuffer fill(ByteBuffer buf, int atleastN) throws IOException, Pausabl
}
}
atleastN -= n;
- } while (buf.position() < atleastN);
+ } while (atleastN > 0);
return buf;
}
+
+ /**
+ * Reads a length-prefixed message in its entirety.
+ *
+ * @param bb The bytebuffer to fill, assuming there is sufficient space (including the bytes for the length). Otherwise, the
+ * contents are copied into a sufficiently big buffer, and the new buffer is returned.
+ *
+ * @param lengthLength The number of bytes occupied by the length. Must be 1, 2 or 4, assumed to be in big-endian order.
+ * @param lengthIncludesItself true if the packet length includes lengthLength
+ * @return the buffer bb passed in if the message fits or a new buffer. Either way, the buffer returned has the entire
+ * message including the initial length prefix.
+ * @throws IOException
+ * @throws Pausable
+ */
+ public ByteBuffer fillMessage(ByteBuffer bb, int lengthLength, boolean lengthIncludesItself) throws IOException, Pausable {
+ int pos = bb.position();
+ int opos = pos; // save orig pos
+ bb = fill(bb, lengthLength);
+ byte a, b, c, d;
+ a = b = c = d = 0;
+ switch (lengthLength) {
+ case 4: a = bb.get(pos); pos++;
+ b = bb.get(pos); pos++; // fall through
+ case 2: c = bb.get(pos); pos++; // fall through
+ case 1: d = bb.get(pos); break;
+ default: throw new IllegalArgumentException("Incorrect lengthLength (may only be 1, 2 or 4): " + lengthLength);
+ }
+ int contentLen = ((a << 24) + (b << 16) + (c << 8) + (d << 0));
+ // TODO: put a limit on len
+ if (lengthIncludesItself) {
+ contentLen -= lengthLength;
+ }
+ // If the fill() above hasn't read in all the content, read the remaining
+ int remaining = contentLen - (bb.position() - opos - lengthLength);
+ if (remaining > 0) {
+ bb = fill(bb, remaining);
+ }
+ return bb;
+ }
+
public void pauseUntilReadble() throws Pausable, IOException {
SockEvent ev = new SockEvent(this, sockch, SelectionKey.OP_READ);
sockEvMbx.putnb(ev);
View
4 src/kilim/nio/ExposedBais.java
@@ -13,6 +13,10 @@
*/
public class ExposedBais extends ByteArrayInputStream {
+ public ExposedBais(int size) {
+ super(new byte[size]);
+ }
+
public ExposedBais(byte[] buf, int offset, int length) {
super(buf, offset, length);
}
View
29 src/kilim/nio/NioSelectorScheduler.java
@@ -13,12 +13,14 @@
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
+import java.util.LinkedList;
import kilim.Mailbox;
import kilim.Pausable;
import kilim.RingQueue;
import kilim.Scheduler;
import kilim.Task;
+import kilim.http.IntList;
/**
* This class wraps a selector and runs it in a separate thread.
@@ -55,6 +57,7 @@
* message on this mailbox.
*/
public Mailbox<SockEvent> registrationMbx = new Mailbox<SockEvent>(1000);
+
/**
* @throws IOException
@@ -83,6 +86,12 @@ public void schedule(Task t) {
}
}
+ @Override
+ public void shutdown() {
+ super.shutdown();
+ sel.wakeup();
+ }
+
synchronized void addRunnable(Task t) {
runnableTasks.put(t);
}
@@ -108,6 +117,22 @@ public void run() {
while (true) {
int n;
try {
+ if (_scheduler.isShutdown()) {
+ Iterator<SelectionKey> it = sel.keys().iterator();
+ while (it.hasNext()) {
+ SelectionKey sk = it.next();
+ sk.cancel();
+ Object o = sk.attachment();
+ if (o instanceof SockEvent && ((SockEvent)o).ch instanceof ServerSocketChannel) {
+ // TODO FIX: Need a proper, orderly shutdown procedure for tasks. This closes down the task
+ // irrespective of the thread it may be running on. Terrible.
+ try {
+ ((ServerSocketChannel)((SockEvent)o).ch).close();
+ } catch (IOException ignore) {}
+ }
+ }
+ break;
+ }
if (_scheduler.numRunnables() > 0) {
n = sel.selectNow();
} else {
@@ -187,6 +212,10 @@ public void execute() throws Pausable, Exception {
int n = 0;
while (true) {
SocketChannel ch = ssc.accept();
+ if (this.scheduler.isShutdown()) {
+ ssc.close();
+ break;
+ }
if (ch == null) {
endpoint.pauseUntilAcceptable();
} else {
View
29 test/kilim/test/All.java
@@ -1,29 +0,0 @@
-/* Copyright (c) 2006, Sriram Srinivasan
- *
- * You may distribute this software under the terms of the license
- * specified in the file "License"
- */
-
-package kilim.test;
-
-import junit.framework.Test;
-import junit.framework.TestSuite;
-
-public class All extends TestSuite {
- public static Test suite() {
- TestSuite ret = new All();
- ret.addTestSuite(TestTypeDesc.class);
- ret.addTestSuite(TestUsage.class);
- ret.addTestSuite(TestValue.class);
- ret.addTestSuite(TestFrame.class);
- ret.addTestSuite(TestBasicBlock.class);
- ret.addTestSuite(TestJSR.class);
- ret.addTestSuite(TestFlow.class);
- ret.addTestSuite(TestExprs.class);
- ret.addTestSuite(TestInvalidPausables.class);
- ret.addTestSuite(TestYield.class);
- ret.addTestSuite(TestYieldExceptions.class);
- ret.addTestSuite(TestYieldJSR.class);
- return ret;
- }
-}
View
2 test/kilim/test/AllWoven.java
@@ -19,6 +19,8 @@ public static Test suite() {
ret.addTestSuite(TestMailbox.class);
ret.addTestSuite(TestLock.class);
ret.addTestSuite(TestGenerics.class);
+ ret.addTestSuite(TestIO.class);
+ ret.addTestSuite(TestHTTP.class);
return ret;
}
}
View
143 test/kilim/test/TestHTTP.java
@@ -0,0 +1,143 @@
+/* Copyright (c) 2006, Sriram Srinivasan
+ *
+ * You may distribute this software under the terms of the license
+ * specified in the file "License"
+ */
+
+package kilim.test;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.URLConnection;
+
+import junit.framework.TestCase;
+import kilim.Pausable;
+import kilim.Scheduler;
+import kilim.http.HttpRequest;
+import kilim.http.HttpResponse;
+import kilim.http.HttpSession;
+import kilim.nio.NioSelectorScheduler;
+
+public class TestHTTP extends TestCase {
+ static int PORT = 9797;
+ static final int ITERS = 10;
+ static final int NCLIENTS = 100;
+ NioSelectorScheduler nio;
+
+ @Override
+ protected void setUp() throws Exception {
+ nio = new NioSelectorScheduler(); // Starts a single thread that manages the select loop
+ nio.listen(PORT, TestHttpServer.class, Scheduler.getDefaultScheduler()); //
+ Thread.sleep(50); // Allow the socket to be registered and opened.
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ nio.shutdown();
+ Scheduler.getDefaultScheduler().shutdown();
+ PORT++; // start the next test with a new socket.
+ }
+
+ public void testReqResp() throws IOException {
+ String path = "/hello";
+ URL url = new URL("http://localhost:" + PORT + path);
+ URLConnection conn = url.openConnection();
+ conn.setDefaultUseCaches(false);
+ BufferedReader in = new BufferedReader(
+ new InputStreamReader(
+ conn.getInputStream()));
+ String s = in.readLine();
+ assertTrue(s.contains(path));
+ in.close();
+ }
+
+ public void testQuery() throws IOException {
+ String path = "/%7ekilim/home.html?info?code=200&desc=Rolls%20Royce";
+ URL url = new URL("http://localhost:" + PORT + path);
+ HttpURLConnection conn = (HttpURLConnection)url.openConnection();
+ conn.setDefaultUseCaches(false);
+ BufferedReader in = new BufferedReader(
+ new InputStreamReader(
+ conn.getInputStream()));
+ String s = in.readLine();
+ assertTrue(s.contains("~kilim"));
+ assertTrue(s.contains("desc:Rolls Royce"));
+ in.close();
+
+ }
+
+ public void testChunking() throws IOException {
+ String path = "/%7ekilim/home.html?buy?code=200&desc=Rolls%20Royce";
+ URL url = new URL("http://localhost:" + PORT + path);
+ HttpURLConnection conn = (HttpURLConnection)url.openConnection();
+ conn.setDefaultUseCaches(false);
+ conn.setDoOutput(true);
+ conn.setRequestMethod("POST");
+ conn.setChunkedStreamingMode(17);
+ StringBuilder sb = new StringBuilder(268);
+ sb.append("BEGIN");
+ for (int i = 0; i < 10; i++) {
+ sb.append("abcdefghijklmnopqrstuvwxyz");
+ }
+ sb.append("END");
+
+ BufferedWriter out = new BufferedWriter(new OutputStreamWriter(
+ conn.getOutputStream()));
+
+ out.write(sb.toString());
+ out.flush();
+ BufferedReader in = new BufferedReader(
+ new InputStreamReader(
+ conn.getInputStream()));
+ String s = in.readLine();
+ assertEquals(s.length(), sb.length());
+ assertTrue(s.startsWith("BEGIN"));
+ assertTrue(s.endsWith("END"));
+
+ in.close();
+ out.close();
+
+ }
+
+ public static class TestHttpServer extends HttpSession {
+ public void execute() throws Pausable, Exception {
+ try {
+ while (true) {
+ HttpRequest req = new HttpRequest();
+ HttpResponse resp = new HttpResponse();
+ // Fill up the request object. This pauses until the entire request has
+ // been read in, including all chunks.
+ super.readRequest(req);
+ // System.out.println(req);
+ if (req.method.equals("GET")) {
+ resp.setContentType("text");
+ PrintWriter pw = new PrintWriter(resp.getOutputStream());
+ pw.append(req.uriPath).append(req.getQueryComponents().toString());
+ pw.flush();
+ sendResponse(resp);
+ } else if (req.method.equals("POST")) {
+ resp.setContentType("text");
+ PrintWriter pw = new PrintWriter(resp.getOutputStream());
+ String s = req.extractRange(req.contentOffset, req.contentOffset + req.contentLength);
+ pw.append(s);
+ pw.flush();
+ sendResponse(resp);
+ } else {
+ problem(resp, HttpResponse.ST_BAD_REQUEST, "Only get accepted");
+ }
+ if (!req.keepAlive()) {
+ break;
+ }
+ }
+ } catch (EOFException ignore) {
+ }
+ }
+ }
+}
View
172 test/kilim/test/TestIO.java
@@ -0,0 +1,172 @@
+/* Copyright (c) 2006, Sriram Srinivasan
+ *
+ * You may distribute this software under the terms of the license
+ * specified in the file "License"
+ */
+
+package kilim.test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.SocketChannel;
+
+import junit.framework.TestCase;
+import kilim.Pausable;
+import kilim.Scheduler;
+import kilim.nio.EndPoint;
+import kilim.nio.NioSelectorScheduler;
+import kilim.nio.SessionTask;
+
+public class TestIO extends TestCase {
+ static final int PORT = 9797;
+ static final int ITERS = 10;
+ static final int NCLIENTS = 100;
+ NioSelectorScheduler nio;
+
+ @Override
+ protected void setUp() throws Exception {
+ nio = new NioSelectorScheduler(); // Starts a single thread that manages the select loop
+ nio.listen(PORT, EchoServer.class, Scheduler.getDefaultScheduler()); //
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ nio.shutdown();
+ Thread.sleep(500); // Allow the socket to be closed
+ }
+
+ /**
+ * Launch many ping clients, each of which is paired with its own instance of {@link EchoServer}.
+ * @throws IOException
+ */
+ public void testParallelEchoes() throws IOException {
+ try {
+ for (int i = 0; i < NCLIENTS; i++) {
+ client();
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ fail("IOException " + e.getClass() + ":" + e.getMessage());
+ }
+ }
+
+ public void testDelay() throws IOException {
+ SocketChannel sc = SocketChannel.open();
+
+ try {
+ sc.socket().connect(new InetSocketAddress("localhost", PORT));
+ String s = "Iteration #0. DONE"; // Only because EchoServer checks for it.
+ byte[] sbytes = s.getBytes();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream(100);
+ DataOutputStream dos = new DataOutputStream(baos);
+ dos.writeInt(sbytes.length);
+ dos.write(sbytes);
+
+ byte[] sendbuf = baos.toByteArray();
+ // Now write the bytes in little dribs and drabs and delaying in between. This tests fill's yield.
+ OutputStream os = sc.socket().getOutputStream();
+ sendChunkWithDelay(os, sendbuf, 0, 1); // splitting the length prefix
+ sendChunkWithDelay(os, sendbuf, 1, 2);
+ sendChunkWithDelay(os, sendbuf, 3, 4);
+ sendChunkWithDelay(os, sendbuf, 7, 3);
+ sendChunkWithDelay(os, sendbuf, 10, sendbuf.length - 10); // the rest
+
+ // Ideally, would like to simulate flow control on the rcv end as well, but would have to turn off
+ // socket buffering on the EchoServer side of things.
+ String rs = rcv(sc);
+ assertEquals(s, rs);
+ } catch (IOException e) {
+ e.printStackTrace();
+ fail("IOException " + e.getClass() + ":" + e.getMessage());
+ }
+ }
+
+
+ public void sendChunkWithDelay(OutputStream os, byte[] sendbuf, int offset, int len) throws IOException {
+ os.write(sendbuf, offset, len);
+ os.flush();
+ try {Thread.sleep(100);} catch (InterruptedException ignore) {}
+ }
+ /**
+ * test client side utility function. uses blocking Java I/O to send a length-prefixed string.
+ */
+ static void send(SocketChannel sc, String s) throws IOException {
+ byte[] bytes = s.getBytes();
+ int len = bytes.length;
+ DataOutputStream dos = new DataOutputStream(sc.socket().getOutputStream());
+ dos.writeInt(len);
+ dos.write(bytes);
+ dos.flush();
+ }
+
+ /**
+ * test client side utility function. uses blocking Java I/O to rcv a length-prefixed string.
+ */
+ static String rcv(SocketChannel sc) throws IOException {
+ // rcv
+ DataInputStream dis = new DataInputStream(sc.socket().getInputStream());
+ byte[] bytes = new byte[100];
+ int len = dis.readInt();
+ assertTrue(len < bytes.length);
+ int offset = 0;
+ while (len > 0) {
+ int n = dis.read(bytes, offset, len);
+ if (n == -1) {
+ throw new IOException("Unexpected termination");
+ }
+ len -= n;
+ offset += n;
+ }
+ return new String(bytes, 0, offset); // offset contains the length.
+ }
+
+
+ static void client() throws IOException {
+ SocketChannel sc = SocketChannel.open();
+ try {
+ // Client using regular JDK I/O API.
+ sc.socket().connect(new InetSocketAddress("localhost", PORT));
+ for (int i = 0 ; i < ITERS; i++) {
+ String s = "Iteration #" + i;
+ if (i == ITERS-1) {s += " DONE";}
+ send(sc, s);
+ String rs = rcv(sc);
+ assertEquals(s, rs);
+ }
+ } finally {
+ sc.close();
+ }
+ }
+
+
+ public static class EchoServer extends SessionTask {
+ @Override
+ public void execute() throws Pausable, Exception {
+ ByteBuffer buf = ByteBuffer.allocate(100);
+ EndPoint ep = getEndPoint();
+ while (true) {
+ buf.clear();
+ buf = ep.fillMessage(buf, 4, /*lengthIncludesItself*/ false);
+ buf.flip();
+ int strlen = buf.getInt();
+ String s= new String(buf.array(), 4, strlen);
+ //System.out.println ("Rcvd: " + s);
+ if (!s.startsWith("Iteration #")) {
+ ep.close();
+ break;
+ }
+ buf.position(0); // reset read pos
+ ep.write(buf); // echo.
+ if (s.endsWith("DONE")) {
+ ep.close();
+ break;
+ }
+ }
+ }
+ }
+}
View
3 test/kilim/test/TestYield.java
@@ -6,8 +6,6 @@
package kilim.test;
-import java.util.Timer;
-
import junit.framework.TestCase;
import kilim.ExitMsg;
import kilim.Mailbox;
@@ -16,7 +14,6 @@
import kilim.test.ex.ExYieldBase;
public class TestYield extends TestCase {
- Timer timer = new Timer();
public void testStackBottom_st() throws Exception {
runTask(new kilim.test.ex.ExYieldStack(0));

0 comments on commit 886fd16

Please sign in to comment.