working towards working keep-alive. need tests
authorRyan <ry@tinyclouds.org>
Fri, 6 Mar 2009 18:49:52 +0000 (19:49 +0100)
committerRyan <ry@tinyclouds.org>
Fri, 6 Mar 2009 18:49:52 +0000 (19:49 +0100)
Makefile
http_api.js
node.cc
node_http.cc
spec/index.html
spec/specification.css
test/test_http_server_echo.rb

index a11ea87..d4790a4 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,7 @@
 EVDIR=$(HOME)/local/libev
 V8INC = $(HOME)/src/v8/include
-#V8LIB = $(HOME)/src/v8/libv8_g.a
-V8LIB = $(HOME)/src/v8/libv8.a
+V8LIB = $(HOME)/src/v8/libv8_g.a
+#V8LIB = $(HOME)/src/v8/libv8.a
 
 CFLAGS = -g -I$(V8INC) -Ideps/oi -DHAVE_GNUTLS=0 -Ideps/ebb 
 LDFLAGS = -lev -pthread # -lefence
index 703e6aa..7eb80eb 100644 (file)
@@ -4,24 +4,16 @@ function encode(data) {
 }
 
 var port = 8000;
-var server = new HTTP.Server("localhost", port);
+var server = new HTTPServer("localhost", port);
 
-server.onRequest = function (request) {
+server.onrequest = function (request) {
+  log("path: " + request.path);
+  log("query string: " + request.query_string);
 
-  // onBody sends null on the last chunk.
-  request.onBody = function (chunk) {
-    if(chunk) { 
-      this.respond(encode(chunk));
-    } else {
-      this.respond(encode("\n"));
-      this.respond("0\r\n\r\n");
-      this.respond(null); // signals end-of-request
-    }
-  }
-  request.respond("HTTP/1.0 200 OK\r\n");
-  request.respond("Content-Type: text/plain\r\n");
-  request.respond("Transfer-Encoding: chunked\r\n");
+  request.respond("HTTP/1.1 200 OK\r\n");
+  request.respond("Content-Length: 0\r\n");
   request.respond("\r\n");
+  request.respond(null);
 };
 
 
diff --git a/node.cc b/node.cc
index 00ccc5f..e412676 100644 (file)
--- a/node.cc
+++ b/node.cc
@@ -123,6 +123,7 @@ LogCallback (const Arguments& args)
   return Undefined();
 }
 
