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;
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;
Connection &connection;
ebb_request parser_info;
- private:
+
+ list<oi_buf*> output;
+ bool done;
Persistent<Object> js_object;
};
{
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)
// 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);
{
Connection *connection = static_cast<Connection*> (data);
- HttpRequest *request = new HttpRequest(*connection);
+ HttpRequest *request = connection->RequestBegin();
return &request->parser_info;
}
)
{
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
)
{
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();
}
parser_info.on_body = on_body;
parser_info.on_complete = on_request_complete;
parser_info.data = this;
+
+ done = false;
}
void
{
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;
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);
result->Set(headers_str, headers);
js_object = Persistent<Object>::New(result);
+ // weak ref?
return scope.Close(result);
}
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)
{
/* 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();
{
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());
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") );
API is only a specification and does not reflect Node's
behavior—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>
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.
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>;
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—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)]
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>
<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>
</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
<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.
+