Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Implement new http-parser binding using Buffer

  • Loading branch information...
commit 42ee16978e81a0f1fba0768e163b5e9584178fa3 1 parent 6f738d6
@ry ry authored
View
2  src/node.cc
@@ -19,6 +19,7 @@
#include <node_file.h>
#include <node_idle_watcher.h>
#include <node_http.h>
+#include <node_http_parser.h>
#include <node_signal_handler.h>
#include <node_stat.h>
#include <node_timer.h>
@@ -996,6 +997,7 @@ static Local<Object> Load(int argc, char *argv[]) {
SignalHandler::Initialize(process); // signal_handler.cc
InitNet2(process); // net2.cc
+ InitHttpParser(process); // http_parser.cc
Stdio::Initialize(process); // stdio.cc
ChildProcess::Initialize(process); // child_process.cc
View
324 src/node_http_parser.cc
@@ -0,0 +1,324 @@
+#include <node_http_parser.h>
+
+#include <v8.h>
+#include <node.h>
+#include <node_buffer.h>
+
+#include <http_parser.h>
+
+#include <strings.h> /* strcasecmp() */
+
+// This is a binding to http_parser (http://github.com/ry/http-parser)
+// The goal is to decouple sockets from parsing for more javascript-level
+// agility. A Buffer is read from a socket and passed to parser.execute().
+// The parser then issues callbacks with slices of the data
+// parser.onMessageBegin
+// parser.onPath
+// parser.onBody
+// ...
+// No copying is performed when slicing the buffer, only small reference
+// allocations.
+
+namespace node {
+
+using namespace v8;
+
+static Persistent<String> on_message_begin_sym;
+static Persistent<String> on_path_sym;
+static Persistent<String> on_query_string_sym;
+static Persistent<String> on_url_sym;
+static Persistent<String> on_fragment_sym;
+static Persistent<String> on_header_field_sym;
+static Persistent<String> on_header_value_sym;
+static Persistent<String> on_headers_complete_sym;
+static Persistent<String> on_body_sym;
+static Persistent<String> on_message_complete_sym;
+
+static Persistent<String> delete_sym;
+static Persistent<String> get_sym;
+static Persistent<String> head_sym;
+static Persistent<String> post_sym;
+static Persistent<String> put_sym;
+static Persistent<String> connect_sym;
+static Persistent<String> options_sym;
+static Persistent<String> trace_sym;
+static Persistent<String> copy_sym;
+static Persistent<String> lock_sym;
+static Persistent<String> mkcol_sym;
+static Persistent<String> move_sym;
+static Persistent<String> propfind_sym;
+static Persistent<String> proppatch_sym;
+static Persistent<String> unlock_sym;
+static Persistent<String> unknown_method_sym;
+
+static Persistent<String> method_sym;
+static Persistent<String> status_code_sym;
+static Persistent<String> http_version_sym;
+static Persistent<String> version_major_sym;
+static Persistent<String> version_minor_sym;
+static Persistent<String> should_keep_alive_sym;
+
+// Callback prototype for http_cb
+#define DEFINE_HTTP_CB(name) \
+ static int name(http_parser *p) { \
+ Parser *parser = static_cast<Parser*>(p->data); \
+ \
+ HandleScope scope; \
+ \
+ Local<Value> cb_value = parser->handle_->Get(name##_sym); \
+ if (!cb_value->IsFunction()) return 0; \
+ Local<Function> cb = Local<Function>::Cast(cb_value); \
+ \
+ Local<Value> ret = cb->Call(parser->handle_, 0, NULL); \
+ return ret.IsEmpty() ? -1 : 0; \
+ }
+
+// Callback prototype for http_data_cb
+#define DEFINE_HTTP_DATA_CB(name) \
+ static int name(http_parser *p, const char *at, size_t length) { \
+ Parser *parser = static_cast<Parser*>(p->data); \
+ \
+ HandleScope scope; \
+ \
+ assert(parser->buffer_); \
+ char * base = buffer_p(parser->buffer_, 0); \
+ \
+ Local<Value> cb_value = parser->handle_->Get(name##_sym); \
+ if (!cb_value->IsFunction()) return 0; \
+ Local<Function> cb = Local<Function>::Cast(cb_value); \
+ \
+ Local<Integer> off = Integer::New(at - base); \
+ Local<Integer> len = Integer::New(length); \
+ Local<Value> argv[2] = { off, len }; \
+ \
+ Local<Value> ret = cb->Call(parser->handle_, 2, argv); \
+ return ret.IsEmpty() ? -1 : 0; \
+ }
+
+
+static inline Persistent<String>
+method_to_str(enum http_method m) {
+ switch (m) {
+ case HTTP_DELETE: return delete_sym;
+ case HTTP_GET: return get_sym;
+ case HTTP_HEAD: return head_sym;
+ case HTTP_POST: return post_sym;
+ case HTTP_PUT: return put_sym;
+ case HTTP_CONNECT: return connect_sym;
+ case HTTP_OPTIONS: return options_sym;
+ case HTTP_TRACE: return trace_sym;
+ case HTTP_COPY: return copy_sym;
+ case HTTP_LOCK: return lock_sym;
+ case HTTP_MKCOL: return mkcol_sym;
+ case HTTP_MOVE: return move_sym;
+ case HTTP_PROPFIND: return propfind_sym;
+ case HTTP_PROPPATCH: return proppatch_sym;
+ case HTTP_UNLOCK: return unlock_sym;
+ default: return unknown_method_sym;
+ }
+}
+
+
+class Parser : public ObjectWrap {
+ public:
+ Parser(enum http_parser_type type) : ObjectWrap() {
+ buffer_ = NULL;
+
+ http_parser_init(&parser_, type);
+
+ parser_.on_message_begin = on_message_begin;
+ parser_.on_path = on_path;
+ parser_.on_query_string = on_query_string;
+ parser_.on_url = on_url;
+ parser_.on_fragment = on_fragment;
+ parser_.on_header_field = on_header_field;
+ parser_.on_header_value = on_header_value;
+ parser_.on_headers_complete = on_headers_complete;
+ parser_.on_body = on_body;
+ parser_.on_message_complete = on_message_complete;
+
+ parser_.data = this;
+ }
+
+ DEFINE_HTTP_CB(on_message_begin)
+ DEFINE_HTTP_CB(on_message_complete)
+
+ DEFINE_HTTP_DATA_CB(on_path)
+ DEFINE_HTTP_DATA_CB(on_url)
+ DEFINE_HTTP_DATA_CB(on_fragment)
+ DEFINE_HTTP_DATA_CB(on_query_string)
+ DEFINE_HTTP_DATA_CB(on_header_field)
+ DEFINE_HTTP_DATA_CB(on_header_value)
+ DEFINE_HTTP_DATA_CB(on_body)
+
+ static int on_headers_complete(http_parser *p) {
+ Parser *parser = static_cast<Parser*>(p->data);
+
+ HandleScope scope;
+
+ Local<Value> cb_value = parser->handle_->Get(on_headers_complete_sym);
+ if (!cb_value->IsFunction()) return 0;
+ Local<Function> cb = Local<Function>::Cast(cb_value);
+
+
+ Local<Object> message_info = Object::New();
+
+ // METHOD
+ if (p->type == HTTP_REQUEST) {
+ message_info->Set(method_sym, method_to_str(p->method));
+ }
+
+ // STATUS
+ if (p->type == HTTP_RESPONSE) {
+ message_info->Set(status_code_sym, Integer::New(p->status_code));
+ }
+
+ // VERSION
+ message_info->Set(version_major_sym, Integer::New(p->http_major));
+ message_info->Set(version_minor_sym, Integer::New(p->http_minor));
+
+ message_info->Set(should_keep_alive_sym,
+ http_should_keep_alive(p) ? True() : False());
+
+ Local<Value> argv[1] = { message_info };
+
+ Local<Value> ret = cb->Call(parser->handle_, 1, argv);
+ return ret.IsEmpty() ? -1 : 0;
+ }
+
+ static Handle<Value> New(const Arguments& args) {
+ HandleScope scope;
+
+ String::Utf8Value type(args[0]->ToString());
+
+ Parser *parser;
+
+ if (0 == strcasecmp(*type, "request")) {
+ parser = new Parser(HTTP_REQUEST);
+ } else if (0 == strcasecmp(*type, "response")) {
+ parser = new Parser(HTTP_RESPONSE);
+ } else {
+ return ThrowException(Exception::Error(
+ String::New("Constructor argument be 'request' or 'response'")));
+ }
+
+ parser->Wrap(args.This());
+
+ return args.This();
+ }
+
+ // var bytesParsed = parser->execute(buffer, off, len);
+ static Handle<Value> Execute(const Arguments& args) {
+ HandleScope scope;
+
+ Parser *parser = ObjectWrap::Unwrap<Parser>(args.This());
+
+ if (parser->buffer_) {
+ return ThrowException(Exception::TypeError(
+ String::New("Already parsing a buffer")));
+ }
+
+ if (!IsBuffer(args[0])) {
+ return ThrowException(Exception::TypeError(
+ String::New("Argument should be a buffer")));
+ }
+
+ struct buffer * buffer = BufferUnwrap(args[0]);
+
+ size_t off = args[1]->Int32Value();
+ if (buffer_p(buffer, off) == NULL) {
+ return ThrowException(Exception::Error(
+ String::New("Offset is out of bounds")));
+ }
+
+ size_t len = args[2]->Int32Value();
+ if (buffer_remaining(buffer, off) < len) {
+ return ThrowException(Exception::Error(
+ String::New("Length is extends beyond buffer")));
+ }
+
+ TryCatch try_catch;
+
+ // Assign 'buffer_' while we parse. The callbacks will access that varible.
+ parser->buffer_ = buffer;
+
+ size_t nparsed =
+ http_parser_execute(&(parser->parser_), buffer_p(buffer, off), len);
+
+ // Unassign the 'buffer_' variable
+ assert(parser->buffer_);
+ parser->buffer_ = NULL;
+
+ // If there was an exception in one of the callbacks
+ if (try_catch.HasCaught()) return try_catch.ReThrow();
+
+ Local<Integer> nparsed_obj = Integer::New(nparsed);
+ // If there was a parse error in one of the callbacks
+ // TODO What if there is an error on EOF?
+ if (nparsed != len) {
+ Local<Value> e = Exception::Error(String::New("Parse Error"));
+ Local<Object> obj = e->ToObject();
+ obj->Set(String::NewSymbol("bytesParsed"), nparsed_obj);
+ return ThrowException(e);
+ }
+
+ return scope.Close(nparsed_obj);
+ }
+
+
+ private:
+
+ http_parser parser_;
+ struct buffer * buffer_; // The buffer currently being parsed.
+};
+
+
+void InitHttpParser(Handle<Object> target) {
+ HandleScope scope;
+
+ Local<FunctionTemplate> t = FunctionTemplate::New(Parser::New);
+ t->InstanceTemplate()->SetInternalFieldCount(1);
+ //t->SetClassName(String::NewSymbol("HTTPParser"));
+
+ NODE_SET_PROTOTYPE_METHOD(t, "execute", Parser::Execute);
+
+ target->Set(String::NewSymbol("HTTPParser"), t->GetFunction());
+
+ on_message_begin_sym = NODE_PSYMBOL("onMessageBegin");
+ on_path_sym = NODE_PSYMBOL("onPath");
+ on_query_string_sym = NODE_PSYMBOL("onQueryString");
+ on_url_sym = NODE_PSYMBOL("onURL");
+ on_fragment_sym = NODE_PSYMBOL("onFragment");
+ on_header_field_sym = NODE_PSYMBOL("onHeaderField");
+ on_header_value_sym = NODE_PSYMBOL("onHeaderValue");
+ on_headers_complete_sym = NODE_PSYMBOL("onHeadersComplete");
+ on_body_sym = NODE_PSYMBOL("onBody");
+ on_message_complete_sym = NODE_PSYMBOL("onMessageComplete");
+
+ delete_sym = NODE_PSYMBOL("DELETE");
+ get_sym = NODE_PSYMBOL("GET");
+ head_sym = NODE_PSYMBOL("HEAD");
+ post_sym = NODE_PSYMBOL("POST");
+ put_sym = NODE_PSYMBOL("PUT");
+ connect_sym = NODE_PSYMBOL("CONNECT");
+ options_sym = NODE_PSYMBOL("OPTIONS");
+ trace_sym = NODE_PSYMBOL("TRACE");
+ copy_sym = NODE_PSYMBOL("COPY");
+ lock_sym = NODE_PSYMBOL("LOCK");
+ mkcol_sym = NODE_PSYMBOL("MKCOL");
+ move_sym = NODE_PSYMBOL("MOVE");
+ propfind_sym = NODE_PSYMBOL("PROPFIND");
+ proppatch_sym = NODE_PSYMBOL("PROPPATCH");
+ unlock_sym = NODE_PSYMBOL("UNLOCK");
+ unknown_method_sym = NODE_PSYMBOL("UNKNOWN_METHOD");
+
+ method_sym = NODE_PSYMBOL("method");
+ status_code_sym = NODE_PSYMBOL("statusCode");
+ http_version_sym = NODE_PSYMBOL("httpVersion");
+ version_major_sym = NODE_PSYMBOL("versionMajor");
+ version_minor_sym = NODE_PSYMBOL("versionMinor");
+ should_keep_alive_sym = NODE_PSYMBOL("shouldKeepAlive");
+}
+
+} // namespace node
+
View
12 src/node_http_parser.h
@@ -0,0 +1,12 @@
+#ifndef NODE_HTTP_PARSER
+#define NODE_HTTP_PARSER
+
+#include <v8.h>
+
+namespace node {
+
+void InitHttpParser(v8::Handle<v8::Object> target);
+
+}
+
+#endif // NODE_HTTP_PARSER
View
60 test/mjsunit/test-http-parser.js
@@ -0,0 +1,60 @@
+process.mixin(require("./common"));
+
+// The purpose of this test is not to check HTTP compliance but to test the
+// binding. Tests for pathological http messages should be submitted
+// upstream to http://github.com/ry/http-parser for inclusion into
+// deps/http-parser/test.c
+
+
+var parser = new process.HTTPParser("request");
+
+var buffer = new process.Buffer(1024);
+
+var request = "GET /hello HTTP/1.1\r\n\r\n";
+
+buffer.asciiWrite(request, 0, request.length);
+
+var callbacks = 0;
+
+parser.onMessageBegin = function () {
+ puts("message begin");
+ callbacks++;
+};
+
+parser.onHeadersComplete = function (info) {
+ puts("headers complete: " + JSON.stringify(info));
+ assert.equal('GET', info.method);
+ assert.equal(1, info.versionMajor);
+ assert.equal(1, info.versionMinor);
+ callbacks++;
+};
+
+parser.onURL = function (off, len) {
+ //throw new Error("hello world");
+ callbacks++;
+};
+
+parser.onPath = function (off, length) {
+ puts("path [" + off + ", " + length + "]");
+ var path = buffer.asciiSlice(off, off+length);
+ puts("path = '" + path + "'");
+ assert.equal('/hello', path);
+ callbacks++;
+};
+
+parser.execute(buffer, 0, request.length);
+assert.equal(4, callbacks);
+
+//
+// Check that if we throw an error in the callbacks that error will be
+// thrown from parser.execute()
+//
+
+parser.onURL = function (off, len) {
+ throw new Error("hello world");
+};
+
+assert.throws(function () {
+ parser.execute(buffer, 0, request.length);
+}, Error, "hello world");
+
View
1  wscript
@@ -346,6 +346,7 @@ def build(bld):
src/node.cc
src/node_buffer.cc
src/node_net2.cc
+ src/node_http_parser.cc
src/node_io_watcher.cc
src/node_child_process.cc
src/node_constants.cc
Please sign in to comment.
Something went wrong with that request. Please try again.