+
 static Handle<Value>
 BlockingFileReadCallback (const Arguments& args)
 {
@@ -164,11 +165,14 @@ main (int argc, char *argv[])
   Context::Scope context_scope(context);
 
   Local<Object> g = Context::GetCurrent()->Global();
-  g->Set( String::New("log"), FunctionTemplate::New(LogCallback)->GetFunction());
 
-  g->Set( String::New("blockingFileRead")
-        , FunctionTemplate::New(BlockingFileReadCallback)->GetFunction()
-        );
+  g->Set ( String::New("log")
+         , FunctionTemplate::New(LogCallback)->GetFunction()
+         );
+
+  g->Set ( String::New("blockingFileRead")
+         , FunctionTemplate::New(BlockingFileReadCallback)->GetFunction()
+         );
 
   Init_timer(g);
   Init_tcp(g);
index ccf615c..0c28948 100644 (file)
@@ -12,6 +12,67 @@ using namespace std;
 
 static Persistent<ObjectTemplate> request_template;
 
+static string status_lines[] = 
+  { "100 Continue"
+  , "101 Switching Protocols"
+#define LEVEL_100  1
+  , "200 OK"
+  , "201 Created"
+  , "202 Accepted"
+  , "203 Non-Authoritative Information" 
+  , "204 No Content"
+  , "205 Reset Content" 
+  , "206 Partial Content"
+  , "207 Multi-Status"
+#define LEVEL_200  7
+  , "300 Multiple Choices"
+  , "301 Moved Permanently"
+  , "302 Moved Temporarily"
+  , "303 See Other" 
+  , "304 Not Modified"
+  , "305 Use Proxy" 
+  , "306 unused"
+  , "307 Temporary Redirect" 
+#define LEVEL_300  7
+  , "400 Bad Request"
+  , "401 Unauthorized"
+  , "402 Payment Required"
+  , "403 Forbidden"
+  , "404 Not Found"
+  , "405 Not Allowed"
+  , "406 Not Acceptable"
+  , "407 Proxy Authentication Required" 
+  , "408 Request Time-out"
+  , "409 Conflict"
+  , "410 Gone"
+  , "411 Length Required"
+  , "412 Precondition Failed"
+  , "413 Request Entity Too Large"
+  , "414 Request-URI Too Large"
+  , "415 Unsupported Media Type"
+  , "416 Requested Range Not Satisfiable"
+  , "417 Expectation Failed" 
+  , "418 unused"
+  , "419 unused" 
+  , "420 unused" 
+  , "421 unused" 
+  , "422 Unprocessable Entity"
+  , "423 Locked"
+  , "424 Failed Dependency" 
+#define LEVEL_400  24
+  , "500 Internal Server Error"
+  , "501 Method Not Implemented"
+  , "502 Bad Gateway"
+  , "503 Service Temporarily Unavailable"
+  , "504 Gateway Time-out"
+  , "505 HTTP Version Not Supported"
+  , "506 Variant Also Negotiates" 
+  , "507 Insufficient Storage"
+  , "508 unused"
+  , "509 unused"
+  , "510 Not Extended" 
+  };
+
 // globals
 static Persistent<String> path_str; 
 static Persistent<String> uri_str; 
@@ -60,27 +121,38 @@ private:
   Persistent<Object> js_server;
 };
 
