Browse Source

working towards working keep-alive. need tests

v0.7.4-release
Ryan 16 years ago
parent
commit
b4985d1a6e
  1. 4
      Makefile
  2. 22
      http_api.js
  3. 12
      node.cc
  4. 289
      node_http.cc
  5. 137
      spec/index.html
  6. 8
      spec/specification.css
  7. 4
      test/test_http_server_echo.rb

4
Makefile

@ -1,7 +1,7 @@
EVDIR=$(HOME)/local/libev EVDIR=$(HOME)/local/libev
V8INC = $(HOME)/src/v8/include V8INC = $(HOME)/src/v8/include
#V8LIB = $(HOME)/src/v8/libv8_g.a V8LIB = $(HOME)/src/v8/libv8_g.a
V8LIB = $(HOME)/src/v8/libv8.a #V8LIB = $(HOME)/src/v8/libv8.a
CFLAGS = -g -I$(V8INC) -Ideps/oi -DHAVE_GNUTLS=0 -Ideps/ebb CFLAGS = -g -I$(V8INC) -Ideps/oi -DHAVE_GNUTLS=0 -Ideps/ebb
LDFLAGS = -lev -pthread # -lefence LDFLAGS = -lev -pthread # -lefence

22
http_api.js

@ -4,24 +4,16 @@ function encode(data) {
} }
var port = 8000; 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.respond("HTTP/1.1 200 OK\r\n");
request.onBody = function (chunk) { request.respond("Content-Length: 0\r\n");
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("\r\n"); request.respond("\r\n");
request.respond(null);
}; };

12
node.cc