+class HttpRequest;
+
 class Connection {
 public:
-  Connection ()
-  {
-    oi_socket_init (&socket, 30.0);
-    ebb_request_parser_init (&parser);
-  }
-  ebb_request_parser parser;
+  Connection();
+  ~Connection();
+
+  void Parse(const void *buf, size_t count);
+  void Write();
+  HttpRequest* RequestBegin ();
+  void         RequestEnd   (HttpRequest*);
+
   oi_socket socket;
-  Persistent<Function> js_onRequest;
+  Persistent<Function> js_onrequest;
 
+private:
+  ebb_request_parser parser;
+  list<HttpRequest*> requests;
   friend class Server;
 };
 
 class HttpRequest {
  public:
   HttpRequest (Connection &c);
+  /* Deleted from C++ as soon as possible.
+   * Javascript object might linger. This is okay
+   */
   ~HttpRequest();
 
   void MakeBodyCallback (const char *base, size_t length);
   Local<Object> CreateJSObject ();
+  void Respond (Handle<Value> data);
 
   string path;
   string query_string;
@@ -92,7 +164,9 @@ class HttpRequest {
 
   Connection &connection;
   ebb_request parser_info;
- private:
+
+  list<oi_buf*> output;
+  bool done;
   Persistent<Object> js_object;
 };
 
@@ -123,38 +197,54 @@ RespondCallback (const Arguments& args)
 {
   HandleScope scope;
 
-  Handle<External> field = Handle<External>::Cast(args.Holder()->GetInternalField(0));
+  // TODO check that args.Holder()->GetInternalField(0)
+  // is not NULL if so raise INVALID_STATE_ERR
 
+  Handle<External> field = Handle<External>::Cast(args.Holder()->GetInternalField(0));
   HttpRequest* request = static_cast<HttpRequest*>(field->Value());
+  request->Respond(args[0]);
+}
 
-  Handle<Value> arg = args[0];
-
-  // TODO Make sure that we write reponses in the correct order. With
-  // keep-alive it's possible that one response can return before the last
-  // one has been sent!!!
-  
-  //printf("response called\n");
+void
+HttpRequest::Respond (Handle<Value> data)
+{
+  // TODO ByteArray ?
 
-  if(arg == Null()) {
+  if(data == Null()) {
+    done = true;
+  } else {
+    Handle<String> s = data->ToString();
+    oi_buf *buf = oi_buf_new2(s->Length());
+    s->WriteAscii(buf->base, 0, s->Length());
+    output.push_back(buf);
+  }
 
-    //printf("response got null\n");
-    delete request; 
+  connection.Write();
+}
 
-  } else {
+/*
+static Handle<Value>
+RespondHeadersCallback (const Arguments& args) 
+{
+  HandleScope scope;
 
-    Handle<String> s = arg->ToString();
+  int status = args[0]->IntegerValue();
+  Local<Array> headers = Local<Array>::Cast(args[1]);
 
-    //printf("response called len %d\n", s->Length());
+  for(int i = 0; i < headers->Length(); i++) {
+    Local<Value> v = headers->Get(i);
+    Local<Array> pair = Local<Array>::Cast(v);
+    if(pair->Length() != 2) {
+      assert(0); //error
+    }
 
-    oi_buf *buf = oi_buf_new2(s->Length());
-    s->WriteAscii(buf->base, 0, s->Length());
 
-    oi_socket_write(&request->connection.socket, buf);
 
   }
-
-  return Undefined();
+  
 }
+*/
+
 
 static void
 on_path (ebb_request *req, const char *buf, size_t len)
@@ -224,7 +314,7 @@ on_headers_complete (ebb_request *req)
   // and one argument, the request.
   const int argc = 1;
   Handle<Value> argv[argc] = { js_request };
-  Handle<Value> r = request->connection.js_onRequest->Call(Context::GetCurrent()->Global(), argc, argv);
+  Handle<Value> r = request->connection.js_onrequest->Call(Context::GetCurrent()->Global(), argc, argv);
 
   if(try_catch.HasCaught())
     node_fatal_exception(try_catch);
@@ -252,7 +342,7 @@ static ebb_request * on_request
 {
   Connection *connection = static_cast<Connection*> (data);
 
-  HttpRequest *request = new HttpRequest(*connection);
+  HttpRequest *request = connection->RequestBegin();
   
   return &request->parser_info;
 }
@@ -264,15 +354,8 @@ static void on_read
   )
 {
   Connection *connection = static_cast<Connection*> (socket->data);
-  ebb_request_parser_execute ( &connection->parser
-                             // FIXME change ebb to use void*
-                             , static_cast<const char*> (buf) 
-                             , count
-                             );
-  if(ebb_request_parser_has_error(&connection->parser)) {
-    fprintf(stderr, "parse error closing connection\n");
-    oi_socket_close(&connection->socket);
-  }
+  write(1, buf, count);
+  connection->Parse(buf, count);  
 }
 
 static void on_close 
@@ -280,27 +363,18 @@ static void on_close
   )
 {
   Connection *connection = static_cast<Connection*> (socket->data);
-  // TODO free requests
   delete connection;
 }
 
-static void on_drain 
-  ( oi_socket *socket
-  )
-{
-  Connection *connection = static_cast<Connection*> (socket->data);
-  //oi_socket_close(&connection->socket);
-}
-
 HttpRequest::~HttpRequest ()
 {
-  //printf("request is being destructed\n");
-
-  connection.socket.on_drain = oi_socket_close;
+  connection.RequestEnd(this);
 
   HandleScope scope;
-  // delete a reference to the respond method
-  js_object->Delete(respond_str);
+  // delete a reference c++ HttpRequest
+  js_object->SetInternalField(0, Null());
+  // dispose of Persistent handle so that 
+  // it can be GC'd normally.
   js_object.Dispose();
 }
 
@@ -317,6 +391,8 @@ HttpRequest::HttpRequest (Connection &c) : connection(c)
   parser_info.on_body             = on_body;
   parser_info.on_complete         = on_request_complete;
   parser_info.data                = this;
+
+  done = false;
 }
 
 void
@@ -324,11 +400,11 @@ HttpRequest::MakeBodyCallback (const char *base, size_t length)
 {
   HandleScope handle_scope;
   // 
-  // XXX don't always allocate onBody strings
+  // XXX don't always allocate onbody strings
   //
-  Handle<Value> onBody_val = js_object->Get(on_body_str);  
-  if (!onBody_val->IsFunction()) return;
-  Handle<Function> onBody = Handle<Function>::Cast(onBody_val);
+  Handle<Value> onbody_val = js_object->Get(on_body_str);  
+  if (!onbody_val->IsFunction()) return;
+  Handle<Function> onbody = Handle<Function>::Cast(onbody_val);
 
   TryCatch try_catch;
   const int argc = 1;
@@ -342,7 +418,7 @@ HttpRequest::MakeBodyCallback (const char *base, size_t length)
     argv[0] = Null();
   }
 
-  Handle<Value> result = onBody->Call(js_object, argc, argv);
+  Handle<Value> result = onbody->Call(js_object, argc, argv);
 
   if(try_catch.HasCaught())
     node_fatal_exception(try_catch);
@@ -420,6 +496,7 @@ HttpRequest::CreateJSObject ()
   result->Set(headers_str, headers);
 
   js_object = Persistent<Object>::New(result);
+  // weak ref?
 
   return scope.Close(result);
 }
@@ -438,22 +515,92 @@ on_connection (oi_server *_server, struct sockaddr *addr, socklen_t len)
     return NULL;
 
   Connection *connection = new Connection();
-    connection->socket.on_read    = on_read;
-    connection->socket.on_error   = NULL;
-    connection->socket.on_close   = on_close;
-    connection->socket.on_timeout = NULL;
-    connection->socket.on_drain   = on_drain;
-    connection->socket.data       = connection;
-
-    connection->parser.new_request = on_request;
-    connection->parser.data        = connection;
 
   Handle<Function> f = Handle<Function>::Cast(callback_v);
-  connection->js_onRequest = Persistent<Function>::New(f);
+  connection->js_onrequest = Persistent<Function>::New(f);
 
   return &connection->socket;
 }
 