@ -123,6 +123,7 @@ LogCallback (const Arguments& args)
return Undefined(); return Undefined();
} }
static Handle<Value> static Handle<Value>
BlockingFileReadCallback (const Arguments& args) BlockingFileReadCallback (const Arguments& args)
{ {
@ -164,11 +165,14 @@ main (int argc, char *argv[])
Context::Scope context_scope(context); Context::Scope context_scope(context);
Local<Object> g = Context::GetCurrent()->Global(); Local<Object> g = Context::GetCurrent()->Global();
g->Set( String::New("log"), FunctionTemplate::New(LogCallback)->GetFunction());
g->Set( String::New("blockingFileRead") g->Set ( String::New("log")
, FunctionTemplate::New(BlockingFileReadCallback)->GetFunction() , FunctionTemplate::New(LogCallback)->GetFunction()
); );
g->Set ( String::New("blockingFileRead")
, FunctionTemplate::New(BlockingFileReadCallback)->GetFunction()
);
Init_timer(g); Init_timer(g);
Init_tcp(g); Init_tcp(g);

289
node_http.cc

@ -12,6 +12,67 @@ using namespace std;
static Persistent<ObjectTemplate> request_template; 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 // globals
static Persistent<String> path_str; static Persistent<String> path_str;
static Persistent<String> uri_str; static Persistent<String> uri_str;
@ -60,27 +121,38 @@ private:
Persistent<Object> js_server; Persistent<Object> js_server;
}; };
class HttpRequest;
class Connection { class Connection {
public: public:
Connection () Connection();
{ ~Connection();
oi_socket_init (&socket, 30.0);
ebb_request_parser_init (&parser); void Parse(const void *buf, size_t count);
} void Write();
ebb_request_parser parser; HttpRequest* RequestBegin ();
void RequestEnd (HttpRequest*);
oi_socket socket; oi_socket socket;
Persistent<Function> js_onRequest; Persistent<Function> js_onrequest;
private:
ebb_request_parser parser;
list<HttpRequest*> requests;
friend class Server; friend class Server;
}; };
class HttpRequest { class HttpRequest {
public: public:
HttpRequest (Connection &c); HttpRequest (Connection &c);
/* Deleted from C++ as soon as possible.
* Javascript object might linger. This is okay
*/
~HttpRequest(); ~HttpRequest();
void MakeBodyCallback (const char *base, size_t length); void MakeBodyCallback (const char *base, size_t length);
Local<Object> CreateJSObject (); Local<Object> CreateJSObject ();
void Respond (Handle<Value> data);
string path; string path;
string query_string; string query_string;
@ -92,7 +164,9 @@ class HttpRequest {
Connection &connection; Connection &connection;
ebb_request parser_info; ebb_request parser_info;
private:
list<oi_buf*> output;
bool done;
Persistent<Object> js_object; Persistent<Object> js_object;
}; };
@ -123,38 +197,54 @@ RespondCallback (const Arguments& args)
{ {
HandleScope scope; 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()); HttpRequest* request = static_cast<HttpRequest*>(field->Value());
request->Respond(args[0]);
}
Handle<Value> arg = args[0]; void
HttpRequest::Respond (Handle<Value> data)
// 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 // TODO ByteArray ?
// one has been sent!!!
//printf("response called\n");
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"); connection.Write();
delete request; }
} 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 static void
on_path (ebb_request *req, const char *buf, size_t len) 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. // and one argument, the request.
const int argc = 1; const int argc = 1;
Handle<Value> argv[argc] = { js_request }; 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()) if(try_catch.HasCaught())
node_fatal_exception(try_catch); node_fatal_exception(try_catch);
@ -252,7 +342,7 @@ static ebb_request * on_request
{ {
Connection *connection = static_cast<Connection*> (data); Connection *connection = static_cast<Connection*> (data);
HttpRequest *request = new HttpRequest(*connection); HttpRequest *request = connection->RequestBegin();
return &request->parser_info; return &request->parser_info;
} }
@ -264,15 +354,8 @@ static void on_read
) )
{ {
Connection *connection = static_cast<Connection*> (socket->data); Connection *connection = static_cast<Connection*> (socket->data);
ebb_request_parser_execute ( &connection->parser write(1, buf, count);
// FIXME change ebb to use void* connection->Parse(buf, count);
, 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);
}
} }
static void on_close static void on_close
@ -280,27 +363,18 @@ static void on_close
) )
{ {
Connection *connection = static_cast<Connection*> (socket->data); Connection *connection = static_cast<Connection*> (socket->data);
// TODO free requests
delete connection; delete connection;
} }
static void on_drain
( oi_socket *socket
)
{
Connection *connection = static_cast<Connection*> (socket->data);
//oi_socket_close(&connection->socket);
}
HttpRequest::~HttpRequest () HttpRequest::~HttpRequest ()
{ {
//printf("request is being destructed\n"); connection.RequestEnd(this);
connection.socket.on_drain = oi_socket_close;
HandleScope scope; HandleScope scope;
// delete a reference to the respond method // delete a reference c++ HttpRequest
js_object->Delete(respond_str); js_object->SetInternalField(0, Null());
// dispose of Persistent handle so that
// it can be GC'd normally.
js_object.Dispose(); js_object.Dispose();
} }
@ -317,6 +391,8 @@ HttpRequest::HttpRequest (Connection &c) : connection(c)
parser_info.on_body = on_body; parser_info.on_body = on_body;
parser_info.on_complete = on_request_complete; parser_info.on_complete = on_request_complete;
parser_info.data = this; parser_info.data = this;
done = false;
} }
void void
@ -324,11 +400,11 @@ HttpRequest::MakeBodyCallback (const char *base, size_t length)
{ {
HandleScope handle_scope; 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); Handle<Value> onbody_val = js_object->Get(on_body_str);
if (!onBody_val->IsFunction()) return; if (!onbody_val->IsFunction()) return;
Handle<Function> onBody = Handle<Function>::Cast(onBody_val); Handle<Function> onbody = Handle<Function>::Cast(onbody_val);
TryCatch try_catch; TryCatch try_catch;
const int argc = 1; const int argc = 1;
@ -342,7 +418,7 @@ HttpRequest::MakeBodyCallback (const char *base, size_t length)
argv[0] = Null(); 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()) if(try_catch.HasCaught())
node_fatal_exception(try_catch); node_fatal_exception(try_catch);
@ -420,6 +496,7 @@ HttpRequest::CreateJSObject ()
result->Set(headers_str, headers); result->Set(headers_str, headers);
js_object = Persistent<Object>::New(result); js_object = Persistent<Object>::New(result);
// weak ref?
return scope.Close(result); return scope.Close(result);
} }
@ -438,22 +515,92 @@ on_connection (oi_server *_server, struct sockaddr *addr, socklen_t len)
return NULL; return NULL;
Connection *connection = new Connection(); 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); 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; 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 static void
server_destroy (Persistent<Value> _, void *data) server_destroy (Persistent<Value> _, void *data)
{ {
@ -498,7 +645,7 @@ Server::Stop()
/* This constructor takes 2 arguments: host, port. */ /* This constructor takes 2 arguments: host, port. */
static Handle<Value> static Handle<Value>
server_constructor (const Arguments& args) newHTTPServer (const Arguments& args)
{ {
if (args.Length() < 2) if (args.Length() < 2)
return Undefined(); return Undefined();
@ -544,7 +691,7 @@ Init_http (Handle<Object> target)
{ {
HandleScope scope; HandleScope scope;
Local<FunctionTemplate> server_t = FunctionTemplate::New(server_constructor); Local<FunctionTemplate> server_t = FunctionTemplate::New(newHTTPServer);
server_t->InstanceTemplate()->SetInternalFieldCount(1); server_t->InstanceTemplate()->SetInternalFieldCount(1);
target->Set(String::New("HTTPServer"), server_t->GetFunction()); 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") ); http_version_str = Persistent<String>::New( String::NewSymbol("http_version") );
headers_str = Persistent<String>::New( String::NewSymbol("headers") ); headers_str = Persistent<String>::New( String::NewSymbol("headers") );
on_request_str = Persistent<String>::New( String::NewSymbol("onRequest") ); on_request_str = Persistent<String>::New( String::NewSymbol("onrequest") );
on_body_str = Persistent<String>::New( String::NewSymbol("onBody") ); on_body_str = Persistent<String>::New( String::NewSymbol("onbody") );
respond_str = Persistent<String>::New( String::NewSymbol("respond") ); respond_str = Persistent<String>::New( String::NewSymbol("respond") );
copy_str = Persistent<String>::New( String::New("COPY") ); copy_str = Persistent<String>::New( String::New("COPY") );

137
spec/index.html

@ -69,10 +69,9 @@
API is only a specification and does not reflect Node's API is only a specification and does not reflect Node's
behavior&mdash;there I will try to note the difference. behavior&mdash;there I will try to note the difference.
<p>Unless otherwise noted, all functions can be considered <p>Unless otherwise noted, a function is non-blocking. Non-blocking means
non-blocking. Non-blocking means that program execution will continue that program execution will continue without waiting for an I/O event
without waiting for some I/O event (be that network or device). (be that network or device).
<h3 id=the-event-loop><span class=secno>1.1 </span>The event loop</h3> <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 running. If however there arn't any pending callbacks waiting for
something to happen, the program will exit. 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> <h3 id=execution-context><span class=secno>1.2 </span>Execution context</h3>
<p>Global data is shared between callbacks. <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>; readonly attribute String <a href="index.html#port">port</a>;
// networking // networking
attribute Function <a href="index.html#onrequest">onRequest</a>; attribute Function <a href="index.html#onrequest">onrequest</a>;
void close(); // yet not implemented void close(); // yet not implemented
};</pre> };</pre>
<p class="big-issue"> error handling? </p>
<h3 id=http_request><span class=secno>2.1 </span>Request object</h3> <h3 id=http_request><span class=secno>2.1 </span>Request object</h3>
<pre class=idl>interface <dfn id=httprequest>HTTPRequest</dfn> { <pre class=idl>interface <dfn id=httprequest>HTTPRequest</dfn> {
readonly attribute String <a href="index.html#path">path</a>; 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#fragment">fragment</a>;
readonly attribute String <a href="index.html#method">method</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 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> };</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 it represents a single HTTP request. Clients might preform HTTP
pipelining (Keep-Alive) and send multiple requests per TCP pipelining (Keep-Alive) and send multiple requests per TCP
connection&mdash;this does not affect this interface. 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> <h2 id=tcp_client><span class=secno>3 </span>TCP Client</h2>
<pre class=idl>[Constructor(in String host, in String port)] <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#onopen">onopen</a>;
attribute Function <a href="index.html#onread">onread</a>; attribute Function <a href="index.html#onread">onread</a>;
attribute Function <a href="index.html#onclose">onclose</a>; attribute Function <a href="index.html#onclose">onclose</a>;
void write(in String data); void write(in ByteArray data);
void disconnect(); void disconnect();
};</pre> };</pre>
@ -157,11 +222,12 @@ interface <dfn id=tcpclient>TCPClient</dfn> {
<dt><code>write(data)</code></dt> <dt><code>write(data)</code></dt>
<dd> <dd>
<p>Transmits data using the connection. If the connection is not yet <p>Transmits data using the connection. If the connection is not yet
established, it must raise an <code>INVALID_STATE_ERR</code> exception. 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 <p><code>write(null)</code> sends an EOF to the peer. Further writing
be executed. is disabled. However the <code>onread</code> callback may still
be executed.
</dd> </dd>
<dt><code>disconnect()</code></dt> <dt><code>disconnect()</code></dt>
@ -176,18 +242,17 @@ interface <dfn id=tcpclient>TCPClient</dfn> {
</dd> </dd>
</dl> </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 represents the state of the connection. When the object is created it must
be set to <code>CONNECTING</code>. be set to <code>CONNECTING</code>.
<p id="onopen">Once a connection is established, the <code <p id="onopen">Once a connection is established, the
>readyState</a></code> <code>readyState</a></code> attribute's value must be changed to
attribute's value must be changed to <code>OPEN</code>, and the <code>OPEN</code>, and the <code>onopen</code> callback will be made.
<code>onopen</code> callback will be made.
<p id="onread">When data is received, the <code>onread</code> callback <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 will be made with a single parameter: a <code>ByteArray</code> containing a
chunk of data. The user does not have the ability to control how much data 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. is received nor the ability to stop the input besides disconnecting.
<!-- conf crit for this <!-- conf crit for this
@ -262,9 +327,25 @@ will be made.
<dt><code>blockingFileRead(filename)</code></dt> <dt><code>blockingFileRead(filename)</code></dt>
<dd> <dd>
<p>This method opens a file from the file system and reads returns <p>This method opens a file from the file system and returns its
its contents. This function can be extremely expensive depending on the contents as a <code>ByteArray</code>. This function can be extremely
response time of the file system. It should only be used in start up. expensive depending on the response time of the file system. It should
only be used in start up.
</dd> </dd>
</dl> </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.

8
spec/specification.css

@ -182,10 +182,10 @@ body.dfnEnabled dfn { cursor: pointer; }
} }
@media screen { @media screen {
body.draft { background-image: url(/images/WD); } body.draft { background-image: url(http://whatwg.org/images/WD); }
body.cfc { background-image: url(/images/CFC); } body.cfc { background-image: url(http://whatwg.org/images/CFC); }
body.cfi { background-image: url(/images/CFI); } body.cfi { background-image: url(http://whatwg.org/images/CFI); }
body.spec { background-image: url(/images/REC); } body.spec { background-image: url(http://whatwg.org/images/REC); }
} }
@media print { @media print {

4
test/test_http_server_echo.rb

@ -34,10 +34,10 @@ function encode(data) {
var port = 8000; var port = 8000;
var server = new HTTPServer("localhost", port); var server = new HTTPServer("localhost", port);
server.onRequest = function (request) { server.onrequest = function (request) {
// onBody sends null on the last chunk. // onBody sends null on the last chunk.
request.onBody = function (chunk) { request.onbody = function (chunk) {
if(chunk) { if(chunk) {
this.respond(encode(chunk)); this.respond(encode(chunk));
} else { } else {

Loading…
Cancel
Save