+Connection::Connection ()
+{
+  oi_socket_init (&socket, 30.0);
+    socket.on_read    = on_read;
+    socket.on_error   = NULL;
+    socket.on_close   = on_close;
+    socket.on_timeout = on_close;
+    socket.on_drain   = NULL;
+    socket.data       = this;
+
+  ebb_request_parser_init (&parser);
+    parser.new_request = on_request;
+    parser.data        = this;
+}
+
+Connection::~Connection ()
+{
+  list<HttpRequest*>::iterator i = requests.begin();
+  while(i != requests.end()) {
+    delete *i; // this will call RequestEnd()
+  }
+}
+
+void
+Connection::Parse(const void *buf, size_t count)
+{
+  // FIXME change ebb_request_parser to use void* arg
+  ebb_request_parser_execute ( &parser
+                             , static_cast<const char*> (buf) 
+                             , count
+                             );
+
+  if(ebb_request_parser_has_error(&parser)) {
+    fprintf(stderr, "parse error closing connection\n");
+    oi_socket_close(&socket);
+  }
+}
+
+HttpRequest *
+Connection::RequestBegin( )
+{
+  HttpRequest *request = new HttpRequest(*this);
+  requests.push_back(request);
+  return request;
+}
+
+void
+Connection::RequestEnd(HttpRequest *request)
+{
+  requests.remove(request);
+}
+
+void
+Connection::Write ( ) 
+{
+  if(requests.size() == 0)
+    return;
+
+  HttpRequest *request = requests.front(); 
+
+  while(request->output.size() > 0) {
+    oi_buf *buf = request->output.front();
+    oi_socket_write(&socket, buf);
+    request->output.pop_front();
+  }
+
+  if(request->done) {
+    if(!ebb_request_should_keep_alive(&request->parser_info)) {
+      printf("not keep-alive closing\n");
+      socket.on_drain = oi_socket_close;
+    } else {
+      printf("keep-alive\n");
+    }
+    requests.pop_front();
+    delete request;
+    Write();
+  }
+}
+
 static void
 server_destroy (Persistent<Value> _, void *data)
 {
@@ -498,7 +645,7 @@ Server::Stop()
 
 /* This constructor takes 2 arguments: host, port. */
 static Handle<Value>
-server_constructor (const Arguments& args) 
+newHTTPServer (const Arguments& args) 
 {
   if (args.Length() < 2)
     return Undefined();
@@ -544,7 +691,7 @@ Init_http (Handle<Object> target)
 {
   HandleScope scope;
 
-  Local<FunctionTemplate> server_t = FunctionTemplate::New(server_constructor);
+  Local<FunctionTemplate> server_t = FunctionTemplate::New(newHTTPServer);
   server_t->InstanceTemplate()->SetInternalFieldCount(1);
 
   target->Set(String::New("HTTPServer"), server_t->GetFunction());
@@ -557,8 +704,8 @@ Init_http (Handle<Object> target)
   http_version_str = Persistent<String>::New( String::NewSymbol("http_version") );
   headers_str      = Persistent<String>::New( String::NewSymbol("headers") );
 
-  on_request_str = Persistent<String>::New( String::NewSymbol("onRequest") );
-  on_body_str    = Persistent<String>::New( String::NewSymbol("onBody") );
+  on_request_str = Persistent<String>::New( String::NewSymbol("onrequest") );
+  on_body_str    = Persistent<String>::New( String::NewSymbol("onbody") );
   respond_str    = Persistent<String>::New( String::NewSymbol("respond") );
 
   copy_str      = Persistent<String>::New( String::New("COPY") );
index 0b88b57..cbb0645 100644 (file)
   API is only a specification and does not reflect Node's
   behavior&mdash;there I will try to note the difference. 
   
-  <p>Unless otherwise noted, all functions can be considered
-  non-blocking. Non-blocking means that program execution will continue
-  without waiting for some I/O event (be that network or device).
-
+  <p>Unless otherwise noted, a function is non-blocking. Non-blocking means
+  that program execution will continue without waiting for an I/O event
+  (be that network or device).
   
 
   <h3 id=the-event-loop><span class=secno>1.1 </span>The event loop</h3>
@@ -82,9 +81,6 @@
   running. If however there arn't any pending callbacks waiting for
   something to happen, the program will exit. 
 
-  <p>Only one callback is executed at a time. 
-
-
   <h3 id=execution-context><span class=secno>1.2 </span>Execution context</h3>
 
   <p>Global data is shared between callbacks. 
@@ -99,10 +95,13 @@ interface <dfn id=httpserver>HTTPServer</dfn>  {
   readonly attribute String <a href="index.html#port">port</a>;
 
   // networking                
-    attribute Function <a href="index.html#onrequest">onRequest</a>;
+    attribute Function <a href="index.html#onrequest">onrequest</a>;
   void close(); // yet not implemented
 };</pre>
 
+  <p class="big-issue"> error handling? </p>
+
+
   <h3 id=http_request><span class=secno>2.1 </span>Request object</h3>
   <pre class=idl>interface <dfn id=httprequest>HTTPRequest</dfn>  {
   readonly attribute String <a href="index.html#path">path</a>;
@@ -111,19 +110,85 @@ interface <dfn id=httpserver>HTTPServer</dfn>  {
   readonly attribute String <a href="index.html#fragment">fragment</a>;
   readonly attribute String <a href="index.html#method">method</a>;
   readonly attribute String <a href="index.html#http_version">http_version</a>;
+  readonly attribute Array <a href="index.html#headers">headers</a>;
 
-  readonly attribute Object <a href="index.html#headers">headers</a>;
+  // ready state
+  const unsigned short HEADERS_RECEIVED = 0;
+  const unsigned short LOADING = 1;
+  const unsigned short DONE = 2;
+  readonly attribute long readyState;
 
-    attribute Function <a href="index.html#onBody">onBody</a>;
+    attribute Function <a href="index.html#onbody">onbody</a>;
 
-  void respond(in String data);
+  void respondHeader (in short status, in Array headers);
+  void respondBody (in ByteArray data);
 };</pre>
 
-  <p> A request object is what is passed to <code>HTTPServer.onRequest</code>. 
+  <p class="big-issue">issue: client ip address</p>
+
+  <p> A request object is what is passed to <code>HTTPServer.onrequest</code>. 
       it represents a single HTTP request. Clients might preform HTTP
       pipelining (Keep-Alive) and send multiple requests per TCP
       connection&mdash;this does not affect this interface.
+
+  <p> If any error is encountered either with the request or while using the
+      two response methods the connection to client immediately terminated.
+
+  <dl>
+    <dt><code>respondHeader(status, headers)</code></dt>
+    <dd>
+      <p>This method sends the response status line and headers.
+         This method may only be called once. After the first, calling it
+         will raise an <code>INVALID_STATE_ERR</code> exception.
+      
+      <p>The <code>status</code> argument is an integer HTTP status response code as
+         defined in 6.1 of <a href="#rfc2616">RFC 2616</a>.
+      
+      <p>The <code>header</code> argument is an <code>Array</code> of
+      tuples (a two-element <code>Array</code>). For example
+
+      <pre>[["Content-Type", "text/plain"], ["Content-Length", 10]]</pre>
+
+      <p>This array determines the response headers. If the
+        <code>header</code> parameter includes elements that are not tuples it
+        raises <code>SYNTAX_ERR</code>. If the elements of the tuples do not
+        respond to <code>toString()</code> the method raises
+        <code>SYNTAX_ERR</code>. 
+      
+      <p>Besides the author response headers interpreters should not
+        include additional response headers.  This ensures that authors 
+        have a reasonably predictable API. 
+
+      <p>If the client connection was closed for any reason, calling
+      <code>respondHeader()</code> will raise a <code>NETWORK_ERR</code>
+      exception.
+    </dd>
+
+    <dt><code>respondBody(data)</code></dt>
+    <dd>
+      <p>This method must be called after <code>respondHeader()</code>. If
+        <code>respondHeader()</code> has not been called it will raise an
+        <code>INVALID_STATE_ERR</code> exception.
+
+      <p>When given a <code>String</code> or <code>ByteArray</code> the 
+         interpreter will send the data.
+
+      <p>Given a <code>null</code> argument signals end-of-response.
+
+      <p class="note"> The author must call <code>respondBody(null)</code>
+      for each response, even if the response has no body.</p>
+
+      <p>After the end-of-response, calling <code>respondHeader()</code> or
+      <code>respondBody()</code> will raise an <code>INVALID_STATE_ERR</code> exception.
+
+      <p>If the client connection was closed for any reason, calling
+      <code>respondBody()</code> will raise a <code>NETWORK_ERR</code>
+      exception.
+
+     </dd>
+
     
+  </dl>
 
   <h2 id=tcp_client><span class=secno>3 </span>TCP Client</h2>
   <pre class=idl>[Constructor(in String host, in String port)]
@@ -141,7 +206,7 @@ interface <dfn id=tcpclient>TCPClient</dfn>  {
     attribute Function <a href="index.html#onopen">onopen</a>;
     attribute Function <a href="index.html#onread">onread</a>;
     attribute Function <a href="index.html#onclose">onclose</a>;
-  void write(in String data);
+  void write(in ByteArray data);
   void disconnect();           
 };</pre>
 
@@ -157,11 +222,12 @@ interface <dfn id=tcpclient>TCPClient</dfn>  {
     <dt><code>write(data)</code></dt>
     <dd>
       <p>Transmits data using the connection. If the connection is not yet
-       established, it must raise an <code>INVALID_STATE_ERR</code> exception. 
-
-       <p><code>write(null)</code> sends an EOF to the peer. Further writing
-       is disabled. However the <code>onread</code> callback may still
-       be executed.
+      established or the connection is closed, calling <code>write()</code>
+      will raise an <code>INVALID_STATE_ERR</code> exception. </p>
+      
+      <p><code>write(null)</code> sends an EOF to the peer. Further writing
+      is disabled. However the <code>onread</code> callback may still
+      be executed.
     </dd>
 
     <dt><code>disconnect()</code></dt>
@@ -176,18 +242,17 @@ interface <dfn id=tcpclient>TCPClient</dfn>  {
     </dd>
   </dl>
 
-  </p><p>The <dfn id="readystate0"><code>readyState</code></dfn> attribute
+  <p>The <dfn id="readystate0"><code>readyState</code></dfn> attribute
    represents the state of the connection. When the object is created it must
    be set to <code>CONNECTING</code>.
 
-  <p id="onopen">Once a connection is established, the <code
->readyState</a></code>
-attribute's value must be changed to <code>OPEN</code>, and the
-<code>onopen</code> callback will be made.
+  <p id="onopen">Once a connection is established, the
+  <code>readyState</a></code> attribute's value must be changed to
+  <code>OPEN</code>, and the <code>onopen</code> callback will be made.
 
   <p id="onread">When data is received, the <code>onread</code> callback
-  will be made with a single parameter: a <code>String</code> containing a
-  chunk of data. The user does not have the ability to control how much data
+  will be made with a single parameter: a <code>ByteArray</code> containing a
+  chunk of data. The author does not have the ability to control how much data
   is received nor the ability to stop the input besides disconnecting.
 
   <!-- conf crit for this
@@ -262,9 +327,25 @@ will be made.
   
     <dt><code>blockingFileRead(filename)</code></dt>
     <dd>
-    <p>This method opens a file from the file system and reads returns 
-    its contents. This function can be extremely expensive depending on the
-    response time of the file system.  It should only be used in start up.
+      <p>This method opens a file from the file system and returns its
+      contents as a <code>ByteArray</code>. This function can be extremely
+      expensive depending on the response time of the file system.  It should
+      only be used in start up.
     </dd>
 
   </dl>
+
+
+
+  <h2 class=no-num id=references>References</h2>
+
+
+  <dl>
+
+     <dt>[<dfn id=rfc2616>RFC2616</dfn>]
+
+     <dd><cite><a href="http://ietf.org/rfc/rfc2616">Hypertext Transfer
+         Protocol -- HTTP/1.1</a></cite>, R. Fielding, J. Gettys, J. Mogul,
+         H.  Frystyk, L. Masinter, P. Leach, T. Berners-Lee, editors.  IETF,
+         June 1999.
+
index f99f38a..c7073f0 100644 (file)
@@ -182,10 +182,10 @@ body.dfnEnabled dfn { cursor: pointer; }
 }
 
 @media screen {
-  body.draft { background-image: url(/images/WD); }
-  body.cfc { background-image: url(/images/CFC); }
-  body.cfi { background-image: url(/images/CFI); }
-  body.spec { background-image: url(/images/REC); }
+  body.draft { background-image: url(http://whatwg.org/images/WD); }
+  body.cfc { background-image: url(http://whatwg.org/images/CFC); }
+  body.cfi { background-image: url(http://whatwg.org/images/CFI); }
+  body.spec { background-image: url(http://whatwg.org/images/REC); }
 }
 
 @media print {
index 1d3e1e3..884ccb8 100755 (executable)
@@ -34,10 +34,10 @@ function encode(data) {
 var port = 8000;
 var server = new HTTPServer("localhost", port);
 
-server.onRequest = function (request) {
+server.onrequest = function (request) {
 
   // onBody sends null on the last chunk.
-  request.onBody = function (chunk) {
+  request.onbody = function (chunk) {
     if(chunk) { 
       this.respond(encode(chunk));
     } else {