From 0afed523299515762e97ff40c12bf3285d24a38a Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Sun, 13 Dec 2009 08:39:20 +0100 Subject: [PATCH 001/105] initial blobs --- src/node.cc | 2 + src/node_blob.cc | 294 +++++++++++++++++++++++++++++++++++++++++++++++ src/node_blob.h | 12 ++ wscript | 1 + 4 files changed, 309 insertions(+) create mode 100644 src/node_blob.cc create mode 100644 src/node_blob.h diff --git a/src/node.cc b/src/node.cc index 5c77e932efa..7af78bb2b2d 100644 --- a/src/node.cc +++ b/src/node.cc @@ -10,6 +10,7 @@ #include #include /* dlopen(), dlsym() */ +#include #include #include #include @@ -847,6 +848,7 @@ static Local Load(int argc, char *argv[]) { // Initialize the C++ modules..................filename of module + InitBlob(process); // stdio.cc Stdio::Initialize(process); // stdio.cc Timer::Initialize(process); // timer.cc SignalHandler::Initialize(process); // signal_handler.cc diff --git a/src/node_blob.cc b/src/node_blob.cc new file mode 100644 index 00000000000..e86b5706c0e --- /dev/null +++ b/src/node_blob.cc @@ -0,0 +1,294 @@ +#include +#include // malloc, free +#include +#include + +namespace node { + +using namespace v8; + +#define MIN(a,b) ((a) < (b) ? (a) : (b)) + +#define SLICE_ARGS(start_arg, end_arg) \ + if (!start_arg->IsInt32() || !end_arg->IsInt32()) { \ + return ThrowException(Exception::TypeError( \ + String::New("Bad argument."))); \ + } \ + int32_t start = start_arg->Int32Value(); \ + int32_t end = end_arg->Int32Value(); \ + if (start < 0 || end < 0) { \ + return ThrowException(Exception::TypeError( \ + String::New("Bad argument."))); \ + } + +static Persistent length_symbol; +static Persistent constructor_template; + +/* A blob is a chunk of memory stored outside the V8 heap mirrored by an + * object in javascript. The object is not totally opaque, one can access + * individual bytes with [] and one can slice the blob into substrings or + * subblobs without copying memory. + * + * blob.asciiSlide(0, 3) // return an ascii encoded string - no memory iscopied + * blob.slice(0, 3) // returns another blob - no memory is copied + * + * Interally, each javascript blob object is backed by a "struct blob" object. + * These "struct blob" objects are either the root object (in the case that + * blob->root == NULL) or slice objects (in which case blob->root != NULL). + * The root blob is only GCed once all it's slices are GCed. + */ + +struct blob { + Persistent handle; // both + bool weak; // both + struct blob *root; // both (NULL for root) + size_t offset; // both (0 for root) + size_t length; // both + unsigned int refs; // root only + char bytes[1]; // root only +}; + + +static inline struct blob* blob_root(blob *blob) { + return blob->root ? blob->root : blob; +} + +/* Determines the absolute position for a relative offset */ +static inline size_t blob_abs_off(blob *blob, size_t off) { + struct blob *root = blob_root(blob); + off += root->offset; + return MIN(root->length, off); +} + + +static inline void blob_ref(struct blob *blob) { + assert(blob->root == NULL); + blob->refs++; +} + + +static inline void blob_unref(struct blob *blob) { + assert(blob->root == NULL); + assert(blob->refs > 0); + blob->refs--; + if (blob->refs == 0 && blob->weak) free(blob); +} + + +static inline struct blob* Unwrap(Handle val) { + assert(val->IsObject()); + HandleScope scope; + Local obj = val->ToObject(); + assert(obj->InternalFieldCount() == 1); + Local ext = Local::Cast(obj->GetInternalField(0)); + return static_cast(ext->Value()); +} + + +static void RootWeakCallback(Persistent value, void *data) +{ + struct blob *blob = static_cast(data); + assert(blob->root == NULL); // this is the root + assert(value == blob->handle); + blob->handle.Dispose(); + if (blob->refs) { + blob->weak = true; + } else { + free(blob); + } +} + + +static void SliceWeakCallback(Persistent value, void *data) +{ + struct blob *blob = static_cast(data); + assert(blob->root != NULL); // this is a slice + assert(value == blob->handle); + blob->handle.Dispose(); + blob_unref(blob->root); +} + + +static Handle Constructor(const Arguments &args) { + HandleScope scope; + + size_t length; + struct blob *blob; + + if (constructor_template->HasInstance(args[0])) { + // slice slice + SLICE_ARGS(args[1], args[2]) + + struct blob *parent = Unwrap(args[0]); + + size_t start_abs = blob_abs_off(parent, start); + size_t end_abs = blob_abs_off(parent, end); + assert(start_abs <= end_abs); + length = end_abs - start_abs; + + void *d = malloc(sizeof(struct blob)); + + if (!d) { + V8::LowMemoryNotification(); + return ThrowException(Exception::Error( + String::New("Could not allocate enough memory"))); + + } + + blob = static_cast(d); + + blob->length = length; + blob->offset = start_abs; + blob->weak = false; + blob->refs = 0; + blob->root = blob_root(parent); + blob->handle = Persistent::New(args.This()); + blob->handle.MakeWeak(blob, SliceWeakCallback); + + blob_ref(blob->root); + } else { + // Root slice + + length = args[0]->Uint32Value(); + + if (length < 1) { + return ThrowException(Exception::TypeError( + String::New("Bad argument. Length must be positive"))); + } + + // TODO alignment. modify the length? + void *d = malloc(sizeof(struct blob) + length - 1); + + if (!d) { + V8::LowMemoryNotification(); + return ThrowException(Exception::Error( + String::New("Could not allocate enough memory"))); + } + + blob = static_cast(d); + + blob->offset = 0; + blob->length = length; + blob->weak = false; + blob->refs = 0; + blob->root = NULL; + blob->handle = Persistent::New(args.This()); + blob->handle.MakeWeak(blob, RootWeakCallback); + } + + args.This()->SetInternalField(0, v8::External::New(blob)); + + struct blob *root = blob_root(blob); + + args.This()-> + SetIndexedPropertiesToExternalArrayData(&root->bytes + blob->offset, + kExternalUnsignedByteArray, + length); + + args.This()->Set(length_symbol, Integer::New(length)); + + return args.This(); +} + + +class AsciiSliceExt: public String::ExternalAsciiStringResource { + public: + + AsciiSliceExt(struct blob *root, size_t start, size_t end) + { + data_ = root->bytes + start; + len_ = end - start; + root_ = root; + blob_ref(root_); + } + + ~AsciiSliceExt() { + blob_unref(root_); + } + + const char* data() const { + return data_; + } + + size_t length() const { + return len_; + } + + private: + const char *data_; + size_t len_; + struct blob *root_; +}; + +static Handle AsciiSlice(const Arguments &args) { + HandleScope scope; + + SLICE_ARGS(args[0], args[1]) + + assert(args.This()->InternalFieldCount() == 1); + struct blob *parent = Unwrap(args.This()); + + size_t start_abs = blob_abs_off(parent, start); + size_t end_abs = blob_abs_off(parent, end); + + assert(start_abs <= end_abs); + + AsciiSliceExt *s = new AsciiSliceExt(blob_root(parent), start_abs, end_abs); + Local string = String::NewExternal(s); + + struct blob *root = blob_root(parent); + assert(root->refs > 0); + + return scope.Close(string); +} + +static Handle Utf8Slice(const Arguments &args) { + HandleScope scope; + + SLICE_ARGS(args[0], args[1]) + + struct blob *parent = Unwrap(args.This()); + size_t start_abs = blob_abs_off(parent, start); + size_t end_abs = blob_abs_off(parent, end); + assert(start_abs <= end_abs); + + struct blob *root = blob_root(parent); + + Local string = + String::New(reinterpret_cast(&root->bytes + start_abs), + end_abs - start_abs); + return scope.Close(string); +} + +static Handle Slice(const Arguments &args) { + HandleScope scope; + + Local argv[3] = { args.This(), args[0], args[1] }; + + Local slice = + constructor_template->GetFunction()->NewInstance(3, argv); + + return scope.Close(slice); +} + +void InitBlob(Handle target) { + HandleScope scope; + + length_symbol = Persistent::New(String::NewSymbol("length")); + + Local t = FunctionTemplate::New(Constructor); + constructor_template = Persistent::New(t); + constructor_template->InstanceTemplate()->SetInternalFieldCount(1); + constructor_template->SetClassName(String::NewSymbol("Blob")); + + // copy free + NODE_SET_PROTOTYPE_METHOD(constructor_template, "asciiSlice", AsciiSlice); + NODE_SET_PROTOTYPE_METHOD(constructor_template, "slice", Slice); + // TODO NODE_SET_PROTOTYPE_METHOD(t, "utf16Slice", Utf16Slice); + // copy + NODE_SET_PROTOTYPE_METHOD(constructor_template, "utf8Slice", Utf8Slice); + + target->Set(String::NewSymbol("Blob"), constructor_template->GetFunction()); +} + +} // namespace node diff --git a/src/node_blob.h b/src/node_blob.h new file mode 100644 index 00000000000..cb88c50d929 --- /dev/null +++ b/src/node_blob.h @@ -0,0 +1,12 @@ +#ifndef NODE_BLOB +#define NODE_BLOB + +#include + +namespace node { + +void InitBlob(v8::Handle target); + +} + +#endif // NODE_BLOB diff --git a/wscript b/wscript index 03341b74d96..e74b9326a41 100644 --- a/wscript +++ b/wscript @@ -322,6 +322,7 @@ def build(bld): node.target = "node" node.source = """ src/node.cc + src/node_blob.cc src/node_child_process.cc src/node_constants.cc src/node_dns.cc From 630bb7a0127df7606fc3d99d36170c378f09f6b9 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Sun, 13 Dec 2009 08:42:45 +0100 Subject: [PATCH 002/105] Rename blob to buffer. --- src/node.cc | 4 +- src/node_blob.h | 12 -- src/{node_blob.cc => node_buffer.cc} | 164 ++++++++++++++------------- src/node_buffer.h | 12 ++ test/mjsunit/test-buffer.js | 35 ++++++ wscript | 2 +- 6 files changed, 134 insertions(+), 95 deletions(-) delete mode 100644 src/node_blob.h rename src/{node_blob.cc => node_buffer.cc} (55%) create mode 100644 src/node_buffer.h create mode 100644 test/mjsunit/test-buffer.js diff --git a/src/node.cc b/src/node.cc index 7af78bb2b2d..cb6b94291e2 100644 --- a/src/node.cc +++ b/src/node.cc @@ -10,7 +10,7 @@ #include #include /* dlopen(), dlsym() */ -#include +#include #include #include #include @@ -848,7 +848,7 @@ static Local Load(int argc, char *argv[]) { // Initialize the C++ modules..................filename of module - InitBlob(process); // stdio.cc + InitBuffer(process); // buffer.cc Stdio::Initialize(process); // stdio.cc Timer::Initialize(process); // timer.cc SignalHandler::Initialize(process); // signal_handler.cc diff --git a/src/node_blob.h b/src/node_blob.h deleted file mode 100644 index cb88c50d929..00000000000 --- a/src/node_blob.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef NODE_BLOB -#define NODE_BLOB - -#include - -namespace node { - -void InitBlob(v8::Handle target); - -} - -#endif // NODE_BLOB diff --git a/src/node_blob.cc b/src/node_buffer.cc similarity index 55% rename from src/node_blob.cc rename to src/node_buffer.cc index e86b5706c0e..47ddf7b1294 100644 --- a/src/node_blob.cc +++ b/src/node_buffer.cc @@ -24,88 +24,92 @@ using namespace v8; static Persistent length_symbol; static Persistent constructor_template; -/* A blob is a chunk of memory stored outside the V8 heap mirrored by an +/* A buffer is a chunk of memory stored outside the V8 heap, mirrored by an * object in javascript. The object is not totally opaque, one can access - * individual bytes with [] and one can slice the blob into substrings or - * subblobs without copying memory. + * individual bytes with [] and slice it into substrings or sub-buffers + * without copying memory. * - * blob.asciiSlide(0, 3) // return an ascii encoded string - no memory iscopied - * blob.slice(0, 3) // returns another blob - no memory is copied + * // return an ascii encoded string - no memory iscopied + * buffer.asciiSlide(0, 3) * - * Interally, each javascript blob object is backed by a "struct blob" object. - * These "struct blob" objects are either the root object (in the case that - * blob->root == NULL) or slice objects (in which case blob->root != NULL). - * The root blob is only GCed once all it's slices are GCed. + * // returns another buffer - no memory is copied + * buffer.slice(0, 3) + * + * Interally, each javascript buffer object is backed by a "struct buffer" + * object. These "struct buffer" objects are either a root buffer (in the + * case that buffer->root == NULL) or slice objects (in which case + * buffer->root != NULL). A root buffer is only GCed once all its slices + * are GCed. */ -struct blob { +struct buffer { Persistent handle; // both bool weak; // both - struct blob *root; // both (NULL for root) + struct buffer *root; // both (NULL for root) size_t offset; // both (0 for root) size_t length; // both unsigned int refs; // root only - char bytes[1]; // root only + char bytes[1]; // root only }; -static inline struct blob* blob_root(blob *blob) { - return blob->root ? blob->root : blob; +static inline struct buffer* buffer_root(buffer *buffer) { + return buffer->root ? buffer->root : buffer; } /* Determines the absolute position for a relative offset */ -static inline size_t blob_abs_off(blob *blob, size_t off) { - struct blob *root = blob_root(blob); +static inline size_t buffer_abs_off(buffer *buffer, size_t off) { + struct buffer *root = buffer_root(buffer); off += root->offset; return MIN(root->length, off); } -static inline void blob_ref(struct blob *blob) { - assert(blob->root == NULL); - blob->refs++; +static inline void buffer_ref(struct buffer *buffer) { + assert(buffer->root == NULL); + buffer->refs++; } -static inline void blob_unref(struct blob *blob) { - assert(blob->root == NULL); - assert(blob->refs > 0); - blob->refs--; - if (blob->refs == 0 && blob->weak) free(blob); +static inline void buffer_unref(struct buffer *buffer) { + assert(buffer->root == NULL); + assert(buffer->refs > 0); + buffer->refs--; + if (buffer->refs == 0 && buffer->weak) free(buffer); } -static inline struct blob* Unwrap(Handle val) { +static inline struct buffer* Unwrap(Handle val) { assert(val->IsObject()); HandleScope scope; Local obj = val->ToObject(); assert(obj->InternalFieldCount() == 1); Local ext = Local::Cast(obj->GetInternalField(0)); - return static_cast(ext->Value()); + return static_cast(ext->Value()); } static void RootWeakCallback(Persistent value, void *data) { - struct blob *blob = static_cast(data); - assert(blob->root == NULL); // this is the root - assert(value == blob->handle); - blob->handle.Dispose(); - if (blob->refs) { - blob->weak = true; + struct buffer *buffer = static_cast(data); + assert(buffer->root == NULL); // this is the root + assert(value == buffer->handle); + buffer->handle.Dispose(); + if (buffer->refs) { + buffer->weak = true; } else { - free(blob); + free(buffer); } } static void SliceWeakCallback(Persistent value, void *data) { - struct blob *blob = static_cast(data); - assert(blob->root != NULL); // this is a slice - assert(value == blob->handle); - blob->handle.Dispose(); - blob_unref(blob->root); + struct buffer *buffer = static_cast(data); + assert(buffer->root != NULL); // this is a slice + assert(value == buffer->handle); + buffer->handle.Dispose(); + buffer_unref(buffer->root); } @@ -113,20 +117,20 @@ static Handle Constructor(const Arguments &args) { HandleScope scope; size_t length; - struct blob *blob; + struct buffer *buffer; if (constructor_template->HasInstance(args[0])) { // slice slice SLICE_ARGS(args[1], args[2]) - struct blob *parent = Unwrap(args[0]); + struct buffer *parent = Unwrap(args[0]); - size_t start_abs = blob_abs_off(parent, start); - size_t end_abs = blob_abs_off(parent, end); + size_t start_abs = buffer_abs_off(parent, start); + size_t end_abs = buffer_abs_off(parent, end); assert(start_abs <= end_abs); length = end_abs - start_abs; - void *d = malloc(sizeof(struct blob)); + void *d = malloc(sizeof(struct buffer)); if (!d) { V8::LowMemoryNotification(); @@ -135,17 +139,17 @@ static Handle Constructor(const Arguments &args) { } - blob = static_cast(d); + buffer = static_cast(d); - blob->length = length; - blob->offset = start_abs; - blob->weak = false; - blob->refs = 0; - blob->root = blob_root(parent); - blob->handle = Persistent::New(args.This()); - blob->handle.MakeWeak(blob, SliceWeakCallback); + buffer->length = length; + buffer->offset = start_abs; + buffer->weak = false; + buffer->refs = 0; + buffer->root = buffer_root(parent); + buffer->handle = Persistent::New(args.This()); + buffer->handle.MakeWeak(buffer, SliceWeakCallback); - blob_ref(blob->root); + buffer_ref(buffer->root); } else { // Root slice @@ -157,7 +161,7 @@ static Handle Constructor(const Arguments &args) { } // TODO alignment. modify the length? - void *d = malloc(sizeof(struct blob) + length - 1); + void *d = malloc(sizeof(struct buffer) + length - 1); if (!d) { V8::LowMemoryNotification(); @@ -165,23 +169,23 @@ static Handle Constructor(const Arguments &args) { String::New("Could not allocate enough memory"))); } - blob = static_cast(d); + buffer = static_cast(d); - blob->offset = 0; - blob->length = length; - blob->weak = false; - blob->refs = 0; - blob->root = NULL; - blob->handle = Persistent::New(args.This()); - blob->handle.MakeWeak(blob, RootWeakCallback); + buffer->offset = 0; + buffer->length = length; + buffer->weak = false; + buffer->refs = 0; + buffer->root = NULL; + buffer->handle = Persistent::New(args.This()); + buffer->handle.MakeWeak(buffer, RootWeakCallback); } - args.This()->SetInternalField(0, v8::External::New(blob)); + args.This()->SetInternalField(0, v8::External::New(buffer)); - struct blob *root = blob_root(blob); + struct buffer *root = buffer_root(buffer); args.This()-> - SetIndexedPropertiesToExternalArrayData(&root->bytes + blob->offset, + SetIndexedPropertiesToExternalArrayData(&root->bytes + buffer->offset, kExternalUnsignedByteArray, length); @@ -194,16 +198,16 @@ static Handle Constructor(const Arguments &args) { class AsciiSliceExt: public String::ExternalAsciiStringResource { public: - AsciiSliceExt(struct blob *root, size_t start, size_t end) + AsciiSliceExt(struct buffer *root, size_t start, size_t end) { data_ = root->bytes + start; len_ = end - start; root_ = root; - blob_ref(root_); + buffer_ref(root_); } ~AsciiSliceExt() { - blob_unref(root_); + buffer_unref(root_); } const char* data() const { @@ -217,7 +221,7 @@ class AsciiSliceExt: public String::ExternalAsciiStringResource { private: const char *data_; size_t len_; - struct blob *root_; + struct buffer *root_; }; static Handle AsciiSlice(const Arguments &args) { @@ -226,17 +230,17 @@ static Handle AsciiSlice(const Arguments &args) { SLICE_ARGS(args[0], args[1]) assert(args.This()->InternalFieldCount() == 1); - struct blob *parent = Unwrap(args.This()); + struct buffer *parent = Unwrap(args.This()); - size_t start_abs = blob_abs_off(parent, start); - size_t end_abs = blob_abs_off(parent, end); + size_t start_abs = buffer_abs_off(parent, start); + size_t end_abs = buffer_abs_off(parent, end); assert(start_abs <= end_abs); - AsciiSliceExt *s = new AsciiSliceExt(blob_root(parent), start_abs, end_abs); + AsciiSliceExt *s = new AsciiSliceExt(buffer_root(parent), start_abs, end_abs); Local string = String::NewExternal(s); - struct blob *root = blob_root(parent); + struct buffer *root = buffer_root(parent); assert(root->refs > 0); return scope.Close(string); @@ -247,12 +251,12 @@ static Handle Utf8Slice(const Arguments &args) { SLICE_ARGS(args[0], args[1]) - struct blob *parent = Unwrap(args.This()); - size_t start_abs = blob_abs_off(parent, start); - size_t end_abs = blob_abs_off(parent, end); + struct buffer *parent = Unwrap(args.This()); + size_t start_abs = buffer_abs_off(parent, start); + size_t end_abs = buffer_abs_off(parent, end); assert(start_abs <= end_abs); - struct blob *root = blob_root(parent); + struct buffer *root = buffer_root(parent); Local string = String::New(reinterpret_cast(&root->bytes + start_abs), @@ -271,7 +275,7 @@ static Handle Slice(const Arguments &args) { return scope.Close(slice); } -void InitBlob(Handle target) { +void InitBuffer(Handle target) { HandleScope scope; length_symbol = Persistent::New(String::NewSymbol("length")); @@ -279,7 +283,7 @@ void InitBlob(Handle target) { Local t = FunctionTemplate::New(Constructor); constructor_template = Persistent::New(t); constructor_template->InstanceTemplate()->SetInternalFieldCount(1); - constructor_template->SetClassName(String::NewSymbol("Blob")); + constructor_template->SetClassName(String::NewSymbol("Buffer")); // copy free NODE_SET_PROTOTYPE_METHOD(constructor_template, "asciiSlice", AsciiSlice); @@ -288,7 +292,7 @@ void InitBlob(Handle target) { // copy NODE_SET_PROTOTYPE_METHOD(constructor_template, "utf8Slice", Utf8Slice); - target->Set(String::NewSymbol("Blob"), constructor_template->GetFunction()); + target->Set(String::NewSymbol("Buffer"), constructor_template->GetFunction()); } } // namespace node diff --git a/src/node_buffer.h b/src/node_buffer.h new file mode 100644 index 00000000000..f7006428a6e --- /dev/null +++ b/src/node_buffer.h @@ -0,0 +1,12 @@ +#ifndef NODE_BUFFER +#define NODE_BUFFER + +#include + +namespace node { + +void InitBuffer(v8::Handle target); + +} + +#endif // NODE_BUFFER diff --git a/test/mjsunit/test-buffer.js b/test/mjsunit/test-buffer.js new file mode 100644 index 00000000000..763faa4e7c1 --- /dev/null +++ b/test/mjsunit/test-buffer.js @@ -0,0 +1,35 @@ +sys = require("sys"); +assert = require("assert"); + + +var b = new process.Buffer(1024); + +sys.puts("b[0] == " + b[0]); +assert.ok(b[0] >= 0); + +sys.puts("b[1] == " + b[1]); +assert.ok(b[1] >= 0); + +sys.puts("b.length == " + b.length); +assert.equal(1024, b.length); + +for (var j = 0; j < 10000; j++) { + var asciiString = "hello world"; + + for (var i = 0; i < asciiString.length; i++) { + b[i] = asciiString.charCodeAt(i); + } + + var asciiSlice = b.asciiSlice(0, asciiString.length); + + assert.equal(asciiString, asciiSlice); +} + + +for (var j = 0; j < 10000; j++) { + var slice = b.slice(100, 150); + assert.equal(50, slice.length); + for (var i = 0; i < 50; i++) { + assert.equal(b[100+i], slice[i]); + } +} diff --git a/wscript b/wscript index e74b9326a41..1f4c131cd29 100644 --- a/wscript +++ b/wscript @@ -322,7 +322,7 @@ def build(bld): node.target = "node" node.source = """ src/node.cc - src/node_blob.cc + src/node_buffer.cc src/node_child_process.cc src/node_constants.cc src/node_dns.cc From e2569c402fc2b1b4f60c71c616a321134e7aa0ca Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Sun, 13 Dec 2009 08:57:54 +0100 Subject: [PATCH 003/105] Add some tests to test-buffer.js --- test/mjsunit/test-buffer.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/test/mjsunit/test-buffer.js b/test/mjsunit/test-buffer.js index 763faa4e7c1..cfeafa1fce0 100644 --- a/test/mjsunit/test-buffer.js +++ b/test/mjsunit/test-buffer.js @@ -4,15 +4,18 @@ assert = require("assert"); var b = new process.Buffer(1024); -sys.puts("b[0] == " + b[0]); -assert.ok(b[0] >= 0); - -sys.puts("b[1] == " + b[1]); -assert.ok(b[1] >= 0); - sys.puts("b.length == " + b.length); assert.equal(1024, b.length); +for (var i = 0; i < 1024; i++) { + assert.ok(b[i] >= 0); + b[i] = i % 256; +} + +for (var i = 0; i < 1024; i++) { + assert.equal(i % 256, b[i]); +} + for (var j = 0; j < 10000; j++) { var asciiString = "hello world"; From 3377a30fb844a9804b895e20d702bbfc865fb70e Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Sun, 13 Dec 2009 08:58:12 +0100 Subject: [PATCH 004/105] Move cb_persist functions out of dns module --- src/node.h | 19 +++++++++++++++++++ src/node_dns.cc | 19 +------------------ src/node_file.cc | 15 +++------------ 3 files changed, 23 insertions(+), 30 deletions(-) diff --git a/src/node.h b/src/node.h index fd872a09607..44cf0dbd53c 100644 --- a/src/node.h +++ b/src/node.h @@ -58,5 +58,24 @@ ssize_t DecodeWrite(char *buf, v8::Local BuildStatsObject(struct stat * s); +static inline v8::Persistent* cb_persist( + const v8::Local &v) { + v8::Persistent *fn = new v8::Persistent(); + *fn = v8::Persistent::New(v8::Local::Cast(v)); + return fn; +} + +static inline v8::Persistent* cb_unwrap(void *data) { + v8::Persistent *cb = + reinterpret_cast*>(data); + assert((*cb)->IsFunction()); + return cb; +} + +static inline void cb_destroy(v8::Persistent * cb) { + cb->Dispose(); + delete cb; +} + } // namespace node #endif // SRC_NODE_H_ diff --git a/src/node_dns.cc b/src/node_dns.cc index 1dadab268f5..f173e7e32c9 100644 --- a/src/node_dns.cc +++ b/src/node_dns.cc @@ -1,5 +1,6 @@ // Copyright 2009 Ryan Dahl #include +#include #include /* exit() */ #include @@ -21,24 +22,6 @@ static ev_timer timer_watcher; static Persistent errno_symbol; -static inline Persistent* cb_persist(const Local &v) { - Persistent *fn = new Persistent(); - *fn = Persistent::New(Local::Cast(v)); - return fn; -} - -static inline Persistent* cb_unwrap(void *data) { - Persistent *cb = - reinterpret_cast*>(data); - assert((*cb)->IsFunction()); - return cb; -} - -static inline void cb_destroy(Persistent * cb) { - cb->Dispose(); - delete cb; -} - static inline void set_timeout() { int maxwait = 20; int wait = dns_timeouts(NULL, maxwait, ev_now(EV_DEFAULT_UC)); diff --git a/src/node_file.cc b/src/node_file.cc index 12890643233..c378cabb29c 100644 --- a/src/node_file.cc +++ b/src/node_file.cc @@ -30,9 +30,7 @@ static inline Local errno_exception(int errorno) { static int After(eio_req *req) { HandleScope scope; - Persistent *callback = - reinterpret_cast*>(req->data); - assert((*callback)->IsFunction()); + Persistent *callback = cb_unwrap(req->data); ev_unref(EV_DEFAULT_UC); @@ -124,21 +122,14 @@ static int After(eio_req *req) { } // Dispose of the persistent handle - callback->Dispose(); - delete callback; + cb_destroy(callback); return 0; } -static Persistent* persistent_callback(const Local &v) { - Persistent *fn = new Persistent(); - *fn = Persistent::New(Local::Cast(v)); - return fn; -} - #define ASYNC_CALL(func, callback, ...) \ eio_req *req = eio_##func(__VA_ARGS__, EIO_PRI_DEFAULT, After, \ - persistent_callback(callback)); \ + cb_persist(callback)); \ assert(req); \ ev_ref(EV_DEFAULT_UC); \ return Undefined(); From f219938b69c0af4a18714fb73ff9a58b3ac831c1 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Sun, 13 Dec 2009 15:43:58 +0100 Subject: [PATCH 005/105] add io watcher --- src/node.cc | 8 ++- src/node_io_watcher.cc | 126 +++++++++++++++++++++++++++++++++++++++++ src/node_io_watcher.h | 40 +++++++++++++ wscript | 1 + 4 files changed, 173 insertions(+), 2 deletions(-) create mode 100644 src/node_io_watcher.cc create mode 100644 src/node_io_watcher.h diff --git a/src/node.cc b/src/node.cc index cb6b94291e2..d150e9e5b4e 100644 --- a/src/node.cc +++ b/src/node.cc @@ -11,6 +11,7 @@ #include /* dlopen(), dlsym() */ #include +#include #include #include #include @@ -849,10 +850,13 @@ static Local Load(int argc, char *argv[]) { // Initialize the C++ modules..................filename of module InitBuffer(process); // buffer.cc - Stdio::Initialize(process); // stdio.cc + + IOWatcher::Initialize(process); // io_watcher.cc Timer::Initialize(process); // timer.cc - SignalHandler::Initialize(process); // signal_handler.cc Stat::Initialize(process); // stat.cc + SignalHandler::Initialize(process); // signal_handler.cc + + Stdio::Initialize(process); // stdio.cc ChildProcess::Initialize(process); // child_process.cc DefineConstants(process); // constants.cc // Create node.dns diff --git a/src/node_io_watcher.cc b/src/node_io_watcher.cc new file mode 100644 index 00000000000..7017d3ea13d --- /dev/null +++ b/src/node_io_watcher.cc @@ -0,0 +1,126 @@ +// Copyright 2009 Ryan Dahl +#include + +#include + +namespace node { + +using namespace v8; + +Persistent IOWatcher::constructor_template; + +void IOWatcher::Initialize(Handle target) { + HandleScope scope; + + Local t = FunctionTemplate::New(IOWatcher::New); + constructor_template = Persistent::New(t); + constructor_template->InstanceTemplate()->SetInternalFieldCount(2); + constructor_template->SetClassName(String::NewSymbol("IOWatcher")); + + NODE_SET_PROTOTYPE_METHOD(constructor_template, "start", IOWatcher::Start); + NODE_SET_PROTOTYPE_METHOD(constructor_template, "stop", IOWatcher::Stop); + + target->Set(String::NewSymbol("IOWatcher"), constructor_template->GetFunction()); +} + + +void IOWatcher::Callback(EV_P_ ev_io *w, int revents) { + IOWatcher *io = static_cast(w->data); + assert(w == &io->watcher_); + HandleScope scope; + + Local callback_v = io->handle_->GetInternalField(1); + assert(callback_v->IsFunction()); + Local callback = Local::Cast(callback_v); + + TryCatch try_catch; + + Local argv[2]; + argv[0] = Local::New(revents & EV_READ ? True() : False()); + argv[1] = Local::New(revents & EV_WRITE ? True() : False()); + + callback->Call(io->handle_, 2, argv); + + if (try_catch.HasCaught()) { + FatalException(try_catch); + } +} + + +// +// var io = new process.IOWatcher(fd, true, true, function (readable, writable) { +// +// }); +// +Handle IOWatcher::New(const Arguments& args) { + HandleScope scope; + + if (!args[0]->IsInt32()) { + return ThrowException(Exception::TypeError( + String::New("First arg should be a file descriptor."))); + } + + int fd = args[0]->Int32Value(); + + if (!args[1]->IsBoolean()) { + return ThrowException(Exception::TypeError( + String::New("Second arg should boolean (readable)."))); + } + + int events = 0; + + if (args[1]->IsTrue()) events |= EV_READ; + + if (!args[2]->IsBoolean()) { + return ThrowException(Exception::TypeError( + String::New("Third arg should boolean (writable)."))); + } + + if (args[2]->IsTrue()) events |= EV_WRITE; + + if (!args[3]->IsFunction()) { + return ThrowException(Exception::TypeError( + String::New("Fourth arg should a callback."))); + } + + Local callback = Local::Cast(args[3]); + + IOWatcher *s = new IOWatcher(fd, events); + + s->Wrap(args.This()); + s->handle_->SetInternalField(1, callback); + + return args.This(); +} + + +Handle IOWatcher::Start(const Arguments& args) { + HandleScope scope; + + IOWatcher *io = ObjectWrap::Unwrap(args.Holder()); + + ev_io_start(EV_DEFAULT_UC_ &io->watcher_); + + io->Ref(); + + return Undefined(); +} + + +Handle IOWatcher::Stop(const Arguments& args) { + HandleScope scope; + IOWatcher *io = ObjectWrap::Unwrap(args.Holder()); + io->Stop(); + return Undefined(); +} + + +void IOWatcher::Stop () { + if (watcher_.active) { + ev_io_stop(EV_DEFAULT_UC_ &watcher_); + Unref(); + } +} + + +} // namespace node diff --git a/src/node_io_watcher.h b/src/node_io_watcher.h new file mode 100644 index 00000000000..4e40593241e --- /dev/null +++ b/src/node_io_watcher.h @@ -0,0 +1,40 @@ +// Copyright 2009 Ryan Dahl +#ifndef NODE_IO_H_ +#define NODE_IO_H_ + +#include +#include + +namespace node { + +class IOWatcher : ObjectWrap { + public: + static void Initialize(v8::Handle target); + + protected: + static v8::Persistent constructor_template; + + IOWatcher(int fd, int events) : ObjectWrap() { + ev_io_init(&watcher_, IOWatcher::Callback, fd, events); + watcher_.data = this; + } + + ~IOWatcher() { + Stop(); + } + + static v8::Handle New(const v8::Arguments& args); + static v8::Handle Start(const v8::Arguments& args); + static v8::Handle Stop(const v8::Arguments& args); + + private: + static void Callback(EV_P_ ev_io *watcher, int revents); + + void Stop(); + + ev_io watcher_; +}; + +} // namespace node +#endif // NODE_IO_H_ + diff --git a/wscript b/wscript index 1f4c131cd29..773a17fb77f 100644 --- a/wscript +++ b/wscript @@ -323,6 +323,7 @@ def build(bld): node.source = """ src/node.cc src/node_buffer.cc + src/node_io_watcher.cc src/node_child_process.cc src/node_constants.cc src/node_dns.cc From c819abccb611735afa8fd2cd26ca23d43a98527c Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Mon, 14 Dec 2009 09:42:02 +0100 Subject: [PATCH 006/105] Start on net2 --- src/node.cc | 3 + src/node_net2.cc | 178 +++++++++++++++++++++++++++++++++++++++++++++++ src/node_net2.h | 12 ++++ wscript | 1 + 4 files changed, 194 insertions(+) create mode 100644 src/node_net2.cc create mode 100644 src/node_net2.h diff --git a/src/node.cc b/src/node.cc index d150e9e5b4e..81a100ad36b 100644 --- a/src/node.cc +++ b/src/node.cc @@ -12,6 +12,7 @@ #include #include +#include #include #include #include @@ -856,6 +857,8 @@ static Local Load(int argc, char *argv[]) { Stat::Initialize(process); // stat.cc SignalHandler::Initialize(process); // signal_handler.cc + InitNet2(process); // net2.cc + Stdio::Initialize(process); // stdio.cc ChildProcess::Initialize(process); // child_process.cc DefineConstants(process); // constants.cc diff --git a/src/node_net2.cc b/src/node_net2.cc new file mode 100644 index 00000000000..6ced4a26ced --- /dev/null +++ b/src/node_net2.cc @@ -0,0 +1,178 @@ +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include /* inet_pton */ + +#include + + + +namespace node { + +using namespace v8; + +static Persistent errno_symbol; +static Persistent syscall_symbol; + +static inline Local ErrnoException(int errorno, const char *syscall, const char *msg = "") { + if (!msg[0]) msg = strerror(errorno); + Local e = Exception::Error(String::NewSymbol(msg)); + Local obj = e->ToObject(); + obj->Set(errno_symbol, Integer::New(errorno)); + obj->Set(syscall_symbol, String::NewSymbol(syscall)); + return e; +} + +// Creates a new non-blocking socket fd +// t.socket("TCP"); +// t.socket("UNIX"); +// t.socket("UDP"); +static Handle Socket(const Arguments& args) { + HandleScope scope; + + // default to TCP + int domain = PF_INET6; + int type = SOCK_STREAM; + + if (args[0]->IsString()) { + String::Utf8Value t(args[0]->ToString()); + if (0 == strcasecmp(*t, "TCP")) { + domain = PF_INET6; + type = SOCK_STREAM; + } else if (0 == strcasecmp(*t, "UDP")) { + domain = PF_INET6; + type = SOCK_DGRAM; + } else if (0 == strcasecmp(*t, "UNIX")) { + domain = PF_UNIX; + type = SOCK_STREAM; + } else { + return ThrowException(Exception::Error( + String::New("Unknown socket type."))); + } + } + + int fd = socket(domain, type, 0); + + if (fd < 0) return ThrowException(ErrnoException(errno, "socket")); + + int fcntl_errno; + + int flags = fcntl(fd, F_GETFL, 0); + if (flags == -1) { + fcntl_errno = errno; + close(fd); + return ThrowException(ErrnoException(fcntl_errno, "fcntl")); + } + + flags |= O_NONBLOCK; + + if (fcntl(fd, F_SETFL, flags) == -1) { + fcntl_errno = errno; + close(fd); + return ThrowException(ErrnoException(fcntl_errno, "fcntl")); + } + + return scope.Close(Integer::New(fd)); +} + +// 2 arguments means connect with unix +// t.connect(fd, "/tmp/socket") +// +// 3 arguments means connect with TCP or UDP +// t.connect(fd, "127.0.0.1", 80) +static Handle Connect(const Arguments& args) { + HandleScope scope; + + if (!args[0]->IsInt32()) { + return ThrowException(Exception::TypeError( + String::New("First argument should be file descriptor"))); + } + + if (args.Length() < 2) { + return ThrowException(Exception::TypeError( + String::New("Must have at least two args"))); + } + + int fd = args[0]->Int32Value(); + + struct sockaddr *addr; + socklen_t addrlen; + + if (args.Length() == 2) { + // UNIX + String::Utf8Value path(args[1]->ToString()); + + struct sockaddr_un un = {0}; + + if (path.length() > sizeof un.sun_path) { + return ThrowException(Exception::Error(String::New("Socket path too long"))); + } + + un.sun_family = AF_UNIX; + strcpy(un.sun_path, *path); + + addr = (struct sockaddr*)&un; + addrlen = path.length() + sizeof(un.sun_family); + + } else { + // TCP or UDP + String::Utf8Value ip(args[1]->ToString()); + int port = args[2]->Int32Value(); + + struct sockaddr_in6 in6 = {0}; + + char ipv6[255] = "::FFFF:"; + + if (inet_pton(AF_INET, *ip, &(in6.sin6_addr)) > 0) { + // If this is an IPv4 address then we need to change it to the + // IPv4-mapped-on-IPv6 format which looks like + // ::FFFF: + // For more information see "Address Format" ipv6(7) and "BUGS" in + // inet_pton(3) + strcat(ipv6, *ip); + } else { + strcpy(ipv6, *ip); + } + + if (inet_pton(AF_INET6, ipv6, &(in6.sin6_addr)) <= 0) { + return ThrowException( + ErrnoException(errno, "inet_pton", "Invalid IP Address")); + } + + in6.sin6_family = AF_INET6; + in6.sin6_port = htons(port); + + addr = (struct sockaddr*)&in6; + addrlen = sizeof in6; + } + + int r = connect(fd, addr, addrlen); + + if (r < 0 && errno != EINPROGRESS) { + return ThrowException(ErrnoException(errno, "connect")); + } + + return Undefined(); +} + + +void InitNet2(Handle target) { + HandleScope scope; + + NODE_SET_METHOD(target, "socket", Socket); + NODE_SET_METHOD(target, "connect", Connect); + + errno_symbol = NODE_PSYMBOL("errno"); + syscall_symbol = NODE_PSYMBOL("syscall"); +} + +} // namespace node diff --git a/src/node_net2.h b/src/node_net2.h new file mode 100644 index 00000000000..cd3e9f083ad --- /dev/null +++ b/src/node_net2.h @@ -0,0 +1,12 @@ +#ifndef NODE_NET2 +#define NODE_NET2 + +#include + +namespace node { + +void InitNet2(v8::Handle target); + +} + +#endif // NODE_NET2 diff --git a/wscript b/wscript index 773a17fb77f..d2e13c9339e 100644 --- a/wscript +++ b/wscript @@ -323,6 +323,7 @@ def build(bld): node.source = """ src/node.cc src/node_buffer.cc + src/node_net2.cc src/node_io_watcher.cc src/node_child_process.cc src/node_constants.cc From 469e2648e59c19154fd67ae1df17487f673fe9fc Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Tue, 15 Dec 2009 09:22:36 +0100 Subject: [PATCH 007/105] More bindings, beginning tcp server code in js --- src/node_buffer.cc | 45 ++--- src/node_buffer.h | 45 +++++ src/node_net2.cc | 455 +++++++++++++++++++++++++++++++++++++++------ tcp.js | 55 ++++++ 4 files changed, 505 insertions(+), 95 deletions(-) create mode 100644 tcp.js diff --git a/src/node_buffer.cc b/src/node_buffer.cc index 47ddf7b1294..be1020d621e 100644 --- a/src/node_buffer.cc +++ b/src/node_buffer.cc @@ -1,14 +1,15 @@ +#include + #include #include // malloc, free #include + #include namespace node { using namespace v8; -#define MIN(a,b) ((a) < (b) ? (a) : (b)) - #define SLICE_ARGS(start_arg, end_arg) \ if (!start_arg->IsInt32() || !end_arg->IsInt32()) { \ return ThrowException(Exception::TypeError( \ @@ -24,33 +25,11 @@ using namespace v8; static Persistent length_symbol; static Persistent constructor_template; -/* A buffer is a chunk of memory stored outside the V8 heap, mirrored by an - * object in javascript. The object is not totally opaque, one can access - * individual bytes with [] and slice it into substrings or sub-buffers - * without copying memory. - * - * // return an ascii encoded string - no memory iscopied - * buffer.asciiSlide(0, 3) - * - * // returns another buffer - no memory is copied - * buffer.slice(0, 3) - * - * Interally, each javascript buffer object is backed by a "struct buffer" - * object. These "struct buffer" objects are either a root buffer (in the - * case that buffer->root == NULL) or slice objects (in which case - * buffer->root != NULL). A root buffer is only GCed once all its slices - * are GCed. - */ - -struct buffer { - Persistent handle; // both - bool weak; // both - struct buffer *root; // both (NULL for root) - size_t offset; // both (0 for root) - size_t length; // both - unsigned int refs; // root only - char bytes[1]; // root only -}; +bool IsBuffer(v8::Handle val) { + if (!val->IsObject()) return false; + Local obj = val->ToObject(); + return constructor_template->HasInstance(obj); +} static inline struct buffer* buffer_root(buffer *buffer) { @@ -79,7 +58,7 @@ static inline void buffer_unref(struct buffer *buffer) { } -static inline struct buffer* Unwrap(Handle val) { +struct buffer* BufferUnwrap(v8::Handle val) { assert(val->IsObject()); HandleScope scope; Local obj = val->ToObject(); @@ -123,7 +102,7 @@ static Handle Constructor(const Arguments &args) { // slice slice SLICE_ARGS(args[1], args[2]) - struct buffer *parent = Unwrap(args[0]); + struct buffer *parent = BufferUnwrap(args[0]); size_t start_abs = buffer_abs_off(parent, start); size_t end_abs = buffer_abs_off(parent, end); @@ -230,7 +209,7 @@ static Handle AsciiSlice(const Arguments &args) { SLICE_ARGS(args[0], args[1]) assert(args.This()->InternalFieldCount() == 1); - struct buffer *parent = Unwrap(args.This()); + struct buffer *parent = BufferUnwrap(args.This()); size_t start_abs = buffer_abs_off(parent, start); size_t end_abs = buffer_abs_off(parent, end); @@ -251,7 +230,7 @@ static Handle Utf8Slice(const Arguments &args) { SLICE_ARGS(args[0], args[1]) - struct buffer *parent = Unwrap(args.This()); + struct buffer *parent = BufferUnwrap(args.This()); size_t start_abs = buffer_abs_off(parent, start); size_t end_abs = buffer_abs_off(parent, end); assert(start_abs <= end_abs); diff --git a/src/node_buffer.h b/src/node_buffer.h index f7006428a6e..3afb3eeaeef 100644 --- a/src/node_buffer.h +++ b/src/node_buffer.h @@ -5,8 +5,53 @@ namespace node { +#define MIN(a,b) ((a) < (b) ? (a) : (b)) + +/* A buffer is a chunk of memory stored outside the V8 heap, mirrored by an + * object in javascript. The object is not totally opaque, one can access + * individual bytes with [] and slice it into substrings or sub-buffers + * without copying memory. + * + * // return an ascii encoded string - no memory iscopied + * buffer.asciiSlide(0, 3) + * + * // returns another buffer - no memory is copied + * buffer.slice(0, 3) + * + * Interally, each javascript buffer object is backed by a "struct buffer" + * object. These "struct buffer" objects are either a root buffer (in the + * case that buffer->root == NULL) or slice objects (in which case + * buffer->root != NULL). A root buffer is only GCed once all its slices + * are GCed. + */ + +struct buffer { + v8::Persistent handle; // both + bool weak; // both + struct buffer *root; // both (NULL for root) + size_t offset; // both (0 for root) + size_t length; // both + unsigned int refs; // root only + char bytes[1]; // root only +}; + void InitBuffer(v8::Handle target); +struct buffer* BufferUnwrap(v8::Handle val); +bool IsBuffer(v8::Handle val); + +static inline char * buffer_p(struct buffer *buffer, size_t off) { + struct buffer *root = buffer->root ? buffer->root : buffer; + if (buffer->offset + off >= root->length) return NULL; + return reinterpret_cast(&(root->bytes) + buffer->offset + off); +} + +static inline size_t buffer_remaining(struct buffer *buffer, size_t off) { + struct buffer *root = buffer->root ? buffer->root : buffer; + char *end = reinterpret_cast(&(root->bytes) + root->length); + return end - buffer_p(buffer, off); +} + } #endif // NODE_BUFFER diff --git a/src/node_net2.cc b/src/node_net2.cc index 6ced4a26ced..13a5aafe7c6 100644 --- a/src/node_net2.cc +++ b/src/node_net2.cc @@ -2,6 +2,7 @@ #include #include +#include #include @@ -14,8 +15,6 @@ #include - - namespace node { using namespace v8; @@ -23,7 +22,20 @@ using namespace v8; static Persistent errno_symbol; static Persistent syscall_symbol; -static inline Local ErrnoException(int errorno, const char *syscall, const char *msg = "") { +static Persistent fd_symbol; +static Persistent remote_address_symbol; +static Persistent remote_port_symbol; + +#define FD_ARG(a) \ + if (!(a)->IsInt32()) { \ + return ThrowException(Exception::TypeError( \ + String::New("Bad file descriptor argument"))); \ + } \ + int fd = (a)->Int32Value(); + +static inline Local ErrnoException(int errorno, + const char *syscall, + const char *msg = "") { if (!msg[0]) msg = strerror(errorno); Local e = Exception::Error(String::NewSymbol(msg)); Local obj = e->ToObject(); @@ -32,6 +44,57 @@ static inline Local ErrnoException(int errorno, const char *syscall, cons return e; } +static inline bool SetNonBlock(int fd) { + int flags = fcntl(fd, F_GETFL, 0); + if (flags == -1) return false; + flags |= O_NONBLOCK; + return (fcntl(fd, F_SETFL, flags) != -1); +} + +// Creates nonblocking pipe +static Handle Pipe(const Arguments& args) { + HandleScope scope; + int fds[2]; + + if (pipe(fds) < 0) return ThrowException(ErrnoException(errno, "pipe")); + + if(!SetNonBlock(fds[0]) || !SetNonBlock(fds[1])) { + int fcntl_errno = errno; + close(fds[0]); + close(fds[1]); + return ThrowException(ErrnoException(fcntl_errno, "fcntl")); + } + + Local a = Array::New(2); + a->Set(Integer::New(0), Integer::New(fds[0])); + a->Set(Integer::New(1), Integer::New(fds[1])); + return scope.Close(a); +} + +// Creates nonblocking socket pair +static Handle SocketPair(const Arguments& args) { + HandleScope scope; + + int fds[2]; + + // XXX support SOCK_DGRAM? + if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds) < 0) { + return ThrowException(ErrnoException(errno, "socketpair")); + } + + if (!SetNonBlock(fds[0]) || !SetNonBlock(fds[1])) { + int fcntl_errno = errno; + close(fds[0]); + close(fds[1]); + return ThrowException(ErrnoException(fcntl_errno, "fcntl")); + } + + Local a = Array::New(2); + a->Set(Integer::New(0), Integer::New(fds[0])); + a->Set(Integer::New(1), Integer::New(fds[1])); + return scope.Close(a); +} + // Creates a new non-blocking socket fd // t.socket("TCP"); // t.socket("UNIX"); @@ -64,19 +127,8 @@ static Handle Socket(const Arguments& args) { if (fd < 0) return ThrowException(ErrnoException(errno, "socket")); - int fcntl_errno; - - int flags = fcntl(fd, F_GETFL, 0); - if (flags == -1) { - fcntl_errno = errno; - close(fd); - return ThrowException(ErrnoException(fcntl_errno, "fcntl")); - } - - flags |= O_NONBLOCK; - - if (fcntl(fd, F_SETFL, flags) == -1) { - fcntl_errno = errno; + if (!SetNonBlock(fd)) { + int fcntl_errno = errno; close(fd); return ThrowException(ErrnoException(fcntl_errno, "fcntl")); } @@ -84,39 +136,26 @@ static Handle Socket(const Arguments& args) { return scope.Close(Integer::New(fd)); } -// 2 arguments means connect with unix -// t.connect(fd, "/tmp/socket") -// -// 3 arguments means connect with TCP or UDP -// t.connect(fd, "127.0.0.1", 80) -static Handle Connect(const Arguments& args) { - HandleScope scope; - if (!args[0]->IsInt32()) { - return ThrowException(Exception::TypeError( - String::New("First argument should be file descriptor"))); - } - - if (args.Length() < 2) { - return ThrowException(Exception::TypeError( - String::New("Must have at least two args"))); - } - - int fd = args[0]->Int32Value(); - - struct sockaddr *addr; - socklen_t addrlen; - - if (args.Length() == 2) { +// NOT AT ALL THREAD SAFE - but that's okay for node.js +// (yes this is all to avoid one small heap alloc) +static struct sockaddr *addr; +static socklen_t addrlen; +static struct sockaddr_un un; +static struct sockaddr_in6 in6; +static inline Handle ParseAddressArgs(Handle first, + Handle second, + struct in6_addr default_addr + ) { + if (first->IsString() && second->IsUndefined()) { // UNIX - String::Utf8Value path(args[1]->ToString()); - - struct sockaddr_un un = {0}; + String::Utf8Value path(first->ToString()); if (path.length() > sizeof un.sun_path) { - return ThrowException(Exception::Error(String::New("Socket path too long"))); + return Exception::Error(String::New("Socket path too long")); } + memset(&un, 0, sizeof un); un.sun_family = AF_UNIX; strcpy(un.sun_path, *path); @@ -125,27 +164,29 @@ static Handle Connect(const Arguments& args) { } else { // TCP or UDP - String::Utf8Value ip(args[1]->ToString()); - int port = args[2]->Int32Value(); - - struct sockaddr_in6 in6 = {0}; - - char ipv6[255] = "::FFFF:"; - - if (inet_pton(AF_INET, *ip, &(in6.sin6_addr)) > 0) { - // If this is an IPv4 address then we need to change it to the - // IPv4-mapped-on-IPv6 format which looks like - // ::FFFF: - // For more information see "Address Format" ipv6(7) and "BUGS" in - // inet_pton(3) - strcat(ipv6, *ip); + int port = first->Int32Value(); + memset(&in6, 0, sizeof in6); + if (!second->IsString()) { + in6.sin6_addr = default_addr; } else { - strcpy(ipv6, *ip); - } + String::Utf8Value ip(second->ToString()); - if (inet_pton(AF_INET6, ipv6, &(in6.sin6_addr)) <= 0) { - return ThrowException( - ErrnoException(errno, "inet_pton", "Invalid IP Address")); + char ipv6[255] = "::FFFF:"; + + if (inet_pton(AF_INET, *ip, &(in6.sin6_addr)) > 0) { + // If this is an IPv4 address then we need to change it + // to the IPv4-mapped-on-IPv6 format which looks like + // ::FFFF: + // For more information see "Address Format" ipv6(7) and + // "BUGS" in inet_pton(3) + strcat(ipv6, *ip); + } else { + strcpy(ipv6, *ip); + } + + if (inet_pton(AF_INET6, ipv6, &(in6.sin6_addr)) <= 0) { + return ErrnoException(errno, "inet_pton", "Invalid IP Address"); + } } in6.sin6_family = AF_INET6; @@ -154,6 +195,106 @@ static Handle Connect(const Arguments& args) { addr = (struct sockaddr*)&in6; addrlen = sizeof in6; } + return Handle(); +} + + +// Bind with UNIX +// t.bind(fd, "/tmp/socket") +// Bind with TCP +// t.bind(fd, 80, "192.168.11.2") +// t.bind(fd, 80) +static Handle Bind(const Arguments& args) { + HandleScope scope; + + if (args.Length() < 2) { + return ThrowException(Exception::TypeError( + String::New("Must have at least two args"))); + } + + FD_ARG(args[0]) + + Handle error = ParseAddressArgs(args[1], args[2], in6addr_any); + if (!error.IsEmpty()) return ThrowException(error); + + int flags = 1; + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *)&flags, sizeof(flags)); + + int r = bind(fd, addr, addrlen); + + if (r < 0) { + return ThrowException(ErrnoException(errno, "bind")); + } + + return Undefined(); +} + + +static Handle Close(const Arguments& args) { + HandleScope scope; + + FD_ARG(args[0]) + + if (0 > close(fd)) { + return ThrowException(ErrnoException(errno, "close")); + } + + return Undefined(); +} + +// t.shutdown(fd, "read"); -- SHUT_RD +// t.shutdown(fd, "write"); -- SHUT_WR +// t.shutdown(fd, "readwrite"); -- SHUT_RDWR +// second arg defaults to "write". +static Handle Shutdown(const Arguments& args) { + HandleScope scope; + + FD_ARG(args[0]) + + int how = SHUT_WR; + + if (args[1]->IsString()) { + String::Utf8Value t(args[0]->ToString()); + if (0 == strcasecmp(*t, "write")) { + how = SHUT_WR; + } else if (0 == strcasecmp(*t, "read")) { + how = SHUT_RD; + } else if (0 == strcasecmp(*t, "readwrite")) { + how = SHUT_RDWR; + } else { + return ThrowException(Exception::Error(String::New( + "Unknown shutdown method. (Use 'read', 'write', or 'readwrite'.)"))); + } + } + + if (0 > shutdown(fd, how)) { + return ThrowException(ErrnoException(errno, "shutdown")); + } + + return Undefined(); +} + + +// Connect with unix +// t.connect(fd, "/tmp/socket") +// +// Connect with TCP or UDP +// t.connect(fd, 80, "192.168.11.2") +// t.connect(fd, 80, "::1") +// t.connect(fd, 80) +// the third argument defaults to "::1" +static Handle Connect(const Arguments& args) { + HandleScope scope; + + if (args.Length() < 2) { + return ThrowException(Exception::TypeError( + String::New("Must have at least two args"))); + } + + FD_ARG(args[0]) + + Handle error = ParseAddressArgs(args[1], args[2], in6addr_loopback); + if (!error.IsEmpty()) return ThrowException(error); int r = connect(fd, addr, addrlen); @@ -165,14 +306,204 @@ static Handle Connect(const Arguments& args) { } +static Handle Listen(const Arguments& args) { + HandleScope scope; + + FD_ARG(args[0]) + int backlog = args[1]->IsInt32() ? args[1]->Int32Value() : 128; + + if (0 > listen(fd, backlog)) { + return ThrowException(ErrnoException(errno, "listen")); + } + + return Undefined(); +} + + +// var peerInfo = t.accept(server_fd); +// +// peerInfo.fd +// peerInfo.remoteAddress +// peerInfo.remotePort +// +// Returns a new nonblocking socket fd. If the listen queue is empty the +// function returns null (wait for server_fd to become readable and try +// again) +static Handle Accept(const Arguments& args) { + HandleScope scope; + + FD_ARG(args[0]) + + struct sockaddr_storage addr; + socklen_t len; + + int peer = accept(fd, (struct sockaddr*) &addr, &len); + + if (peer < 0) { + if (errno == EAGAIN) return Null(); + return ThrowException(ErrnoException(errno, "accept")); + } + + if (!SetNonBlock(peer)) { + int fcntl_errno = errno; + close(peer); + return ThrowException(ErrnoException(fcntl_errno, "fcntl")); + } + + Local peer_info = Object::New(); + + peer_info->Set(fd_symbol, Integer::New(fd)); + + if (addr.ss_family == AF_INET6) { + struct sockaddr_in6 *a = reinterpret_cast(&addr); + + char ip[INET6_ADDRSTRLEN]; + inet_ntop(AF_INET6, &a->sin6_addr, ip, INET6_ADDRSTRLEN); + + int port = ntohs(a->sin6_port); + + peer_info->Set(remote_address_symbol, String::New(ip)); + peer_info->Set(remote_port_symbol, Integer::New(port)); + } + + return scope.Close(peer_info); +} + + +static Handle GetSocketError(const Arguments& args) { + HandleScope scope; + + FD_ARG(args[0]) + + int error; + socklen_t len = sizeof(int); + int r = getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &len); + + if (r < 0) { + return ThrowException(ErrnoException(errno, "getsockopt")); + } + + return scope.Close(Integer::New(error)); +} + +// var bytesRead = t.read(fd, buffer, offset, length); +// returns null on EAGAIN or EINTR, raises an exception on all other errors +// returns 0 on EOF. +static Handle Read(const Arguments& args) { + HandleScope scope; + + if (args.Length() < 4) { + return ThrowException(Exception::TypeError( + String::New("Takes 4 parameters"))); + } + + FD_ARG(args[0]) + + if (!IsBuffer(args[1])) { + return ThrowException(Exception::TypeError( + String::New("Second argument should be a buffer"))); + } + + struct buffer * buffer = BufferUnwrap(args[1]); + + size_t off = args[2]->Int32Value(); + if (buffer_p(buffer, off) == NULL) { + return ThrowException(Exception::Error( + String::New("Offset is out of bounds"))); + } + + size_t len = args[3]->Int32Value(); + if (buffer_remaining(buffer, off) < len) { + return ThrowException(Exception::Error( + String::New("Length is extends beyond buffer"))); + } + + size_t bytes_read = read(fd, + buffer_p(buffer, off), + buffer_remaining(buffer, off)); + + if (bytes_read < 0) { + if (errno == EAGAIN || errno == EINTR) return Null(); + return ThrowException(ErrnoException(errno, "read")); + } + + return Integer::New(bytes_read); +} + +// var bytesWritten = t.write(fd, buffer, offset, length); +// returns null on EAGAIN or EINTR, raises an exception on all other errors +static Handle Write(const Arguments& args) { + HandleScope scope; + + if (args.Length() < 4) { + return ThrowException(Exception::TypeError( + String::New("Takes 4 parameters"))); + } + + FD_ARG(args[0]) + + if (!IsBuffer(args[1])) { + return ThrowException(Exception::TypeError( + String::New("Second argument should be a buffer"))); + } + + struct buffer * buffer = BufferUnwrap(args[1]); + + size_t off = args[2]->Int32Value(); + if (buffer_p(buffer, off) == NULL) { + return ThrowException(Exception::Error( + String::New("Offset is out of bounds"))); + } + + size_t len = args[3]->Int32Value(); + if (buffer_remaining(buffer, off) < len) { + return ThrowException(Exception::Error( + String::New("Length is extends beyond buffer"))); + } + + size_t written = write(fd, + buffer_p(buffer, off), + buffer_remaining(buffer, off)); + + if (written < 0) { + if (errno == EAGAIN || errno == EINTR) return Null(); + return ThrowException(ErrnoException(errno, "write")); + } + + return Integer::New(written); +} + void InitNet2(Handle target) { HandleScope scope; + NODE_SET_METHOD(target, "write", Write); + NODE_SET_METHOD(target, "read", Read); + NODE_SET_METHOD(target, "socket", Socket); + NODE_SET_METHOD(target, "close", Close); + NODE_SET_METHOD(target, "shutdown", Shutdown); + NODE_SET_METHOD(target, "pipe", Pipe); + NODE_SET_METHOD(target, "socketpair", SocketPair); + NODE_SET_METHOD(target, "connect", Connect); + NODE_SET_METHOD(target, "bind", Bind); + NODE_SET_METHOD(target, "listen", Listen); + NODE_SET_METHOD(target, "accept", Accept); + NODE_SET_METHOD(target, "getSocketError", GetSocketError); + + + target->Set(String::NewSymbol("EINPROGRESS"), Integer::New(EINPROGRESS)); + target->Set(String::NewSymbol("EINTR"), Integer::New(EINTR)); + target->Set(String::NewSymbol("EACCES"), Integer::New(EACCES)); + target->Set(String::NewSymbol("EPERM"), Integer::New(EPERM)); + target->Set(String::NewSymbol("EADDRINUSE"), Integer::New(EADDRINUSE)); + target->Set(String::NewSymbol("ECONNREFUSED"), Integer::New(ECONNREFUSED)); errno_symbol = NODE_PSYMBOL("errno"); syscall_symbol = NODE_PSYMBOL("syscall"); + fd_symbol = NODE_PSYMBOL("fd"); + remote_address_symbol = NODE_PSYMBOL("remoteAddress"); + remote_port_symbol = NODE_PSYMBOL("remotePort"); } } // namespace node diff --git a/tcp.js b/tcp.js new file mode 100644 index 00000000000..af7334945f6 --- /dev/null +++ b/tcp.js @@ -0,0 +1,55 @@ +var socket = process.socket; +var bind = process.bind; +var listen = process.listen; +var accept = process.accept; +var close = process.close; + +var Server = function (listener) { + var self = this; + + if (listener) { + self.addListener("connection", listener); + } + +}; +process.inherits(Server, process.EventEmitter); + +Server.prototype.listen = function (port, host) { + var self = this; + + if (self.fd) throw new Error("Already running"); + + self.fd = process.socket("TCP"); + // TODO dns resolution + bind(self.fd, port, host); + listen(self.fd, 128); // TODO configurable backlog + + self.watcher = new process.IOWatcher(self.fd, true, false, function () { + var peerInfo; + while (self.fd) { + peerInfo = accept(self.fd); + if (peerInfo === null) return; + self.emit("connection", peerInfo); + } + }); + + self.watcher.start(); +}; + +Server.prototype.close = function () { + var self = this; + if (!self.fd) throw new Error("Not running"); + self.watcher.stop(); + close(self.fd); + this.watcher = null; + this.fd = null; +}; + +/////////////////////////////////////////////////////// + +var sys = require("sys"); +var server = new Server(function () { + sys.puts("connection"); + server.close(); +}); +server.listen(8000); From 0ecd0fa598e958c7f67fd3617cfb51045516d0e2 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Tue, 15 Dec 2009 17:17:45 +0100 Subject: [PATCH 008/105] IOWatcher callback isn't internal, fix bug in Accept --- src/node_io_watcher.cc | 76 +++++++++++++++++++++------------ src/node_io_watcher.h | 7 +-- src/node_net2.cc | 32 +++++++------- tcp.js | 96 +++++++++++++++++++++++++++++++++--------- 4 files changed, 144 insertions(+), 67 deletions(-) diff --git a/src/node_io_watcher.cc b/src/node_io_watcher.cc index 7017d3ea13d..3c60a7bacf3 100644 --- a/src/node_io_watcher.cc +++ b/src/node_io_watcher.cc @@ -1,6 +1,9 @@ // Copyright 2009 Ryan Dahl #include +#include +#include + #include namespace node { @@ -8,19 +11,23 @@ namespace node { using namespace v8; Persistent IOWatcher::constructor_template; +Persistent callback_symbol; void IOWatcher::Initialize(Handle target) { HandleScope scope; Local t = FunctionTemplate::New(IOWatcher::New); constructor_template = Persistent::New(t); - constructor_template->InstanceTemplate()->SetInternalFieldCount(2); + constructor_template->InstanceTemplate()->SetInternalFieldCount(1); constructor_template->SetClassName(String::NewSymbol("IOWatcher")); NODE_SET_PROTOTYPE_METHOD(constructor_template, "start", IOWatcher::Start); NODE_SET_PROTOTYPE_METHOD(constructor_template, "stop", IOWatcher::Stop); + NODE_SET_PROTOTYPE_METHOD(constructor_template, "set", IOWatcher::Set); target->Set(String::NewSymbol("IOWatcher"), constructor_template->GetFunction()); + + callback_symbol = NODE_PSYMBOL("callback"); } @@ -29,7 +36,7 @@ void IOWatcher::Callback(EV_P_ ev_io *w, int revents) { assert(w == &io->watcher_); HandleScope scope; - Local callback_v = io->handle_->GetInternalField(1); + Local callback_v = io->handle_->Get(callback_symbol); assert(callback_v->IsFunction()); Local callback = Local::Cast(callback_v); @@ -48,13 +55,49 @@ void IOWatcher::Callback(EV_P_ ev_io *w, int revents) { // -// var io = new process.IOWatcher(fd, true, true, function (readable, writable) { +// var io = new process.IOWatcher(function (readable, writable) { // // }); +// io.set(fd, true, false); +// io.start(); // Handle IOWatcher::New(const Arguments& args) { HandleScope scope; + if (!args[0]->IsFunction()) { + return ThrowException(Exception::TypeError( + String::New("First arg should a callback."))); + } + + Local callback = Local::Cast(args[0]); + + IOWatcher *s = new IOWatcher(); + + s->Wrap(args.This()); + + s->handle_->Set(callback_symbol, callback); + + return args.This(); +} + + +Handle IOWatcher::Start(const Arguments& args) { + HandleScope scope; + + IOWatcher *io = ObjectWrap::Unwrap(args.Holder()); + + ev_io_start(EV_DEFAULT_UC_ &io->watcher_); + + io->Ref(); + + return Undefined(); +} + +Handle IOWatcher::Set(const Arguments& args) { + HandleScope scope; + + IOWatcher *io = ObjectWrap::Unwrap(args.Holder()); + if (!args[0]->IsInt32()) { return ThrowException(Exception::TypeError( String::New("First arg should be a file descriptor."))); @@ -78,35 +121,11 @@ Handle IOWatcher::New(const Arguments& args) { if (args[2]->IsTrue()) events |= EV_WRITE; - if (!args[3]->IsFunction()) { - return ThrowException(Exception::TypeError( - String::New("Fourth arg should a callback."))); - } - - Local callback = Local::Cast(args[3]); - - IOWatcher *s = new IOWatcher(fd, events); - - s->Wrap(args.This()); - s->handle_->SetInternalField(1, callback); - - return args.This(); -} - - -Handle IOWatcher::Start(const Arguments& args) { - HandleScope scope; - - IOWatcher *io = ObjectWrap::Unwrap(args.Holder()); - - ev_io_start(EV_DEFAULT_UC_ &io->watcher_); - - io->Ref(); + ev_io_set(&io->watcher_, fd, events); return Undefined(); } - Handle IOWatcher::Stop(const Arguments& args) { HandleScope scope; IOWatcher *io = ObjectWrap::Unwrap(args.Holder()); @@ -117,6 +136,7 @@ Handle IOWatcher::Stop(const Arguments& args) { void IOWatcher::Stop () { if (watcher_.active) { + HandleScope scope; ev_io_stop(EV_DEFAULT_UC_ &watcher_); Unref(); } diff --git a/src/node_io_watcher.h b/src/node_io_watcher.h index 4e40593241e..420c6de5cc2 100644 --- a/src/node_io_watcher.h +++ b/src/node_io_watcher.h @@ -2,7 +2,7 @@ #ifndef NODE_IO_H_ #define NODE_IO_H_ -#include +#include #include namespace node { @@ -14,8 +14,8 @@ class IOWatcher : ObjectWrap { protected: static v8::Persistent constructor_template; - IOWatcher(int fd, int events) : ObjectWrap() { - ev_io_init(&watcher_, IOWatcher::Callback, fd, events); + IOWatcher() : ObjectWrap() { + ev_init(&watcher_, IOWatcher::Callback); watcher_.data = this; } @@ -26,6 +26,7 @@ class IOWatcher : ObjectWrap { static v8::Handle New(const v8::Arguments& args); static v8::Handle Start(const v8::Arguments& args); static v8::Handle Stop(const v8::Arguments& args); + static v8::Handle Set(const v8::Arguments& args); private: static void Callback(EV_P_ ev_io *watcher, int revents); diff --git a/src/node_net2.cc b/src/node_net2.cc index 13a5aafe7c6..cf2433e5b37 100644 --- a/src/node_net2.cc +++ b/src/node_net2.cc @@ -334,31 +334,31 @@ static Handle Accept(const Arguments& args) { FD_ARG(args[0]) - struct sockaddr_storage addr; - socklen_t len; + struct sockaddr_storage address_storage; + socklen_t len = sizeof(struct sockaddr_storage); - int peer = accept(fd, (struct sockaddr*) &addr, &len); + int peer_fd = accept(fd, (struct sockaddr*) &address_storage, &len); - if (peer < 0) { - if (errno == EAGAIN) return Null(); + if (peer_fd < 0) { + if (errno == EAGAIN) return scope.Close(Null()); return ThrowException(ErrnoException(errno, "accept")); } - if (!SetNonBlock(peer)) { + if (!SetNonBlock(peer_fd)) { int fcntl_errno = errno; - close(peer); - return ThrowException(ErrnoException(fcntl_errno, "fcntl")); + close(peer_fd); + return ThrowException(ErrnoException(fcntl_errno, "fcntl", "Cannot make peer non-blocking")); } Local peer_info = Object::New(); - peer_info->Set(fd_symbol, Integer::New(fd)); + peer_info->Set(fd_symbol, Integer::New(peer_fd)); - if (addr.ss_family == AF_INET6) { - struct sockaddr_in6 *a = reinterpret_cast(&addr); + if (address_storage.ss_family == AF_INET6) { + struct sockaddr_in6 *a = (struct sockaddr_in6*)&address_storage; char ip[INET6_ADDRSTRLEN]; - inet_ntop(AF_INET6, &a->sin6_addr, ip, INET6_ADDRSTRLEN); + inet_ntop(AF_INET6, &(a->sin6_addr), ip, INET6_ADDRSTRLEN); int port = ntohs(a->sin6_port); @@ -499,11 +499,11 @@ void InitNet2(Handle target) { target->Set(String::NewSymbol("EADDRINUSE"), Integer::New(EADDRINUSE)); target->Set(String::NewSymbol("ECONNREFUSED"), Integer::New(ECONNREFUSED)); - errno_symbol = NODE_PSYMBOL("errno"); - syscall_symbol = NODE_PSYMBOL("syscall"); - fd_symbol = NODE_PSYMBOL("fd"); + errno_symbol = NODE_PSYMBOL("errno"); + syscall_symbol = NODE_PSYMBOL("syscall"); + fd_symbol = NODE_PSYMBOL("fd"); remote_address_symbol = NODE_PSYMBOL("remoteAddress"); - remote_port_symbol = NODE_PSYMBOL("remotePort"); + remote_port_symbol = NODE_PSYMBOL("remotePort"); } } // namespace node diff --git a/tcp.js b/tcp.js index af7334945f6..eafa1448707 100644 --- a/tcp.js +++ b/tcp.js @@ -1,8 +1,55 @@ -var socket = process.socket; -var bind = process.bind; -var listen = process.listen; -var accept = process.accept; -var close = process.close; +var debugLevel = 0; +if ("NODE_DEBUG" in process.ENV) debugLevel = 1; +function debug (x) { + if (debugLevel > 0) { + process.stdio.writeError(x + "\n"); + } +} + +var socket = process.socket; +var bind = process.bind; +var listen = process.listen; +var accept = process.accept; +var close = process.close; +var shutdown = process.shutdown; + +var Peer = function (peerInfo) { + process.EventEmitter.call(); + + var self = this; + + self.fd = peerInfo.fd; + self.remoteAddress = peerInfo.remoteAddress; + self.remotePort = peerInfo.remotePort; + + self.readWatcher = new process.IOWatcher(function () { + debug(self.fd + " readable"); + }); + self.readWatcher.set(self.fd, true, false); + self.readWatcher.start(); + + self.writeWatcher = new process.IOWatcher(function () { + debug(self.fd + " writable"); + }); + self.writeWatcher.set(self.fd, false, true); + + self.readable = true; + self.writable = true; +}; +process.inherits(Peer, process.EventEmitter); + +Peer.prototype.close = function () { + this.readable = false; + this.writable = false; + + this.writeWatcher.stop(); + this.readWatcher.stop(); + close(this.fd); + debug("close peer " + this.fd); + this.fd = null; +}; + + var Server = function (listener) { var self = this; @@ -10,7 +57,19 @@ var Server = function (listener) { if (listener) { self.addListener("connection", listener); } - + + self.watcher = new process.IOWatcher(function (readable, writeable) { + debug("readable " + readable); + debug("writable " + writeable); + while (self.fd) { + debug("accept from " + self.fd); + var peerInfo = accept(self.fd); + debug("accept: " + JSON.stringify(peerInfo)); + if (!peerInfo) return; + var peer = new Peer(peerInfo); + self.emit("connection", peer); + } + }); }; process.inherits(Server, process.EventEmitter); @@ -24,15 +83,7 @@ Server.prototype.listen = function (port, host) { bind(self.fd, port, host); listen(self.fd, 128); // TODO configurable backlog - self.watcher = new process.IOWatcher(self.fd, true, false, function () { - var peerInfo; - while (self.fd) { - peerInfo = accept(self.fd); - if (peerInfo === null) return; - self.emit("connection", peerInfo); - } - }); - + self.watcher.set(self.fd, true, false); self.watcher.start(); }; @@ -41,15 +92,20 @@ Server.prototype.close = function () { if (!self.fd) throw new Error("Not running"); self.watcher.stop(); close(self.fd); - this.watcher = null; - this.fd = null; + self.fd = null; }; /////////////////////////////////////////////////////// var sys = require("sys"); -var server = new Server(function () { - sys.puts("connection"); - server.close(); +var server = new Server(function (peer) { + sys.puts("connection (" + peer.fd + "): " + + peer.remoteAddress + + " port " + + peer.remotePort + ); + sys.puts("server fd: " + server.fd); + peer.close(); }); server.listen(8000); +sys.puts("server fd: " + server.fd); From 1da15d623e0a2d817d31762b3dc8837d0ebbdf18 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Wed, 16 Dec 2009 08:35:09 +0100 Subject: [PATCH 009/105] Fix buffer bug, implement tcp recv --- src/node_buffer.cc | 2 +- src/node_net2.cc | 31 ++++++++++++++- tcp.js | 97 ++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 116 insertions(+), 14 deletions(-) diff --git a/src/node_buffer.cc b/src/node_buffer.cc index be1020d621e..f211283c436 100644 --- a/src/node_buffer.cc +++ b/src/node_buffer.cc @@ -39,7 +39,7 @@ static inline struct buffer* buffer_root(buffer *buffer) { /* Determines the absolute position for a relative offset */ static inline size_t buffer_abs_off(buffer *buffer, size_t off) { struct buffer *root = buffer_root(buffer); - off += root->offset; + off += buffer->offset; return MIN(root->length, off); } diff --git a/src/node_net2.cc b/src/node_net2.cc index cf2433e5b37..3d8a1fadd67 100644 --- a/src/node_net2.cc +++ b/src/node_net2.cc @@ -13,6 +13,13 @@ #include #include /* inet_pton */ +#include +#include + +#include +#include + + #include namespace node { @@ -427,7 +434,7 @@ static Handle Read(const Arguments& args) { return ThrowException(ErrnoException(errno, "read")); } - return Integer::New(bytes_read); + return scope.Close(Integer::New(bytes_read)); } // var bytesWritten = t.write(fd, buffer, offset, length); @@ -470,9 +477,28 @@ static Handle Write(const Arguments& args) { return ThrowException(ErrnoException(errno, "write")); } - return Integer::New(written); + return scope.Close(Integer::New(written)); } + +// Probably only works for Linux TCP sockets? +// Returns the amount of data on the read queue. +static Handle ToRead(const Arguments& args) { + HandleScope scope; + + FD_ARG(args[0]) + + int value; + int r = ioctl(fd, SIOCINQ, &value); + + if (r < 0) { + return ThrowException(ErrnoException(errno, "ioctl")); + } + + return scope.Close(Integer::New(value)); +} + + void InitNet2(Handle target) { HandleScope scope; @@ -490,6 +516,7 @@ void InitNet2(Handle target) { NODE_SET_METHOD(target, "listen", Listen); NODE_SET_METHOD(target, "accept", Accept); NODE_SET_METHOD(target, "getSocketError", GetSocketError); + NODE_SET_METHOD(target, "toRead", ToRead); target->Set(String::NewSymbol("EINPROGRESS"), Integer::New(EINPROGRESS)); diff --git a/tcp.js b/tcp.js index eafa1448707..6dd29f86886 100644 --- a/tcp.js +++ b/tcp.js @@ -12,18 +12,47 @@ var listen = process.listen; var accept = process.accept; var close = process.close; var shutdown = process.shutdown; +var read = process.read; +var write = process.write; +var toRead = process.toRead; var Peer = function (peerInfo) { process.EventEmitter.call(); var self = this; - self.fd = peerInfo.fd; - self.remoteAddress = peerInfo.remoteAddress; - self.remotePort = peerInfo.remotePort; + process.mixin(self, peerInfo); + + // Allocated on demand. + self.recvBuffer = null; self.readWatcher = new process.IOWatcher(function () { - debug(self.fd + " readable"); + debug("\n" + self.fd + " readable"); + + // If this is the first recv (recvBuffer doesn't exist) or we've used up + // most of the recvBuffer, allocate a new one. + if (!self.recvBuffer || + self.recvBuffer.length - self.recvBufferBytesUsed < 128) { + self._allocateNewRecvBuf(); + } + + debug("recvBufferBytesUsed " + self.recvBufferBytesUsed); + var bytesRead = read(self.fd, + self.recvBuffer, + self.recvBufferBytesUsed, + self.recvBuffer.length - self.recvBufferBytesUsed); + debug("bytesRead " + bytesRead + "\n"); + + if (bytesRead == 0) { + self.readable = false; + self.readWatcher.stop(); + self.emit("eof"); + } else { + var slice = self.recvBuffer.slice(self.recvBufferBytesUsed, + self.recvBufferBytesUsed + bytesRead); + self.recvBufferBytesUsed += bytesRead; + self.emit("receive", slice); + } }); self.readWatcher.set(self.fd, true, false); self.readWatcher.start(); @@ -35,9 +64,36 @@ var Peer = function (peerInfo) { self.readable = true; self.writable = true; + + self._out = []; }; process.inherits(Peer, process.EventEmitter); +Peer.prototype._allocateNewRecvBuf = function () { + var self = this; + + var newBufferSize = 1024; // TODO make this adjustable from user API + + if (toRead) { + // Note: only Linux supports toRead(). + // Is the extra system call even worth it? + var bytesToRead = toRead(self.fd); + if (bytesToRead > 1024) { + newBufferSize = 4*1024; + } else if (bytesToRead == 0) { + // Probably getting an EOF - so let's not allocate so much. + if (self.recvBuffer && + self.recvBuffer.length - self.recvBufferBytesUsed > 0) { + return; // just recv the eof on the old buf. + } + newBufferSize = 128; + } + } + + self.recvBuffer = new process.Buffer(newBufferSize); + self.recvBufferBytesUsed = 0; +}; + Peer.prototype.close = function () { this.readable = false; this.writable = false; @@ -49,8 +105,6 @@ Peer.prototype.close = function () { this.fd = null; }; - - var Server = function (listener) { var self = this; @@ -73,14 +127,26 @@ var Server = function (listener) { }; process.inherits(Server, process.EventEmitter); -Server.prototype.listen = function (port, host) { +Server.prototype.listen = function () { var self = this; if (self.fd) throw new Error("Already running"); - self.fd = process.socket("TCP"); - // TODO dns resolution - bind(self.fd, port, host); + if (typeof(arguments[0]) == "string" && arguments.length == 1) { + // the first argument specifies a path + self.fd = process.socket("UNIX"); + // TODO unlink sockfile if exists? + // if (lstat(SOCKFILE, &tstat) == 0) { + // assert(S_ISSOCK(tstat.st_mode)); + // unlink(SOCKFILE); + // } + bind(self.fd, arguments[0]); + } else { + // the first argument is the port, the second an IP + self.fd = process.socket("TCP"); + // TODO dns resolution on arguments[1] + bind(self.fd, arguments[0], arguments[1]); + } listen(self.fd, 128); // TODO configurable backlog self.watcher.set(self.fd, true, false); @@ -97,7 +163,12 @@ Server.prototype.close = function () { /////////////////////////////////////////////////////// +process.Buffer.prototype.toString = function () { + return this.utf8Slice(0, this.length); +}; + var sys = require("sys"); + var server = new Server(function (peer) { sys.puts("connection (" + peer.fd + "): " + peer.remoteAddress @@ -105,7 +176,11 @@ var server = new Server(function (peer) { + peer.remotePort ); sys.puts("server fd: " + server.fd); - peer.close(); + + peer.addListener("receive", function (b) { + sys.puts("recv (" + b.length + "): " + b); + }); }); +//server.listen(8000); server.listen(8000); sys.puts("server fd: " + server.fd); From bddd6e9ca33a069140501b76bdb336fa9cd65d07 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Wed, 16 Dec 2009 13:50:28 +0100 Subject: [PATCH 010/105] Implement stream.send() --- src/node_buffer.cc | 89 ++++++++++++++++++++++++++ src/node_net2.cc | 16 ++--- tcp.js | 153 ++++++++++++++++++++++++++++++++++++++------- 3 files changed, 227 insertions(+), 31 deletions(-) diff --git a/src/node_buffer.cc b/src/node_buffer.cc index f211283c436..584ed184b62 100644 --- a/src/node_buffer.cc +++ b/src/node_buffer.cc @@ -254,6 +254,90 @@ static Handle Slice(const Arguments &args) { return scope.Close(slice); } + +// var charsWritten = buffer.utf8Write(string, offset, length); +static Handle Utf8Write(const Arguments &args) { + HandleScope scope; + + struct buffer *buffer = BufferUnwrap(args.This()); + + if (!args[0]->IsString()) { + return ThrowException(Exception::TypeError(String::New( + "Argument must be a string"))); + } + + Local s = args[0]->ToString(); + + size_t offset = args[1]->Int32Value(); + + char *p = buffer_p(buffer, offset); + if (buffer_p(buffer, offset) == NULL) { + return ThrowException(Exception::TypeError(String::New( + "Offset is out of bounds"))); + } + + size_t toWrite = args[2]->Int32Value(); + + if (buffer_remaining(buffer, offset) < toWrite) { + return ThrowException(Exception::TypeError(String::New( + "Length is out of bounds"))); + } + + int written = s->WriteUtf8(p, toWrite); + + return scope.Close(Integer::New(written)); +} + + +// var charsWritten = buffer.asciiWrite(string, offset, length); +static Handle AsciiWrite(const Arguments &args) { + HandleScope scope; + + struct buffer *buffer = BufferUnwrap(args.This()); + + if (!args[0]->IsString()) { + return ThrowException(Exception::TypeError(String::New( + "Argument must be a string"))); + } + + Local s = args[0]->ToString(); + + size_t offset = args[1]->Int32Value(); + + char *p = buffer_p(buffer, offset); + if (buffer_p(buffer, offset) == NULL) { + return ThrowException(Exception::TypeError(String::New( + "Offset is out of bounds"))); + } + + size_t toWrite = args[2]->Int32Value(); + + if (buffer_remaining(buffer, offset) < toWrite) { + return ThrowException(Exception::TypeError(String::New( + "Length is out of bounds"))); + } + + // TODO Expose the second argument of WriteAscii? + // Could avoid doing slices when the string doesn't fit in a buffer. V8 + // slice() does copy the string, so exposing that argument would help. + + int written = s->WriteAscii(p, 0, toWrite); + + return scope.Close(Integer::New(written)); +} + + +static Handle Utf8Length(const Arguments &args) { + HandleScope scope; + if (!args[0]->IsString()) { + return ThrowException(Exception::TypeError(String::New( + "Argument must be a string"))); + } + Local s = args[0]->ToString(); + return scope.Close(Integer::New(s->Utf8Length())); +} + + void InitBuffer(Handle target) { HandleScope scope; @@ -271,6 +355,11 @@ void InitBuffer(Handle target) { // copy NODE_SET_PROTOTYPE_METHOD(constructor_template, "utf8Slice", Utf8Slice); + NODE_SET_PROTOTYPE_METHOD(constructor_template, "utf8Write", Utf8Write); + NODE_SET_PROTOTYPE_METHOD(constructor_template, "asciiWrite", AsciiWrite); + + NODE_SET_METHOD(constructor_template->GetFunction(), "utf8Length", Utf8Length); + target->Set(String::NewSymbol("Buffer"), constructor_template->GetFunction()); } diff --git a/src/node_net2.cc b/src/node_net2.cc index 3d8a1fadd67..7b4a788ca70 100644 --- a/src/node_net2.cc +++ b/src/node_net2.cc @@ -425,9 +425,9 @@ static Handle Read(const Arguments& args) { String::New("Length is extends beyond buffer"))); } - size_t bytes_read = read(fd, - buffer_p(buffer, off), - buffer_remaining(buffer, off)); + ssize_t bytes_read = read(fd, + buffer_p(buffer, off), + buffer_remaining(buffer, off)); if (bytes_read < 0) { if (errno == EAGAIN || errno == EINTR) return Null(); @@ -457,20 +457,20 @@ static Handle Write(const Arguments& args) { struct buffer * buffer = BufferUnwrap(args[1]); size_t off = args[2]->Int32Value(); - if (buffer_p(buffer, off) == NULL) { + char *p = buffer_p(buffer, off); + if (p == NULL) { return ThrowException(Exception::Error( String::New("Offset is out of bounds"))); } size_t len = args[3]->Int32Value(); - if (buffer_remaining(buffer, off) < len) { + size_t remaining = buffer_remaining(buffer, off); + if (remaining < len) { return ThrowException(Exception::Error( String::New("Length is extends beyond buffer"))); } - size_t written = write(fd, - buffer_p(buffer, off), - buffer_remaining(buffer, off)); + ssize_t written = write(fd, p, len); if (written < 0) { if (errno == EAGAIN || errno == EINTR) return Null(); diff --git a/tcp.js b/tcp.js index 6dd29f86886..36416d243d6 100644 --- a/tcp.js +++ b/tcp.js @@ -16,15 +16,18 @@ var read = process.read; var write = process.write; var toRead = process.toRead; -var Peer = function (peerInfo) { +var Stream = function (peerInfo) { process.EventEmitter.call(); var self = this; - process.mixin(self, peerInfo); + self.fd = peerInfo.fd; + self.remoteAddress = peerInfo.remoteAddress; + self.remotePort = peerInfo.remotePort; // Allocated on demand. self.recvBuffer = null; + self.sendQueue = []; self.readWatcher = new process.IOWatcher(function () { debug("\n" + self.fd + " readable"); @@ -32,25 +35,25 @@ var Peer = function (peerInfo) { // If this is the first recv (recvBuffer doesn't exist) or we've used up // most of the recvBuffer, allocate a new one. if (!self.recvBuffer || - self.recvBuffer.length - self.recvBufferBytesUsed < 128) { + self.recvBuffer.length - self.recvBuffer.used < 128) { self._allocateNewRecvBuf(); } - debug("recvBufferBytesUsed " + self.recvBufferBytesUsed); + debug("recvBuffer.used " + self.recvBuffer.used); var bytesRead = read(self.fd, self.recvBuffer, - self.recvBufferBytesUsed, - self.recvBuffer.length - self.recvBufferBytesUsed); + self.recvBuffer.used, + self.recvBuffer.length - self.recvBuffer.used); debug("bytesRead " + bytesRead + "\n"); - if (bytesRead == 0) { + if (bytesRead == 0) { self.readable = false; self.readWatcher.stop(); self.emit("eof"); } else { - var slice = self.recvBuffer.slice(self.recvBufferBytesUsed, - self.recvBufferBytesUsed + bytesRead); - self.recvBufferBytesUsed += bytesRead; + var slice = self.recvBuffer.slice(self.recvBuffer.used, + self.recvBuffer.used + bytesRead); + self.recvBuffer.used += bytesRead; self.emit("receive", slice); } }); @@ -58,18 +61,16 @@ var Peer = function (peerInfo) { self.readWatcher.start(); self.writeWatcher = new process.IOWatcher(function () { - debug(self.fd + " writable"); + self.flush(); }); self.writeWatcher.set(self.fd, false, true); self.readable = true; self.writable = true; - - self._out = []; }; -process.inherits(Peer, process.EventEmitter); +process.inherits(Stream, process.EventEmitter); -Peer.prototype._allocateNewRecvBuf = function () { +Stream.prototype._allocateNewRecvBuf = function () { var self = this; var newBufferSize = 1024; // TODO make this adjustable from user API @@ -83,7 +84,7 @@ Peer.prototype._allocateNewRecvBuf = function () { } else if (bytesToRead == 0) { // Probably getting an EOF - so let's not allocate so much. if (self.recvBuffer && - self.recvBuffer.length - self.recvBufferBytesUsed > 0) { + self.recvBuffer.length - self.recvBuffer.used > 0) { return; // just recv the eof on the old buf. } newBufferSize = 128; @@ -91,10 +92,115 @@ Peer.prototype._allocateNewRecvBuf = function () { } self.recvBuffer = new process.Buffer(newBufferSize); - self.recvBufferBytesUsed = 0; + self.recvBuffer.used = 0; }; -Peer.prototype.close = function () { +Stream.prototype._allocateSendBuffer = function () { + var b = new process.Buffer(1024); + b.used = 0; + b.sent = 0; + this.sendQueue.push(b); + return b; +}; + +Stream.prototype.send = function (data, encoding) { + var self = this; + if (typeof(data) == "string") { + var buffer; + if (self.sendQueue.length == 0) { + buffer = self._allocateSendBuffer(); + } else { + // walk through the sendQueue, find the first empty buffer + for (var i = 0; i < self.sendQueue.length; i++) { + if (self.sendQueue[i].used == 0) { + buffer = self.sendQueue[i]; + break; + } + } + // if we didn't find one, take the last + if (!buffer) { + buffer = self.sendQueue[self.sendQueue.length-1]; + // if last buffer is empty + if (buffer.length == buffer.used) buffer = self._allocateSendBuffer(); + } + } + + encoding = encoding || "ascii"; // default to ascii since it's faster + + var charsWritten; + + if (encoding.toLowerCase() == "utf8") { + charsWritten = buffer.utf8Write(data, + buffer.used, + buffer.length - buffer.used); + buffer.used += process.Buffer.utf8Length(data.slice(0, charsWritten)); + } else { + // ascii + charsWritten = buffer.asciiWrite(data, + buffer.used, + buffer.length - buffer.used); + buffer.used += charsWritten; + debug("ascii charsWritten " + charsWritten); + debug("ascii buffer.used " + buffer.used); + } + + + // If we didn't finish, then recurse with the rest of the string. + if (charsWritten < data.length) { + debug("recursive send"); + self.send(data.slice(charsWritten), encoding); + } + } else { + // data is a process.Buffer + + // walk through the sendQueue, find the first empty buffer + var inserted = false; + data.sent = 0; + data.used = data.length; + for (var i = 0; i < self.sendQueue.length; i++) { + if (self.sendQueue[i].used == 0) { + // if found, insert the data there + self.sendQueue.splice(i, 0, data); + inserted = true; + break; + } + } + + if (!inserted) self.sendQueue.push(data); + } + this.flush(); +}; + +// returns true if flushed without getting EAGAIN +// false if it got EAGAIN +Stream.prototype.flush = function () { + var self = this; + var bytesWritten; + while (self.sendQueue.length > 0) { + var b = self.sendQueue[0]; + + if (b.sent == b.used) { + // this can be improved - save the buffer for later? + self.sendQueue.shift() + continue; + } + + bytesWritten = write(self.fd, + b, + b.sent, + b.used - b.sent); + if (bytesWritten === null) { + this.writeWatcher.start(); + return false; + } + b.sent += bytesWritten; + debug("bytes sent: " + b.sent); + } + this.writeWatcher.stop(); + return true; +}; + +Stream.prototype.close = function () { this.readable = false; this.writable = false; @@ -113,14 +219,11 @@ var Server = function (listener) { } self.watcher = new process.IOWatcher(function (readable, writeable) { - debug("readable " + readable); - debug("writable " + writeable); while (self.fd) { - debug("accept from " + self.fd); var peerInfo = accept(self.fd); debug("accept: " + JSON.stringify(peerInfo)); if (!peerInfo) return; - var peer = new Peer(peerInfo); + var peer = new Stream(peerInfo); self.emit("connection", peer); } }); @@ -132,6 +235,7 @@ Server.prototype.listen = function () { if (self.fd) throw new Error("Already running"); + var backlogIndex; if (typeof(arguments[0]) == "string" && arguments.length == 1) { // the first argument specifies a path self.fd = process.socket("UNIX"); @@ -141,13 +245,15 @@ Server.prototype.listen = function () { // unlink(SOCKFILE); // } bind(self.fd, arguments[0]); + backlogIndex = 1; } else { // the first argument is the port, the second an IP self.fd = process.socket("TCP"); // TODO dns resolution on arguments[1] bind(self.fd, arguments[0], arguments[1]); + backlogIndex = typeof(arguments[1]) == "string" ? 2 : 1; } - listen(self.fd, 128); // TODO configurable backlog + listen(self.fd, arguments[backlogIndex] ? arguments[backlogIndex] : 128); self.watcher.set(self.fd, true, false); self.watcher.start(); @@ -179,6 +285,7 @@ var server = new Server(function (peer) { peer.addListener("receive", function (b) { sys.puts("recv (" + b.length + "): " + b); + peer.send("pong\r\n"); }); }); //server.listen(8000); From b23ed4a98cdd78493e7d9111bd6819f16bf2c8ac Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Thu, 17 Dec 2009 07:39:22 +0100 Subject: [PATCH 011/105] Rename new streaming code to 'net' --- tcp.js => lib/net.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tcp.js => lib/net.js (100%) diff --git a/tcp.js b/lib/net.js similarity index 100% rename from tcp.js rename to lib/net.js From 3fce98c85349601898442cc95a8550016f31f19c Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Thu, 17 Dec 2009 08:37:25 +0100 Subject: [PATCH 012/105] Add Stream.prototype.connect() and test-net-server.js --- test-net-server.js | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 test-net-server.js diff --git a/test-net-server.js b/test-net-server.js new file mode 100644 index 00000000000..5b9fbd085c2 --- /dev/null +++ b/test-net-server.js @@ -0,0 +1,23 @@ +process.Buffer.prototype.toString = function () { + return this.utf8Slice(0, this.length); +}; + +var sys = require("sys"); +var net = require("./lib/net"); + +var server = new net.Server(function (stream) { + sys.puts("connection (" + stream.fd + "): " + + stream.remoteAddress + + " port " + + stream.remotePort + ); + sys.puts("server fd: " + server.fd); + + stream.addListener("receive", function (b) { + stream.send("pong ascii\r\n", "ascii"); + stream.send(b); + stream.send("pong utf8\r\n", "utf8"); + }); +}); +server.listen(8000); +sys.puts("server fd: " + server.fd); From 0618f02f6f2fa326cef971522fc8d5066d7c38f3 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Thu, 17 Dec 2009 09:31:10 +0100 Subject: [PATCH 013/105] Implement half-closed streams --- lib/net.js | 312 ++++++++++++++++++++++++++++----------------- src/node_net2.cc | 4 +- test-net-server.js | 19 +++ 3 files changed, 214 insertions(+), 121 deletions(-) diff --git a/lib/net.js b/lib/net.js index 36416d243d6..16826a5a4d5 100644 --- a/lib/net.js +++ b/lib/net.js @@ -1,36 +1,37 @@ var debugLevel = 0; -if ("NODE_DEBUG" in process.ENV) debugLevel = 1; +if ('NODE_DEBUG' in process.ENV) debugLevel = 1; function debug (x) { if (debugLevel > 0) { - process.stdio.writeError(x + "\n"); + process.stdio.writeError(x + '\n'); } } -var socket = process.socket; -var bind = process.bind; -var listen = process.listen; -var accept = process.accept; -var close = process.close; -var shutdown = process.shutdown; -var read = process.read; -var write = process.write; -var toRead = process.toRead; -var Stream = function (peerInfo) { +var socket = process.socket; +var bind = process.bind; +var connect = process.connect; +var listen = process.listen; +var accept = process.accept; +var close = process.close; +var shutdown = process.shutdown; +var read = process.read; +var write = process.write; +var toRead = process.toRead; +var socketError = process.socketError; +var EINPROGRESS = process.EINPROGRESS; + + +function Stream (peerInfo) { process.EventEmitter.call(); var self = this; - self.fd = peerInfo.fd; - self.remoteAddress = peerInfo.remoteAddress; - self.remotePort = peerInfo.remotePort; - // Allocated on demand. self.recvBuffer = null; self.sendQueue = []; self.readWatcher = new process.IOWatcher(function () { - debug("\n" + self.fd + " readable"); + debug('\n' + self.fd + ' readable'); // If this is the first recv (recvBuffer doesn't exist) or we've used up // most of the recvBuffer, allocate a new one. @@ -39,36 +40,50 @@ var Stream = function (peerInfo) { self._allocateNewRecvBuf(); } - debug("recvBuffer.used " + self.recvBuffer.used); + debug('recvBuffer.used ' + self.recvBuffer.used); var bytesRead = read(self.fd, self.recvBuffer, self.recvBuffer.used, self.recvBuffer.length - self.recvBuffer.used); - debug("bytesRead " + bytesRead + "\n"); + debug('bytesRead ' + bytesRead + '\n'); if (bytesRead == 0) { self.readable = false; self.readWatcher.stop(); - self.emit("eof"); + self.emit('eof'); + if (!self.writable) self.forceClose(); } else { var slice = self.recvBuffer.slice(self.recvBuffer.used, self.recvBuffer.used + bytesRead); self.recvBuffer.used += bytesRead; - self.emit("receive", slice); + self.emit('receive', slice); } }); - self.readWatcher.set(self.fd, true, false); - self.readWatcher.start(); - self.writeWatcher = new process.IOWatcher(function () { + self._onWriteFlush = function () { self.flush(); - }); - self.writeWatcher.set(self.fd, false, true); + }; - self.readable = true; - self.writable = true; + self.writeWatcher = new process.IOWatcher(self._onWriteFlush); + + self.readable = false; + self.writable = false; + + if (peerInfo) { + self.fd = peerInfo.fd; + self.remoteAddress = peerInfo.remoteAddress; + self.remotePort = peerInfo.remotePort; + + self.readWatcher.set(self.fd, true, false); + self.readWatcher.start(); + self.writeWatcher.set(self.fd, false, true); + self.readable = true; + self.writable = true; + } }; process.inherits(Stream, process.EventEmitter); +exports.Stream = Stream; + Stream.prototype._allocateNewRecvBuf = function () { var self = this; @@ -95,6 +110,7 @@ Stream.prototype._allocateNewRecvBuf = function () { self.recvBuffer.used = 0; }; + Stream.prototype._allocateSendBuffer = function () { var b = new process.Buffer(1024); b.used = 0; @@ -103,56 +119,62 @@ Stream.prototype._allocateSendBuffer = function () { return b; }; + +Stream.prototype._sendString = function (data, encoding) { + var self = this; + var buffer; + if (self.sendQueue.length == 0) { + buffer = self._allocateSendBuffer(); + } else { + // walk through the sendQueue, find the buffer with free space + for (var i = 0; i < self.sendQueue.length; i++) { + if (self.sendQueue[i].used == 0) { + buffer = self.sendQueue[i]; + break; + } + } + // if we didn't find one, take the last + if (!buffer) { + buffer = self.sendQueue[self.sendQueue.length-1]; + // if last buffer is used up + if (buffer.length == buffer.used) buffer = self._allocateSendBuffer(); + } + } + + encoding = encoding || 'ascii'; // default to ascii since it's faster + + var charsWritten; + + if (encoding.toLowerCase() == 'utf8') { + charsWritten = buffer.utf8Write(data, + buffer.used, + buffer.length - buffer.used); + buffer.used += process.Buffer.utf8Length(data.slice(0, charsWritten)); + } else { + // ascii + charsWritten = buffer.asciiWrite(data, + buffer.used, + buffer.length - buffer.used); + buffer.used += charsWritten; + debug('ascii charsWritten ' + charsWritten); + debug('ascii buffer.used ' + buffer.used); + } + + + // If we didn't finish, then recurse with the rest of the string. + if (charsWritten < data.length) { + debug('recursive send'); + self._sendString(data.slice(charsWritten), encoding); + } +}; + + Stream.prototype.send = function (data, encoding) { var self = this; - if (typeof(data) == "string") { - var buffer; - if (self.sendQueue.length == 0) { - buffer = self._allocateSendBuffer(); - } else { - // walk through the sendQueue, find the first empty buffer - for (var i = 0; i < self.sendQueue.length; i++) { - if (self.sendQueue[i].used == 0) { - buffer = self.sendQueue[i]; - break; - } - } - // if we didn't find one, take the last - if (!buffer) { - buffer = self.sendQueue[self.sendQueue.length-1]; - // if last buffer is empty - if (buffer.length == buffer.used) buffer = self._allocateSendBuffer(); - } - } - - encoding = encoding || "ascii"; // default to ascii since it's faster - - var charsWritten; - - if (encoding.toLowerCase() == "utf8") { - charsWritten = buffer.utf8Write(data, - buffer.used, - buffer.length - buffer.used); - buffer.used += process.Buffer.utf8Length(data.slice(0, charsWritten)); - } else { - // ascii - charsWritten = buffer.asciiWrite(data, - buffer.used, - buffer.length - buffer.used); - buffer.used += charsWritten; - debug("ascii charsWritten " + charsWritten); - debug("ascii buffer.used " + buffer.used); - } - - - // If we didn't finish, then recurse with the rest of the string. - if (charsWritten < data.length) { - debug("recursive send"); - self.send(data.slice(charsWritten), encoding); - } + if (typeof(data) == 'string') { + self._sendString(data, encoding); } else { // data is a process.Buffer - // walk through the sendQueue, find the first empty buffer var inserted = false; data.sent = 0; @@ -171,6 +193,7 @@ Stream.prototype.send = function (data, encoding) { this.flush(); }; + // returns true if flushed without getting EAGAIN // false if it got EAGAIN Stream.prototype.flush = function () { @@ -194,51 +217,124 @@ Stream.prototype.flush = function () { return false; } b.sent += bytesWritten; - debug("bytes sent: " + b.sent); + debug('bytes sent: ' + b.sent); } this.writeWatcher.stop(); return true; }; -Stream.prototype.close = function () { - this.readable = false; - this.writable = false; - this.writeWatcher.stop(); - this.readWatcher.stop(); - close(this.fd); - debug("close peer " + this.fd); - this.fd = null; +// var stream = new Stream(); +// stream.connect(80) - TCP connect to port 80 on the localhost +// stream.connect(80, 'nodejs.org') - TCP connect to port 80 on nodejs.org +// stream.connect('/tmp/socket') - UNIX connect to socket specified by path +Stream.prototype.connect = function () { + var self = this; + if (self.fd) throw new Error('Stream already opened'); + + if (typeof(arguments[0]) == 'string' && arguments.length == 1) { + self.fd = process.socket('UNIX'); + // TODO check if sockfile exists? + } else { + self.fd = process.socket('TCP'); + // TODO dns resolution on arguments[1] + } + + try { + connect(self.fd, arguments[0], arguments[1]); + } catch (e) { + close(self.fd); + throw e; + } + + // Don't start the read watcher until connection is established + self.readWatcher.set(self.fd, true, false); + + // How to connect on POSIX: Wait for fd to become writable, then call + // socketError() if there isn't an error, we're connected. AFAIK this a + // platform independent way determining when a non-blocking connection + // is established, but I have only seen it documented in the Linux + // Manual Page connect(2) under the error code EINPROGRESS. + self.writeWatcher.set(self.fd, false, true); + self.writeWatcher.start(); + self.writeWatcher.callback = function () { + var errno = socketError(self.fd); + if (errno == 0) { + // connection established + self.emit('connect'); + self.readWatcher.start(); + self.readable = true; + self.writable = true; + self.writeWatcher.callback = self._onWriteFlush; + } else if (errno != EINPROGRESS) { + var e = new Error('connection error'); + e.errno = errno; + self.readWatcher.stop(); + self.writeWatcher.stop(); + close(self.fd); + } + }; }; -var Server = function (listener) { + +Stream.prototype.forceClose = function (exception) { + if (this.fd) { + this.readable = false; + this.writable = false; + + this.writeWatcher.stop(); + this.readWatcher.stop(); + close(this.fd); + debug('close peer ' + this.fd); + this.fd = null; + this.emit('close', exception); + } +}; + + +Stream.prototype.close = function () { + if (this.readable && this.writable) { + this.writable = false; + shutdown(this.fd, "write"); + } else if (!this.readable && this.writable) { + // already got EOF + this.forceClose(this.fd); + } + // In the case we've already shutdown write side, + // but haven't got EOF: ignore. In the case we're + // fully closed already: ignore. +}; + + +function Server (listener) { var self = this; if (listener) { - self.addListener("connection", listener); + self.addListener('connection', listener); } self.watcher = new process.IOWatcher(function (readable, writeable) { while (self.fd) { var peerInfo = accept(self.fd); - debug("accept: " + JSON.stringify(peerInfo)); + debug('accept: ' + JSON.stringify(peerInfo)); if (!peerInfo) return; var peer = new Stream(peerInfo); - self.emit("connection", peer); + self.emit('connection', peer); } }); }; process.inherits(Server, process.EventEmitter); +exports.Server = Server; + Server.prototype.listen = function () { var self = this; - - if (self.fd) throw new Error("Already running"); + if (self.fd) throw new Error('Server already opened'); var backlogIndex; - if (typeof(arguments[0]) == "string" && arguments.length == 1) { + if (typeof(arguments[0]) == 'string' && arguments.length == 1) { // the first argument specifies a path - self.fd = process.socket("UNIX"); + self.fd = process.socket('UNIX'); // TODO unlink sockfile if exists? // if (lstat(SOCKFILE, &tstat) == 0) { // assert(S_ISSOCK(tstat.st_mode)); @@ -248,46 +344,24 @@ Server.prototype.listen = function () { backlogIndex = 1; } else { // the first argument is the port, the second an IP - self.fd = process.socket("TCP"); + self.fd = process.socket('TCP'); // TODO dns resolution on arguments[1] bind(self.fd, arguments[0], arguments[1]); - backlogIndex = typeof(arguments[1]) == "string" ? 2 : 1; + backlogIndex = typeof(arguments[1]) == 'string' ? 2 : 1; } listen(self.fd, arguments[backlogIndex] ? arguments[backlogIndex] : 128); + self.emit("listening"); + self.watcher.set(self.fd, true, false); self.watcher.start(); }; + Server.prototype.close = function () { var self = this; - if (!self.fd) throw new Error("Not running"); + if (!self.fd) throw new Error('Not running'); self.watcher.stop(); close(self.fd); self.fd = null; }; - -/////////////////////////////////////////////////////// - -process.Buffer.prototype.toString = function () { - return this.utf8Slice(0, this.length); -}; - -var sys = require("sys"); - -var server = new Server(function (peer) { - sys.puts("connection (" + peer.fd + "): " - + peer.remoteAddress - + " port " - + peer.remotePort - ); - sys.puts("server fd: " + server.fd); - - peer.addListener("receive", function (b) { - sys.puts("recv (" + b.length + "): " + b); - peer.send("pong\r\n"); - }); -}); -//server.listen(8000); -server.listen(8000); -sys.puts("server fd: " + server.fd); diff --git a/src/node_net2.cc b/src/node_net2.cc index 7b4a788ca70..af96216e200 100644 --- a/src/node_net2.cc +++ b/src/node_net2.cc @@ -377,7 +377,7 @@ static Handle Accept(const Arguments& args) { } -static Handle GetSocketError(const Arguments& args) { +static Handle SocketError(const Arguments& args) { HandleScope scope; FD_ARG(args[0]) @@ -515,7 +515,7 @@ void InitNet2(Handle target) { NODE_SET_METHOD(target, "bind", Bind); NODE_SET_METHOD(target, "listen", Listen); NODE_SET_METHOD(target, "accept", Accept); - NODE_SET_METHOD(target, "getSocketError", GetSocketError); + NODE_SET_METHOD(target, "socketError", SocketError); NODE_SET_METHOD(target, "toRead", ToRead); diff --git a/test-net-server.js b/test-net-server.js index 5b9fbd085c2..aabd7f2b2e4 100644 --- a/test-net-server.js +++ b/test-net-server.js @@ -18,6 +18,25 @@ var server = new net.Server(function (stream) { stream.send(b); stream.send("pong utf8\r\n", "utf8"); }); + + stream.addListener("eof", function () { + sys.puts("server peer eof"); + stream.close(); + }); }); server.listen(8000); sys.puts("server fd: " + server.fd); + + +var stream = new net.Stream(); +stream.addListener('connect', function () { + sys.puts("!!!client connected"); + stream.send("hello\n"); +}); + +stream.addListener('receive', function (d) { + sys.puts("!!!client got: " + JSON.stringify(d.toString())); +}); + +stream.connect(8000); + From 6e5abf4551634ee27d6e3a25499ec6f4f94215c0 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Fri, 18 Dec 2009 14:52:02 +0100 Subject: [PATCH 014/105] implement getaddrinfo --- lib/net.js | 141 +++++++++++++++++++++--------- src/node_net2.cc | 213 +++++++++++++++++++++++++++++++++++++++++++-- test-net-server.js | 18 ++-- wscript | 2 +- 4 files changed, 322 insertions(+), 52 deletions(-) diff --git a/lib/net.js b/lib/net.js index 16826a5a4d5..cf2a8bf8a10 100644 --- a/lib/net.js +++ b/lib/net.js @@ -7,6 +7,7 @@ function debug (x) { } +var assert = process.assert; var socket = process.socket; var bind = process.bind; var connect = process.connect; @@ -18,6 +19,9 @@ var read = process.read; var write = process.write; var toRead = process.toRead; var socketError = process.socketError; +var getsockname = process.getsockname; +var getaddrinfo = process.getaddrinfo; +var needsLookup = process.needsLookup; var EINPROGRESS = process.EINPROGRESS; @@ -28,11 +32,8 @@ function Stream (peerInfo) { // Allocated on demand. self.recvBuffer = null; - self.sendQueue = []; self.readWatcher = new process.IOWatcher(function () { - debug('\n' + self.fd + ' readable'); - // If this is the first recv (recvBuffer doesn't exist) or we've used up // most of the recvBuffer, allocate a new one. if (!self.recvBuffer || @@ -59,14 +60,19 @@ function Stream (peerInfo) { self.emit('receive', slice); } }); - - self._onWriteFlush = function () { - self.flush(); - }; - - self.writeWatcher = new process.IOWatcher(self._onWriteFlush); - self.readable = false; + + self.sendQueue = []; // queue of buffers that need to be written to socket + // XXX use link list? + self.sendQueueSize = 0; // in bytes, not to be confused with sendQueue.length! + self._doFlush = function () { + assert(self.sendQueueSize > 0); + if (self.flush()) { + assert(self.sendQueueSize == 0); + self.emit("drain"); + } + }; + self.writeWatcher = new process.IOWatcher(self._doFlush); self.writable = false; if (peerInfo) { @@ -76,8 +82,9 @@ function Stream (peerInfo) { self.readWatcher.set(self.fd, true, false); self.readWatcher.start(); - self.writeWatcher.set(self.fd, false, true); self.readable = true; + + self.writeWatcher.set(self.fd, false, true); self.writable = true; } }; @@ -85,6 +92,13 @@ process.inherits(Stream, process.EventEmitter); exports.Stream = Stream; +exports.createConnection = function (port, host) { + var s = new Stream(); + s.connect(port, host); + return s; +}; + + Stream.prototype._allocateNewRecvBuf = function () { var self = this; @@ -122,6 +136,7 @@ Stream.prototype._allocateSendBuffer = function () { Stream.prototype._sendString = function (data, encoding) { var self = this; + if (!self.writable) throw new Error('Stream is not writable'); var buffer; if (self.sendQueue.length == 0) { buffer = self._allocateSendBuffer(); @@ -144,22 +159,26 @@ Stream.prototype._sendString = function (data, encoding) { encoding = encoding || 'ascii'; // default to ascii since it's faster var charsWritten; + var bytesWritten; if (encoding.toLowerCase() == 'utf8') { charsWritten = buffer.utf8Write(data, buffer.used, buffer.length - buffer.used); - buffer.used += process.Buffer.utf8Length(data.slice(0, charsWritten)); + bytesWritten = process.Buffer.utf8Length(data.slice(0, charsWritten)); } else { // ascii charsWritten = buffer.asciiWrite(data, buffer.used, buffer.length - buffer.used); - buffer.used += charsWritten; - debug('ascii charsWritten ' + charsWritten); - debug('ascii buffer.used ' + buffer.used); + bytesWritten = charsWritten; } + + buffer.used += bytesWritten; + self.sendQueueSize += bytesWritten; + debug('charsWritten ' + charsWritten); + debug('buffer.used ' + buffer.used); // If we didn't finish, then recurse with the rest of the string. if (charsWritten < data.length) { @@ -169,8 +188,12 @@ Stream.prototype._sendString = function (data, encoding) { }; +// Returns true if all the data was flushed to socket. Returns false if +// something was queued. If data was queued, then the "drain" event will +// signal when it has been finally flushed to socket. Stream.prototype.send = function (data, encoding) { var self = this; + if (!self.writable) throw new Error('Stream is not writable'); if (typeof(data) == 'string') { self._sendString(data, encoding); } else { @@ -189,15 +212,18 @@ Stream.prototype.send = function (data, encoding) { } if (!inserted) self.sendQueue.push(data); + + self.sendQueueSize += data.used; } - this.flush(); + return this.flush(); }; -// returns true if flushed without getting EAGAIN -// false if it got EAGAIN +// Flushes the write buffer out. Emits "drain" if the buffer is empty. Stream.prototype.flush = function () { var self = this; + if (!self.writable) throw new Error('Stream is not writable'); + var bytesWritten; while (self.sendQueue.length > 0) { var b = self.sendQueue[0]; @@ -213,13 +239,16 @@ Stream.prototype.flush = function () { b.sent, b.used - b.sent); if (bytesWritten === null) { - this.writeWatcher.start(); + // could not flush everything + self.writeWatcher.start(); + assert(self.sendQueueSize > 0); return false; } b.sent += bytesWritten; + self.sendQueueSize -= bytesWritten; debug('bytes sent: ' + b.sent); } - this.writeWatcher.stop(); + self.writeWatcher.stop(); return true; }; @@ -261,17 +290,15 @@ Stream.prototype.connect = function () { var errno = socketError(self.fd); if (errno == 0) { // connection established - self.emit('connect'); self.readWatcher.start(); self.readable = true; self.writable = true; - self.writeWatcher.callback = self._onWriteFlush; + self.writeWatcher.callback = self._doFlush; + self.emit('connect'); } else if (errno != EINPROGRESS) { var e = new Error('connection error'); e.errno = errno; - self.readWatcher.stop(); - self.writeWatcher.stop(); - close(self.fd); + self.forceClose(e); } }; }; @@ -292,17 +319,35 @@ Stream.prototype.forceClose = function (exception) { }; -Stream.prototype.close = function () { - if (this.readable && this.writable) { +Stream.prototype._shutdown = function () { + if (this.writable) { this.writable = false; shutdown(this.fd, "write"); - } else if (!this.readable && this.writable) { + } +}; + + +Stream.prototype.close = function () { + var self = this; + var closeMethod; + if (self.readable && self.writable) { + closeMethod = self._shutdown; + } else if (!self.readable && self.writable) { // already got EOF - this.forceClose(this.fd); + closeMethod = self.forceClose; } // In the case we've already shutdown write side, // but haven't got EOF: ignore. In the case we're // fully closed already: ignore. + + if (closeMethod) { + if (self.sendQueueSize == 0) { + // no queue. just shut down the socket. + closeMethod(); + } else { + self.addListener("drain", closeMethod); + } + } }; @@ -327,30 +372,43 @@ process.inherits(Server, process.EventEmitter); exports.Server = Server; +exports.createServer = function (listener) { + return new Server(listener); +}; + + Server.prototype.listen = function () { var self = this; if (self.fd) throw new Error('Server already opened'); - var backlogIndex; if (typeof(arguments[0]) == 'string' && arguments.length == 1) { // the first argument specifies a path self.fd = process.socket('UNIX'); + self.type = 'UNIX'; // TODO unlink sockfile if exists? // if (lstat(SOCKFILE, &tstat) == 0) { // assert(S_ISSOCK(tstat.st_mode)); // unlink(SOCKFILE); // } bind(self.fd, arguments[0]); - backlogIndex = 1; + } else if (arguments.length == 0) { + self.fd = process.socket('TCP'); + self.type = 'TCP'; + // Don't bind(). OS will assign a port with INADDR_ANY. The port will be + // passed to the 'listening' event. } else { // the first argument is the port, the second an IP self.fd = process.socket('TCP'); + self.type = 'TCP'; + if (needsLookup(arguments[1])) { + getaddrinfo(arguments[1], function (ip) { + }); + } // TODO dns resolution on arguments[1] bind(self.fd, arguments[0], arguments[1]); - backlogIndex = typeof(arguments[1]) == 'string' ? 2 : 1; } - listen(self.fd, arguments[backlogIndex] ? arguments[backlogIndex] : 128); + listen(self.fd, 128); self.emit("listening"); self.watcher.set(self.fd, true, false); @@ -358,10 +416,15 @@ Server.prototype.listen = function () { }; -Server.prototype.close = function () { - var self = this; - if (!self.fd) throw new Error('Not running'); - self.watcher.stop(); - close(self.fd); - self.fd = null; +Server.prototype.sockName = function () { + return getsockname(self.fd); +}; + + +Server.prototype.close = function () { + if (!this.fd) throw new Error('Not running'); + this.watcher.stop(); + close(this.fd); + this.fd = null; + this.emit("close"); }; diff --git a/src/node_net2.cc b/src/node_net2.cc index af96216e200..3457cdbf54a 100644 --- a/src/node_net2.cc +++ b/src/node_net2.cc @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -32,6 +33,8 @@ static Persistent syscall_symbol; static Persistent fd_symbol; static Persistent remote_address_symbol; static Persistent remote_port_symbol; +static Persistent address_symbol; +static Persistent port_symbol; #define FD_ARG(a) \ if (!(a)->IsInt32()) { \ @@ -181,11 +184,11 @@ static inline Handle ParseAddressArgs(Handle first, char ipv6[255] = "::FFFF:"; if (inet_pton(AF_INET, *ip, &(in6.sin6_addr)) > 0) { - // If this is an IPv4 address then we need to change it - // to the IPv4-mapped-on-IPv6 format which looks like + // If this is an IPv4 address then we need to change it + // to the IPv4-mapped-on-IPv6 format which looks like // ::FFFF: - // For more information see "Address Format" ipv6(7) and - // "BUGS" in inet_pton(3) + // For more information see "Address Format" ipv6(7) and + // "BUGS" in inet_pton(3) strcat(ipv6, *ip); } else { strcpy(ipv6, *ip); @@ -313,6 +316,38 @@ static Handle Connect(const Arguments& args) { } +static Handle GetSockName(const Arguments& args) { + HandleScope scope; + + FD_ARG(args[0]) + + struct sockaddr_storage address_storage; + socklen_t len = sizeof(struct sockaddr_storage); + + int r = getsockname(fd, (struct sockaddr *) &address_storage, &len); + + if (r < 0) { + return ThrowException(ErrnoException(errno, "getsockname")); + } + + Local info = Object::New(); + + if (address_storage.ss_family == AF_INET6) { + struct sockaddr_in6 *a = (struct sockaddr_in6*)&address_storage; + + char ip[INET6_ADDRSTRLEN]; + inet_ntop(AF_INET6, &(a->sin6_addr), ip, INET6_ADDRSTRLEN); + + int port = ntohs(a->sin6_port); + + info->Set(address_symbol, String::New(ip)); + info->Set(port_symbol, Integer::New(port)); + } + + return scope.Close(info); +} + + static Handle Listen(const Arguments& args) { HandleScope scope; @@ -323,6 +358,7 @@ static Handle Listen(const Arguments& args) { return ThrowException(ErrnoException(errno, "listen")); } + return Undefined(); } @@ -499,6 +535,167 @@ static Handle ToRead(const Arguments& args) { } +// G E T A D D R I N F O + +struct resolve_request { + Persistent cb; + int ai_family; // AF_INET or AF_INET6 + char hostname[1]; +}; + + +static int AfterResolve(eio_req *req) { + ev_unref(EV_DEFAULT_UC); + + struct resolve_request * rreq = (struct resolve_request *)(req->data); + + struct addrinfo *address = NULL, + *address_list = static_cast(req->ptr2); + + HandleScope scope; + Local argv[1]; + + if (req->result != 0) { + argv[0] = ErrnoException(errno, "getaddrinfo"); + } else { + int n = 0; + for (address = address_list; address; address = address->ai_next) { n++; } + + Local results = Array::New(n); + + char ip[INET6_ADDRSTRLEN]; + + n = 0; + address = address_list; + while (address) { + HandleScope scope; + assert(address->ai_family == AF_INET || address->ai_family == AF_INET6); + assert(address->ai_socktype == SOCK_STREAM); + const char *c = inet_ntop(address->ai_family, &(address->ai_addr), ip, INET6_ADDRSTRLEN); + Local s = String::New(c); + results->Set(Integer::New(n), s); + + n++; + address = address->ai_next; + } + + argv[0] = results; + } + + TryCatch try_catch; + + rreq->cb->Call(Context::GetCurrent()->Global(), 1, argv); + + if (try_catch.HasCaught()) { + FatalException(try_catch); + } + + rreq->cb.Dispose(); // Dispose of the persistent handle + free(rreq); + freeaddrinfo(address_list); +} + +static int Resolve(eio_req *req) { + // Note: this function is executed in the thread pool! CAREFUL + struct resolve_request * rreq = (struct resolve_request *) req->data; + struct addrinfo *address_list = NULL; + + struct addrinfo hints; + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = rreq->ai_family; + hints.ai_socktype = SOCK_STREAM; + + req->result = getaddrinfo((char*)rreq->hostname, NULL, &hints, &address_list); + req->ptr2 = address_list; + return 0; +} + + +static Handle GetAddrInfo(const Arguments& args) { + HandleScope scope; + + String::Utf8Value hostname(args[0]->ToString()); + + int type = args[1]->Int32Value(); + int fam = AF_INET; + switch (type) { + case 4: + fam = AF_INET; + break; + case 6: + fam = AF_INET6; + break; + default: + return ThrowException(Exception::TypeError( + String::New("Second argument must be an integer 4 or 6"))); + } + + if (!args[2]->IsFunction()) { + return ThrowException(Exception::TypeError( + String::New("Thrid argument must be a callback"))); + } + + Local cb = Local::Cast(args[2]); + + struct resolve_request *rreq = (struct resolve_request *) + malloc(sizeof(struct resolve_request) + hostname.length()); + + if (!rreq) { + V8::LowMemoryNotification(); + return ThrowException(Exception::Error( + String::New("Could not allocate enough memory"))); + } + + strcpy(rreq->hostname, *hostname); + rreq->cb = Persistent::New(cb); + rreq->ai_family = fam; + + // For the moment I will do DNS lookups in the eio thread pool. This is + // sub-optimal and cannot handle massive numbers of requests. + // + // (One particularly annoying problem is that the pthread stack size needs + // to be increased dramatically to handle getaddrinfo() see X_STACKSIZE in + // wscript ). + // + // In the future I will move to a system using c-ares: + // http://lists.schmorp.de/pipermail/libev/2009q1/000632.html + eio_custom(Resolve, EIO_PRI_DEFAULT, AfterResolve, rreq); + + // There will not be any active watchers from this object on the event + // loop while getaddrinfo() runs. If the only thing happening in the + // script was this hostname resolution, then the event loop would drop + // out. Thus we need to add ev_ref() until AfterResolve(). + ev_ref(EV_DEFAULT_UC); + + return Undefined(); +} + + +static Handle NeedsLookup(const Arguments& args) { + HandleScope scope; + + if (args[0]->IsNull() || args[0]->IsUndefined()) return False(); + + String::Utf8Value s(args[0]->ToString()); + + // avoiding buffer overflows in the following strcat + // 2001:0db8:85a3:08d3:1319:8a2e:0370:7334 + // 39 = max ipv6 address. + if (s.length() > INET6_ADDRSTRLEN) return True(); + + struct sockaddr_in6 a; + + if (inet_pton(AF_INET, *s, &(a.sin6_addr)) > 0) return False(); + if (inet_pton(AF_INET6, *s, &(a.sin6_addr)) > 0) return False(); + + char ipv6[255] = "::FFFF:"; + strcat(ipv6, *s); + if (inet_pton(AF_INET6, ipv6, &(a.sin6_addr)) > 0) return False(); + + return True(); +} + + void InitNet2(Handle target) { HandleScope scope; @@ -517,7 +714,9 @@ void InitNet2(Handle target) { NODE_SET_METHOD(target, "accept", Accept); NODE_SET_METHOD(target, "socketError", SocketError); NODE_SET_METHOD(target, "toRead", ToRead); - + NODE_SET_METHOD(target, "getsocksame", GetSockName); + NODE_SET_METHOD(target, "getaddrinfo", GetAddrInfo); + NODE_SET_METHOD(target, "needsLookup", NeedsLookup); target->Set(String::NewSymbol("EINPROGRESS"), Integer::New(EINPROGRESS)); target->Set(String::NewSymbol("EINTR"), Integer::New(EINTR)); @@ -525,12 +724,14 @@ void InitNet2(Handle target) { target->Set(String::NewSymbol("EPERM"), Integer::New(EPERM)); target->Set(String::NewSymbol("EADDRINUSE"), Integer::New(EADDRINUSE)); target->Set(String::NewSymbol("ECONNREFUSED"), Integer::New(ECONNREFUSED)); - + errno_symbol = NODE_PSYMBOL("errno"); syscall_symbol = NODE_PSYMBOL("syscall"); fd_symbol = NODE_PSYMBOL("fd"); remote_address_symbol = NODE_PSYMBOL("remoteAddress"); remote_port_symbol = NODE_PSYMBOL("remotePort"); + address_symbol = NODE_PSYMBOL("address"); + port_symbol = NODE_PSYMBOL("port"); } } // namespace node diff --git a/test-net-server.js b/test-net-server.js index aabd7f2b2e4..6968eae0661 100644 --- a/test-net-server.js +++ b/test-net-server.js @@ -19,6 +19,10 @@ var server = new net.Server(function (stream) { stream.send("pong utf8\r\n", "utf8"); }); + stream.addListener('drain', function () { + sys.puts("server-side socket drain"); + }); + stream.addListener("eof", function () { sys.puts("server peer eof"); stream.close(); @@ -28,15 +32,17 @@ server.listen(8000); sys.puts("server fd: " + server.fd); -var stream = new net.Stream(); -stream.addListener('connect', function () { +var c = net.createConnection(8000); +c.addListener('connect', function () { sys.puts("!!!client connected"); - stream.send("hello\n"); + c.send("hello\n"); }); -stream.addListener('receive', function (d) { +c.addListener('drain', function () { + sys.puts("!!!client drain"); +}); + +c.addListener('receive', function (d) { sys.puts("!!!client got: " + JSON.stringify(d.toString())); }); -stream.connect(8000); - diff --git a/wscript b/wscript index d2e13c9339e..43a7a5322ef 100644 --- a/wscript +++ b/wscript @@ -139,7 +139,7 @@ def configure(conf): conf.define("HAVE_CONFIG_H", 1) - conf.env.append_value("CCFLAGS", "-DX_STACKSIZE=%d" % (1024*64)) + conf.env.append_value("CCFLAGS", "-DX_STACKSIZE=%d" % (2*1024*1024)) # LFS conf.env.append_value('CCFLAGS', '-D_LARGEFILE_SOURCE') From 153b75593678a801985ce27a2af0d2235dcf74d3 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Mon, 28 Dec 2009 16:18:03 +0100 Subject: [PATCH 015/105] Change IOWatcher constructor to have no arguments --- lib/net.js | 66 ++++++++++++++++++++++-------------------- src/node_io_watcher.cc | 17 ++++------- src/node_io_watcher.h | 2 +- 3 files changed, 41 insertions(+), 44 deletions(-) diff --git a/lib/net.js b/lib/net.js index cf2a8bf8a10..3108feebde7 100644 --- a/lib/net.js +++ b/lib/net.js @@ -7,6 +7,7 @@ function debug (x) { } +var IOWatcher = process.IOWatcher; var assert = process.assert; var socket = process.socket; var bind = process.bind; @@ -25,7 +26,7 @@ var needsLookup = process.needsLookup; var EINPROGRESS = process.EINPROGRESS; -function Stream (peerInfo) { +function Socket (peerInfo) { process.EventEmitter.call(); var self = this; @@ -33,10 +34,11 @@ function Stream (peerInfo) { // Allocated on demand. self.recvBuffer = null; - self.readWatcher = new process.IOWatcher(function () { + self.readWatcher = new IOWatcher() + self.readWatcher.callback = function () { // If this is the first recv (recvBuffer doesn't exist) or we've used up // most of the recvBuffer, allocate a new one. - if (!self.recvBuffer || + if (!self.recvBuffer || self.recvBuffer.length - self.recvBuffer.used < 128) { self._allocateNewRecvBuf(); } @@ -52,18 +54,18 @@ function Stream (peerInfo) { self.readable = false; self.readWatcher.stop(); self.emit('eof'); - if (!self.writable) self.forceClose(); + if (!self.writable) self.forceClose(); } else { var slice = self.recvBuffer.slice(self.recvBuffer.used, self.recvBuffer.used + bytesRead); self.recvBuffer.used += bytesRead; self.emit('receive', slice); } - }); + }; self.readable = false; self.sendQueue = []; // queue of buffers that need to be written to socket - // XXX use link list? + // XXX use link list? self.sendQueueSize = 0; // in bytes, not to be confused with sendQueue.length! self._doFlush = function () { assert(self.sendQueueSize > 0); @@ -72,7 +74,8 @@ function Stream (peerInfo) { self.emit("drain"); } }; - self.writeWatcher = new process.IOWatcher(self._doFlush); + self.writeWatcher = new IOWatcher(); + self.writeWatcher.callback = self._doFlush; self.writable = false; if (peerInfo) { @@ -88,18 +91,18 @@ function Stream (peerInfo) { self.writable = true; } }; -process.inherits(Stream, process.EventEmitter); -exports.Stream = Stream; +process.inherits(Socket, process.EventEmitter); +exports.Socket = Socket; exports.createConnection = function (port, host) { - var s = new Stream(); + var s = new Socket(); s.connect(port, host); return s; }; -Stream.prototype._allocateNewRecvBuf = function () { +Socket.prototype._allocateNewRecvBuf = function () { var self = this; var newBufferSize = 1024; // TODO make this adjustable from user API @@ -125,7 +128,7 @@ Stream.prototype._allocateNewRecvBuf = function () { }; -Stream.prototype._allocateSendBuffer = function () { +Socket.prototype._allocateSendBuffer = function () { var b = new process.Buffer(1024); b.used = 0; b.sent = 0; @@ -134,9 +137,9 @@ Stream.prototype._allocateSendBuffer = function () { }; -Stream.prototype._sendString = function (data, encoding) { +Socket.prototype._sendString = function (data, encoding) { var self = this; - if (!self.writable) throw new Error('Stream is not writable'); + if (!self.writable) throw new Error('Socket is not writable'); var buffer; if (self.sendQueue.length == 0) { buffer = self._allocateSendBuffer(); @@ -191,9 +194,9 @@ Stream.prototype._sendString = function (data, encoding) { // Returns true if all the data was flushed to socket. Returns false if // something was queued. If data was queued, then the "drain" event will // signal when it has been finally flushed to socket. -Stream.prototype.send = function (data, encoding) { +Socket.prototype.send = function (data, encoding) { var self = this; - if (!self.writable) throw new Error('Stream is not writable'); + if (!self.writable) throw new Error('Socket is not writable'); if (typeof(data) == 'string') { self._sendString(data, encoding); } else { @@ -220,9 +223,9 @@ Stream.prototype.send = function (data, encoding) { // Flushes the write buffer out. Emits "drain" if the buffer is empty. -Stream.prototype.flush = function () { +Socket.prototype.flush = function () { var self = this; - if (!self.writable) throw new Error('Stream is not writable'); + if (!self.writable) throw new Error('Socket is not writable'); var bytesWritten; while (self.sendQueue.length > 0) { @@ -253,13 +256,13 @@ Stream.prototype.flush = function () { }; -// var stream = new Stream(); +// var stream = new Socket(); // stream.connect(80) - TCP connect to port 80 on the localhost // stream.connect(80, 'nodejs.org') - TCP connect to port 80 on nodejs.org // stream.connect('/tmp/socket') - UNIX connect to socket specified by path -Stream.prototype.connect = function () { +Socket.prototype.connect = function () { var self = this; - if (self.fd) throw new Error('Stream already opened'); + if (self.fd) throw new Error('Socket already opened'); if (typeof(arguments[0]) == 'string' && arguments.length == 1) { self.fd = process.socket('UNIX'); @@ -304,7 +307,7 @@ Stream.prototype.connect = function () { }; -Stream.prototype.forceClose = function (exception) { +Socket.prototype.forceClose = function (exception) { if (this.fd) { this.readable = false; this.writable = false; @@ -314,12 +317,12 @@ Stream.prototype.forceClose = function (exception) { close(this.fd); debug('close peer ' + this.fd); this.fd = null; - this.emit('close', exception); + this.emit('close', exception); } }; -Stream.prototype._shutdown = function () { +Socket.prototype._shutdown = function () { if (this.writable) { this.writable = false; shutdown(this.fd, "write"); @@ -327,7 +330,7 @@ Stream.prototype._shutdown = function () { }; -Stream.prototype.close = function () { +Socket.prototype.close = function () { var self = this; var closeMethod; if (self.readable && self.writable) { @@ -336,7 +339,7 @@ Stream.prototype.close = function () { // already got EOF closeMethod = self.forceClose; } - // In the case we've already shutdown write side, + // In the case we've already shutdown write side, // but haven't got EOF: ignore. In the case we're // fully closed already: ignore. @@ -358,16 +361,17 @@ function Server (listener) { self.addListener('connection', listener); } - self.watcher = new process.IOWatcher(function (readable, writeable) { + self.watcher = new IOWatcher(); + self.watcher.callback = function (readable, writeable) { while (self.fd) { var peerInfo = accept(self.fd); debug('accept: ' + JSON.stringify(peerInfo)); if (!peerInfo) return; - var peer = new Stream(peerInfo); + var peer = new Socket(peerInfo); self.emit('connection', peer); } - }); -}; + }; +} process.inherits(Server, process.EventEmitter); exports.Server = Server; @@ -411,7 +415,7 @@ Server.prototype.listen = function () { listen(self.fd, 128); self.emit("listening"); - self.watcher.set(self.fd, true, false); + self.watcher.set(self.fd, true, false); self.watcher.start(); }; diff --git a/src/node_io_watcher.cc b/src/node_io_watcher.cc index 3c60a7bacf3..4a2d9378d93 100644 --- a/src/node_io_watcher.cc +++ b/src/node_io_watcher.cc @@ -37,7 +37,11 @@ void IOWatcher::Callback(EV_P_ ev_io *w, int revents) { HandleScope scope; Local callback_v = io->handle_->Get(callback_symbol); - assert(callback_v->IsFunction()); + if (!callback_v->IsFunction()) { + io->Stop(); + return; + } + Local callback = Local::Cast(callback_v); TryCatch try_catch; @@ -64,19 +68,9 @@ void IOWatcher::Callback(EV_P_ ev_io *w, int revents) { Handle IOWatcher::New(const Arguments& args) { HandleScope scope; - if (!args[0]->IsFunction()) { - return ThrowException(Exception::TypeError( - String::New("First arg should a callback."))); - } - - Local callback = Local::Cast(args[0]); - IOWatcher *s = new IOWatcher(); - s->Wrap(args.This()); - s->handle_->Set(callback_symbol, callback); - return args.This(); } @@ -136,7 +130,6 @@ Handle IOWatcher::Stop(const Arguments& args) { void IOWatcher::Stop () { if (watcher_.active) { - HandleScope scope; ev_io_stop(EV_DEFAULT_UC_ &watcher_); Unref(); } diff --git a/src/node_io_watcher.h b/src/node_io_watcher.h index 420c6de5cc2..5e731770762 100644 --- a/src/node_io_watcher.h +++ b/src/node_io_watcher.h @@ -20,7 +20,7 @@ class IOWatcher : ObjectWrap { } ~IOWatcher() { - Stop(); + ev_io_stop(EV_DEFAULT_UC_ &watcher_); } static v8::Handle New(const v8::Arguments& args); From 25700e65eeb3493fc42c234e5dfd9413c4c02795 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Mon, 28 Dec 2009 16:42:48 +0100 Subject: [PATCH 016/105] [net2] Better EOF marking, rename events --- lib/net.js | 51 +++++++++++++++++++++++++--------------------- src/node_net2.cc | 2 +- test-net-server.js | 28 ++++++++++++------------- 3 files changed, 43 insertions(+), 38 deletions(-) diff --git a/lib/net.js b/lib/net.js index 3108feebde7..1b1d285f27b 100644 --- a/lib/net.js +++ b/lib/net.js @@ -24,6 +24,7 @@ var getsockname = process.getsockname; var getaddrinfo = process.getaddrinfo; var needsLookup = process.needsLookup; var EINPROGRESS = process.EINPROGRESS; +var END_OF_FILE = 42; function Socket (peerInfo) { @@ -59,7 +60,7 @@ function Socket (peerInfo) { var slice = self.recvBuffer.slice(self.recvBuffer.used, self.recvBuffer.used + bytesRead); self.recvBuffer.used += bytesRead; - self.emit('receive', slice); + self.emit('data', slice); } }; self.readable = false; @@ -153,7 +154,7 @@ Socket.prototype._sendString = function (data, encoding) { } // if we didn't find one, take the last if (!buffer) { - buffer = self.sendQueue[self.sendQueue.length-1]; + buffer = self._sendQueueLast(); // if last buffer is used up if (buffer.length == buffer.used) buffer = self._allocateSendBuffer(); } @@ -191,12 +192,24 @@ Socket.prototype._sendString = function (data, encoding) { }; +Socket.prototype._sendQueueLast = function () { + return this.sendQueue.length > 0 ? this.sendQueue[this.sendQueue.length-1] + : null; +}; + + // Returns true if all the data was flushed to socket. Returns false if // something was queued. If data was queued, then the "drain" event will // signal when it has been finally flushed to socket. Socket.prototype.send = function (data, encoding) { var self = this; + if (!self.writable) throw new Error('Socket is not writable'); + + if (self._sendQueueLast == END_OF_FILE) { + throw new Error('socket.close() called already; cannot write.'); + } + if (typeof(data) == 'string') { self._sendString(data, encoding); } else { @@ -225,12 +238,18 @@ Socket.prototype.send = function (data, encoding) { // Flushes the write buffer out. Emits "drain" if the buffer is empty. Socket.prototype.flush = function () { var self = this; - if (!self.writable) throw new Error('Socket is not writable'); var bytesWritten; while (self.sendQueue.length > 0) { + if (!self.writable) throw new Error('Socket is not writable'); + var b = self.sendQueue[0]; + if (b == END_OF_FILE) { + self._shutdown(); + break; + } + if (b.sent == b.used) { // this can be improved - save the buffer for later? self.sendQueue.shift() @@ -315,7 +334,7 @@ Socket.prototype.forceClose = function (exception) { this.writeWatcher.stop(); this.readWatcher.stop(); close(this.fd); - debug('close peer ' + this.fd); + debug('close socket ' + this.fd); this.fd = null; this.emit('close', exception); } @@ -325,30 +344,16 @@ Socket.prototype.forceClose = function (exception) { Socket.prototype._shutdown = function () { if (this.writable) { this.writable = false; - shutdown(this.fd, "write"); + shutdown(this.fd, 'write'); } }; Socket.prototype.close = function () { - var self = this; - var closeMethod; - if (self.readable && self.writable) { - closeMethod = self._shutdown; - } else if (!self.readable && self.writable) { - // already got EOF - closeMethod = self.forceClose; - } - // In the case we've already shutdown write side, - // but haven't got EOF: ignore. In the case we're - // fully closed already: ignore. - - if (closeMethod) { - if (self.sendQueueSize == 0) { - // no queue. just shut down the socket. - closeMethod(); - } else { - self.addListener("drain", closeMethod); + if (this.writable) { + if (this._sendQueueLast() != END_OF_FILE) { + this.sendQueue.push(END_OF_FILE); + this.flush(); } } }; diff --git a/src/node_net2.cc b/src/node_net2.cc index 3457cdbf54a..1cebd867613 100644 --- a/src/node_net2.cc +++ b/src/node_net2.cc @@ -264,7 +264,7 @@ static Handle Shutdown(const Arguments& args) { int how = SHUT_WR; if (args[1]->IsString()) { - String::Utf8Value t(args[0]->ToString()); + String::Utf8Value t(args[1]->ToString()); if (0 == strcasecmp(*t, "write")) { how = SHUT_WR; } else if (0 == strcasecmp(*t, "read")) { diff --git a/test-net-server.js b/test-net-server.js index 6968eae0661..76f10574ed4 100644 --- a/test-net-server.js +++ b/test-net-server.js @@ -5,27 +5,27 @@ process.Buffer.prototype.toString = function () { var sys = require("sys"); var net = require("./lib/net"); -var server = new net.Server(function (stream) { - sys.puts("connection (" + stream.fd + "): " - + stream.remoteAddress +var server = new net.Server(function (socket) { + sys.puts("connection (" + socket.fd + "): " + + socket.remoteAddress + " port " - + stream.remotePort + + socket.remotePort ); sys.puts("server fd: " + server.fd); - stream.addListener("receive", function (b) { - stream.send("pong ascii\r\n", "ascii"); - stream.send(b); - stream.send("pong utf8\r\n", "utf8"); + socket.addListener("data", function (b) { + socket.send("pong ascii\r\n", "ascii"); + socket.send(b); + socket.send("pong utf8\r\n", "utf8"); }); - stream.addListener('drain', function () { - sys.puts("server-side socket drain"); - }); - - stream.addListener("eof", function () { + socket.addListener("eof", function () { sys.puts("server peer eof"); - stream.close(); + socket.close(); + }); + + socket.addListener('drain', function () { + sys.puts("server-side socket drain"); }); }); server.listen(8000); From 48ccbb9afafd8f9bd2c3a93ac9db51afb2259f6c Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Mon, 28 Dec 2009 17:01:49 +0100 Subject: [PATCH 017/105] [net2] lower-case socket.type --- lib/net.js | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/lib/net.js b/lib/net.js index 1b1d285f27b..89688b1f03a 100644 --- a/lib/net.js +++ b/lib/net.js @@ -284,10 +284,12 @@ Socket.prototype.connect = function () { if (self.fd) throw new Error('Socket already opened'); if (typeof(arguments[0]) == 'string' && arguments.length == 1) { - self.fd = process.socket('UNIX'); + self.fd = socket('unix'); + self.type = 'unix'; // TODO check if sockfile exists? } else { - self.fd = process.socket('TCP'); + self.fd = socket('tcp'); + self.type = 'tcp'; // TODO dns resolution on arguments[1] } @@ -386,14 +388,22 @@ exports.createServer = function (listener) { }; +// Listen on a UNIX socket +// server.listen("/tmp/socket"); +// +// Listen on port 8000, accept connections from INADDR_ANY. +// server.listen(8000); +// +// Listen on port 8000, accept connections to "192.168.1.2" +// server.listen(8000, "192.168.1.2"); Server.prototype.listen = function () { var self = this; if (self.fd) throw new Error('Server already opened'); if (typeof(arguments[0]) == 'string' && arguments.length == 1) { // the first argument specifies a path - self.fd = process.socket('UNIX'); - self.type = 'UNIX'; + self.fd = socket('unix'); + self.type = 'unix'; // TODO unlink sockfile if exists? // if (lstat(SOCKFILE, &tstat) == 0) { // assert(S_ISSOCK(tstat.st_mode)); @@ -401,14 +411,14 @@ Server.prototype.listen = function () { // } bind(self.fd, arguments[0]); } else if (arguments.length == 0) { - self.fd = process.socket('TCP'); - self.type = 'TCP'; + self.fd = socket('tcp'); + self.type = 'tcp'; // Don't bind(). OS will assign a port with INADDR_ANY. The port will be // passed to the 'listening' event. } else { // the first argument is the port, the second an IP - self.fd = process.socket('TCP'); - self.type = 'TCP'; + self.fd = socket('tcp'); + self.type = 'tcp'; if (needsLookup(arguments[1])) { getaddrinfo(arguments[1], function (ip) { }); From 07333a4ab05282eafa10669fad5da2992f5f88d0 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Mon, 28 Dec 2009 17:52:26 +0100 Subject: [PATCH 018/105] [net2] Set FD_CLOEXEC on created socket fds. --- lib/net.js | 2 +- src/node_net2.cc | 23 ++++++++++++++--------- test-net-server.js | 6 +++++- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/lib/net.js b/lib/net.js index 89688b1f03a..32a6ec2b262 100644 --- a/lib/net.js +++ b/lib/net.js @@ -177,7 +177,7 @@ Socket.prototype._sendString = function (data, encoding) { buffer.length - buffer.used); bytesWritten = charsWritten; } - + buffer.used += bytesWritten; self.sendQueueSize += bytesWritten; diff --git a/src/node_net2.cc b/src/node_net2.cc index 1cebd867613..cab14bb9876 100644 --- a/src/node_net2.cc +++ b/src/node_net2.cc @@ -54,11 +54,16 @@ static inline Local ErrnoException(int errorno, return e; } +static inline bool SetCloseOnExec(int fd) { + return (fcntl(fd, F_SETFD, FD_CLOEXEC) != -1); +} + static inline bool SetNonBlock(int fd) { - int flags = fcntl(fd, F_GETFL, 0); - if (flags == -1) return false; - flags |= O_NONBLOCK; - return (fcntl(fd, F_SETFL, flags) != -1); + return (fcntl(fd, F_SETFL, O_NONBLOCK) != -1); +} + +static inline bool SetSockFlags(int fd) { + return SetNonBlock(fd) && SetCloseOnExec(fd); } // Creates nonblocking pipe @@ -68,7 +73,7 @@ static Handle Pipe(const Arguments& args) { if (pipe(fds) < 0) return ThrowException(ErrnoException(errno, "pipe")); - if(!SetNonBlock(fds[0]) || !SetNonBlock(fds[1])) { + if (!SetSockFlags(fds[0]) || !SetSockFlags(fds[1])) { int fcntl_errno = errno; close(fds[0]); close(fds[1]); @@ -92,7 +97,7 @@ static Handle SocketPair(const Arguments& args) { return ThrowException(ErrnoException(errno, "socketpair")); } - if (!SetNonBlock(fds[0]) || !SetNonBlock(fds[1])) { + if (!SetSockFlags(fds[0]) || !SetSockFlags(fds[1])) { int fcntl_errno = errno; close(fds[0]); close(fds[1]); @@ -137,7 +142,7 @@ static Handle Socket(const Arguments& args) { if (fd < 0) return ThrowException(ErrnoException(errno, "socket")); - if (!SetNonBlock(fd)) { + if (!SetSockFlags(fd)) { int fcntl_errno = errno; close(fd); return ThrowException(ErrnoException(fcntl_errno, "fcntl")); @@ -387,10 +392,10 @@ static Handle Accept(const Arguments& args) { return ThrowException(ErrnoException(errno, "accept")); } - if (!SetNonBlock(peer_fd)) { + if (!SetSockFlags(peer_fd)) { int fcntl_errno = errno; close(peer_fd); - return ThrowException(ErrnoException(fcntl_errno, "fcntl", "Cannot make peer non-blocking")); + return ThrowException(ErrnoException(fcntl_errno, "fcntl")); } Local peer_info = Object::New(); diff --git a/test-net-server.js b/test-net-server.js index 76f10574ed4..114cb67a851 100644 --- a/test-net-server.js +++ b/test-net-server.js @@ -42,7 +42,11 @@ c.addListener('drain', function () { sys.puts("!!!client drain"); }); -c.addListener('receive', function (d) { +c.addListener('data', function (d) { sys.puts("!!!client got: " + JSON.stringify(d.toString())); + c.close(); }); +c.addListener('dataEnd', function (d) { + sys.puts("!!!client eof"); +}); From 8330316014678c6404bb49e09e353d8f9130321c Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Wed, 30 Dec 2009 08:53:19 +0100 Subject: [PATCH 019/105] Fix errors in getaddrinfo --- src/node_net2.cc | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/src/node_net2.cc b/src/node_net2.cc index cab14bb9876..0d4bb6f1f3f 100644 --- a/src/node_net2.cc +++ b/src/node_net2.cc @@ -544,6 +544,7 @@ static Handle ToRead(const Arguments& args) { struct resolve_request { Persistent cb; + struct addrinfo *address_list; int ai_family; // AF_INET or AF_INET6 char hostname[1]; }; @@ -554,29 +555,32 @@ static int AfterResolve(eio_req *req) { struct resolve_request * rreq = (struct resolve_request *)(req->data); - struct addrinfo *address = NULL, - *address_list = static_cast(req->ptr2); - HandleScope scope; Local argv[1]; if (req->result != 0) { argv[0] = ErrnoException(errno, "getaddrinfo"); } else { + struct addrinfo *address; int n = 0; - for (address = address_list; address; address = address->ai_next) { n++; } + + for (address = rreq->address_list; address; address = address->ai_next) { n++; } Local results = Array::New(n); char ip[INET6_ADDRSTRLEN]; + const char *addr; n = 0; - address = address_list; + address = rreq->address_list; while (address) { - HandleScope scope; - assert(address->ai_family == AF_INET || address->ai_family == AF_INET6); assert(address->ai_socktype == SOCK_STREAM); - const char *c = inet_ntop(address->ai_family, &(address->ai_addr), ip, INET6_ADDRSTRLEN); + assert(address->ai_family == AF_INET || address->ai_family == AF_INET6); + addr = ( address->ai_family == AF_INET + ? (char *) &((struct sockaddr_in *) address->ai_addr)->sin_addr + : (char *) &((struct sockaddr_in6 *) address->ai_addr)->sin6_addr + ); + const char *c = inet_ntop(address->ai_family, addr, ip, INET6_ADDRSTRLEN); Local s = String::New(c); results->Set(Integer::New(n), s); @@ -595,23 +599,27 @@ static int AfterResolve(eio_req *req) { FatalException(try_catch); } + if (rreq->address_list) freeaddrinfo(rreq->address_list); rreq->cb.Dispose(); // Dispose of the persistent handle free(rreq); - freeaddrinfo(address_list); + + return 0; } + static int Resolve(eio_req *req) { // Note: this function is executed in the thread pool! CAREFUL struct resolve_request * rreq = (struct resolve_request *) req->data; - struct addrinfo *address_list = NULL; struct addrinfo hints; memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = rreq->ai_family; hints.ai_socktype = SOCK_STREAM; - req->result = getaddrinfo((char*)rreq->hostname, NULL, &hints, &address_list); - req->ptr2 = address_list; + req->result = getaddrinfo((char*)rreq->hostname, + NULL, + &hints, + &(rreq->address_list)); return 0; } @@ -643,7 +651,7 @@ static Handle GetAddrInfo(const Arguments& args) { Local cb = Local::Cast(args[2]); struct resolve_request *rreq = (struct resolve_request *) - malloc(sizeof(struct resolve_request) + hostname.length()); + calloc(1, sizeof(struct resolve_request) + hostname.length()); if (!rreq) { V8::LowMemoryNotification(); From 1beb840fec05948b47d7d698558c258193bcdecb Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Wed, 30 Dec 2009 09:01:28 +0100 Subject: [PATCH 020/105] Back to 64kb stack size - enlarging it was a typo --- wscript | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wscript b/wscript index 43a7a5322ef..d2e13c9339e 100644 --- a/wscript +++ b/wscript @@ -139,7 +139,7 @@ def configure(conf): conf.define("HAVE_CONFIG_H", 1) - conf.env.append_value("CCFLAGS", "-DX_STACKSIZE=%d" % (2*1024*1024)) + conf.env.append_value("CCFLAGS", "-DX_STACKSIZE=%d" % (1024*64)) # LFS conf.env.append_value('CCFLAGS', '-D_LARGEFILE_SOURCE') From 2582560f913132deed61786cdaa9884aa6cb7980 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Wed, 30 Dec 2009 00:53:14 -0800 Subject: [PATCH 021/105] [net2] Do hostname lookups in connect and listen --- lib/net.js | 126 +++++++++++++++++++++++++++++---------------- src/node_net2.cc | 8 ++- test-net-server.js | 4 +- 3 files changed, 90 insertions(+), 48 deletions(-) diff --git a/lib/net.js b/lib/net.js index 32a6ec2b262..655a51f6fdc 100644 --- a/lib/net.js +++ b/lib/net.js @@ -283,48 +283,55 @@ Socket.prototype.connect = function () { var self = this; if (self.fd) throw new Error('Socket already opened'); + function doConnect () { + try { + connect(self.fd, arguments[0], arguments[1]); + } catch (e) { + close(self.fd); + throw e; + } + + // Don't start the read watcher until connection is established + self.readWatcher.set(self.fd, true, false); + + // How to connect on POSIX: Wait for fd to become writable, then call + // socketError() if there isn't an error, we're connected. AFAIK this a + // platform independent way determining when a non-blocking connection + // is established, but I have only seen it documented in the Linux + // Manual Page connect(2) under the error code EINPROGRESS. + self.writeWatcher.set(self.fd, false, true); + self.writeWatcher.start(); + self.writeWatcher.callback = function () { + var errno = socketError(self.fd); + if (errno == 0) { + // connection established + self.readWatcher.start(); + self.readable = true; + self.writable = true; + self.writeWatcher.callback = self._doFlush; + self.emit('connect'); + } else if (errno != EINPROGRESS) { + var e = new Error('connection error'); + e.errno = errno; + self.forceClose(e); + } + }; + } + if (typeof(arguments[0]) == 'string' && arguments.length == 1) { self.fd = socket('unix'); self.type = 'unix'; // TODO check if sockfile exists? + doConnect(arguments[0]); } else { self.fd = socket('tcp'); self.type = 'tcp'; // TODO dns resolution on arguments[1] + var port = arguments[0]; + lookupDomainName(arguments[1], function (ip) { + doConnect(port, ip); + }); } - - try { - connect(self.fd, arguments[0], arguments[1]); - } catch (e) { - close(self.fd); - throw e; - } - - // Don't start the read watcher until connection is established - self.readWatcher.set(self.fd, true, false); - - // How to connect on POSIX: Wait for fd to become writable, then call - // socketError() if there isn't an error, we're connected. AFAIK this a - // platform independent way determining when a non-blocking connection - // is established, but I have only seen it documented in the Linux - // Manual Page connect(2) under the error code EINPROGRESS. - self.writeWatcher.set(self.fd, false, true); - self.writeWatcher.start(); - self.writeWatcher.callback = function () { - var errno = socketError(self.fd); - if (errno == 0) { - // connection established - self.readWatcher.start(); - self.readable = true; - self.writable = true; - self.writeWatcher.callback = self._doFlush; - self.emit('connect'); - } else if (errno != EINPROGRESS) { - var e = new Error('connection error'); - e.errno = errno; - self.forceClose(e); - } - }; }; @@ -387,6 +394,34 @@ exports.createServer = function (listener) { return new Server(listener); }; +/* This function does both an ipv4 and ipv6 look up. + * It first tries the ipv4 look up, if that fails, then it does the ipv6. + */ +function lookupDomainName (dn, callback) { + if (!needsLookup(dn)) { + callback(dn); + } else { + debug("getaddrinfo 4 " + dn); + getaddrinfo(dn, 4, function (r4) { + if (r4 instanceof Error) throw r4; + if (r4.length > 0) { + debug("getaddrinfo 4 found " + r4); + callback(r4[0]); + } else { + debug("getaddrinfo 6 " + dn); + getaddrinfo(dn, 6, function (r6) { + if (r6 instanceof Error) throw r6; + if (r6.length < 0) { + throw new Error("No address associated with hostname " + dn); + } + debug("getaddrinfo 6 found " + r6); + callback(r6[0]); + }); + } + }); + } +} + // Listen on a UNIX socket // server.listen("/tmp/socket"); @@ -400,6 +435,13 @@ Server.prototype.listen = function () { var self = this; if (self.fd) throw new Error('Server already opened'); + function doListen () { + listen(self.fd, 128); + self.watcher.set(self.fd, true, false); + self.watcher.start(); + self.emit("listening"); + } + if (typeof(arguments[0]) == 'string' && arguments.length == 1) { // the first argument specifies a path self.fd = socket('unix'); @@ -415,23 +457,17 @@ Server.prototype.listen = function () { self.type = 'tcp'; // Don't bind(). OS will assign a port with INADDR_ANY. The port will be // passed to the 'listening' event. + doListen(); } else { // the first argument is the port, the second an IP self.fd = socket('tcp'); self.type = 'tcp'; - if (needsLookup(arguments[1])) { - getaddrinfo(arguments[1], function (ip) { - }); - } - // TODO dns resolution on arguments[1] - bind(self.fd, arguments[0], arguments[1]); + var port = arguments[0]; + lookupDomainName(arguments[1], function (ip) { + bind(self.fd, port, ip); + doListen(); + }); } - - listen(self.fd, 128); - self.emit("listening"); - - self.watcher.set(self.fd, true, false); - self.watcher.start(); }; diff --git a/src/node_net2.cc b/src/node_net2.cc index 0d4bb6f1f3f..2d7c6c2afbb 100644 --- a/src/node_net2.cc +++ b/src/node_net2.cc @@ -559,7 +559,13 @@ static int AfterResolve(eio_req *req) { Local argv[1]; if (req->result != 0) { - argv[0] = ErrnoException(errno, "getaddrinfo"); + if (req->result == EAI_NODATA) { + argv[0] = Array::New(); + } else { + argv[0] = ErrnoException(req->result, + "getaddrinfo", + gai_strerror(req->result)); + } } else { struct addrinfo *address; int n = 0; diff --git a/test-net-server.js b/test-net-server.js index 114cb67a851..8b3ce9d14cd 100644 --- a/test-net-server.js +++ b/test-net-server.js @@ -28,11 +28,11 @@ var server = new net.Server(function (socket) { sys.puts("server-side socket drain"); }); }); -server.listen(8000); +server.listen(8000, "localhost"); sys.puts("server fd: " + server.fd); -var c = net.createConnection(8000); +var c = net.createConnection(8000, "localhost"); c.addListener('connect', function () { sys.puts("!!!client connected"); c.send("hello\n"); From 402755b14a9d7828c0ae805c3d792774178cd6ee Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Wed, 30 Dec 2009 00:57:55 -0800 Subject: [PATCH 022/105] Add socket.address() --- lib/net.js | 9 +++++++-- test-net-server.js | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/net.js b/lib/net.js index 655a51f6fdc..6d0566418ad 100644 --- a/lib/net.js +++ b/lib/net.js @@ -335,6 +335,11 @@ Socket.prototype.connect = function () { }; +Socket.prototype.address = function () { + return getsockname(this.fd); +}; + + Socket.prototype.forceClose = function (exception) { if (this.fd) { this.readable = false; @@ -471,8 +476,8 @@ Server.prototype.listen = function () { }; -Server.prototype.sockName = function () { - return getsockname(self.fd); +Server.prototype.address = function () { + return getsockname(this.fd); }; diff --git a/test-net-server.js b/test-net-server.js index 8b3ce9d14cd..721a7627208 100644 --- a/test-net-server.js +++ b/test-net-server.js @@ -28,7 +28,7 @@ var server = new net.Server(function (socket) { sys.puts("server-side socket drain"); }); }); -server.listen(8000, "localhost"); +server.listen(8000); sys.puts("server fd: " + server.fd); From de9bfdea8e0aaf9eab86b5ea3d08bb52a4f625ff Mon Sep 17 00:00:00 2001 From: David Sklar Date: Wed, 30 Dec 2009 10:06:40 -0800 Subject: [PATCH 023/105] [net2] toRead() for non-linux (SIOCINQ -> FIONREAD) --- lib/net.js | 1 - src/node_net2.cc | 10 ++++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/net.js b/lib/net.js index 6d0566418ad..035272781db 100644 --- a/lib/net.js +++ b/lib/net.js @@ -109,7 +109,6 @@ Socket.prototype._allocateNewRecvBuf = function () { var newBufferSize = 1024; // TODO make this adjustable from user API if (toRead) { - // Note: only Linux supports toRead(). // Is the extra system call even worth it? var bytesToRead = toRead(self.fd); if (bytesToRead > 1024) { diff --git a/src/node_net2.cc b/src/node_net2.cc index 2d7c6c2afbb..4056fc28d81 100644 --- a/src/node_net2.cc +++ b/src/node_net2.cc @@ -18,8 +18,14 @@ #include #include -#include +#ifdef __linux__ +# include /* For the SIOCINQ / FIONREAD ioctl */ +#endif +/* Non-linux platforms like OS X define this ioctl elsewhere */ +#ifndef FIONREAD +#include +#endif #include @@ -530,7 +536,7 @@ static Handle ToRead(const Arguments& args) { FD_ARG(args[0]) int value; - int r = ioctl(fd, SIOCINQ, &value); + int r = ioctl(fd, FIONREAD, &value); if (r < 0) { return ThrowException(ErrnoException(errno, "ioctl")); From 0d31e9875f782c476e6f85791ceb8ae8a6758ae8 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Wed, 30 Dec 2009 10:43:47 -0800 Subject: [PATCH 024/105] [net2] socket.setNoDelay --- lib/net.js | 6 ++++++ src/node_net2.cc | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/lib/net.js b/lib/net.js index 035272781db..72d852da7b4 100644 --- a/lib/net.js +++ b/lib/net.js @@ -19,6 +19,7 @@ var shutdown = process.shutdown; var read = process.read; var write = process.write; var toRead = process.toRead; +var setNoDelay = process.setNoDelay; var socketError = process.socketError; var getsockname = process.getsockname; var getaddrinfo = process.getaddrinfo; @@ -339,6 +340,11 @@ Socket.prototype.address = function () { }; +Socket.prototype.setNoDelay = function (v) { + setNoDelay(this.fd, v); +}; + + Socket.prototype.forceClose = function (exception) { if (this.fd) { this.readable = false; diff --git a/src/node_net2.cc b/src/node_net2.cc index 4056fc28d81..7b7108ad8dc 100644 --- a/src/node_net2.cc +++ b/src/node_net2.cc @@ -546,6 +546,23 @@ static Handle ToRead(const Arguments& args) { } +static Handle SetNoDelay(const Arguments& args) { + int flags, r; + HandleScope scope; + + FD_ARG(args[0]) + + flags = args[1]->IsFalse() ? 0 : 1; + r = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (void *)&flags, sizeof(flags)); + + if (r < 0) { + return ThrowException(ErrnoException(errno, "setsockopt")); + } + + return Undefined(); +} + + // G E T A D D R I N F O struct resolve_request { @@ -739,6 +756,7 @@ void InitNet2(Handle target) { NODE_SET_METHOD(target, "accept", Accept); NODE_SET_METHOD(target, "socketError", SocketError); NODE_SET_METHOD(target, "toRead", ToRead); + NODE_SET_METHOD(target, "setNoDelay", SetNoDelay); NODE_SET_METHOD(target, "getsocksame", GetSockName); NODE_SET_METHOD(target, "getaddrinfo", GetAddrInfo); NODE_SET_METHOD(target, "needsLookup", NeedsLookup); From a8ede8dd9e7b716624cb9b4a160c1260516ae3b3 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Wed, 30 Dec 2009 10:58:46 -0800 Subject: [PATCH 025/105] [net2] port ping pong test --- lib/net.js | 1 + test/mjsunit/test-net-pingpong.js | 96 +++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 test/mjsunit/test-net-pingpong.js diff --git a/lib/net.js b/lib/net.js index 72d852da7b4..d99bc60ab2b 100644 --- a/lib/net.js +++ b/lib/net.js @@ -392,6 +392,7 @@ function Server (listener) { debug('accept: ' + JSON.stringify(peerInfo)); if (!peerInfo) return; var peer = new Socket(peerInfo); + peer.server = self; self.emit('connection', peer); } }; diff --git a/test/mjsunit/test-net-pingpong.js b/test/mjsunit/test-net-pingpong.js new file mode 100644 index 00000000000..955a18a3e20 --- /dev/null +++ b/test/mjsunit/test-net-pingpong.js @@ -0,0 +1,96 @@ +process.mixin(require("./common")); +net = require("net"); + +process.Buffer.prototype.toString = function () { + return this.utf8Slice(0, this.length); +}; + +var tests_run = 0; + +function pingPongTest (port, host, on_complete) { + var N = 1000; + var count = 0; + var sent_final_ping = false; + + var server = net.createServer(function (socket) { + puts("connection: " + socket.remoteAddress); + + assert.equal(true, socket.remoteAddress !== null); + assert.equal(true, socket.remoteAddress !== undefined); + assert.equal(server, socket.server); + + socket.setNoDelay(); + socket.timeout = 0; + + socket.addListener("data", function (data) { + puts("server got: " + data); + assert.equal(true, socket.writable); + assert.equal(true, socket.readable); + assert.equal(true, count <= N); + if (/PING/.exec(data)) { + socket.send("PONG"); + } + }); + + socket.addListener("eof", function () { + assert.equal(true, socket.writable); + assert.equal(false, socket.readable); + socket.close(); + }); + + socket.addListener("close", function () { + assert.equal(false, socket.writable); + assert.equal(false, socket.readable); + socket.server.close(); + }); + }); + server.listen(port, host); + + var client = net.createConnection(port, host); + + client.addListener("connect", function () { + assert.equal(true, client.readable); + assert.equal(true, client.writable); + client.send("PING"); + }); + + client.addListener("data", function (data) { + puts("client got: " + data); + + assert.equal("PONG", data); + count += 1; + + if (sent_final_ping) { + assert.equal(false, client.writable); + assert.equal(true, client.readable); + return; + } else { + assert.equal(true, client.writable); + assert.equal(true, client.readable); + } + + if (count < N) { + client.send("PING"); + } else { + sent_final_ping = true; + client.send("PING"); + client.close(); + } + }); + + client.addListener("close", function () { + assert.equal(N+1, count); + assert.equal(true, sent_final_ping); + if (on_complete) on_complete(); + tests_run += 1; + }); +} + +/* All are run at once, so run on different ports */ +pingPongTest(20989, "localhost"); +pingPongTest(20988, null); +pingPongTest(20997, "::1"); + +process.addListener("exit", function () { + assert.equal(3, tests_run); +}); From 8d0f756158f49eb9427991b0923b629d8e0e5332 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Wed, 30 Dec 2009 11:32:07 -0800 Subject: [PATCH 026/105] [net2] delete unix sockfile on server start and shutdown --- lib/net.js | 55 ++++++++++++++++++++++++++++++++++++---------- src/node_net2.cc | 1 + test-net-server.js | 40 +++++++++++++++++++-------------- 3 files changed, 67 insertions(+), 29 deletions(-) diff --git a/lib/net.js b/lib/net.js index d99bc60ab2b..05273c729f8 100644 --- a/lib/net.js +++ b/lib/net.js @@ -25,6 +25,7 @@ var getsockname = process.getsockname; var getaddrinfo = process.getaddrinfo; var needsLookup = process.needsLookup; var EINPROGRESS = process.EINPROGRESS; +var ENOENT = process.ENOENT; var END_OF_FILE = 42; @@ -318,7 +319,7 @@ Socket.prototype.connect = function () { }; } - if (typeof(arguments[0]) == 'string' && arguments.length == 1) { + if (typeof(arguments[0]) == 'string') { self.fd = socket('unix'); self.type = 'unix'; // TODO check if sockfile exists? @@ -457,12 +458,32 @@ Server.prototype.listen = function () { // the first argument specifies a path self.fd = socket('unix'); self.type = 'unix'; - // TODO unlink sockfile if exists? - // if (lstat(SOCKFILE, &tstat) == 0) { - // assert(S_ISSOCK(tstat.st_mode)); - // unlink(SOCKFILE); - // } - bind(self.fd, arguments[0]); + var path = arguments[0]; + self.path = path; + // unlink sockfile if it exists + process.fs.stat(path, function (r) { + if (r instanceof Error) { + if (r.errno == ENOENT) { + bind(self.fd, path); + doListen(); + } else { + throw r; + } + } else { + if (!r.isFile()) { + throw new Error("Non-file exists at " + path); + } else { + process.fs.unlink(path, function (err) { + if (err) { + throw err; + } else { + bind(self.fd, path); + doListen(); + } + }); + } + } + }); } else if (arguments.length == 0) { self.fd = socket('tcp'); self.type = 'tcp'; @@ -488,9 +509,19 @@ Server.prototype.address = function () { Server.prototype.close = function () { - if (!this.fd) throw new Error('Not running'); - this.watcher.stop(); - close(this.fd); - this.fd = null; - this.emit("close"); + var self = this; + if (!self.fd) throw new Error('Not running'); + + self.watcher.stop(); + + close(self.fd); + self.fd = null; + + if (self.type === "unix") { + process.fs.unlink(self.path, function () { + self.emit("close"); + }); + } else { + self.emit("close"); + } }; diff --git a/src/node_net2.cc b/src/node_net2.cc index 7b7108ad8dc..90a05002b57 100644 --- a/src/node_net2.cc +++ b/src/node_net2.cc @@ -761,6 +761,7 @@ void InitNet2(Handle target) { NODE_SET_METHOD(target, "getaddrinfo", GetAddrInfo); NODE_SET_METHOD(target, "needsLookup", NeedsLookup); + target->Set(String::NewSymbol("ENOENT"), Integer::New(ENOENT)); target->Set(String::NewSymbol("EINPROGRESS"), Integer::New(EINPROGRESS)); target->Set(String::NewSymbol("EINTR"), Integer::New(EINTR)); target->Set(String::NewSymbol("EACCES"), Integer::New(EACCES)); diff --git a/test-net-server.js b/test-net-server.js index 721a7627208..9d7cd995e38 100644 --- a/test-net-server.js +++ b/test-net-server.js @@ -17,6 +17,10 @@ var server = new net.Server(function (socket) { socket.send("pong ascii\r\n", "ascii"); socket.send(b); socket.send("pong utf8\r\n", "utf8"); + if (/^quit/.test(b)) { + socket.close(); + server.close(); + } }); socket.addListener("eof", function () { @@ -28,25 +32,27 @@ var server = new net.Server(function (socket) { sys.puts("server-side socket drain"); }); }); -server.listen(8000); +server.listen("/tmp/node.sock"); sys.puts("server fd: " + server.fd); +server.addListener("listening", function () { + var c = net.createConnection("/tmp/node.sock"); + c.addListener('connect', function () { + sys.puts("!!!client connected"); + c.send("hello\n"); + }); -var c = net.createConnection(8000, "localhost"); -c.addListener('connect', function () { - sys.puts("!!!client connected"); - c.send("hello\n"); + c.addListener('drain', function () { + sys.puts("!!!client drain"); + }); + + c.addListener('data', function (d) { + sys.puts("!!!client got: " + JSON.stringify(d.toString())); + c.close(); + }); + + c.addListener('eof', function (d) { + sys.puts("!!!client eof"); + }); }); -c.addListener('drain', function () { - sys.puts("!!!client drain"); -}); - -c.addListener('data', function (d) { - sys.puts("!!!client got: " + JSON.stringify(d.toString())); - c.close(); -}); - -c.addListener('dataEnd', function (d) { - sys.puts("!!!client eof"); -}); From 20eec646b3bf5681fb93475d1f2272f448bd10ea Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Wed, 30 Dec 2009 11:51:43 -0800 Subject: [PATCH 027/105] [net2] add unix server to ping-pong test --- lib/net.js | 12 +++-- test-net-server.js | 4 +- test/mjsunit/test-net-pingpong.js | 78 ++++++++++++++++--------------- 3 files changed, 49 insertions(+), 45 deletions(-) diff --git a/lib/net.js b/lib/net.js index 05273c729f8..4f659ba06c3 100644 --- a/lib/net.js +++ b/lib/net.js @@ -342,7 +342,7 @@ Socket.prototype.address = function () { Socket.prototype.setNoDelay = function (v) { - setNoDelay(this.fd, v); + if (this.type == 'tcp') setNoDelay(this.fd, v); }; @@ -454,7 +454,7 @@ Server.prototype.listen = function () { self.emit("listening"); } - if (typeof(arguments[0]) == 'string' && arguments.length == 1) { + if (typeof(arguments[0]) == 'string') { // the first argument specifies a path self.fd = socket('unix'); self.type = 'unix'; @@ -484,18 +484,20 @@ Server.prototype.listen = function () { } } }); - } else if (arguments.length == 0) { + } else if (!arguments[0]) { + // Don't bind(). OS will assign a port with INADDR_ANY. + // The port can be found with server.address() self.fd = socket('tcp'); self.type = 'tcp'; - // Don't bind(). OS will assign a port with INADDR_ANY. The port will be - // passed to the 'listening' event. doListen(); } else { // the first argument is the port, the second an IP self.fd = socket('tcp'); self.type = 'tcp'; var port = arguments[0]; + debug("starting tcp server on port " + port); lookupDomainName(arguments[1], function (ip) { + debug("starting tcp server on ip " + ip); bind(self.fd, port, ip); doListen(); }); diff --git a/test-net-server.js b/test-net-server.js index 9d7cd995e38..9c1ddb0bf00 100644 --- a/test-net-server.js +++ b/test-net-server.js @@ -32,8 +32,6 @@ var server = new net.Server(function (socket) { sys.puts("server-side socket drain"); }); }); -server.listen("/tmp/node.sock"); -sys.puts("server fd: " + server.fd); server.addListener("listening", function () { var c = net.createConnection("/tmp/node.sock"); @@ -56,3 +54,5 @@ server.addListener("listening", function () { }); }); +server.listen("/tmp/node.sock"); +sys.puts("server fd: " + server.fd); diff --git a/test/mjsunit/test-net-pingpong.js b/test/mjsunit/test-net-pingpong.js index 955a18a3e20..7569991921d 100644 --- a/test/mjsunit/test-net-pingpong.js +++ b/test/mjsunit/test-net-pingpong.js @@ -7,16 +7,13 @@ process.Buffer.prototype.toString = function () { var tests_run = 0; -function pingPongTest (port, host, on_complete) { +function pingPongTest (port, host) { var N = 1000; var count = 0; var sent_final_ping = false; var server = net.createServer(function (socket) { puts("connection: " + socket.remoteAddress); - - assert.equal(true, socket.remoteAddress !== null); - assert.equal(true, socket.remoteAddress !== undefined); assert.equal(server, socket.server); socket.setNoDelay(); @@ -44,53 +41,58 @@ function pingPongTest (port, host, on_complete) { socket.server.close(); }); }); - server.listen(port, host); - var client = net.createConnection(port, host); + server.addListener("listening", function () { + puts("server listening on " + port + " " + host); - client.addListener("connect", function () { - assert.equal(true, client.readable); - assert.equal(true, client.writable); - client.send("PING"); - }); + var client = net.createConnection(port, host); - client.addListener("data", function (data) { - puts("client got: " + data); - - assert.equal("PONG", data); - count += 1; - - if (sent_final_ping) { - assert.equal(false, client.writable); + client.addListener("connect", function () { assert.equal(true, client.readable); - return; - } else { assert.equal(true, client.writable); - assert.equal(true, client.readable); - } + client.send("PING"); + }); - if (count < N) { - client.send("PING"); - } else { - sent_final_ping = true; - client.send("PING"); - client.close(); - } + client.addListener("data", function (data) { + puts("client got: " + data); + + assert.equal("PONG", data); + count += 1; + + if (sent_final_ping) { + assert.equal(false, client.writable); + assert.equal(true, client.readable); + return; + } else { + assert.equal(true, client.writable); + assert.equal(true, client.readable); + } + + if (count < N) { + client.send("PING"); + } else { + sent_final_ping = true; + client.send("PING"); + client.close(); + } + }); + + client.addListener("close", function () { + assert.equal(N+1, count); + assert.equal(true, sent_final_ping); + tests_run += 1; + }); }); - client.addListener("close", function () { - assert.equal(N+1, count); - assert.equal(true, sent_final_ping); - if (on_complete) on_complete(); - tests_run += 1; - }); + server.listen(port, host); } /* All are run at once, so run on different ports */ pingPongTest(20989, "localhost"); -pingPongTest(20988, null); +pingPongTest(20988); pingPongTest(20997, "::1"); +pingPongTest("/tmp/pingpong.sock"); process.addListener("exit", function () { - assert.equal(3, tests_run); + assert.equal(4, tests_run); }); From a876df6c715bfcff48a1fc518ec0cab9d53f20e4 Mon Sep 17 00:00:00 2001 From: David Sklar Date: Thu, 7 Jan 2010 16:50:19 -0500 Subject: [PATCH 028/105] Initial take on passing FDs between processes --- lib/net.js | 115 +++++++++--- src/node_net2.cc | 169 +++++++++++++++++- .../fixtures/net-fd-passing-receiver.js | 37 ++++ test/mjsunit/test-net-fd-passing.js | 67 +++++++ 4 files changed, 364 insertions(+), 24 deletions(-) create mode 100644 test/mjsunit/fixtures/net-fd-passing-receiver.js create mode 100644 test/mjsunit/test-net-fd-passing.js diff --git a/lib/net.js b/lib/net.js index 4f659ba06c3..f513a9a8029 100644 --- a/lib/net.js +++ b/lib/net.js @@ -17,6 +17,8 @@ var accept = process.accept; var close = process.close; var shutdown = process.shutdown; var read = process.read; +var recvMsg = process.recvMsg; +var sendFD = process.sendFD; var write = process.write; var toRead = process.toRead; var setNoDelay = process.setNoDelay; @@ -28,7 +30,6 @@ var EINPROGRESS = process.EINPROGRESS; var ENOENT = process.ENOENT; var END_OF_FILE = 42; - function Socket (peerInfo) { process.EventEmitter.call(); @@ -37,7 +38,7 @@ function Socket (peerInfo) { // Allocated on demand. self.recvBuffer = null; - self.readWatcher = new IOWatcher() + self.readWatcher = new IOWatcher(); self.readWatcher.callback = function () { // If this is the first recv (recvBuffer doesn't exist) or we've used up // most of the recvBuffer, allocate a new one. @@ -47,10 +48,23 @@ function Socket (peerInfo) { } debug('recvBuffer.used ' + self.recvBuffer.used); - var bytesRead = read(self.fd, + var bytesRead; + var receivedFd = -1; + + if (self.type == "unix") { + var msgInfo = recvMsg(self.fd, + self.recvBuffer, + self.recvBuffer.used, + self.recvBuffer.length - self.recvBuffer.used); + bytesRead = msgInfo[0]; + receivedFd = msgInfo[1]; + debug('receivedFd ' + receivedFd); + } else { + bytesRead = read(self.fd, self.recvBuffer, self.recvBuffer.used, self.recvBuffer.length - self.recvBuffer.used); + } debug('bytesRead ' + bytesRead + '\n'); if (bytesRead == 0) { @@ -59,10 +73,15 @@ function Socket (peerInfo) { self.emit('eof'); if (!self.writable) self.forceClose(); } else { - var slice = self.recvBuffer.slice(self.recvBuffer.used, - self.recvBuffer.used + bytesRead); - self.recvBuffer.used += bytesRead; - self.emit('data', slice); + if (receivedFd == -1) { + var slice = self.recvBuffer.slice(self.recvBuffer.used, + self.recvBuffer.used + bytesRead); + self.recvBuffer.used += bytesRead; + self.emit('data', slice); + } else { + self.recvBuffer.used += bytesRead; + self.emit('fd', receivedFd); + } } }; self.readable = false; @@ -70,10 +89,16 @@ function Socket (peerInfo) { self.sendQueue = []; // queue of buffers that need to be written to socket // XXX use link list? self.sendQueueSize = 0; // in bytes, not to be confused with sendQueue.length! + self.sendMessageQueueSize = 0; // number of messages remaining to be sent self._doFlush = function () { - assert(self.sendQueueSize > 0); + /* Socket becomes writeable on connect() but don't flush if there's + * nothing actually to write */ + if ((self.sendQueueSize == 0) && (self.sendMessageQueueSize == 0)) { + return; + } if (self.flush()) { assert(self.sendQueueSize == 0); + assert(self.sendMessageQueueSize == 0); self.emit("drain"); } }; @@ -134,6 +159,7 @@ Socket.prototype._allocateSendBuffer = function () { var b = new process.Buffer(1024); b.used = 0; b.sent = 0; + b.isMsg = false; this.sendQueue.push(b); return b; }; @@ -154,6 +180,7 @@ Socket.prototype._sendString = function (data, encoding) { } } // if we didn't find one, take the last + // TODO what if this isn't empty but encoding == fd ? if (!buffer) { buffer = self._sendQueueLast(); // if last buffer is used up @@ -166,13 +193,22 @@ Socket.prototype._sendString = function (data, encoding) { var charsWritten; var bytesWritten; - if (encoding.toLowerCase() == 'utf8') { + // The special encoding "fd" means that data is an integer FD and we want + // to pass the FD on the socket with sendmsg() + if (encoding == "fd") { + buffer.isFd = true; + // TODO is this OK -- does it guarantee that the fd is the only thing in the buffer? + charsWritten = buffer.asciiWrite(data, buffer.used, buffer.length - buffer.used); + bytesWritten = charsWritten; + } else if (encoding.toLowerCase() == 'utf8') { + buffer.isFd = false; charsWritten = buffer.utf8Write(data, buffer.used, buffer.length - buffer.used); bytesWritten = process.Buffer.utf8Length(data.slice(0, charsWritten)); } else { // ascii + buffer.isFd = false; charsWritten = buffer.asciiWrite(data, buffer.used, buffer.length - buffer.used); @@ -180,7 +216,11 @@ Socket.prototype._sendString = function (data, encoding) { } buffer.used += bytesWritten; - self.sendQueueSize += bytesWritten; + if (buffer.isFd) { + self.sendMessageQueueSize += 1; + } else { + self.sendQueueSize += bytesWritten; + } debug('charsWritten ' + charsWritten); debug('buffer.used ' + buffer.used); @@ -235,6 +275,27 @@ Socket.prototype.send = function (data, encoding) { return this.flush(); }; +// Sends a file descriptor over a unix socket +Socket.prototype.sendFD = function(socketToPass) { + var self = this; + + if (!self.writable) throw new Error('Socket is not writable'); + + if (self._sendQueueLast == END_OF_FILE) { + throw new Error('socket.close() called already; cannot write.'); + } + + if (self.type != "unix") { + throw new Error('FD passing only available on unix sockets'); + } + + if (! socketToPass instanceof Socket) { + throw new Error('Provided arg is not a socket'); + } + + return self.send(socketToPass.fd.toString(), "fd"); +}; + // Flushes the write buffer out. Emits "drain" if the buffer is empty. Socket.prototype.flush = function () { @@ -253,23 +314,35 @@ Socket.prototype.flush = function () { if (b.sent == b.used) { // this can be improved - save the buffer for later? - self.sendQueue.shift() + self.sendQueue.shift(); continue; } - bytesWritten = write(self.fd, - b, - b.sent, - b.used - b.sent); + var fdToSend = null; + if (b.isFd) { + fdToSend = parseInt(b.asciiSlice(b.sent, b.used - b.sent)); + bytesWritten = sendFD(self.fd, fdToSend); + } else { + bytesWritten = write(self.fd, + b, + b.sent, + b.used - b.sent); + } if (bytesWritten === null) { // could not flush everything self.writeWatcher.start(); assert(self.sendQueueSize > 0); return false; } - b.sent += bytesWritten; - self.sendQueueSize -= bytesWritten; - debug('bytes sent: ' + b.sent); + if (b.isFd) { + b.sent = b.used; + self.sendMessageQueueSize -= 1; + debug('sent fd: ' + fdToSend); + } else { + b.sent += bytesWritten; + self.sendQueueSize -= bytesWritten; + debug('bytes sent: ' + b.sent); + } } self.writeWatcher.stop(); return true; @@ -299,11 +372,11 @@ Socket.prototype.connect = function () { // socketError() if there isn't an error, we're connected. AFAIK this a // platform independent way determining when a non-blocking connection // is established, but I have only seen it documented in the Linux - // Manual Page connect(2) under the error code EINPROGRESS. + // Manual Page connect(2) under the error code EINPROGRESS. self.writeWatcher.set(self.fd, false, true); self.writeWatcher.start(); self.writeWatcher.callback = function () { - var errno = socketError(self.fd); + var errno = socketError(self.fd); if (errno == 0) { // connection established self.readWatcher.start(); @@ -340,7 +413,6 @@ Socket.prototype.address = function () { return getsockname(this.fd); }; - Socket.prototype.setNoDelay = function (v) { if (this.type == 'tcp') setNoDelay(this.fd, v); }; @@ -393,6 +465,7 @@ function Server (listener) { debug('accept: ' + JSON.stringify(peerInfo)); if (!peerInfo) return; var peer = new Socket(peerInfo); + peer.type = self.type; peer.server = self; self.emit('connection', peer); } diff --git a/src/node_net2.cc b/src/node_net2.cc index 90a05002b57..4e52cddb4b3 100644 --- a/src/node_net2.cc +++ b/src/node_net2.cc @@ -41,6 +41,9 @@ static Persistent remote_address_symbol; static Persistent remote_port_symbol; static Persistent address_symbol; static Persistent port_symbol; +static Persistent type_symbol; +static Persistent tcp_symbol; +static Persistent unix_symbol; #define FD_ARG(a) \ if (!(a)->IsInt32()) { \ @@ -181,7 +184,7 @@ static inline Handle ParseAddressArgs(Handle first, strcpy(un.sun_path, *path); addr = (struct sockaddr*)&un; - addrlen = path.length() + sizeof(un.sun_family); + addrlen = path.length() + sizeof(un.sun_family) + 1; } else { // TCP or UDP @@ -326,7 +329,6 @@ static Handle Connect(const Arguments& args) { return Undefined(); } - static Handle GetSockName(const Arguments& args) { HandleScope scope; @@ -358,6 +360,37 @@ static Handle GetSockName(const Arguments& args) { return scope.Close(info); } +static Handle GetPeerName(const Arguments& args) { + HandleScope scope; + + FD_ARG(args[0]) + + struct sockaddr_storage address_storage; + socklen_t len = sizeof(struct sockaddr_storage); + + int r = getpeername(fd, (struct sockaddr *) &address_storage, &len); + + if (r < 0) { + return ThrowException(ErrnoException(errno, "getsockname")); + } + + Local info = Object::New(); + + if (address_storage.ss_family == AF_INET6) { + struct sockaddr_in6 *a = (struct sockaddr_in6*)&address_storage; + + char ip[INET6_ADDRSTRLEN]; + inet_ntop(AF_INET6, &(a->sin6_addr), ip, INET6_ADDRSTRLEN); + + int port = ntohs(a->sin6_port); + + info->Set(remote_address_symbol, String::New(ip)); + info->Set(remote_port_symbol, Integer::New(port)); + } + + return scope.Close(info); +} + static Handle Listen(const Arguments& args) { HandleScope scope; @@ -484,6 +517,78 @@ static Handle Read(const Arguments& args) { return scope.Close(Integer::New(bytes_read)); } +// bytesRead, receivedFd = t.recvMsg(fd, buffer, offset, length) +static Handle RecvMsg(const Arguments& args) { + HandleScope scope; + + if (args.Length() < 4) { + return ThrowException(Exception::TypeError( + String::New("Takes 4 parameters"))); + } + + FD_ARG(args[0]) + + if (!IsBuffer(args[1])) { + return ThrowException(Exception::TypeError( + String::New("Second argument should be a buffer"))); + } + + struct buffer * buffer = BufferUnwrap(args[1]); + + size_t off = args[2]->Int32Value(); + if (buffer_p(buffer, off) == NULL) { + return ThrowException(Exception::Error( + String::New("Offset is out of bounds"))); + } + + size_t len = args[3]->Int32Value(); + if (buffer_remaining(buffer, off) < len) { + return ThrowException(Exception::Error( + String::New("Length is extends beyond buffer"))); + } + + struct iovec iov[1]; + struct msghdr msg; + int received_fd; + char control_msg[CMSG_SPACE(sizeof(received_fd))]; + struct cmsghdr *cmsg; + + // TODO: zero out control_msg ? + + iov[0].iov_base = buffer_p(buffer, off); + iov[0].iov_len = buffer_remaining(buffer, off); + msg.msg_iov = iov; + msg.msg_iovlen = 1; + msg.msg_name = NULL; + msg.msg_namelen = 0; + /* Set up to receive a descriptor even if one isn't in the message */ + msg.msg_control = (void *) control_msg; + msg.msg_controllen = CMSG_LEN(sizeof(received_fd)); + + ssize_t bytes_read = recvmsg(fd, &msg, 0); + + if (bytes_read < 0) { + if (errno == EAGAIN || errno == EINTR) return Null(); + return ThrowException(ErrnoException(errno, "recvMsg")); + } + + // Return array of [bytesRead, fd] with fd == -1 if there was no FD + Local a = Array::New(2); + a->Set(Integer::New(0), Integer::New(bytes_read)); + + cmsg = CMSG_FIRSTHDR(&msg); + if (cmsg->cmsg_type == SCM_RIGHTS) { + received_fd = *(int *) CMSG_DATA(cmsg); + } + else { + received_fd = -1; + } + + a->Set(Integer::New(1), Integer::New(received_fd)); + return scope.Close(a); +} + + // var bytesWritten = t.write(fd, buffer, offset, length); // returns null on EAGAIN or EINTR, raises an exception on all other errors static Handle Write(const Arguments& args) { @@ -527,6 +632,60 @@ static Handle Write(const Arguments& args) { return scope.Close(Integer::New(written)); } +// var bytesWritten = t.sendFD(self.fd) +// returns null on EAGAIN or EINTR, raises an exception on all other errors +static Handle SendFD(const Arguments& args) { + HandleScope scope; + + if (args.Length() < 2) { + return ThrowException(Exception::TypeError( + String::New("Takes 2 parameters"))); + } + + FD_ARG(args[0]) + + // TODO: make sure fd is a unix domain socket? + + if (!args[1]->IsInt32()) { + return ThrowException(Exception::TypeError( + String::New("FD to send is not an integer"))); + } + + int fd_to_send = args[1]->Int32Value(); + + struct msghdr msg; + struct iovec iov[1]; + char control_msg[CMSG_SPACE(sizeof(fd_to_send))]; + struct cmsghdr *cmsg; + char *dummy = "d"; // Need to send at least a byte of data in the message + + iov[0].iov_base = dummy; + iov[0].iov_len = 1; + msg.msg_iov = iov; + msg.msg_iovlen = 1; + msg.msg_name = NULL; + msg.msg_namelen = 0; + msg.msg_flags = 0; + msg.msg_control = (void *) control_msg; + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(fd_to_send)); + *(int*) CMSG_DATA(cmsg) = fd_to_send; + msg.msg_controllen = cmsg->cmsg_len; + + ssize_t written = sendmsg(fd, &msg, 0); + + if (written < 0) { + if (errno == EAGAIN || errno == EINTR) return Null(); + return ThrowException(ErrnoException(errno, "sendmsg")); + } + + /* Note that the FD isn't explicitly closed here, this + * happens in the JS */ + + return scope.Close(Integer::New(written)); +} // Probably only works for Linux TCP sockets? // Returns the amount of data on the read queue. @@ -744,6 +903,9 @@ void InitNet2(Handle target) { NODE_SET_METHOD(target, "write", Write); NODE_SET_METHOD(target, "read", Read); + NODE_SET_METHOD(target, "sendFD", SendFD); + NODE_SET_METHOD(target, "recvMsg", RecvMsg); + NODE_SET_METHOD(target, "socket", Socket); NODE_SET_METHOD(target, "close", Close); NODE_SET_METHOD(target, "shutdown", Shutdown); @@ -757,7 +919,8 @@ void InitNet2(Handle target) { NODE_SET_METHOD(target, "socketError", SocketError); NODE_SET_METHOD(target, "toRead", ToRead); NODE_SET_METHOD(target, "setNoDelay", SetNoDelay); - NODE_SET_METHOD(target, "getsocksame", GetSockName); + NODE_SET_METHOD(target, "getsockname", GetSockName); + NODE_SET_METHOD(target, "getpeername", GetPeerName); NODE_SET_METHOD(target, "getaddrinfo", GetAddrInfo); NODE_SET_METHOD(target, "needsLookup", NeedsLookup); diff --git a/test/mjsunit/fixtures/net-fd-passing-receiver.js b/test/mjsunit/fixtures/net-fd-passing-receiver.js new file mode 100644 index 00000000000..be29a287233 --- /dev/null +++ b/test/mjsunit/fixtures/net-fd-passing-receiver.js @@ -0,0 +1,37 @@ +process.mixin(require("../common")); +net = require("net"); + +process.Buffer.prototype.toString = function () { + return this.utf8Slice(0, this.length); +}; + + +path = process.ARGV[2]; +greeting = process.ARGV[3]; + +receiver = net.createServer(function(socket) { + socket.addListener("fd", function(fd) { + var peerInfo = process.getpeername(fd); + peerInfo.fd = fd; + var passedSocket = new net.Socket(peerInfo); + + passedSocket.addListener("eof", function() { + passedSocket.close(); + }); + + passedSocket.addListener("data", function(data) { + passedSocket.send("[echo] " + data); + }); + passedSocket.addListener("close", function() { + receiver.close(); + }); + passedSocket.send("[greeting] " + greeting); + }); +}); + +/* To signal the test runne we're up and listening */ +receiver.addListener("listening", function() { + print("ready"); +}); + +receiver.listen(path); diff --git a/test/mjsunit/test-net-fd-passing.js b/test/mjsunit/test-net-fd-passing.js new file mode 100644 index 00000000000..a3bd0edc20a --- /dev/null +++ b/test/mjsunit/test-net-fd-passing.js @@ -0,0 +1,67 @@ +process.mixin(require("./common")); +net = require("net"); + +process.Buffer.prototype.toString = function () { + return this.utf8Slice(0, this.length); +}; + +var tests_run = 0; + +function fdPassingTest(path, port) { + var greeting = "howdy"; + var message = "beep toot"; + var expectedData = ["[greeting] " + greeting, "[echo] " + message]; + + puts(fixturesDir); + var receiverArgs = [fixturesDir + "/net-fd-passing-receiver.js", path, greeting]; + var receiver = process.createChildProcess(process.ARGV[0], receiverArgs); + + var initializeSender = function() { + var fdHighway = new net.Socket(); + fdHighway.connect(path); + + var sender = net.createServer(function(socket) { + fdHighway.sendFD(socket); + socket.flush(); + socket.forceClose(); // want to close() the fd, not shutdown() + }); + + sender.addListener("listening", function() { + var client = net.createConnection(port); + + client.addListener("connect", function() { + client.send(message); + }); + + client.addListener("data", function(data) { + assert.equal(expectedData[0], data); + if (expectedData.length > 1) { + expectedData.shift(); + } + else { + // time to shut down + fdHighway.close(); + sender.close(); + client.forceClose(); + } + }); + }); + + tests_run += 1; + sender.listen(port); + }; + + receiver.addListener("output", function(data) { + var initialized = false; + if ((! initialized) && (data == "ready")) { + initializeSender(); + initialized = true; + } + }); +} + +fdPassingTest("/tmp/passing-socket-test", 31075); + +process.addListener("exit", function () { + assert.equal(1, tests_run); +}); From df59f067345e3a45be7637122ba8566009aab830 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Wed, 13 Jan 2010 08:41:01 -0800 Subject: [PATCH 029/105] recvMsg shouldn't return array for efficiency. --- lib/net.js | 34 ++++++++++++++----------------- src/node_net2.cc | 52 ++++++++++++++++++++++++++++-------------------- 2 files changed, 45 insertions(+), 41 deletions(-) diff --git a/lib/net.js b/lib/net.js index f513a9a8029..2604cb5c6bb 100644 --- a/lib/net.js +++ b/lib/net.js @@ -49,39 +49,35 @@ function Socket (peerInfo) { debug('recvBuffer.used ' + self.recvBuffer.used); var bytesRead; - var receivedFd = -1; if (self.type == "unix") { - var msgInfo = recvMsg(self.fd, - self.recvBuffer, - self.recvBuffer.used, - self.recvBuffer.length - self.recvBuffer.used); - bytesRead = msgInfo[0]; - receivedFd = msgInfo[1]; - debug('receivedFd ' + receivedFd); + bytesRead = recvMsg(self.fd, + self.recvBuffer, + self.recvBuffer.used, + self.recvBuffer.length - self.recvBuffer.used); + debug('recvMsg.fd ' + recvMsg.fd); + if (recvMsg.fd) { + self.emit('fd', recvMsg.fd); + } } else { bytesRead = read(self.fd, self.recvBuffer, self.recvBuffer.used, self.recvBuffer.length - self.recvBuffer.used); } + debug('bytesRead ' + bytesRead + '\n'); - if (bytesRead == 0) { + if (!recvMsg.fd && bytesRead == 0) { self.readable = false; self.readWatcher.stop(); self.emit('eof'); if (!self.writable) self.forceClose(); - } else { - if (receivedFd == -1) { - var slice = self.recvBuffer.slice(self.recvBuffer.used, - self.recvBuffer.used + bytesRead); - self.recvBuffer.used += bytesRead; - self.emit('data', slice); - } else { - self.recvBuffer.used += bytesRead; - self.emit('fd', receivedFd); - } + } else if (bytesRead > 0) { + var slice = self.recvBuffer.slice(self.recvBuffer.used, + self.recvBuffer.used + bytesRead); + self.recvBuffer.used += bytesRead; + self.emit('data', slice); } }; self.readable = false; diff --git a/src/node_net2.cc b/src/node_net2.cc index 4e52cddb4b3..47e8da3cf0b 100644 --- a/src/node_net2.cc +++ b/src/node_net2.cc @@ -45,6 +45,8 @@ static Persistent type_symbol; static Persistent tcp_symbol; static Persistent unix_symbol; +static Persistent recv_msg_template; + #define FD_ARG(a) \ if (!(a)->IsInt32()) { \ return ThrowException(Exception::TypeError( \ @@ -517,7 +519,10 @@ static Handle Read(const Arguments& args) { return scope.Close(Integer::New(bytes_read)); } -// bytesRead, receivedFd = t.recvMsg(fd, buffer, offset, length) +// bytesRead = t.recvMsg(fd, buffer, offset, length) +// if (recvMsg.fd) { +// receivedFd = recvMsg.fd; +// } static Handle RecvMsg(const Arguments& args) { HandleScope scope; @@ -528,7 +533,7 @@ static Handle RecvMsg(const Arguments& args) { FD_ARG(args[0]) - if (!IsBuffer(args[1])) { + if (!IsBuffer(args[1])) { return ThrowException(Exception::TypeError( String::New("Second argument should be a buffer"))); } @@ -547,23 +552,21 @@ static Handle RecvMsg(const Arguments& args) { String::New("Length is extends beyond buffer"))); } - struct iovec iov[1]; - struct msghdr msg; int received_fd; - char control_msg[CMSG_SPACE(sizeof(received_fd))]; - struct cmsghdr *cmsg; - - // TODO: zero out control_msg ? + struct iovec iov[1]; iov[0].iov_base = buffer_p(buffer, off); - iov[0].iov_len = buffer_remaining(buffer, off); + iov[0].iov_len = len; + + struct msghdr msg; msg.msg_iov = iov; msg.msg_iovlen = 1; msg.msg_name = NULL; msg.msg_namelen = 0; /* Set up to receive a descriptor even if one isn't in the message */ - msg.msg_control = (void *) control_msg; - msg.msg_controllen = CMSG_LEN(sizeof(received_fd)); + char cmsg_space[64]; // should be big enough + msg.msg_controllen = 64; + msg.msg_control = (void *) cmsg_space; ssize_t bytes_read = recvmsg(fd, &msg, 0); @@ -572,20 +575,22 @@ static Handle RecvMsg(const Arguments& args) { return ThrowException(ErrnoException(errno, "recvMsg")); } - // Return array of [bytesRead, fd] with fd == -1 if there was no FD - Local a = Array::New(2); - a->Set(Integer::New(0), Integer::New(bytes_read)); + // Why not return a two element array here [bytesRead, fd]? Because + // creating an object for each recvmsg() action is heavy. Instead we just + // assign the recved fd to a globalally accessable variable (recvMsg.fd) + // that the wrapper can pick up. Since we're single threaded, this is not + // a problem - just make sure to copy out that variable before the next + // call to recvmsg(). - cmsg = CMSG_FIRSTHDR(&msg); - if (cmsg->cmsg_type == SCM_RIGHTS) { + struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); + if (cmsg && cmsg->cmsg_type == SCM_RIGHTS) { received_fd = *(int *) CMSG_DATA(cmsg); - } - else { - received_fd = -1; + recv_msg_template->GetFunction()->Set(fd_symbol, Integer::New(received_fd)); + } else { + recv_msg_template->GetFunction()->Set(fd_symbol, Null()); } - a->Set(Integer::New(1), Integer::New(received_fd)); - return scope.Close(a); + return scope.Close(Integer::New(bytes_read)); } @@ -904,7 +909,10 @@ void InitNet2(Handle target) { NODE_SET_METHOD(target, "read", Read); NODE_SET_METHOD(target, "sendFD", SendFD); - NODE_SET_METHOD(target, "recvMsg", RecvMsg); + + recv_msg_template = + Persistent::New(FunctionTemplate::New(RecvMsg)); + target->Set(String::NewSymbol("recvMsg"), recv_msg_template->GetFunction()); NODE_SET_METHOD(target, "socket", Socket); NODE_SET_METHOD(target, "close", Close); From 2788064bc2f85325ea328258faabc226edb373d6 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Wed, 20 Jan 2010 15:43:06 -0800 Subject: [PATCH 030/105] Make callback_symbols static so they don't conflict --- src/node_idle_watcher.cc | 2 +- src/node_io_watcher.cc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/node_idle_watcher.cc b/src/node_idle_watcher.cc index fc09953e692..a1fb202dac5 100644 --- a/src/node_idle_watcher.cc +++ b/src/node_idle_watcher.cc @@ -11,7 +11,7 @@ namespace node { using namespace v8; Persistent IdleWatcher::constructor_template; -Persistent callback_symbol; +static Persistent callback_symbol; void IdleWatcher::Initialize(Handle target) { HandleScope scope; diff --git a/src/node_io_watcher.cc b/src/node_io_watcher.cc index 4a2d9378d93..61e49f6bc52 100644 --- a/src/node_io_watcher.cc +++ b/src/node_io_watcher.cc @@ -11,7 +11,7 @@ namespace node { using namespace v8; Persistent IOWatcher::constructor_template; -Persistent callback_symbol; +static Persistent callback_symbol; void IOWatcher::Initialize(Handle target) { HandleScope scope; From 6f738d6e7a90f5c7659074340baaeb098c9e40f7 Mon Sep 17 00:00:00 2001 From: David Sklar Date: Fri, 22 Jan 2010 17:03:54 -0500 Subject: [PATCH 031/105] Adjust passing-FDs test to wait until socket is really writeable --- test/mjsunit/test-net-fd-passing.js | 62 +++++++++++++++-------------- 1 file changed, 33 insertions(+), 29 deletions(-) diff --git a/test/mjsunit/test-net-fd-passing.js b/test/mjsunit/test-net-fd-passing.js index a3bd0edc20a..9b8f8882d7d 100644 --- a/test/mjsunit/test-net-fd-passing.js +++ b/test/mjsunit/test-net-fd-passing.js @@ -12,43 +12,47 @@ function fdPassingTest(path, port) { var message = "beep toot"; var expectedData = ["[greeting] " + greeting, "[echo] " + message]; - puts(fixturesDir); var receiverArgs = [fixturesDir + "/net-fd-passing-receiver.js", path, greeting]; var receiver = process.createChildProcess(process.ARGV[0], receiverArgs); var initializeSender = function() { var fdHighway = new net.Socket(); + + fdHighway.addListener("connect", function() { + var sender = net.createServer(function(socket) { + fdHighway.sendFD(socket); + socket.flush(); + socket.forceClose(); // want to close() the fd, not shutdown() + }); + + sender.addListener("listening", function() { + var client = net.createConnection(port); + + client.addListener("connect", function() { + client.send(message); + }); + + client.addListener("data", function(data) { + assert.equal(expectedData[0], data); + if (expectedData.length > 1) { + expectedData.shift(); + } + else { + // time to shut down + fdHighway.close(); + sender.close(); + client.forceClose(); + } + }); + }); + + tests_run += 1; + sender.listen(port); + }); + fdHighway.connect(path); - var sender = net.createServer(function(socket) { - fdHighway.sendFD(socket); - socket.flush(); - socket.forceClose(); // want to close() the fd, not shutdown() - }); - sender.addListener("listening", function() { - var client = net.createConnection(port); - - client.addListener("connect", function() { - client.send(message); - }); - - client.addListener("data", function(data) { - assert.equal(expectedData[0], data); - if (expectedData.length > 1) { - expectedData.shift(); - } - else { - // time to shut down - fdHighway.close(); - sender.close(); - client.forceClose(); - } - }); - }); - - tests_run += 1; - sender.listen(port); }; receiver.addListener("output", function(data) { From 42ee16978e81a0f1fba0768e163b5e9584178fa3 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Sun, 24 Jan 2010 11:21:45 -0800 Subject: [PATCH 032/105] Implement new http-parser binding using Buffer --- src/node.cc | 2 + src/node_http_parser.cc | 324 +++++++++++++++++++++++++++++++ src/node_http_parser.h | 12 ++ test/mjsunit/test-http-parser.js | 60 ++++++ wscript | 1 + 5 files changed, 399 insertions(+) create mode 100644 src/node_http_parser.cc create mode 100644 src/node_http_parser.h create mode 100644 test/mjsunit/test-http-parser.js diff --git a/src/node.cc b/src/node.cc index ea7a8c0f88f..6c4fd51fddc 100644 --- a/src/node.cc +++ b/src/node.cc @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -996,6 +997,7 @@ static Local 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 diff --git a/src/node_http_parser.cc b/src/node_http_parser.cc new file mode 100644 index 00000000000..f90eb3c2c7b --- /dev/null +++ b/src/node_http_parser.cc @@ -0,0 +1,324 @@ +#include + +#include +#include +#include + +#include + +#include /* 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 on_message_begin_sym; +static Persistent on_path_sym; +static Persistent on_query_string_sym; +static Persistent on_url_sym; +static Persistent on_fragment_sym; +static Persistent on_header_field_sym; +static Persistent on_header_value_sym; +static Persistent on_headers_complete_sym; +static Persistent on_body_sym; +static Persistent on_message_complete_sym; + +static Persistent delete_sym; +static Persistent get_sym; +static Persistent head_sym; +static Persistent post_sym; +static Persistent put_sym; +static Persistent connect_sym; +static Persistent options_sym; +static Persistent trace_sym; +static Persistent copy_sym; +static Persistent lock_sym; +static Persistent mkcol_sym; +static Persistent move_sym; +static Persistent propfind_sym; +static Persistent proppatch_sym; +static Persistent unlock_sym; +static Persistent unknown_method_sym; + +static Persistent method_sym; +static Persistent status_code_sym; +static Persistent http_version_sym; +static Persistent version_major_sym; +static Persistent version_minor_sym; +static Persistent should_keep_alive_sym; + +// Callback prototype for http_cb +#define DEFINE_HTTP_CB(name) \ + static int name(http_parser *p) { \ + Parser *parser = static_cast(p->data); \ + \ + HandleScope scope; \ + \ + Local cb_value = parser->handle_->Get(name##_sym); \ + if (!cb_value->IsFunction()) return 0; \ + Local cb = Local::Cast(cb_value); \ + \ + Local 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(p->data); \ + \ + HandleScope scope; \ + \ + assert(parser->buffer_); \ + char * base = buffer_p(parser->buffer_, 0); \ + \ + Local cb_value = parser->handle_->Get(name##_sym); \ + if (!cb_value->IsFunction()) return 0; \ + Local cb = Local::Cast(cb_value); \ + \ + Local off = Integer::New(at - base); \ + Local len = Integer::New(length); \ + Local argv[2] = { off, len }; \ + \ + Local ret = cb->Call(parser->handle_, 2, argv); \ + return ret.IsEmpty() ? -1 : 0; \ + } + + +static inline Persistent +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(p->data); + + HandleScope scope; + + Local cb_value = parser->handle_->Get(on_headers_complete_sym); + if (!cb_value->IsFunction()) return 0; + Local cb = Local::Cast(cb_value); + + + Local 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 argv[1] = { message_info }; + + Local ret = cb->Call(parser->handle_, 1, argv); + return ret.IsEmpty() ? -1 : 0; + } + + static Handle 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 Execute(const Arguments& args) { + HandleScope scope; + + Parser *parser = ObjectWrap::Unwrap(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 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 e = Exception::Error(String::New("Parse Error")); + Local 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 target) { + HandleScope scope; + + Local 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 + diff --git a/src/node_http_parser.h b/src/node_http_parser.h new file mode 100644 index 00000000000..78ba175eed1 --- /dev/null +++ b/src/node_http_parser.h @@ -0,0 +1,12 @@ +#ifndef NODE_HTTP_PARSER +#define NODE_HTTP_PARSER + +#include + +namespace node { + +void InitHttpParser(v8::Handle target); + +} + +#endif // NODE_HTTP_PARSER diff --git a/test/mjsunit/test-http-parser.js b/test/mjsunit/test-http-parser.js new file mode 100644 index 00000000000..fa6ac6e5928 --- /dev/null +++ b/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"); + diff --git a/wscript b/wscript index 54c3f213806..2431ecb1c3d 100644 --- a/wscript +++ b/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 From bffa18befcdb275e5b1d3104c6a80dec56af0f28 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Sun, 24 Jan 2010 14:06:07 -0800 Subject: [PATCH 033/105] Expose buffer_root() --- src/node_buffer.cc | 4 ---- src/node_buffer.h | 8 ++++++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/node_buffer.cc b/src/node_buffer.cc index 584ed184b62..fa11ae42cff 100644 --- a/src/node_buffer.cc +++ b/src/node_buffer.cc @@ -32,10 +32,6 @@ bool IsBuffer(v8::Handle val) { } -static inline struct buffer* buffer_root(buffer *buffer) { - return buffer->root ? buffer->root : buffer; -} - /* Determines the absolute position for a relative offset */ static inline size_t buffer_abs_off(buffer *buffer, size_t off) { struct buffer *root = buffer_root(buffer); diff --git a/src/node_buffer.h b/src/node_buffer.h index 3afb3eeaeef..2c189fd2325 100644 --- a/src/node_buffer.h +++ b/src/node_buffer.h @@ -40,14 +40,18 @@ void InitBuffer(v8::Handle target); struct buffer* BufferUnwrap(v8::Handle val); bool IsBuffer(v8::Handle val); +static inline struct buffer * buffer_root(struct buffer *buffer) { + return buffer->root ? buffer->root : buffer; +} + static inline char * buffer_p(struct buffer *buffer, size_t off) { - struct buffer *root = buffer->root ? buffer->root : buffer; + struct buffer *root = buffer_root(buffer); if (buffer->offset + off >= root->length) return NULL; return reinterpret_cast(&(root->bytes) + buffer->offset + off); } static inline size_t buffer_remaining(struct buffer *buffer, size_t off) { - struct buffer *root = buffer->root ? buffer->root : buffer; + struct buffer *root = buffer_root(buffer); char *end = reinterpret_cast(&(root->bytes) + root->length); return end - buffer_p(buffer, off); } From dda1d681f73aa90cf7dc4ae7c4373507612b61a0 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Sun, 24 Jan 2010 14:12:15 -0800 Subject: [PATCH 034/105] Provide buffer in HTTPParser callbacks. --- src/node_http_parser.cc | 13 +++++++------ test/mjsunit/test-http-parser.js | 8 ++++---- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/node_http_parser.cc b/src/node_http_parser.cc index f90eb3c2c7b..972e2bad892 100644 --- a/src/node_http_parser.cc +++ b/src/node_http_parser.cc @@ -81,17 +81,18 @@ static Persistent should_keep_alive_sym; HandleScope scope; \ \ assert(parser->buffer_); \ - char * base = buffer_p(parser->buffer_, 0); \ + struct buffer * root = buffer_root(parser->buffer_); \ + char * base = buffer_p(root, 0); \ \ Local cb_value = parser->handle_->Get(name##_sym); \ if (!cb_value->IsFunction()) return 0; \ Local cb = Local::Cast(cb_value); \ \ - Local off = Integer::New(at - base); \ - Local len = Integer::New(length); \ - Local argv[2] = { off, len }; \ - \ - Local ret = cb->Call(parser->handle_, 2, argv); \ + Local argv[3] = { Local::New(root->handle) \ + , Integer::New(at - base) \ + , Integer::New(length) \ + }; \ + Local ret = cb->Call(parser->handle_, 3, argv); \ return ret.IsEmpty() ? -1 : 0; \ } diff --git a/test/mjsunit/test-http-parser.js b/test/mjsunit/test-http-parser.js index fa6ac6e5928..db148c7c118 100644 --- a/test/mjsunit/test-http-parser.js +++ b/test/mjsunit/test-http-parser.js @@ -29,14 +29,14 @@ parser.onHeadersComplete = function (info) { callbacks++; }; -parser.onURL = function (off, len) { +parser.onURL = function (b, off, len) { //throw new Error("hello world"); callbacks++; }; -parser.onPath = function (off, length) { +parser.onPath = function (b, off, length) { puts("path [" + off + ", " + length + "]"); - var path = buffer.asciiSlice(off, off+length); + var path = b.asciiSlice(off, off+length); puts("path = '" + path + "'"); assert.equal('/hello', path); callbacks++; @@ -50,7 +50,7 @@ assert.equal(4, callbacks); // thrown from parser.execute() // -parser.onURL = function (off, len) { +parser.onURL = function (b, off, len) { throw new Error("hello world"); }; From 741e3fa91bdeec0c5cdc6e96bc4ce0ae29acae75 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Mon, 25 Jan 2010 17:55:02 -0800 Subject: [PATCH 035/105] HTTP works somewhat on net2 now However it's not working very well: Hitting a 'hello world' server with many requests (ab -t 60 -c 10) will cause it to crash with the following error. Obtained 3 stack frames. ./node(_Z11print_tracev+0x1c) [0x80d1b3c] ./node(_ZN4node6Parser7ExecuteERKN2v89ArgumentsE+0x69) [0x80d3759] ./node [0x811f44b] TypeError: Already parsing a buffer at Socket. (/home/ryan/projects/node/lib/http2.js:393:20) at IOWatcher.callback (/home/ryan/projects/node/lib/net.js:81:12) at node.js:985:9 at node.js:989:1 --- lib/net.js | 14 +++++++++---- src/node_buffer.cc | 28 +++++++++++++------------ src/node_buffer.h | 5 ++++- src/node_http_parser.cc | 46 +++++++++++++++++++++++++++++++++++++++++ src/node_io_watcher.cc | 20 +++++++++--------- src/node_io_watcher.h | 41 +++++++++++++++++++++++++++++++++--- src/node_object_wrap.h | 1 + 7 files changed, 124 insertions(+), 31 deletions(-) diff --git a/lib/net.js b/lib/net.js index 2604cb5c6bb..3b02993cef7 100644 --- a/lib/net.js +++ b/lib/net.js @@ -28,7 +28,7 @@ var getaddrinfo = process.getaddrinfo; var needsLookup = process.needsLookup; var EINPROGRESS = process.EINPROGRESS; var ENOENT = process.ENOENT; -var END_OF_FILE = 42; +var END_OF_FILE = 0; function Socket (peerInfo) { process.EventEmitter.call(); @@ -39,6 +39,7 @@ function Socket (peerInfo) { self.recvBuffer = null; self.readWatcher = new IOWatcher(); + self.readWatcher.host = this; self.readWatcher.callback = function () { // If this is the first recv (recvBuffer doesn't exist) or we've used up // most of the recvBuffer, allocate a new one. @@ -61,9 +62,9 @@ function Socket (peerInfo) { } } else { bytesRead = read(self.fd, - self.recvBuffer, - self.recvBuffer.used, - self.recvBuffer.length - self.recvBuffer.used); + self.recvBuffer, + self.recvBuffer.used, + self.recvBuffer.length - self.recvBuffer.used); } debug('bytesRead ' + bytesRead + '\n'); @@ -99,6 +100,7 @@ function Socket (peerInfo) { } }; self.writeWatcher = new IOWatcher(); + self.writeWatcher.host = this; self.writeWatcher.callback = self._doFlush; self.writable = false; @@ -131,6 +133,7 @@ Socket.prototype._allocateNewRecvBuf = function () { var newBufferSize = 1024; // TODO make this adjustable from user API + /* if (toRead) { // Is the extra system call even worth it? var bytesToRead = toRead(self.fd); @@ -145,6 +148,7 @@ Socket.prototype._allocateNewRecvBuf = function () { newBufferSize = 128; } } + */ self.recvBuffer = new process.Buffer(newBufferSize); self.recvBuffer.used = 0; @@ -421,6 +425,7 @@ Socket.prototype.forceClose = function (exception) { this.writeWatcher.stop(); this.readWatcher.stop(); + close(this.fd); debug('close socket ' + this.fd); this.fd = null; @@ -455,6 +460,7 @@ function Server (listener) { } self.watcher = new IOWatcher(); + self.watcher.host = self; self.watcher.callback = function (readable, writeable) { while (self.fd) { var peerInfo = accept(self.fd); diff --git a/src/node_buffer.cc b/src/node_buffer.cc index fa11ae42cff..ee9ce8406ff 100644 --- a/src/node_buffer.cc +++ b/src/node_buffer.cc @@ -33,24 +33,15 @@ bool IsBuffer(v8::Handle val) { /* Determines the absolute position for a relative offset */ -static inline size_t buffer_abs_off(buffer *buffer, size_t off) { +size_t buffer_abs_off(buffer *buffer, size_t off) { struct buffer *root = buffer_root(buffer); off += buffer->offset; return MIN(root->length, off); } -static inline void buffer_ref(struct buffer *buffer) { - assert(buffer->root == NULL); - buffer->refs++; -} - - -static inline void buffer_unref(struct buffer *buffer) { - assert(buffer->root == NULL); - assert(buffer->refs > 0); - buffer->refs--; - if (buffer->refs == 0 && buffer->weak) free(buffer); +void buffer_ref(struct buffer *buffer) { + buffer_root(buffer)->refs++; } @@ -69,15 +60,26 @@ static void RootWeakCallback(Persistent value, void *data) struct buffer *buffer = static_cast(data); assert(buffer->root == NULL); // this is the root assert(value == buffer->handle); - buffer->handle.Dispose(); + value.ClearWeak(); if (buffer->refs) { buffer->weak = true; } else { + buffer->handle.Dispose(); free(buffer); } } +void buffer_unref(struct buffer *buffer) { + struct buffer * root = buffer_root(buffer); + assert(root->refs > 0); + root->refs--; + if (root->refs == 0 && root->weak) { + root->handle.MakeWeak(root, RootWeakCallback); + } +} + + static void SliceWeakCallback(Persistent value, void *data) { struct buffer *buffer = static_cast(data); diff --git a/src/node_buffer.h b/src/node_buffer.h index 2c189fd2325..122d589d85a 100644 --- a/src/node_buffer.h +++ b/src/node_buffer.h @@ -56,6 +56,9 @@ static inline size_t buffer_remaining(struct buffer *buffer, size_t off) { return end - buffer_p(buffer, off); } -} +void buffer_ref(struct buffer *buffer); +void buffer_unref(struct buffer *buffer); + +} // namespace node buffer #endif // NODE_BUFFER diff --git a/src/node_http_parser.cc b/src/node_http_parser.cc index 972e2bad892..5d133318ef0 100644 --- a/src/node_http_parser.cc +++ b/src/node_http_parser.cc @@ -19,6 +19,30 @@ // No copying is performed when slicing the buffer, only small reference // allocations. + #include + #include + #include + + /* Obtain a backtrace and print it to stdout. */ + void + print_trace (void) + { + void *array[10]; + size_t size; + char **strings; + size_t i; + + size = backtrace (array, 10); + strings = backtrace_symbols (array, size); + + printf ("Obtained %zd stack frames.\n", size); + + for (i = 0; i < size; i++) + printf ("%s\n", strings[i]); + + free (strings); + } + namespace node { using namespace v8; @@ -93,6 +117,7 @@ static Persistent should_keep_alive_sym; , Integer::New(length) \ }; \ Local ret = cb->Call(parser->handle_, 3, argv); \ + assert(parser->buffer_); \ return ret.IsEmpty() ? -1 : 0; \ } @@ -141,6 +166,10 @@ class Parser : public ObjectWrap { parser_.data = this; } + ~Parser() { + assert(buffer_ == NULL && "Destroying a parser while it's parsing"); + } + DEFINE_HTTP_CB(on_message_begin) DEFINE_HTTP_CB(on_message_complete) @@ -215,6 +244,7 @@ class Parser : public ObjectWrap { Parser *parser = ObjectWrap::Unwrap(args.This()); if (parser->buffer_) { + print_trace(); return ThrowException(Exception::TypeError( String::New("Already parsing a buffer"))); } @@ -243,9 +273,13 @@ class Parser : public ObjectWrap { // Assign 'buffer_' while we parse. The callbacks will access that varible. parser->buffer_ = buffer; + buffer_ref(parser->buffer_); + size_t nparsed = http_parser_execute(&(parser->parser_), buffer_p(buffer, off), len); + buffer_unref(parser->buffer_); + // Unassign the 'buffer_' variable assert(parser->buffer_); parser->buffer_ = NULL; @@ -266,6 +300,17 @@ class Parser : public ObjectWrap { return scope.Close(nparsed_obj); } + static Handle ExecuteEOF(const Arguments& args) { + HandleScope scope; + + Parser *parser = ObjectWrap::Unwrap(args.This()); + + assert(!parser->buffer_); + http_parser_execute(&parser->parser_, NULL, 0); + + return Undefined(); + } + private: @@ -282,6 +327,7 @@ void InitHttpParser(Handle target) { //t->SetClassName(String::NewSymbol("HTTPParser")); NODE_SET_PROTOTYPE_METHOD(t, "execute", Parser::Execute); + NODE_SET_PROTOTYPE_METHOD(t, "executeEOF", Parser::ExecuteEOF); target->Set(String::NewSymbol("HTTPParser"), t->GetFunction()); diff --git a/src/node_io_watcher.cc b/src/node_io_watcher.cc index 61e49f6bc52..2f05c7fd4e2 100644 --- a/src/node_io_watcher.cc +++ b/src/node_io_watcher.cc @@ -59,9 +59,8 @@ void IOWatcher::Callback(EV_P_ ev_io *w, int revents) { // -// var io = new process.IOWatcher(function (readable, writable) { -// -// }); +// var io = new process.IOWatcher(); +// io.callback = function (readable, writable) { ... }; // io.set(fd, true, false); // io.start(); // @@ -69,6 +68,8 @@ Handle IOWatcher::New(const Arguments& args) { HandleScope scope; IOWatcher *s = new IOWatcher(); + + s->Wrap(args.This()); return args.This(); @@ -78,11 +79,10 @@ Handle IOWatcher::New(const Arguments& args) { Handle IOWatcher::Start(const Arguments& args) { HandleScope scope; - IOWatcher *io = ObjectWrap::Unwrap(args.Holder()); + IOWatcher *io = Unwrap(args.Holder()); ev_io_start(EV_DEFAULT_UC_ &io->watcher_); - - io->Ref(); + assert(ev_is_active(&io->watcher_)); return Undefined(); } @@ -90,7 +90,7 @@ Handle IOWatcher::Start(const Arguments& args) { Handle IOWatcher::Set(const Arguments& args) { HandleScope scope; - IOWatcher *io = ObjectWrap::Unwrap(args.Holder()); + IOWatcher *io = Unwrap(args.Holder()); if (!args[0]->IsInt32()) { return ThrowException(Exception::TypeError( @@ -122,16 +122,16 @@ Handle IOWatcher::Set(const Arguments& args) { Handle IOWatcher::Stop(const Arguments& args) { HandleScope scope; - IOWatcher *io = ObjectWrap::Unwrap(args.Holder()); + IOWatcher *io = Unwrap(args.This()); io->Stop(); return Undefined(); } void IOWatcher::Stop () { - if (watcher_.active) { + if (ev_is_active(&watcher_)) { ev_io_stop(EV_DEFAULT_UC_ &watcher_); - Unref(); + assert(!ev_is_active(&watcher_)); } } diff --git a/src/node_io_watcher.h b/src/node_io_watcher.h index 5e731770762..d1d9d2dc2a3 100644 --- a/src/node_io_watcher.h +++ b/src/node_io_watcher.h @@ -7,20 +7,20 @@ namespace node { -class IOWatcher : ObjectWrap { +class IOWatcher { public: static void Initialize(v8::Handle target); protected: static v8::Persistent constructor_template; - IOWatcher() : ObjectWrap() { + IOWatcher() { ev_init(&watcher_, IOWatcher::Callback); watcher_.data = this; } ~IOWatcher() { - ev_io_stop(EV_DEFAULT_UC_ &watcher_); + assert(!ev_is_active(&watcher_)); } static v8::Handle New(const v8::Arguments& args); @@ -28,12 +28,47 @@ class IOWatcher : ObjectWrap { static v8::Handle Stop(const v8::Arguments& args); static v8::Handle Set(const v8::Arguments& args); + inline void Wrap(v8::Handle handle) { + assert(handle_.IsEmpty()); + assert(handle->InternalFieldCount() > 0); + handle_ = v8::Persistent::New(handle); + handle_->SetInternalField(0, v8::External::New(this)); + MakeWeak(); + } + + inline void MakeWeak(void) { + handle_.MakeWeak(this, WeakCallback); + } + + + private: static void Callback(EV_P_ ev_io *watcher, int revents); + static void WeakCallback (v8::Persistent value, void *data) + { + IOWatcher *io = static_cast(data); + assert(value == io->handle_); + if (!ev_is_active(&io->watcher_)) { + value.Dispose(); + delete io; + } else { + //value.ClearWeak(); + io->MakeWeak(); + } + } + + static IOWatcher* Unwrap(v8::Handle handle) { + assert(!handle.IsEmpty()); + assert(handle->InternalFieldCount() > 0); + return static_cast(v8::Handle::Cast( + handle->GetInternalField(0))->Value()); + } + void Stop(); ev_io watcher_; + v8::Persistent handle_; }; } // namespace node diff --git a/src/node_object_wrap.h b/src/node_object_wrap.h index 6656664bdfd..c392055a63b 100644 --- a/src/node_object_wrap.h +++ b/src/node_object_wrap.h @@ -53,6 +53,7 @@ class ObjectWrap { assert(!handle_.IsEmpty()); assert(handle_.IsWeak()); refs_++; + MakeWeak(); } /* Unref() marks an object as detached from the event loop. This is its From ccd632f2784f6f3eea7ef62fbddb38844f8b6e9d Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Tue, 26 Jan 2010 12:00:17 -0800 Subject: [PATCH 036/105] Simplify ObjectWrap; make fewer Weak callbacks --- src/node_object_wrap.h | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/src/node_object_wrap.h b/src/node_object_wrap.h index c392055a63b..457b234581d 100644 --- a/src/node_object_wrap.h +++ b/src/node_object_wrap.h @@ -13,12 +13,10 @@ class ObjectWrap { } virtual ~ObjectWrap ( ) { - if (!handle_.IsEmpty()) { - assert(handle_.IsNearDeath()); - handle_->SetInternalField(0, v8::Undefined()); - handle_.Dispose(); - handle_.Clear(); - } + assert(handle_.IsNearDeath()); + handle_->SetInternalField(0, v8::Undefined()); + handle_.Dispose(); + handle_.Clear(); } protected: @@ -37,11 +35,6 @@ class ObjectWrap { assert(handle->InternalFieldCount() > 0); handle_ = v8::Persistent::New(handle); handle_->SetInternalField(0, v8::External::New(this)); - MakeWeak(); - } - - inline void MakeWeak (void) - { handle_.MakeWeak(this, WeakCallback); } @@ -53,7 +46,6 @@ class ObjectWrap { assert(!handle_.IsEmpty()); assert(handle_.IsWeak()); refs_++; - MakeWeak(); } /* Unref() marks an object as detached from the event loop. This is its @@ -83,8 +75,6 @@ class ObjectWrap { assert(value == obj->handle_); if (obj->refs_ == 0) { delete obj; - } else { - obj->MakeWeak(); } } }; From b8c3d715fdb0c08065b98a640ee72dbcbd6c5c8c Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Tue, 26 Jan 2010 18:34:42 -0800 Subject: [PATCH 037/105] use efence again --- src/node_object_wrap.h | 17 +++++++++++++---- wscript | 6 +++--- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/node_object_wrap.h b/src/node_object_wrap.h index 457b234581d..6656664bdfd 100644 --- a/src/node_object_wrap.h +++ b/src/node_object_wrap.h @@ -13,10 +13,12 @@ class ObjectWrap { } virtual ~ObjectWrap ( ) { - assert(handle_.IsNearDeath()); - handle_->SetInternalField(0, v8::Undefined()); - handle_.Dispose(); - handle_.Clear(); + if (!handle_.IsEmpty()) { + assert(handle_.IsNearDeath()); + handle_->SetInternalField(0, v8::Undefined()); + handle_.Dispose(); + handle_.Clear(); + } } protected: @@ -35,6 +37,11 @@ class ObjectWrap { assert(handle->InternalFieldCount() > 0); handle_ = v8::Persistent::New(handle); handle_->SetInternalField(0, v8::External::New(this)); + MakeWeak(); + } + + inline void MakeWeak (void) + { handle_.MakeWeak(this, WeakCallback); } @@ -75,6 +82,8 @@ class ObjectWrap { assert(value == obj->handle_); if (obj->refs_ == 0) { delete obj; + } else { + obj->MakeWeak(); } } }; diff --git a/wscript b/wscript index 2431ecb1c3d..bd2425035c0 100644 --- a/wscript +++ b/wscript @@ -118,8 +118,8 @@ def configure(conf): #if Options.options.debug: # conf.check(lib='profiler', uselib_store='PROFILER') - #if Options.options.efence: - # conf.check(lib='efence', libpath=['/usr/lib', '/usr/local/lib'], uselib_store='EFENCE') + if Options.options.efence: + conf.check(lib='efence', libpath=['/usr/lib', '/usr/local/lib'], uselib_store='EFENCE') if not conf.check(lib="execinfo", libpath=['/usr/lib', '/usr/local/lib'], uselib_store="EXECINFO"): # Note on Darwin/OS X: This will fail, but will still be used as the @@ -373,7 +373,7 @@ def build(bld): """ node.add_objects = 'ev eio evcom http_parser coupling' node.uselib_local = '' - node.uselib = 'GNUTLS GPGERROR UDNS V8 EXECINFO DL KVM SOCKET NSL' + node.uselib = 'GNUTLS GPGERROR UDNS V8 EXECINFO DL KVM SOCKET NSL EFENCE' node.install_path = '${PREFIX}/lib' node.install_path = '${PREFIX}/bin' From c106c3740b0b24e15f25982b246781c77707bd16 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Tue, 26 Jan 2010 18:35:01 -0800 Subject: [PATCH 038/105] Use ObjectWrap again for IOWatchers. I think the problem is in Buffers. --- src/node_io_watcher.cc | 41 ++++++++++++++++++++++++++--------------- src/node_io_watcher.h | 42 +++++------------------------------------- 2 files changed, 31 insertions(+), 52 deletions(-) diff --git a/src/node_io_watcher.cc b/src/node_io_watcher.cc index 2f05c7fd4e2..baf46411bc9 100644 --- a/src/node_io_watcher.cc +++ b/src/node_io_watcher.cc @@ -34,6 +34,7 @@ void IOWatcher::Initialize(Handle target) { void IOWatcher::Callback(EV_P_ ev_io *w, int revents) { IOWatcher *io = static_cast(w->data); assert(w == &io->watcher_); + assert(!(revents & EV_ERROR)); HandleScope scope; Local callback_v = io->handle_->Get(callback_symbol); @@ -50,7 +51,9 @@ void IOWatcher::Callback(EV_P_ ev_io *w, int revents) { argv[0] = Local::New(revents & EV_READ ? True() : False()); argv[1] = Local::New(revents & EV_WRITE ? True() : False()); + io->Ref(); callback->Call(io->handle_, 2, argv); + io->Unref(); if (try_catch.HasCaught()) { FatalException(try_catch); @@ -76,27 +79,16 @@ Handle IOWatcher::New(const Arguments& args) { } -Handle IOWatcher::Start(const Arguments& args) { - HandleScope scope; - - IOWatcher *io = Unwrap(args.Holder()); - - ev_io_start(EV_DEFAULT_UC_ &io->watcher_); - assert(ev_is_active(&io->watcher_)); - - return Undefined(); -} - Handle IOWatcher::Set(const Arguments& args) { HandleScope scope; - IOWatcher *io = Unwrap(args.Holder()); - if (!args[0]->IsInt32()) { return ThrowException(Exception::TypeError( String::New("First arg should be a file descriptor."))); } + IOWatcher *io = ObjectWrap::Unwrap(args.This()); + int fd = args[0]->Int32Value(); if (!args[1]->IsBoolean()) { @@ -120,19 +112,38 @@ Handle IOWatcher::Set(const Arguments& args) { return Undefined(); } + +Handle IOWatcher::Start(const Arguments& args) { + HandleScope scope; + IOWatcher *io = ObjectWrap::Unwrap(args.This()); + io->Start(); + return Undefined(); +} + + Handle IOWatcher::Stop(const Arguments& args) { HandleScope scope; - IOWatcher *io = Unwrap(args.This()); + IOWatcher *io = ObjectWrap::Unwrap(args.This()); io->Stop(); return Undefined(); } +void IOWatcher::Start () { + if (!ev_is_active(&watcher_)) { + ev_io_start(EV_DEFAULT_UC_ &watcher_); + Ref(); + } + assert(ev_is_active(&watcher_)); +} + + void IOWatcher::Stop () { if (ev_is_active(&watcher_)) { ev_io_stop(EV_DEFAULT_UC_ &watcher_); - assert(!ev_is_active(&watcher_)); + Unref(); } + assert(!ev_is_active(&watcher_)); } diff --git a/src/node_io_watcher.h b/src/node_io_watcher.h index d1d9d2dc2a3..95a135e7660 100644 --- a/src/node_io_watcher.h +++ b/src/node_io_watcher.h @@ -7,20 +7,22 @@ namespace node { -class IOWatcher { +class IOWatcher : ObjectWrap { public: static void Initialize(v8::Handle target); protected: static v8::Persistent constructor_template; - IOWatcher() { + IOWatcher() : ObjectWrap() { ev_init(&watcher_, IOWatcher::Callback); watcher_.data = this; } ~IOWatcher() { + Stop(); assert(!ev_is_active(&watcher_)); + assert(!ev_is_pending(&watcher_)); } static v8::Handle New(const v8::Arguments& args); @@ -28,47 +30,13 @@ class IOWatcher { static v8::Handle Stop(const v8::Arguments& args); static v8::Handle Set(const v8::Arguments& args); - inline void Wrap(v8::Handle handle) { - assert(handle_.IsEmpty()); - assert(handle->InternalFieldCount() > 0); - handle_ = v8::Persistent::New(handle); - handle_->SetInternalField(0, v8::External::New(this)); - MakeWeak(); - } - - inline void MakeWeak(void) { - handle_.MakeWeak(this, WeakCallback); - } - - - private: static void Callback(EV_P_ ev_io *watcher, int revents); - static void WeakCallback (v8::Persistent value, void *data) - { - IOWatcher *io = static_cast(data); - assert(value == io->handle_); - if (!ev_is_active(&io->watcher_)) { - value.Dispose(); - delete io; - } else { - //value.ClearWeak(); - io->MakeWeak(); - } - } - - static IOWatcher* Unwrap(v8::Handle handle) { - assert(!handle.IsEmpty()); - assert(handle->InternalFieldCount() > 0); - return static_cast(v8::Handle::Cast( - handle->GetInternalField(0))->Value()); - } - + void Start(); void Stop(); ev_io watcher_; - v8::Persistent handle_; }; } // namespace node From 824a2fd1c63dd027c394cb03a4bde4b10d05bd4f Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Tue, 26 Jan 2010 18:36:24 -0800 Subject: [PATCH 039/105] Clean up a few bugs in net.js --- lib/net.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/net.js b/lib/net.js index 3b02993cef7..955c482dc0d 100644 --- a/lib/net.js +++ b/lib/net.js @@ -31,7 +31,7 @@ var ENOENT = process.ENOENT; var END_OF_FILE = 0; function Socket (peerInfo) { - process.EventEmitter.call(); + process.EventEmitter.call(this); var self = this; @@ -131,9 +131,8 @@ exports.createConnection = function (port, host) { Socket.prototype._allocateNewRecvBuf = function () { var self = this; - var newBufferSize = 1024; // TODO make this adjustable from user API + var newBufferSize = 128; // TODO make this adjustable from user API - /* if (toRead) { // Is the extra system call even worth it? var bytesToRead = toRead(self.fd); @@ -148,7 +147,6 @@ Socket.prototype._allocateNewRecvBuf = function () { newBufferSize = 128; } } - */ self.recvBuffer = new process.Buffer(newBufferSize); self.recvBuffer.used = 0; @@ -247,7 +245,7 @@ Socket.prototype.send = function (data, encoding) { if (!self.writable) throw new Error('Socket is not writable'); - if (self._sendQueueLast == END_OF_FILE) { + if (self._sendQueueLast() == END_OF_FILE) { throw new Error('socket.close() called already; cannot write.'); } @@ -281,7 +279,7 @@ Socket.prototype.sendFD = function(socketToPass) { if (!self.writable) throw new Error('Socket is not writable'); - if (self._sendQueueLast == END_OF_FILE) { + if (self._sendQueueLast() == END_OF_FILE) { throw new Error('socket.close() called already; cannot write.'); } @@ -453,6 +451,7 @@ Socket.prototype.close = function () { function Server (listener) { + process.EventEmitter.call(this); var self = this; if (listener) { From a668d07484671c832b696c93f4895a8dfa23ea8a Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Tue, 26 Jan 2010 18:37:16 -0800 Subject: [PATCH 040/105] Clean up http_parser binding - add asserts --- src/node_http_parser.cc | 97 ++++++++++++++++++++++------------------- 1 file changed, 51 insertions(+), 46 deletions(-) diff --git a/src/node_http_parser.cc b/src/node_http_parser.cc index 5d133318ef0..9ec50053618 100644 --- a/src/node_http_parser.cc +++ b/src/node_http_parser.cc @@ -23,30 +23,12 @@ #include #include - /* Obtain a backtrace and print it to stdout. */ - void - print_trace (void) - { - void *array[10]; - size_t size; - char **strings; - size_t i; - - size = backtrace (array, 10); - strings = backtrace_symbols (array, size); - - printf ("Obtained %zd stack frames.\n", size); - - for (i = 0; i < size; i++) - printf ("%s\n", strings[i]); - - free (strings); - } - namespace node { using namespace v8; + static int deep = 0; + static Persistent on_message_begin_sym; static Persistent on_path_sym; static Persistent on_query_string_sym; @@ -92,7 +74,6 @@ static Persistent should_keep_alive_sym; Local cb_value = parser->handle_->Get(name##_sym); \ if (!cb_value->IsFunction()) return 0; \ Local cb = Local::Cast(cb_value); \ - \ Local ret = cb->Call(parser->handle_, 0, NULL); \ return ret.IsEmpty() ? -1 : 0; \ } @@ -149,21 +130,7 @@ 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; + Init(type); } ~Parser() { @@ -213,6 +180,7 @@ class Parser : public ObjectWrap { Local argv[1] = { message_info }; Local ret = cb->Call(parser->handle_, 1, argv); + return ret.IsEmpty() ? -1 : 0; } @@ -233,6 +201,7 @@ class Parser : public ObjectWrap { } parser->Wrap(args.This()); + assert(!parser->buffer_); return args.This(); } @@ -243,8 +212,8 @@ class Parser : public ObjectWrap { Parser *parser = ObjectWrap::Unwrap(args.This()); + assert(!parser->buffer_); if (parser->buffer_) { - print_trace(); return ThrowException(Exception::TypeError( String::New("Already parsing a buffer"))); } @@ -273,13 +242,9 @@ class Parser : public ObjectWrap { // Assign 'buffer_' while we parse. The callbacks will access that varible. parser->buffer_ = buffer; - buffer_ref(parser->buffer_); - size_t nparsed = http_parser_execute(&(parser->parser_), buffer_p(buffer, off), len); - buffer_unref(parser->buffer_); - // Unassign the 'buffer_' variable assert(parser->buffer_); parser->buffer_ = NULL; @@ -297,25 +262,64 @@ class Parser : public ObjectWrap { return ThrowException(e); } + assert(!parser->buffer_); return scope.Close(nparsed_obj); } - static Handle ExecuteEOF(const Arguments& args) { + static Handle Finish(const Arguments& args) { HandleScope scope; Parser *parser = ObjectWrap::Unwrap(args.This()); assert(!parser->buffer_); - http_parser_execute(&parser->parser_, NULL, 0); + parser->Ref(); + http_parser_execute(&(parser->parser_), NULL, 0); + parser->Unref(); + + return Undefined(); + } + + static Handle Reinitialize(const Arguments& args) { + HandleScope scope; + Parser *parser = ObjectWrap::Unwrap(args.This()); + + String::Utf8Value type(args[0]->ToString()); + + if (0 == strcasecmp(*type, "request")) { + parser->Init(HTTP_REQUEST); + } else if (0 == strcasecmp(*type, "response")) { + parser->Init(HTTP_RESPONSE); + } else { + return ThrowException(Exception::Error( + String::New("Argument be 'request' or 'response'"))); + } return Undefined(); } private: - http_parser parser_; + void Init (enum http_parser_type type) { + assert(buffer_ == NULL); // don't call this during Execute() + 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; + } + struct buffer * buffer_; // The buffer currently being parsed. + http_parser parser_; }; @@ -324,10 +328,11 @@ void InitHttpParser(Handle target) { Local t = FunctionTemplate::New(Parser::New); t->InstanceTemplate()->SetInternalFieldCount(1); - //t->SetClassName(String::NewSymbol("HTTPParser")); + t->SetClassName(String::NewSymbol("HTTPParser")); NODE_SET_PROTOTYPE_METHOD(t, "execute", Parser::Execute); - NODE_SET_PROTOTYPE_METHOD(t, "executeEOF", Parser::ExecuteEOF); + NODE_SET_PROTOTYPE_METHOD(t, "finish", Parser::Finish); + NODE_SET_PROTOTYPE_METHOD(t, "reinitialize", Parser::Reinitialize); target->Set(String::NewSymbol("HTTPParser"), t->GetFunction()); From bf803f478bfdee522adf42b5c1207deb75cdb7dc Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Wed, 27 Jan 2010 15:40:09 -0800 Subject: [PATCH 041/105] Reimplment Buffers --- src/node.cc | 2 +- src/node_buffer.cc | 405 ++++++++++++++++-------------------- src/node_buffer.h | 76 +++---- src/node_http_parser.cc | 72 +++---- src/node_io_watcher.cc | 51 ++--- src/node_net2.cc | 38 ++-- src/node_object_wrap.h | 5 +- test/mjsunit/test-buffer.js | 23 +- wscript | 2 +- 9 files changed, 308 insertions(+), 366 deletions(-) diff --git a/src/node.cc b/src/node.cc index 6c4fd51fddc..f7785ff7749 100644 --- a/src/node.cc +++ b/src/node.cc @@ -989,7 +989,7 @@ static Local Load(int argc, char *argv[]) { // Initialize the C++ modules..................filename of module - InitBuffer(process); // buffer.cc + Buffer::Initialize(process); // buffer.cc IOWatcher::Initialize(process); // io_watcher.cc IdleWatcher::Initialize(process); // idle_watcher.cc Timer::Initialize(process); // timer.cc diff --git a/src/node_buffer.cc b/src/node_buffer.cc index ee9ce8406ff..7ebfc089f34 100644 --- a/src/node_buffer.cc +++ b/src/node_buffer.cc @@ -10,254 +10,198 @@ namespace node { using namespace v8; -#define SLICE_ARGS(start_arg, end_arg) \ - if (!start_arg->IsInt32() || !end_arg->IsInt32()) { \ - return ThrowException(Exception::TypeError( \ - String::New("Bad argument."))); \ - } \ - int32_t start = start_arg->Int32Value(); \ - int32_t end = end_arg->Int32Value(); \ - if (start < 0 || end < 0) { \ - return ThrowException(Exception::TypeError( \ - String::New("Bad argument."))); \ - } +#define SLICE_ARGS(start_arg, end_arg) \ + if (!start_arg->IsInt32() || !end_arg->IsInt32()) { \ + return ThrowException(Exception::TypeError( \ + String::New("Bad argument."))); \ + } \ + int32_t start = start_arg->Int32Value(); \ + int32_t end = end_arg->Int32Value(); \ + if (start < 0 || end < 0) { \ + return ThrowException(Exception::TypeError( \ + String::New("Bad argument."))); \ + } \ + if (!(start <= end)) { \ + return ThrowException(Exception::Error( \ + String::New("Must have start <= end"))); \ + } \ + if ((size_t)end > parent->length_) { \ + return ThrowException(Exception::Error( \ + String::New("end cannot be longer than parent.length"))); \ + } + static Persistent length_symbol; -static Persistent constructor_template; - -bool IsBuffer(v8::Handle val) { - if (!val->IsObject()) return false; - Local obj = val->ToObject(); - return constructor_template->HasInstance(obj); -} +Persistent Buffer::constructor_template; -/* Determines the absolute position for a relative offset */ -size_t buffer_abs_off(buffer *buffer, size_t off) { - struct buffer *root = buffer_root(buffer); - off += buffer->offset; - return MIN(root->length, off); -} - - -void buffer_ref(struct buffer *buffer) { - buffer_root(buffer)->refs++; -} - - -struct buffer* BufferUnwrap(v8::Handle val) { - assert(val->IsObject()); - HandleScope scope; - Local obj = val->ToObject(); - assert(obj->InternalFieldCount() == 1); - Local ext = Local::Cast(obj->GetInternalField(0)); - return static_cast(ext->Value()); -} - - -static void RootWeakCallback(Persistent value, void *data) -{ - struct buffer *buffer = static_cast(data); - assert(buffer->root == NULL); // this is the root - assert(value == buffer->handle); - value.ClearWeak(); - if (buffer->refs) { - buffer->weak = true; - } else { - buffer->handle.Dispose(); - free(buffer); - } -} - - -void buffer_unref(struct buffer *buffer) { - struct buffer * root = buffer_root(buffer); - assert(root->refs > 0); - root->refs--; - if (root->refs == 0 && root->weak) { - root->handle.MakeWeak(root, RootWeakCallback); - } -} - - -static void SliceWeakCallback(Persistent value, void *data) -{ - struct buffer *buffer = static_cast(data); - assert(buffer->root != NULL); // this is a slice - assert(value == buffer->handle); - buffer->handle.Dispose(); - buffer_unref(buffer->root); -} - - -static Handle Constructor(const Arguments &args) { - HandleScope scope; - +// Each javascript Buffer object is backed by a Blob object. +// the Blob is just a C-level chunk of bytes. +// It has a reference count. +struct Blob_ { + unsigned int refs; size_t length; - struct buffer *buffer; + char data[1]; +}; +typedef struct Blob_ Blob; - if (constructor_template->HasInstance(args[0])) { - // slice slice - SLICE_ARGS(args[1], args[2]) - struct buffer *parent = BufferUnwrap(args[0]); +static inline Blob * blob_new(size_t length) { + size_t s = sizeof(Blob) - 1 + length; + Blob * blob = (Blob*) malloc(s); + if (!blob) return NULL; + V8::AdjustAmountOfExternalAllocatedMemory(s); + blob->length = length; + blob->refs = 0; + //fprintf(stderr, "alloc %d bytes\n", length); + return blob; +} - size_t start_abs = buffer_abs_off(parent, start); - size_t end_abs = buffer_abs_off(parent, end); - assert(start_abs <= end_abs); - length = end_abs - start_abs; - void *d = malloc(sizeof(struct buffer)); +static inline void blob_ref(Blob *blob) { + blob->refs++; +} - if (!d) { - V8::LowMemoryNotification(); - return ThrowException(Exception::Error( - String::New("Could not allocate enough memory"))); - } +static inline void blob_unref(Blob *blob) { + assert(blob->refs > 0); + if (--blob->refs == 0) { + //fprintf(stderr, "free %d bytes\n", blob->length); + size_t s = sizeof(Blob) - 1 + blob->length; + V8::AdjustAmountOfExternalAllocatedMemory(-s); + free(blob); + } +} - buffer = static_cast(d); - buffer->length = length; - buffer->offset = start_abs; - buffer->weak = false; - buffer->refs = 0; - buffer->root = buffer_root(parent); - buffer->handle = Persistent::New(args.This()); - buffer->handle.MakeWeak(buffer, SliceWeakCallback); +// When someone calls buffer.asciiSlice, data is not copied. Instead V8 +// references in the underlying Blob with this ExternalAsciiStringResource. +class AsciiSliceExt: public String::ExternalAsciiStringResource { + friend class Buffer; + public: + AsciiSliceExt(Buffer *parent, size_t start, size_t end) { + blob_ = parent->blob(); + blob_ref(blob_); - buffer_ref(buffer->root); - } else { - // Root slice - - length = args[0]->Uint32Value(); - - if (length < 1) { - return ThrowException(Exception::TypeError( - String::New("Bad argument. Length must be positive"))); - } - - // TODO alignment. modify the length? - void *d = malloc(sizeof(struct buffer) + length - 1); - - if (!d) { - V8::LowMemoryNotification(); - return ThrowException(Exception::Error( - String::New("Could not allocate enough memory"))); - } - - buffer = static_cast(d); - - buffer->offset = 0; - buffer->length = length; - buffer->weak = false; - buffer->refs = 0; - buffer->root = NULL; - buffer->handle = Persistent::New(args.This()); - buffer->handle.MakeWeak(buffer, RootWeakCallback); + assert(start <= end); + length_ = end - start; + assert(length_ <= parent->length()); + data_ = parent->data() + start; } - args.This()->SetInternalField(0, v8::External::New(buffer)); - struct buffer *root = buffer_root(buffer); + ~AsciiSliceExt() { + //fprintf(stderr, "free ascii slice (%d refs left)\n", blob_->refs); + blob_unref(blob_); + } - args.This()-> - SetIndexedPropertiesToExternalArrayData(&root->bytes + buffer->offset, - kExternalUnsignedByteArray, - length); - args.This()->Set(length_symbol, Integer::New(length)); + const char* data() const { return data_; } + size_t length() const { return length_; } + private: + const char *data_; + size_t length_; + Blob *blob_; +}; + + +Handle Buffer::New(const Arguments &args) { + HandleScope scope; + + Buffer *buffer; + if (args[0]->IsInt32()) { + // var buffer = new Buffer(1024); + size_t length = args[0]->Uint32Value(); + buffer = new Buffer(length); + + } else if (Buffer::HasInstance(args[0]) && args.Length() > 2) { + // var slice = new Buffer(buffer, 123, 130); + // args: parent, start, end + Buffer *parent = ObjectWrap::Unwrap(args[0]->ToObject()); + SLICE_ARGS(args[1], args[2]) + buffer = new Buffer(parent, start, end); + } else { + return ThrowException(Exception::TypeError(String::New("Bad argument"))); + } + + buffer->Wrap(args.This()); + args.This()->SetIndexedPropertiesToExternalArrayData((void*)buffer->data_, + kExternalUnsignedByteArray, + buffer->length_); + args.This()->Set(length_symbol, Integer::New(buffer->length_)); return args.This(); } -class AsciiSliceExt: public String::ExternalAsciiStringResource { - public: +Buffer::Buffer(size_t length) : ObjectWrap() { + blob_ = blob_new(length); + length_ = length; + data_ = blob_->data; + blob_ref(blob_); - AsciiSliceExt(struct buffer *root, size_t start, size_t end) - { - data_ = root->bytes + start; - len_ = end - start; - root_ = root; - buffer_ref(root_); - } + V8::AdjustAmountOfExternalAllocatedMemory(sizeof(Buffer)); +} - ~AsciiSliceExt() { - buffer_unref(root_); - } - const char* data() const { - return data_; - } +Buffer::Buffer(Buffer *parent, size_t start, size_t end) : ObjectWrap() { + blob_ = parent->blob_; + assert(blob_->refs > 0); + blob_ref(blob_); - size_t length() const { - return len_; - } + assert(start <= end); + length_ = end - start; + assert(length_ <= parent->length_); + data_ = parent->data_ + start; - private: - const char *data_; - size_t len_; - struct buffer *root_; -}; + V8::AdjustAmountOfExternalAllocatedMemory(sizeof(Buffer)); +} -static Handle AsciiSlice(const Arguments &args) { + +Buffer::~Buffer() { + assert(blob_->refs > 0); + //fprintf(stderr, "free buffer (%d refs left)\n", blob_->refs); + blob_unref(blob_); + V8::AdjustAmountOfExternalAllocatedMemory(-sizeof(Buffer)); +} + + +Handle Buffer::AsciiSlice(const Arguments &args) { HandleScope scope; - + Buffer *parent = ObjectWrap::Unwrap(args.This()); SLICE_ARGS(args[0], args[1]) - - assert(args.This()->InternalFieldCount() == 1); - struct buffer *parent = BufferUnwrap(args.This()); - - size_t start_abs = buffer_abs_off(parent, start); - size_t end_abs = buffer_abs_off(parent, end); - - assert(start_abs <= end_abs); - - AsciiSliceExt *s = new AsciiSliceExt(buffer_root(parent), start_abs, end_abs); - Local string = String::NewExternal(s); - - struct buffer *root = buffer_root(parent); - assert(root->refs > 0); - + AsciiSliceExt *ext = new AsciiSliceExt(parent, start, end); + Local string = String::NewExternal(ext); + // There should be at least two references to the blob now - the parent + // and the slice. + assert(parent->blob_->refs >= 2); return scope.Close(string); } -static Handle Utf8Slice(const Arguments &args) { + +Handle Buffer::Utf8Slice(const Arguments &args) { HandleScope scope; - + Buffer *parent = ObjectWrap::Unwrap(args.This()); SLICE_ARGS(args[0], args[1]) - - struct buffer *parent = BufferUnwrap(args.This()); - size_t start_abs = buffer_abs_off(parent, start); - size_t end_abs = buffer_abs_off(parent, end); - assert(start_abs <= end_abs); - - struct buffer *root = buffer_root(parent); - - Local string = - String::New(reinterpret_cast(&root->bytes + start_abs), - end_abs - start_abs); + const char *data = reinterpret_cast(parent->data_ + start); + Local string = String::New(data, end - start); return scope.Close(string); } -static Handle Slice(const Arguments &args) { - HandleScope scope; +Handle Buffer::Slice(const Arguments &args) { + HandleScope scope; Local argv[3] = { args.This(), args[0], args[1] }; - Local slice = constructor_template->GetFunction()->NewInstance(3, argv); - return scope.Close(slice); } -// var charsWritten = buffer.utf8Write(string, offset, length); -static Handle Utf8Write(const Arguments &args) { +// var charsWritten = buffer.utf8Write(string, offset); +Handle Buffer::Utf8Write(const Arguments &args) { HandleScope scope; - - struct buffer *buffer = BufferUnwrap(args.This()); + Buffer *buffer = ObjectWrap::Unwrap(args.This()); if (!args[0]->IsString()) { return ThrowException(Exception::TypeError(String::New( @@ -268,30 +212,28 @@ static Handle Utf8Write(const Arguments &args) { size_t offset = args[1]->Int32Value(); - char *p = buffer_p(buffer, offset); - if (buffer_p(buffer, offset) == NULL) { + if (offset >= buffer->length_) { return ThrowException(Exception::TypeError(String::New( "Offset is out of bounds"))); } - size_t toWrite = args[2]->Int32Value(); + const char *p = buffer->data_ + offset; - if (buffer_remaining(buffer, offset) < toWrite) { + if (s->Length() + offset > buffer->length_) { return ThrowException(Exception::TypeError(String::New( - "Length is out of bounds"))); + "Not enough space in Buffer for string"))); } - int written = s->WriteUtf8(p, toWrite); - + int written = s->WriteUtf8((char*)p); return scope.Close(Integer::New(written)); } -// var charsWritten = buffer.asciiWrite(string, offset, length); -static Handle AsciiWrite(const Arguments &args) { +// var charsWritten = buffer.asciiWrite(string, offset); +Handle Buffer::AsciiWrite(const Arguments &args) { HandleScope scope; - struct buffer *buffer = BufferUnwrap(args.This()); + Buffer *buffer = ObjectWrap::Unwrap(args.This()); if (!args[0]->IsString()) { return ThrowException(Exception::TypeError(String::New( @@ -302,30 +244,25 @@ static Handle AsciiWrite(const Arguments &args) { size_t offset = args[1]->Int32Value(); - char *p = buffer_p(buffer, offset); - if (buffer_p(buffer, offset) == NULL) { + if (offset >= buffer->length_) { return ThrowException(Exception::TypeError(String::New( "Offset is out of bounds"))); } - size_t toWrite = args[2]->Int32Value(); + const char *p = buffer->data_ + offset; - if (buffer_remaining(buffer, offset) < toWrite) { + if (s->Length() + offset > buffer->length_) { return ThrowException(Exception::TypeError(String::New( - "Length is out of bounds"))); + "Not enough space in Buffer for string"))); } - // TODO Expose the second argument of WriteAscii? - // Could avoid doing slices when the string doesn't fit in a buffer. V8 - // slice() does copy the string, so exposing that argument would help. - - int written = s->WriteAscii(p, 0, toWrite); - + int written = s->WriteAscii((char*)p); return scope.Close(Integer::New(written)); } -static Handle Utf8Length(const Arguments &args) { +// var nbytes = Buffer.utf8Length("string") +Handle Buffer::Utf8Length(const Arguments &args) { HandleScope scope; if (!args[0]->IsString()) { return ThrowException(Exception::TypeError(String::New( @@ -336,29 +273,37 @@ static Handle Utf8Length(const Arguments &args) { } -void InitBuffer(Handle target) { +bool Buffer::HasInstance(Handle val) { + if (!val->IsObject()) return false; + Local obj = val->ToObject(); + return constructor_template->HasInstance(obj); +} + + +void Buffer::Initialize(Handle target) { HandleScope scope; length_symbol = Persistent::New(String::NewSymbol("length")); - Local t = FunctionTemplate::New(Constructor); + Local t = FunctionTemplate::New(Buffer::New); constructor_template = Persistent::New(t); constructor_template->InstanceTemplate()->SetInternalFieldCount(1); constructor_template->SetClassName(String::NewSymbol("Buffer")); // copy free - NODE_SET_PROTOTYPE_METHOD(constructor_template, "asciiSlice", AsciiSlice); - NODE_SET_PROTOTYPE_METHOD(constructor_template, "slice", Slice); + NODE_SET_PROTOTYPE_METHOD(constructor_template, "asciiSlice", Buffer::AsciiSlice); + NODE_SET_PROTOTYPE_METHOD(constructor_template, "slice", Buffer::Slice); // TODO NODE_SET_PROTOTYPE_METHOD(t, "utf16Slice", Utf16Slice); - // copy - NODE_SET_PROTOTYPE_METHOD(constructor_template, "utf8Slice", Utf8Slice); + // copy + NODE_SET_PROTOTYPE_METHOD(constructor_template, "utf8Slice", Buffer::Utf8Slice); - NODE_SET_PROTOTYPE_METHOD(constructor_template, "utf8Write", Utf8Write); - NODE_SET_PROTOTYPE_METHOD(constructor_template, "asciiWrite", AsciiWrite); + NODE_SET_PROTOTYPE_METHOD(constructor_template, "utf8Write", Buffer::Utf8Write); + NODE_SET_PROTOTYPE_METHOD(constructor_template, "asciiWrite", Buffer::AsciiWrite); - NODE_SET_METHOD(constructor_template->GetFunction(), "utf8Length", Utf8Length); + NODE_SET_METHOD(constructor_template->GetFunction(), "utf8Length", Buffer::Utf8Length); target->Set(String::NewSymbol("Buffer"), constructor_template->GetFunction()); } + } // namespace node diff --git a/src/node_buffer.h b/src/node_buffer.h index 122d589d85a..d6b83fe00b6 100644 --- a/src/node_buffer.h +++ b/src/node_buffer.h @@ -1,19 +1,19 @@ -#ifndef NODE_BUFFER -#define NODE_BUFFER +#ifndef NODE_BUFFER_H_ +#define NODE_BUFFER_H_ +#include +#include #include namespace node { -#define MIN(a,b) ((a) < (b) ? (a) : (b)) - /* A buffer is a chunk of memory stored outside the V8 heap, mirrored by an * object in javascript. The object is not totally opaque, one can access * individual bytes with [] and slice it into substrings or sub-buffers * without copying memory. * * // return an ascii encoded string - no memory iscopied - * buffer.asciiSlide(0, 3) + * buffer.asciiSlide(0, 3) * * // returns another buffer - no memory is copied * buffer.slice(0, 3) @@ -25,40 +25,42 @@ namespace node { * are GCed. */ -struct buffer { - v8::Persistent handle; // both - bool weak; // both - struct buffer *root; // both (NULL for root) - size_t offset; // both (0 for root) - size_t length; // both - unsigned int refs; // root only - char bytes[1]; // root only + +struct Blob_; + +class Buffer : public ObjectWrap { + public: + static void Initialize(v8::Handle target); + static bool HasInstance(v8::Handle val); + + const char* data() const { return data_; } + size_t length() const { return length_; } + struct Blob_* blob() const { return blob_; } + + protected: + static v8::Persistent constructor_template; + static v8::Handle New(const v8::Arguments &args); + static v8::Handle Slice(const v8::Arguments &args); + static v8::Handle AsciiSlice(const v8::Arguments &args); + static v8::Handle Utf8Slice(const v8::Arguments &args); + static v8::Handle AsciiWrite(const v8::Arguments &args); + static v8::Handle Utf8Write(const v8::Arguments &args); + static v8::Handle Utf8Length(const v8::Arguments &args); + + int AsciiWrite(char *string, int offset, int length); + int Utf8Write(char *string, int offset, int length); + + private: + Buffer(size_t length); + Buffer(Buffer *parent, size_t start, size_t end); + ~Buffer(); + + const char *data_; + size_t length_; + struct Blob_ *blob_; }; -void InitBuffer(v8::Handle target); - -struct buffer* BufferUnwrap(v8::Handle val); -bool IsBuffer(v8::Handle val); - -static inline struct buffer * buffer_root(struct buffer *buffer) { - return buffer->root ? buffer->root : buffer; -} - -static inline char * buffer_p(struct buffer *buffer, size_t off) { - struct buffer *root = buffer_root(buffer); - if (buffer->offset + off >= root->length) return NULL; - return reinterpret_cast(&(root->bytes) + buffer->offset + off); -} - -static inline size_t buffer_remaining(struct buffer *buffer, size_t off) { - struct buffer *root = buffer_root(buffer); - char *end = reinterpret_cast(&(root->bytes) + root->length); - return end - buffer_p(buffer, off); -} - -void buffer_ref(struct buffer *buffer); -void buffer_unref(struct buffer *buffer); } // namespace node buffer -#endif // NODE_BUFFER +#endif // NODE_BUFFER_H_ diff --git a/src/node_http_parser.cc b/src/node_http_parser.cc index 9ec50053618..2236b47359a 100644 --- a/src/node_http_parser.cc +++ b/src/node_http_parser.cc @@ -65,41 +65,35 @@ static Persistent version_minor_sym; static Persistent should_keep_alive_sym; // Callback prototype for http_cb -#define DEFINE_HTTP_CB(name) \ - static int name(http_parser *p) { \ - Parser *parser = static_cast(p->data); \ - \ - HandleScope scope; \ - \ - Local cb_value = parser->handle_->Get(name##_sym); \ - if (!cb_value->IsFunction()) return 0; \ - Local cb = Local::Cast(cb_value); \ - Local ret = cb->Call(parser->handle_, 0, NULL); \ - return ret.IsEmpty() ? -1 : 0; \ +#define DEFINE_HTTP_CB(name) \ + static int name(http_parser *p) { \ + Parser *parser = static_cast(p->data); \ + \ + HandleScope scope; \ + \ + Local cb_value = parser->handle_->Get(name##_sym); \ + if (!cb_value->IsFunction()) return 0; \ + Local cb = Local::Cast(cb_value); \ + Local 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(p->data); \ - \ - HandleScope scope; \ - \ - assert(parser->buffer_); \ - struct buffer * root = buffer_root(parser->buffer_); \ - char * base = buffer_p(root, 0); \ - \ - Local cb_value = parser->handle_->Get(name##_sym); \ - if (!cb_value->IsFunction()) return 0; \ - Local cb = Local::Cast(cb_value); \ - \ - Local argv[3] = { Local::New(root->handle) \ - , Integer::New(at - base) \ - , Integer::New(length) \ - }; \ - Local ret = cb->Call(parser->handle_, 3, argv); \ - assert(parser->buffer_); \ - return ret.IsEmpty() ? -1 : 0; \ +#define DEFINE_HTTP_DATA_CB(name) \ + static int name(http_parser *p, const char *at, size_t length) { \ + Parser *parser = static_cast(p->data); \ + HandleScope scope; \ + assert(parser->buffer_); \ + Local cb_value = parser->handle_->Get(name##_sym); \ + if (!cb_value->IsFunction()) return 0; \ + Local cb = Local::Cast(cb_value); \ + Local argv[3] = { Local::New(parser->buffer_->handle_) \ + , Integer::New(at - parser->buffer_->data()) \ + , Integer::New(length) \ + }; \ + Local ret = cb->Call(parser->handle_, 3, argv); \ + assert(parser->buffer_); \ + return ret.IsEmpty() ? -1 : 0; \ } @@ -218,21 +212,21 @@ class Parser : public ObjectWrap { String::New("Already parsing a buffer"))); } - if (!IsBuffer(args[0])) { + if (!Buffer::HasInstance(args[0])) { return ThrowException(Exception::TypeError( String::New("Argument should be a buffer"))); } - struct buffer * buffer = BufferUnwrap(args[0]); + Buffer * buffer = ObjectWrap::Unwrap(args[0]->ToObject()); size_t off = args[1]->Int32Value(); - if (buffer_p(buffer, off) == NULL) { + if (off >= buffer->length()) { return ThrowException(Exception::Error( String::New("Offset is out of bounds"))); } size_t len = args[2]->Int32Value(); - if (buffer_remaining(buffer, off) < len) { + if (off+len > buffer->length()) { return ThrowException(Exception::Error( String::New("Length is extends beyond buffer"))); } @@ -242,8 +236,8 @@ class Parser : public ObjectWrap { // 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); + size_t nparsed = + http_parser_execute(&parser->parser_, buffer->data()+off, len); // Unassign the 'buffer_' variable assert(parser->buffer_); @@ -318,7 +312,7 @@ class Parser : public ObjectWrap { parser_.data = this; } - struct buffer * buffer_; // The buffer currently being parsed. + Buffer * buffer_; // The buffer currently being parsed. http_parser parser_; }; diff --git a/src/node_io_watcher.cc b/src/node_io_watcher.cc index baf46411bc9..4a2d9378d93 100644 --- a/src/node_io_watcher.cc +++ b/src/node_io_watcher.cc @@ -11,7 +11,7 @@ namespace node { using namespace v8; Persistent IOWatcher::constructor_template; -static Persistent callback_symbol; +Persistent callback_symbol; void IOWatcher::Initialize(Handle target) { HandleScope scope; @@ -34,7 +34,6 @@ void IOWatcher::Initialize(Handle target) { void IOWatcher::Callback(EV_P_ ev_io *w, int revents) { IOWatcher *io = static_cast(w->data); assert(w == &io->watcher_); - assert(!(revents & EV_ERROR)); HandleScope scope; Local callback_v = io->handle_->Get(callback_symbol); @@ -51,9 +50,7 @@ void IOWatcher::Callback(EV_P_ ev_io *w, int revents) { argv[0] = Local::New(revents & EV_READ ? True() : False()); argv[1] = Local::New(revents & EV_WRITE ? True() : False()); - io->Ref(); callback->Call(io->handle_, 2, argv); - io->Unref(); if (try_catch.HasCaught()) { FatalException(try_catch); @@ -62,8 +59,9 @@ void IOWatcher::Callback(EV_P_ ev_io *w, int revents) { // -// var io = new process.IOWatcher(); -// io.callback = function (readable, writable) { ... }; +// var io = new process.IOWatcher(function (readable, writable) { +// +// }); // io.set(fd, true, false); // io.start(); // @@ -71,24 +69,34 @@ Handle IOWatcher::New(const Arguments& args) { HandleScope scope; IOWatcher *s = new IOWatcher(); - - s->Wrap(args.This()); return args.This(); } +Handle IOWatcher::Start(const Arguments& args) { + HandleScope scope; + + IOWatcher *io = ObjectWrap::Unwrap(args.Holder()); + + ev_io_start(EV_DEFAULT_UC_ &io->watcher_); + + io->Ref(); + + return Undefined(); +} + Handle IOWatcher::Set(const Arguments& args) { HandleScope scope; + IOWatcher *io = ObjectWrap::Unwrap(args.Holder()); + if (!args[0]->IsInt32()) { return ThrowException(Exception::TypeError( String::New("First arg should be a file descriptor."))); } - IOWatcher *io = ObjectWrap::Unwrap(args.This()); - int fd = args[0]->Int32Value(); if (!args[1]->IsBoolean()) { @@ -112,38 +120,19 @@ Handle IOWatcher::Set(const Arguments& args) { return Undefined(); } - -Handle IOWatcher::Start(const Arguments& args) { - HandleScope scope; - IOWatcher *io = ObjectWrap::Unwrap(args.This()); - io->Start(); - return Undefined(); -} - - Handle IOWatcher::Stop(const Arguments& args) { HandleScope scope; - IOWatcher *io = ObjectWrap::Unwrap(args.This()); + IOWatcher *io = ObjectWrap::Unwrap(args.Holder()); io->Stop(); return Undefined(); } -void IOWatcher::Start () { - if (!ev_is_active(&watcher_)) { - ev_io_start(EV_DEFAULT_UC_ &watcher_); - Ref(); - } - assert(ev_is_active(&watcher_)); -} - - void IOWatcher::Stop () { - if (ev_is_active(&watcher_)) { + if (watcher_.active) { ev_io_stop(EV_DEFAULT_UC_ &watcher_); Unref(); } - assert(!ev_is_active(&watcher_)); } diff --git a/src/node_net2.cc b/src/node_net2.cc index 47e8da3cf0b..241469873e7 100644 --- a/src/node_net2.cc +++ b/src/node_net2.cc @@ -488,28 +488,26 @@ static Handle Read(const Arguments& args) { FD_ARG(args[0]) - if (!IsBuffer(args[1])) { + if (!Buffer::HasInstance(args[1])) { return ThrowException(Exception::TypeError( String::New("Second argument should be a buffer"))); } - struct buffer * buffer = BufferUnwrap(args[1]); + Buffer * buffer = ObjectWrap::Unwrap(args[1]->ToObject()); size_t off = args[2]->Int32Value(); - if (buffer_p(buffer, off) == NULL) { + if (off >= buffer->length()) { return ThrowException(Exception::Error( String::New("Offset is out of bounds"))); } size_t len = args[3]->Int32Value(); - if (buffer_remaining(buffer, off) < len) { + if (off + len > buffer->length()) { return ThrowException(Exception::Error( String::New("Length is extends beyond buffer"))); } - ssize_t bytes_read = read(fd, - buffer_p(buffer, off), - buffer_remaining(buffer, off)); + ssize_t bytes_read = read(fd, (char*)buffer->data() + off, len); if (bytes_read < 0) { if (errno == EAGAIN || errno == EINTR) return Null(); @@ -533,21 +531,21 @@ static Handle RecvMsg(const Arguments& args) { FD_ARG(args[0]) - if (!IsBuffer(args[1])) { + if (!Buffer::HasInstance(args[1])) { return ThrowException(Exception::TypeError( String::New("Second argument should be a buffer"))); } - struct buffer * buffer = BufferUnwrap(args[1]); + Buffer * buffer = ObjectWrap::Unwrap(args[1]->ToObject()); size_t off = args[2]->Int32Value(); - if (buffer_p(buffer, off) == NULL) { + if (off >= buffer->length()) { return ThrowException(Exception::Error( String::New("Offset is out of bounds"))); } size_t len = args[3]->Int32Value(); - if (buffer_remaining(buffer, off) < len) { + if (off + len > buffer->length()) { return ThrowException(Exception::Error( String::New("Length is extends beyond buffer"))); } @@ -555,7 +553,7 @@ static Handle RecvMsg(const Arguments& args) { int received_fd; struct iovec iov[1]; - iov[0].iov_base = buffer_p(buffer, off); + iov[0].iov_base = (char*)buffer->data() + off; iov[0].iov_len = len; struct msghdr msg; @@ -606,28 +604,26 @@ static Handle Write(const Arguments& args) { FD_ARG(args[0]) - if (!IsBuffer(args[1])) { + if (!Buffer::HasInstance(args[1])) { return ThrowException(Exception::TypeError( String::New("Second argument should be a buffer"))); } - struct buffer * buffer = BufferUnwrap(args[1]); + Buffer * buffer = ObjectWrap::Unwrap(args[1]->ToObject()); size_t off = args[2]->Int32Value(); - char *p = buffer_p(buffer, off); - if (p == NULL) { + if (off >= buffer->length()) { return ThrowException(Exception::Error( String::New("Offset is out of bounds"))); } size_t len = args[3]->Int32Value(); - size_t remaining = buffer_remaining(buffer, off); - if (remaining < len) { + if (off + len > buffer->length()) { return ThrowException(Exception::Error( String::New("Length is extends beyond buffer"))); } - ssize_t written = write(fd, p, len); + ssize_t written = write(fd, (char*)buffer->data() + off, len); if (written < 0) { if (errno == EAGAIN || errno == EINTR) return Null(); @@ -662,9 +658,9 @@ static Handle SendFD(const Arguments& args) { struct iovec iov[1]; char control_msg[CMSG_SPACE(sizeof(fd_to_send))]; struct cmsghdr *cmsg; - char *dummy = "d"; // Need to send at least a byte of data in the message + static char dummy = 'd'; // Need to send at least a byte of data in the message - iov[0].iov_base = dummy; + iov[0].iov_base = &dummy; iov[0].iov_len = 1; msg.msg_iov = iov; msg.msg_iovlen = 1; diff --git a/src/node_object_wrap.h b/src/node_object_wrap.h index 6656664bdfd..1d3fafa48bd 100644 --- a/src/node_object_wrap.h +++ b/src/node_object_wrap.h @@ -21,7 +21,8 @@ class ObjectWrap { } } - protected: + v8::Persistent handle_; // ro + template static inline T* Unwrap (v8::Handle handle) { @@ -31,6 +32,7 @@ class ObjectWrap { handle->GetInternalField(0))->Value()); } + protected: inline void Wrap (v8::Handle handle) { assert(handle_.IsEmpty()); @@ -72,7 +74,6 @@ class ObjectWrap { if (refs_ == 0 && handle_.IsNearDeath()) delete this; } - v8::Persistent handle_; // ro int refs_; // ro private: diff --git a/test/mjsunit/test-buffer.js b/test/mjsunit/test-buffer.js index cfeafa1fce0..ebdbf673790 100644 --- a/test/mjsunit/test-buffer.js +++ b/test/mjsunit/test-buffer.js @@ -16,16 +16,28 @@ for (var i = 0; i < 1024; i++) { assert.equal(i % 256, b[i]); } -for (var j = 0; j < 10000; j++) { - var asciiString = "hello world"; +var asciiString = "hello world"; +var offset = 100; +for (var j = 0; j < 50000; j++) { for (var i = 0; i < asciiString.length; i++) { b[i] = asciiString.charCodeAt(i); } - var asciiSlice = b.asciiSlice(0, asciiString.length); - assert.equal(asciiString, asciiSlice); + + var written = b.asciiWrite(asciiString, offset); + assert.equal(asciiString.length, written); + var asciiSlice = b.asciiSlice(offset, offset+asciiString.length); + assert.equal(asciiString, asciiSlice); + + var sliceA = b.slice(offset, offset+asciiString.length); + var sliceB = b.slice(offset, offset+asciiString.length); + for (var i = 0; i < asciiString.length; i++) { + assert.equal(sliceA[i], sliceB[i]); + } + + // TODO utf8 slice tests } @@ -36,3 +48,6 @@ for (var j = 0; j < 10000; j++) { assert.equal(b[100+i], slice[i]); } } + + + diff --git a/wscript b/wscript index bd2425035c0..c077d8aff8f 100644 --- a/wscript +++ b/wscript @@ -345,8 +345,8 @@ def build(bld): node.source = """ src/node.cc src/node_buffer.cc - src/node_net2.cc src/node_http_parser.cc + src/node_net2.cc src/node_io_watcher.cc src/node_child_process.cc src/node_constants.cc From e82893d3f26e2ecb21a4108ada57f4170d382156 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Wed, 27 Jan 2010 16:07:15 -0800 Subject: [PATCH 042/105] Clean up IOWatcher --- src/node_io_watcher.cc | 56 +++++++++++++++++++++++------------------- src/node_io_watcher.h | 2 +- 2 files changed, 32 insertions(+), 26 deletions(-) diff --git a/src/node_io_watcher.cc b/src/node_io_watcher.cc index 4a2d9378d93..9b5c3b3ff6c 100644 --- a/src/node_io_watcher.cc +++ b/src/node_io_watcher.cc @@ -13,6 +13,7 @@ using namespace v8; Persistent IOWatcher::constructor_template; Persistent callback_symbol; + void IOWatcher::Initialize(Handle target) { HandleScope scope; @@ -50,7 +51,9 @@ void IOWatcher::Callback(EV_P_ ev_io *w, int revents) { argv[0] = Local::New(revents & EV_READ ? True() : False()); argv[1] = Local::New(revents & EV_WRITE ? True() : False()); + io->Ref(); callback->Call(io->handle_, 2, argv); + io->Unref(); if (try_catch.HasCaught()) { FatalException(try_catch); @@ -59,34 +62,51 @@ void IOWatcher::Callback(EV_P_ ev_io *w, int revents) { // -// var io = new process.IOWatcher(function (readable, writable) { -// -// }); +// var io = new process.IOWatcher(); +// process.callback = function (readable, writable) { ... }; // io.set(fd, true, false); // io.start(); // Handle IOWatcher::New(const Arguments& args) { HandleScope scope; - IOWatcher *s = new IOWatcher(); s->Wrap(args.This()); - return args.This(); } Handle IOWatcher::Start(const Arguments& args) { HandleScope scope; - IOWatcher *io = ObjectWrap::Unwrap(args.Holder()); - - ev_io_start(EV_DEFAULT_UC_ &io->watcher_); - - io->Ref(); - + io->Start(); return Undefined(); } + +Handle IOWatcher::Stop(const Arguments& args) { + HandleScope scope; + IOWatcher *io = ObjectWrap::Unwrap(args.Holder()); + io->Stop(); + return Undefined(); +} + + +void IOWatcher::Start() { + if (!ev_is_active(&watcher_)) { + ev_io_start(EV_DEFAULT_UC_ &watcher_); + Ref(); + } +} + + +void IOWatcher::Stop() { + if (ev_is_active(&watcher_)) { + ev_io_stop(EV_DEFAULT_UC_ &watcher_); + Unref(); + } +} + + Handle IOWatcher::Set(const Arguments& args) { HandleScope scope; @@ -120,20 +140,6 @@ Handle IOWatcher::Set(const Arguments& args) { return Undefined(); } -Handle IOWatcher::Stop(const Arguments& args) { - HandleScope scope; - IOWatcher *io = ObjectWrap::Unwrap(args.Holder()); - io->Stop(); - return Undefined(); -} - - -void IOWatcher::Stop () { - if (watcher_.active) { - ev_io_stop(EV_DEFAULT_UC_ &watcher_); - Unref(); - } -} } // namespace node diff --git a/src/node_io_watcher.h b/src/node_io_watcher.h index 95a135e7660..06d431ece94 100644 --- a/src/node_io_watcher.h +++ b/src/node_io_watcher.h @@ -20,7 +20,7 @@ class IOWatcher : ObjectWrap { } ~IOWatcher() { - Stop(); + ev_io_stop(EV_DEFAULT_UC_ &watcher_); assert(!ev_is_active(&watcher_)); assert(!ev_is_pending(&watcher_)); } From b93b09a877cf01a0cd2dc5350bbcc7150c2f94dd Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Wed, 27 Jan 2010 18:23:35 -0800 Subject: [PATCH 043/105] ObjectWrap fixed - buffers working! --- src/node_object_wrap.h | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/src/node_object_wrap.h b/src/node_object_wrap.h index 1d3fafa48bd..7e26f25b0dd 100644 --- a/src/node_object_wrap.h +++ b/src/node_object_wrap.h @@ -13,16 +13,12 @@ class ObjectWrap { } virtual ~ObjectWrap ( ) { - if (!handle_.IsEmpty()) { - assert(handle_.IsNearDeath()); - handle_->SetInternalField(0, v8::Undefined()); - handle_.Dispose(); - handle_.Clear(); - } + assert(handle_.IsNearDeath()); + handle_->SetInternalField(0, v8::Undefined()); + handle_.Dispose(); + handle_.Clear(); } - v8::Persistent handle_; // ro - template static inline T* Unwrap (v8::Handle handle) { @@ -32,6 +28,8 @@ class ObjectWrap { handle->GetInternalField(0))->Value()); } + v8::Persistent handle_; // ro + protected: inline void Wrap (v8::Handle handle) { @@ -53,8 +51,8 @@ class ObjectWrap { */ virtual void Ref() { assert(!handle_.IsEmpty()); - assert(handle_.IsWeak()); refs_++; + handle_.ClearWeak(); } /* Unref() marks an object as detached from the event loop. This is its @@ -68,10 +66,9 @@ class ObjectWrap { */ virtual void Unref() { assert(!handle_.IsEmpty()); - assert(handle_.IsWeak()); + assert(!handle_.IsWeak()); assert(refs_ > 0); - refs_--; - if (refs_ == 0 && handle_.IsNearDeath()) delete this; + if (--refs_ == 0) { MakeWeak(); } } int refs_; // ro @@ -81,11 +78,8 @@ class ObjectWrap { { ObjectWrap *obj = static_cast(data); assert(value == obj->handle_); - if (obj->refs_ == 0) { - delete obj; - } else { - obj->MakeWeak(); - } + assert(!obj->refs_); + if (value.IsNearDeath()) delete obj; } }; From 4347906b7fa916a5e3ad9e53ab1801aba47d8b89 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Wed, 27 Jan 2010 18:45:07 -0800 Subject: [PATCH 044/105] Fix require() in test-buffer.js --- test/mjsunit/test-buffer.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/mjsunit/test-buffer.js b/test/mjsunit/test-buffer.js index ebdbf673790..50dba628712 100644 --- a/test/mjsunit/test-buffer.js +++ b/test/mjsunit/test-buffer.js @@ -1,10 +1,10 @@ -sys = require("sys"); +process.mixin(require("./common")); assert = require("assert"); var b = new process.Buffer(1024); -sys.puts("b.length == " + b.length); +puts("b.length == " + b.length); assert.equal(1024, b.length); for (var i = 0; i < 1024; i++) { From c328f3e6c61a1fc97a921eb57a54c9954fa7b1bf Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Thu, 28 Jan 2010 11:17:21 -0800 Subject: [PATCH 045/105] Add new http server library --- lib/http2.js | 425 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 425 insertions(+) create mode 100644 lib/http2.js diff --git a/lib/http2.js b/lib/http2.js new file mode 100644 index 00000000000..846a4c31f20 --- /dev/null +++ b/lib/http2.js @@ -0,0 +1,425 @@ +var sys = require('./sys'); +var net = require('./net'); +var events = require('events'); + +var CRLF = "\r\n"; +var STATUS_CODES = exports.STATUS_CODES = { + 100 : 'Continue', + 101 : 'Switching Protocols', + 200 : 'OK', + 201 : 'Created', + 202 : 'Accepted', + 203 : 'Non-Authoritative Information', + 204 : 'No Content', + 205 : 'Reset Content', + 206 : 'Partial Content', + 300 : 'Multiple Choices', + 301 : 'Moved Permanently', + 302 : 'Moved Temporarily', + 303 : 'See Other', + 304 : 'Not Modified', + 305 : 'Use Proxy', + 400 : 'Bad Request', + 401 : 'Unauthorized', + 402 : 'Payment Required', + 403 : 'Forbidden', + 404 : 'Not Found', + 405 : 'Method 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', + 500 : 'Internal Server Error', + 501 : 'Not Implemented', + 502 : 'Bad Gateway', + 503 : 'Service Unavailable', + 504 : 'Gateway Time-out', + 505 : 'HTTP Version not supported' +}; + +var connectionExpression = /Connection/i; +var transferEncodingExpression = /Transfer-Encoding/i; +var closeExpression = /close/i; +var chunkExpression = /chunk/i; +var contentLengthExpression = /Content-Length/i; + + +/* Abstract base class for ServerRequest and ClientResponse. */ +function IncomingMessage (socket) { + events.EventEmitter.call(this); + + this.socket = socket; + this.httpVersion = null; + this.headers = {}; + + this.method = null; + + // response (client) only + this.statusCode = null; +} +sys.inherits(IncomingMessage, events.EventEmitter); +exports.IncomingMessage = IncomingMessage; + +IncomingMessage.prototype._parseQueryString = function () { + throw new Error("_parseQueryString is deprecated. Use require(\"querystring\") to parse query strings.\n"); +}; + +IncomingMessage.prototype.setBodyEncoding = function (enc) { + // TODO: Find a cleaner way of doing this. + this.socket.setEncoding(enc); +}; + +IncomingMessage.prototype.pause = function () { + this.socket.readPause(); +}; + +IncomingMessage.prototype.resume = function () { + this.socket.readResume(); +}; + +IncomingMessage.prototype._addHeaderLine = function (field, value) { + if (field in this.headers) { + // TODO Certain headers like 'Content-Type' should not be concatinated. + // See https://www.google.com/reader/view/?tab=my#overview-page + this.headers[field] += ", " + value; + } else { + this.headers[field] = value; + } +}; + +function OutgoingMessage () { + events.EventEmitter.call(this); + + this.output = []; + this.outputEncodings = []; + + this.closeOnFinish = false; + this.chunkEncoding = false; + this.shouldKeepAlive = true; + this.useChunkedEncodingByDefault = true; + + this.flushing = false; + + this.finished = false; +} +sys.inherits(OutgoingMessage, events.EventEmitter); +exports.OutgoingMessage = OutgoingMessage; + +OutgoingMessage.prototype.send = function (data, encoding) { + var length = this.output.length; + + if (length === 0) { + this.output.push(data); + encoding = encoding || "ascii"; + this.outputEncodings.push(encoding); + return; + } + + var lastEncoding = this.outputEncodings[length-1]; + var lastData = this.output[length-1]; + + if ((lastEncoding === encoding) || + (!encoding && data.constructor === lastData.constructor)) { + if (lastData.constructor === String) { + this.output[length-1] = lastData + data; + } else { + this.output[length-1] = lastData.concat(data); + } + return; + } + + this.output.push(data); + encoding = encoding || "ascii"; + this.outputEncodings.push(encoding); +}; + +OutgoingMessage.prototype.sendHeaderLines = function (first_line, headers) { + var sentConnectionHeader = false; + var sendContentLengthHeader = false; + var sendTransferEncodingHeader = false; + + // first_line in the case of request is: "GET /index.html HTTP/1.1\r\n" + // in the case of response it is: "HTTP/1.1 200 OK\r\n" + var messageHeader = first_line; + var field, value; + for (var i in headers) { + if (headers[i] instanceof Array) { + field = headers[i][0]; + value = headers[i][1]; + } else { + if (!headers.hasOwnProperty(i)) continue; + field = i; + value = headers[i]; + } + + messageHeader += field + ": " + value + CRLF; + + if (connectionExpression.test(field)) { + sentConnectionHeader = true; + if (closeExpression.test(value)) this.closeOnFinish = true; + + } else if (transferEncodingExpression.test(field)) { + sendTransferEncodingHeader = true; + if (chunkExpression.test(value)) this.chunkEncoding = true; + + } else if (contentLengthExpression.test(field)) { + sendContentLengthHeader = true; + + } + } + + // keep-alive logic + if (sentConnectionHeader == false) { + if (this.shouldKeepAlive && + (sendContentLengthHeader || this.useChunkedEncodingByDefault)) { + messageHeader += "Connection: keep-alive\r\n"; + } else { + this.closeOnFinish = true; + messageHeader += "Connection: close\r\n"; + } + } + + if (sendContentLengthHeader == false && sendTransferEncodingHeader == false) { + if (this.useChunkedEncodingByDefault) { + messageHeader += "Transfer-Encoding: chunked\r\n"; + this.chunkEncoding = true; + } + else { + this.closeOnFinish = true; + } + } + + messageHeader += CRLF; + + this.send(messageHeader); + // wait until the first body chunk, or finish(), is sent to flush. +}; + +OutgoingMessage.prototype.sendBody = function (chunk, encoding) { + encoding = encoding || "ascii"; + if (this.chunkEncoding) { + this.send(process._byteLength(chunk, encoding).toString(16)); + this.send(CRLF); + this.send(chunk, encoding); + this.send(CRLF); + } else { + this.send(chunk, encoding); + } + + if (this.flushing) { + this.flush(); + } else { + this.flushing = true; + } +}; + +OutgoingMessage.prototype.flush = function () { + this.emit("flush"); +}; + +OutgoingMessage.prototype.finish = function () { + if (this.chunkEncoding) this.send("0\r\n\r\n"); // last chunk + this.finished = true; + this.flush(); +}; + + +function ServerResponse (req) { + OutgoingMessage.call(this); + + if (req.httpVersionMajor < 1 || req.httpVersionMinor < 1) { + this.useChunkedEncodingByDefault = false; + this.shouldKeepAlive = false; + } +} +sys.inherits(ServerResponse, OutgoingMessage); +exports.ServerResponse = ServerResponse; + +ServerResponse.prototype.sendHeader = function (statusCode, headers) { + var reason = STATUS_CODES[statusCode] || "unknown"; + var status_line = "HTTP/1.1 " + statusCode.toString() + " " + reason + CRLF; + this.sendHeaderLines(status_line, headers); +}; + + +function ClientRequest (method, url, headers) { + OutgoingMessage.call(this); + + this.shouldKeepAlive = false; + if (method === "GET" || method === "HEAD") { + this.useChunkedEncodingByDefault = false; + } else { + this.useChunkedEncodingByDefault = true; + } + this.closeOnFinish = true; + + this.sendHeaderLines(method + " " + url + " HTTP/1.1\r\n", headers); +} +sys.inherits(ClientRequest, OutgoingMessage); +exports.ClientRequest = ClientRequest; + +ClientRequest.prototype.finish = function (responseListener) { + this.addListener("response", responseListener); + OutgoingMessage.prototype.finish.call(this); +}; + + +function createIncomingMessageStream (socket, cb) { + var incoming, field, value; + + socket._parser.onMessageBegin = function () { + incoming = new IncomingMessage(socket); + field = null; + value = null; + }; + + // Only servers will get URL events. + socket._parser.onURL = function (b, start, len) { + var slice = b.asciiSlice(start, start+len); + if (incoming.url) { + incoming.url += slice; + } else { + // Almost always will branch here. + incoming.url = slice; + } + }; + + socket._parser.onHeaderField = function (b, start, len) { + var slice = b.asciiSlice(start, start+len).toLowerCase(); + if (value) { + incoming._addHeaderLine(field, value); + field = null; + value = null; + } + if (field) { + field += slice; + } else { + field = slice; + } + }; + + socket._parser.onHeaderValue = function (b, start, len) { + var slice = b.asciiSlice(start, start+len); + if (value) { + value += slice; + } else { + value = slice; + } + }; + + socket._parser.onHeadersComplete = function (info) { + if (field && value) { + incoming._addHeaderLine(field, value); + } + + incoming.httpVersionMajor = info.versionMajor; + incoming.httpVersionMinor = info.versionMinor; + + if (info.method) { + // server only + incoming.method = info.method; + } else { + // client only + incoming.statusCode = info.statusCode; + } + + cb(incoming, info.shouldKeepAlive); + }; + + socket._parser.onBody = function (b, start, len) { + incoming.emit("data", b.slice(start, start+len)); + }; + + socket._parser.onMessageComplete = function () { + incoming.emit("eof"); + }; +} + + +/* Returns true if the message queue is finished and the socket + * should be closed. */ +function flushMessageQueue (socket, queue) { + while (queue[0]) { + var message = queue[0]; + + while (message.output.length > 0) { + if (!socket.writable) return true; + + var data = message.output.shift(); + var encoding = message.outputEncodings.shift(); + + socket.send(data, encoding); + } + + if (!message.finished) break; + + message.emit("sent"); + queue.shift(); + + if (message.closeOnFinish) return true; + } + return false; +} + + +function connectionListener (socket) { + var self = this; + if (socket._parser) throw new Error("socket already has a parser?"); + socket._parser = new process.HTTPParser('request'); + // An array of responses for each socket. In pipelined connections + // we need to keep track of the order they were sent. + var responses = []; + + socket.addListener('data', function (d) { + socket._parser.execute(d, 0, d.length); + }); + + // is this really needed? + socket.addListener('eof', function () { + socket._parser.finish(); + // unref the parser for easy gc + socket._parser.host = null; + socket._parser = null; + + if (responses.length == 0) { + socket.close(); + } else { + responses[responses.length-1].closeOnFinish = true; + } + }); + + createIncomingMessageStream(socket, function (incoming, shouldKeepAlive) { + var req = incoming; + + var res = new ServerResponse(req); + res.shouldKeepAlive = shouldKeepAlive; + res.addListener('flush', function () { + if (flushMessageQueue(socket, responses)) { + socket.close(); + } + }); + responses.push(res); + + self.emit('request', req, res); + }); +} + + +function Server (requestListener, options) { + net.Server.call(this, connectionListener); + //server.setOptions(options); + this.addListener('request', requestListener); +} +process.inherits(Server, net.Server); +exports.Server = Server; +exports.createServer = function (requestListener, options) { + return new Server(requestListener, options); +}; + + From aadce8e1a9b1bfd6a3b6e7de060e8bf8401e8ea8 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Thu, 28 Jan 2010 14:30:31 -0800 Subject: [PATCH 046/105] Optimize, clean up net2 net.js and http2.js --- lib/http2.js | 178 +++++++++++++++++++++++++++------------------------ lib/net.js | 175 ++++++++++++++++++++++++++++++-------------------- 2 files changed, 202 insertions(+), 151 deletions(-) diff --git a/lib/http2.js b/lib/http2.js index 846a4c31f20..cdbbbf69750 100644 --- a/lib/http2.js +++ b/lib/http2.js @@ -270,78 +270,6 @@ ClientRequest.prototype.finish = function (responseListener) { }; -function createIncomingMessageStream (socket, cb) { - var incoming, field, value; - - socket._parser.onMessageBegin = function () { - incoming = new IncomingMessage(socket); - field = null; - value = null; - }; - - // Only servers will get URL events. - socket._parser.onURL = function (b, start, len) { - var slice = b.asciiSlice(start, start+len); - if (incoming.url) { - incoming.url += slice; - } else { - // Almost always will branch here. - incoming.url = slice; - } - }; - - socket._parser.onHeaderField = function (b, start, len) { - var slice = b.asciiSlice(start, start+len).toLowerCase(); - if (value) { - incoming._addHeaderLine(field, value); - field = null; - value = null; - } - if (field) { - field += slice; - } else { - field = slice; - } - }; - - socket._parser.onHeaderValue = function (b, start, len) { - var slice = b.asciiSlice(start, start+len); - if (value) { - value += slice; - } else { - value = slice; - } - }; - - socket._parser.onHeadersComplete = function (info) { - if (field && value) { - incoming._addHeaderLine(field, value); - } - - incoming.httpVersionMajor = info.versionMajor; - incoming.httpVersionMinor = info.versionMinor; - - if (info.method) { - // server only - incoming.method = info.method; - } else { - // client only - incoming.statusCode = info.statusCode; - } - - cb(incoming, info.shouldKeepAlive); - }; - - socket._parser.onBody = function (b, start, len) { - incoming.emit("data", b.slice(start, start+len)); - }; - - socket._parser.onMessageComplete = function () { - incoming.emit("eof"); - }; -} - - /* Returns true if the message queue is finished and the socket * should be closed. */ function flushMessageQueue (socket, queue) { @@ -368,24 +296,106 @@ function flushMessageQueue (socket, queue) { } +var parserFreeList = []; + +function newParser (type) { + var parser; + if (parserFreeList.length) { + parser = parserFreeList.shift(); + parser.reinitialize(type); + } else { + parser = new process.HTTPParser(type); + + parser.onMessageBegin = function () { + parser.incoming = new IncomingMessage(parser.socket); + parser.field = null; + parser.value = null; + }; + + // Only servers will get URL events. + parser.onURL = function (b, start, len) { + var slice = b.asciiSlice(start, start+len); + if (parser.incoming.url) { + parser.incoming.url += slice; + } else { + // Almost always will branch here. + parser.incoming.url = slice; + } + }; + + parser.onHeaderField = function (b, start, len) { + var slice = b.asciiSlice(start, start+len).toLowerCase(); + if (parser.value) { + parser.incoming._addHeaderLine(parser.field, parser.value); + parser.field = null; + parser.value = null; + } + if (parser.field) { + parser.field += slice; + } else { + parser.field = slice; + } + }; + + parser.onHeaderValue = function (b, start, len) { + var slice = b.asciiSlice(start, start+len); + if (parser.value) { + parser.value += slice; + } else { + parser.value = slice; + } + }; + + parser.onHeadersComplete = function (info) { + if (parser.field && parser.value) { + parser.incoming._addHeaderLine(parser.field, parser.value); + } + + parser.incoming.httpVersionMajor = info.versionMajor; + parser.incoming.httpVersionMinor = info.versionMinor; + + if (info.method) { + // server only + parser.incoming.method = info.method; + } else { + // client only + parser.incoming.statusCode = info.statusCode; + } + + parser.onIncoming(parser.incoming, info.shouldKeepAlive); + }; + + parser.onBody = function (b, start, len) { + parser.incoming.emit("data", b.slice(start, start+len)); + }; + + parser.onMessageComplete = function () { + parser.incoming.emit("eof"); + }; + } + return parser; +} + +function freeParser (parser) { + if (parserFreeList.length < 1000) parserFreeList.push(parser); +} + function connectionListener (socket) { var self = this; - if (socket._parser) throw new Error("socket already has a parser?"); - socket._parser = new process.HTTPParser('request'); + var parser = newParser('request'); // An array of responses for each socket. In pipelined connections // we need to keep track of the order they were sent. var responses = []; - socket.addListener('data', function (d) { - socket._parser.execute(d, 0, d.length); + socket.addListener('dataLite', function (d, start, end) { + parser.execute(d, start, end - start); }); // is this really needed? socket.addListener('eof', function () { - socket._parser.finish(); + parser.finish(); // unref the parser for easy gc - socket._parser.host = null; - socket._parser = null; + freeParser(parser); if (responses.length == 0) { socket.close(); @@ -394,10 +404,14 @@ function connectionListener (socket) { } }); - createIncomingMessageStream(socket, function (incoming, shouldKeepAlive) { + parser.socket = socket; + // The following callback is issued after the headers have been read on a + // new message. In this callback we setup the response object and pass it + // to the user. + parser.onIncoming = function (incoming, shouldKeepAlive) { var req = incoming; - var res = new ServerResponse(req); + res.shouldKeepAlive = shouldKeepAlive; res.addListener('flush', function () { if (flushMessageQueue(socket, responses)) { @@ -407,7 +421,7 @@ function connectionListener (socket) { responses.push(res); self.emit('request', req, res); - }); + }; } diff --git a/lib/net.js b/lib/net.js index 955c482dc0d..ab79493bc2a 100644 --- a/lib/net.js +++ b/lib/net.js @@ -28,57 +28,105 @@ var getaddrinfo = process.getaddrinfo; var needsLookup = process.needsLookup; var EINPROGRESS = process.EINPROGRESS; var ENOENT = process.ENOENT; -var END_OF_FILE = 0; +var END_OF_FILE = 0; + +// This is a free list to avoid creating so many of the same object. + +function FreeList (name, max, constructor) { + this.name = name; + this.constructor = constructor; + this.max = max; + this.list = []; +} + +FreeList.prototype.alloc = function () { + debug("alloc " + this.name + " " + this.list.length); + return this.list.length ? this.list.shift() + : this.constructor.apply(this, arguments); +} + +FreeList.prototype.free = function (obj) { + debug("free " + this.name + " " + this.list.length); + if (this.list.length < this.max) { + this.list.push(obj); + } +} + + +var ioWatchers = new FreeList("iowatcher", 100, function () { + return new IOWatcher(); +}); + +var buffers = new FreeList("buffer", 100, function (l) { + return new process.Buffer(1000); +}); + +// Allocated on demand. +var recvBuffer = null; + +function allocRecvBuffer () { + recvBuffer = new process.Buffer(8*1024); + recvBuffer.used = 0; +} function Socket (peerInfo) { process.EventEmitter.call(this); var self = this; - // Allocated on demand. - self.recvBuffer = null; - - self.readWatcher = new IOWatcher(); - self.readWatcher.host = this; - self.readWatcher.callback = function () { + self._readWatcher = ioWatchers.alloc(); + self._readWatcher.callback = function () { // If this is the first recv (recvBuffer doesn't exist) or we've used up // most of the recvBuffer, allocate a new one. - if (!self.recvBuffer || - self.recvBuffer.length - self.recvBuffer.used < 128) { - self._allocateNewRecvBuf(); + if (recvBuffer) { + if (recvBuffer.length - recvBuffer.used < 128) { + // discard the old recvBuffer. Can't add to the free list because + // users might have refernces to slices on it. + recvBuffer = null; + allocRecvBuffer(); + } + } else { + allocRecvBuffer(); } - debug('recvBuffer.used ' + self.recvBuffer.used); + debug('recvBuffer.used ' + recvBuffer.used); var bytesRead; if (self.type == "unix") { bytesRead = recvMsg(self.fd, - self.recvBuffer, - self.recvBuffer.used, - self.recvBuffer.length - self.recvBuffer.used); + recvBuffer, + recvBuffer.used, + recvBuffer.length - recvBuffer.used); debug('recvMsg.fd ' + recvMsg.fd); if (recvMsg.fd) { self.emit('fd', recvMsg.fd); } } else { bytesRead = read(self.fd, - self.recvBuffer, - self.recvBuffer.used, - self.recvBuffer.length - self.recvBuffer.used); + recvBuffer, + recvBuffer.used, + recvBuffer.length - recvBuffer.used); } debug('bytesRead ' + bytesRead + '\n'); if (!recvMsg.fd && bytesRead == 0) { self.readable = false; - self.readWatcher.stop(); + self._readWatcher.stop(); self.emit('eof'); if (!self.writable) self.forceClose(); } else if (bytesRead > 0) { - var slice = self.recvBuffer.slice(self.recvBuffer.used, - self.recvBuffer.used + bytesRead); - self.recvBuffer.used += bytesRead; - self.emit('data', slice); + var start = recvBuffer.used; + var end = recvBuffer.used + bytesRead; + if (self.listeners('data').length) { + // emit a slice + self.emit('data', recvBuffer.slice(start, end)); + } + if (self.listeners('dataLite').length) { + // emit the original buffer with end points. + self.emit('dataLite', recvBuffer, start, end); + } + recvBuffer.used += bytesRead; } }; self.readable = false; @@ -99,9 +147,8 @@ function Socket (peerInfo) { self.emit("drain"); } }; - self.writeWatcher = new IOWatcher(); - self.writeWatcher.host = this; - self.writeWatcher.callback = self._doFlush; + self._writeWatcher = ioWatchers.alloc(); + self._writeWatcher.callback = self._doFlush; self.writable = false; if (peerInfo) { @@ -109,11 +156,11 @@ function Socket (peerInfo) { self.remoteAddress = peerInfo.remoteAddress; self.remotePort = peerInfo.remotePort; - self.readWatcher.set(self.fd, true, false); - self.readWatcher.start(); + self._readWatcher.set(self.fd, true, false); + self._readWatcher.start(); self.readable = true; - self.writeWatcher.set(self.fd, false, true); + self._writeWatcher.set(self.fd, false, true); self.writable = true; } }; @@ -128,33 +175,8 @@ exports.createConnection = function (port, host) { }; -Socket.prototype._allocateNewRecvBuf = function () { - var self = this; - - var newBufferSize = 128; // TODO make this adjustable from user API - - if (toRead) { - // Is the extra system call even worth it? - var bytesToRead = toRead(self.fd); - if (bytesToRead > 1024) { - newBufferSize = 4*1024; - } else if (bytesToRead == 0) { - // Probably getting an EOF - so let's not allocate so much. - if (self.recvBuffer && - self.recvBuffer.length - self.recvBuffer.used > 0) { - return; // just recv the eof on the old buf. - } - newBufferSize = 128; - } - } - - self.recvBuffer = new process.Buffer(newBufferSize); - self.recvBuffer.used = 0; -}; - - Socket.prototype._allocateSendBuffer = function () { - var b = new process.Buffer(1024); + var b = buffers.alloc(1024); b.used = 0; b.sent = 0; b.isMsg = false; @@ -313,6 +335,7 @@ Socket.prototype.flush = function () { if (b.sent == b.used) { // this can be improved - save the buffer for later? self.sendQueue.shift(); + buffers.free(b); continue; } @@ -328,7 +351,7 @@ Socket.prototype.flush = function () { } if (bytesWritten === null) { // could not flush everything - self.writeWatcher.start(); + self._writeWatcher.start(); assert(self.sendQueueSize > 0); return false; } @@ -342,7 +365,7 @@ Socket.prototype.flush = function () { debug('bytes sent: ' + b.sent); } } - self.writeWatcher.stop(); + if (self._writeWatcher) self._writeWatcher.stop(); return true; }; @@ -364,23 +387,23 @@ Socket.prototype.connect = function () { } // Don't start the read watcher until connection is established - self.readWatcher.set(self.fd, true, false); + self._readWatcher.set(self.fd, true, false); // How to connect on POSIX: Wait for fd to become writable, then call // socketError() if there isn't an error, we're connected. AFAIK this a // platform independent way determining when a non-blocking connection // is established, but I have only seen it documented in the Linux // Manual Page connect(2) under the error code EINPROGRESS. - self.writeWatcher.set(self.fd, false, true); - self.writeWatcher.start(); - self.writeWatcher.callback = function () { + self._writeWatcher.set(self.fd, false, true); + self._writeWatcher.start(); + self._writeWatcher.callback = function () { var errno = socketError(self.fd); if (errno == 0) { // connection established - self.readWatcher.start(); + self._readWatcher.start(); self.readable = true; self.writable = true; - self.writeWatcher.callback = self._doFlush; + self._writeWatcher.callback = self._doFlush; self.emit('connect'); } else if (errno != EINPROGRESS) { var e = new Error('connection error'); @@ -417,15 +440,28 @@ Socket.prototype.setNoDelay = function (v) { Socket.prototype.forceClose = function (exception) { + // recvBuffer is shared between sockets, so don't need to free it here. + + var b; + while (this.sendQueue.length) { + b = this.sendQueue.shift(); + if (b instanceof process.Buffer) buffers.free(b); + } + + if (this._writeWatcher) { + this._writeWatcher.stop(); + ioWatchers.free(this._writeWatcher); + this._writeWatcher = null; + } + + if (this._readWatcher) { + this._readWatcher.stop(); + ioWatchers.free(this._readWatcher); + this._readWatcher = null; + } + if (this.fd) { - this.readable = false; - this.writable = false; - - this.writeWatcher.stop(); - this.readWatcher.stop(); - close(this.fd); - debug('close socket ' + this.fd); this.fd = null; this.emit('close', exception); } @@ -436,6 +472,7 @@ Socket.prototype._shutdown = function () { if (this.writable) { this.writable = false; shutdown(this.fd, 'write'); + if (!this.readable) this.forceClose(); } }; From 344243db9232e668bff0467043735ecbbcaa1386 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Thu, 28 Jan 2010 19:13:13 -0800 Subject: [PATCH 047/105] Simplify send buffer logic --- lib/net.js | 36 ++++++++---------------------------- 1 file changed, 8 insertions(+), 28 deletions(-) diff --git a/lib/net.js b/lib/net.js index ab79493bc2a..93e9366af8a 100644 --- a/lib/net.js +++ b/lib/net.js @@ -57,15 +57,15 @@ var ioWatchers = new FreeList("iowatcher", 100, function () { return new IOWatcher(); }); +var nb = 0; var buffers = new FreeList("buffer", 100, function (l) { - return new process.Buffer(1000); + return new process.Buffer(500); }); // Allocated on demand. var recvBuffer = null; - function allocRecvBuffer () { - recvBuffer = new process.Buffer(8*1024); + recvBuffer = new process.Buffer(40*1024); recvBuffer.used = 0; } @@ -192,19 +192,9 @@ Socket.prototype._sendString = function (data, encoding) { if (self.sendQueue.length == 0) { buffer = self._allocateSendBuffer(); } else { - // walk through the sendQueue, find the buffer with free space - for (var i = 0; i < self.sendQueue.length; i++) { - if (self.sendQueue[i].used == 0) { - buffer = self.sendQueue[i]; - break; - } - } - // if we didn't find one, take the last - // TODO what if this isn't empty but encoding == fd ? - if (!buffer) { - buffer = self._sendQueueLast(); - // if last buffer is used up - if (buffer.length == buffer.used) buffer = self._allocateSendBuffer(); + buffer = self._sendQueueLast(); + if (buffer.used == buffer.length) { + buffer = self._allocateSendBuffer(); } } @@ -279,17 +269,7 @@ Socket.prototype.send = function (data, encoding) { var inserted = false; data.sent = 0; data.used = data.length; - for (var i = 0; i < self.sendQueue.length; i++) { - if (self.sendQueue[i].used == 0) { - // if found, insert the data there - self.sendQueue.splice(i, 0, data); - inserted = true; - break; - } - } - if (!inserted) self.sendQueue.push(data); - self.sendQueueSize += data.used; } return this.flush(); @@ -322,7 +302,7 @@ Socket.prototype.flush = function () { var self = this; var bytesWritten; - while (self.sendQueue.length > 0) { + while (self.sendQueue.length) { if (!self.writable) throw new Error('Socket is not writable'); var b = self.sendQueue[0]; @@ -333,7 +313,7 @@ Socket.prototype.flush = function () { } if (b.sent == b.used) { - // this can be improved - save the buffer for later? + // shift! self.sendQueue.shift(); buffers.free(b); continue; From 785531691b20ca490ce24c4aac0c569124fe1273 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Fri, 29 Jan 2010 09:57:47 -0800 Subject: [PATCH 048/105] Add buffer.unpack --- src/node_buffer.cc | 68 +++++++++++++++++++++++++++++++++++++ src/node_buffer.h | 1 + test/mjsunit/test-buffer.js | 22 ++++++++++++ 3 files changed, 91 insertions(+) diff --git a/src/node_buffer.cc b/src/node_buffer.cc index 7ebfc089f34..522d3f520f6 100644 --- a/src/node_buffer.cc +++ b/src/node_buffer.cc @@ -4,6 +4,8 @@ #include // malloc, free #include +#include // htons, htonl + #include namespace node { @@ -261,6 +263,70 @@ Handle Buffer::AsciiWrite(const Arguments &args) { } +// buffer.unpack(format, index); +// Starting at 'index', unpacks binary from the buffer into an array. +// 'format' is a string +// +// FORMAT RETURNS +// N uint32_t a 32bit unsigned integer in network byte order +// n uint16_t a 16bit unsigned integer in network byte order +// o uint8_t a 8bit unsigned integer +Handle Buffer::Unpack(const Arguments &args) { + HandleScope scope; + Buffer *buffer = ObjectWrap::Unwrap(args.This()); + + if (!args[0]->IsString()) { + return ThrowException(Exception::TypeError(String::New( + "Argument must be a string"))); + } + + String::AsciiValue format(args[0]->ToString()); + int index = args[1]->IntegerValue(); + +#define OUT_OF_BOUNDS ThrowException(Exception::Error(String::New("Out of bounds"))) + + Local array = Array::New(format.length()); + + uint8_t uint8; + uint16_t uint16; + uint32_t uint32; + + for (int i = 0; i < format.length(); i++) { + switch ((*format)[i]) { + // 32bit unsigned integer in network byte order + case 'N': + if (index + 3 >= buffer->length_) return OUT_OF_BOUNDS; + uint32 = htonl(*(uint32_t*)(buffer->data_ + index)); + array->Set(Integer::New(i), Integer::NewFromUnsigned(uint32)); + index += 4; + break; + + // 16bit unsigned integer in network byte order + case 'n': + if (index + 1 >= buffer->length_) return OUT_OF_BOUNDS; + uint16 = htons(*(uint16_t*)(buffer->data_ + index)); + array->Set(Integer::New(i), Integer::NewFromUnsigned(uint16)); + index += 2; + break; + + // a single octet, unsigned. + case 'o': + if (index >= buffer->length_) return OUT_OF_BOUNDS; + uint8 = (uint8_t)buffer->data_[index]; + array->Set(Integer::New(i), Integer::NewFromUnsigned(uint8)); + index += 1; + break; + + default: + return ThrowException(Exception::Error( + String::New("Unknown format character"))); + } + } + + return scope.Close(array); +} + + // var nbytes = Buffer.utf8Length("string") Handle Buffer::Utf8Length(const Arguments &args) { HandleScope scope; @@ -280,6 +346,7 @@ bool Buffer::HasInstance(Handle val) { } + void Buffer::Initialize(Handle target) { HandleScope scope; @@ -299,6 +366,7 @@ void Buffer::Initialize(Handle target) { NODE_SET_PROTOTYPE_METHOD(constructor_template, "utf8Write", Buffer::Utf8Write); NODE_SET_PROTOTYPE_METHOD(constructor_template, "asciiWrite", Buffer::AsciiWrite); + NODE_SET_PROTOTYPE_METHOD(constructor_template, "unpack", Buffer::Unpack); NODE_SET_METHOD(constructor_template->GetFunction(), "utf8Length", Buffer::Utf8Length); diff --git a/src/node_buffer.h b/src/node_buffer.h index d6b83fe00b6..793c9346151 100644 --- a/src/node_buffer.h +++ b/src/node_buffer.h @@ -46,6 +46,7 @@ class Buffer : public ObjectWrap { static v8::Handle AsciiWrite(const v8::Arguments &args); static v8::Handle Utf8Write(const v8::Arguments &args); static v8::Handle Utf8Length(const v8::Arguments &args); + static v8::Handle Unpack(const v8::Arguments &args); int AsciiWrite(char *string, int offset, int length); int Utf8Write(char *string, int offset, int length); diff --git a/test/mjsunit/test-buffer.js b/test/mjsunit/test-buffer.js index 50dba628712..c8792e8980a 100644 --- a/test/mjsunit/test-buffer.js +++ b/test/mjsunit/test-buffer.js @@ -50,4 +50,26 @@ for (var j = 0; j < 10000; j++) { } +// unpack +var b = new process.Buffer(10); +b[0] = 0x00; +b[1] = 0x01; +b[2] = 0x03; +b[3] = 0x00; + +assert.deepEqual([0x0001], b.unpack('n', 0)); +assert.deepEqual([0x0001, 0x0300], b.unpack('nn', 0)); +assert.deepEqual([0x0103], b.unpack('n', 1)); +assert.deepEqual([0x0300], b.unpack('n', 2)); +assert.deepEqual([0x00010300], b.unpack('N', 0)); +assert.throws(function () { + b.unpack('N', 8); +}); + +b[4] = 0xDE; +b[5] = 0xAD; +b[6] = 0xBE; +b[7] = 0xEF; + +assert.deepEqual([0xDEADBEEF], b.unpack('N', 4)); From 4f56d8ae24909301a6fa144c494bf5fc2512ddf5 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Mon, 1 Feb 2010 08:24:44 -0800 Subject: [PATCH 049/105] Rename Buffer.utf8Length to Buffer.utf8ByteLength --- lib/net.js | 2 +- src/node_buffer.cc | 8 +++++--- src/node_buffer.h | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/net.js b/lib/net.js index 93e9366af8a..59ee2d04216 100644 --- a/lib/net.js +++ b/lib/net.js @@ -215,7 +215,7 @@ Socket.prototype._sendString = function (data, encoding) { charsWritten = buffer.utf8Write(data, buffer.used, buffer.length - buffer.used); - bytesWritten = process.Buffer.utf8Length(data.slice(0, charsWritten)); + bytesWritten = process.Buffer.utf8ByteLength(data.slice(0, charsWritten)); } else { // ascii buffer.isFd = false; diff --git a/src/node_buffer.cc b/src/node_buffer.cc index 522d3f520f6..71afbd71565 100644 --- a/src/node_buffer.cc +++ b/src/node_buffer.cc @@ -327,8 +327,8 @@ Handle Buffer::Unpack(const Arguments &args) { } -// var nbytes = Buffer.utf8Length("string") -Handle Buffer::Utf8Length(const Arguments &args) { +// var nbytes = Buffer.utf8ByteLength("string") +Handle Buffer::Utf8ByteLength(const Arguments &args) { HandleScope scope; if (!args[0]->IsString()) { return ThrowException(Exception::TypeError(String::New( @@ -368,7 +368,9 @@ void Buffer::Initialize(Handle target) { NODE_SET_PROTOTYPE_METHOD(constructor_template, "asciiWrite", Buffer::AsciiWrite); NODE_SET_PROTOTYPE_METHOD(constructor_template, "unpack", Buffer::Unpack); - NODE_SET_METHOD(constructor_template->GetFunction(), "utf8Length", Buffer::Utf8Length); + NODE_SET_METHOD(constructor_template->GetFunction(), + "utf8ByteLength", + Buffer::Utf8ByteLength); target->Set(String::NewSymbol("Buffer"), constructor_template->GetFunction()); } diff --git a/src/node_buffer.h b/src/node_buffer.h index 793c9346151..ae3b6bfbff3 100644 --- a/src/node_buffer.h +++ b/src/node_buffer.h @@ -45,7 +45,7 @@ class Buffer : public ObjectWrap { static v8::Handle Utf8Slice(const v8::Arguments &args); static v8::Handle AsciiWrite(const v8::Arguments &args); static v8::Handle Utf8Write(const v8::Arguments &args); - static v8::Handle Utf8Length(const v8::Arguments &args); + static v8::Handle Utf8ByteLength(const v8::Arguments &args); static v8::Handle Unpack(const v8::Arguments &args); int AsciiWrite(char *string, int offset, int length); From 33509bdbe5e4f38a4b25c5793f64fb6a78d2d0ec Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Mon, 1 Feb 2010 18:19:08 -0800 Subject: [PATCH 050/105] eof -> end --- lib/http2.js | 4 ++-- lib/net.js | 6 +++--- test/mjsunit/test-net-pingpong.js | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/http2.js b/lib/http2.js index cdbbbf69750..ab2255bae54 100644 --- a/lib/http2.js +++ b/lib/http2.js @@ -370,7 +370,7 @@ function newParser (type) { }; parser.onMessageComplete = function () { - parser.incoming.emit("eof"); + parser.incoming.emit("end"); }; } return parser; @@ -392,7 +392,7 @@ function connectionListener (socket) { }); // is this really needed? - socket.addListener('eof', function () { + socket.addListener('end', function () { parser.finish(); // unref the parser for easy gc freeParser(parser); diff --git a/lib/net.js b/lib/net.js index 59ee2d04216..232cf906bc6 100644 --- a/lib/net.js +++ b/lib/net.js @@ -113,7 +113,7 @@ function Socket (peerInfo) { if (!recvMsg.fd && bytesRead == 0) { self.readable = false; self._readWatcher.stop(); - self.emit('eof'); + self.emit('end'); if (!self.writable) self.forceClose(); } else if (bytesRead > 0) { var start = recvBuffer.used; @@ -266,10 +266,10 @@ Socket.prototype.send = function (data, encoding) { } else { // data is a process.Buffer // walk through the sendQueue, find the first empty buffer - var inserted = false; + //var inserted = false; data.sent = 0; data.used = data.length; - if (!inserted) self.sendQueue.push(data); + self.sendQueue.push(data); self.sendQueueSize += data.used; } return this.flush(); diff --git a/test/mjsunit/test-net-pingpong.js b/test/mjsunit/test-net-pingpong.js index 7569991921d..7017e414a6b 100644 --- a/test/mjsunit/test-net-pingpong.js +++ b/test/mjsunit/test-net-pingpong.js @@ -29,7 +29,7 @@ function pingPongTest (port, host) { } }); - socket.addListener("eof", function () { + socket.addListener("end", function () { assert.equal(true, socket.writable); assert.equal(false, socket.readable); socket.close(); From 7c9919f8108be7f8c52a338c08b66b4bcb548fd1 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Tue, 2 Feb 2010 18:37:14 -0800 Subject: [PATCH 051/105] Remove some unnecessary handlescopes --- src/node_http_parser.cc | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/node_http_parser.cc b/src/node_http_parser.cc index 2236b47359a..083f496dea9 100644 --- a/src/node_http_parser.cc +++ b/src/node_http_parser.cc @@ -68,9 +68,6 @@ static Persistent should_keep_alive_sym; #define DEFINE_HTTP_CB(name) \ static int name(http_parser *p) { \ Parser *parser = static_cast(p->data); \ - \ - HandleScope scope; \ - \ Local cb_value = parser->handle_->Get(name##_sym); \ if (!cb_value->IsFunction()) return 0; \ Local cb = Local::Cast(cb_value); \ @@ -82,7 +79,6 @@ static Persistent should_keep_alive_sym; #define DEFINE_HTTP_DATA_CB(name) \ static int name(http_parser *p, const char *at, size_t length) { \ Parser *parser = static_cast(p->data); \ - HandleScope scope; \ assert(parser->buffer_); \ Local cb_value = parser->handle_->Get(name##_sym); \ if (!cb_value->IsFunction()) return 0; \ @@ -145,8 +141,6 @@ class Parser : public ObjectWrap { static int on_headers_complete(http_parser *p) { Parser *parser = static_cast(p->data); - HandleScope scope; - Local cb_value = parser->handle_->Get(on_headers_complete_sym); if (!cb_value->IsFunction()) return 0; Local cb = Local::Cast(cb_value); @@ -267,9 +261,7 @@ class Parser : public ObjectWrap { assert(!parser->buffer_); - parser->Ref(); http_parser_execute(&(parser->parser_), NULL, 0); - parser->Unref(); return Undefined(); } From 1660db6b87703ca7c9c2ea1ac7cf3b27ea8c14bf Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Tue, 2 Feb 2010 19:35:06 -0800 Subject: [PATCH 052/105] Inline Buffer::HasInstance --- src/node_buffer.cc | 8 -------- src/node_buffer.h | 6 +++++- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/node_buffer.cc b/src/node_buffer.cc index 71afbd71565..d5b68aa7c56 100644 --- a/src/node_buffer.cc +++ b/src/node_buffer.cc @@ -339,14 +339,6 @@ Handle Buffer::Utf8ByteLength(const Arguments &args) { } -bool Buffer::HasInstance(Handle val) { - if (!val->IsObject()) return false; - Local obj = val->ToObject(); - return constructor_template->HasInstance(obj); -} - - - void Buffer::Initialize(Handle target) { HandleScope scope; diff --git a/src/node_buffer.h b/src/node_buffer.h index ae3b6bfbff3..78059be7efe 100644 --- a/src/node_buffer.h +++ b/src/node_buffer.h @@ -31,7 +31,11 @@ struct Blob_; class Buffer : public ObjectWrap { public: static void Initialize(v8::Handle target); - static bool HasInstance(v8::Handle val); + static inline bool HasInstance(v8::Handle val) { + if (!val->IsObject()) return false; + v8::Local obj = val->ToObject(); + return constructor_template->HasInstance(obj); + } const char* data() const { return data_; } size_t length() const { return length_; } From 8fdb39a4aeeb064bb26ad49e7aacd22e9e027317 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Fri, 5 Feb 2010 19:26:52 -0800 Subject: [PATCH 053/105] Add errno symbol to error messages --- src/node_net2.cc | 331 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 330 insertions(+), 1 deletion(-) diff --git a/src/node_net2.cc b/src/node_net2.cc index 241469873e7..1e4d25044bb 100644 --- a/src/node_net2.cc +++ b/src/node_net2.cc @@ -54,11 +54,340 @@ static Persistent recv_msg_template; } \ int fd = (a)->Int32Value(); +static inline const char *errno_string(int errorno) { +#define ERRNO_CASE(e) case e: return #e; + switch (errorno) { + +#ifdef EACCES + ERRNO_CASE(EACCES); +#endif + +#ifdef EADDRINUSE + ERRNO_CASE(EADDRINUSE); +#endif + +#ifdef EADDRNOTAVAIL + ERRNO_CASE(EADDRNOTAVAIL); +#endif + +#ifdef EAFNOSUPPORT + ERRNO_CASE(EAFNOSUPPORT); +#endif + +#ifdef EAGAIN + ERRNO_CASE(EAGAIN); +#else +# ifdef EWOULDBLOCK + ERRNO_CASE(EWOULDBLOCK); +# endif +#endif + +#ifdef EALREADY + ERRNO_CASE(EALREADY); +#endif + +#ifdef EBADF + ERRNO_CASE(EBADF); +#endif + +#ifdef EBADMSG + ERRNO_CASE(EBADMSG); +#endif + +#ifdef EBUSY + ERRNO_CASE(EBUSY); +#endif + +#ifdef ECANCELED + ERRNO_CASE(ECANCELED); +#endif + +#ifdef ECHILD + ERRNO_CASE(ECHILD); +#endif + +#ifdef ECONNABORTED + ERRNO_CASE(ECONNABORTED); +#endif + +#ifdef ECONNREFUSED + ERRNO_CASE(ECONNREFUSED); +#endif + +#ifdef ECONNRESET + ERRNO_CASE(ECONNRESET); +#endif + +#ifdef EDEADLK + ERRNO_CASE(EDEADLK); +#endif + +#ifdef EDESTADDRREQ + ERRNO_CASE(EDESTADDRREQ); +#endif + +#ifdef EDOM + ERRNO_CASE(EDOM); +#endif + +#ifdef EDQUOT + ERRNO_CASE(EDQUOT); +#endif + +#ifdef EEXIST + ERRNO_CASE(EEXIST); +#endif + +#ifdef EFAULT + ERRNO_CASE(EFAULT); +#endif + +#ifdef EFBIG + ERRNO_CASE(EFBIG); +#endif + +#ifdef EHOSTUNREACH + ERRNO_CASE(EHOSTUNREACH); +#endif + +#ifdef EIDRM + ERRNO_CASE(EIDRM); +#endif + +#ifdef EILSEQ + ERRNO_CASE(EILSEQ); +#endif + +#ifdef EINPROGRESS + ERRNO_CASE(EINPROGRESS); +#endif + +#ifdef EINTR + ERRNO_CASE(EINTR); +#endif + +#ifdef EINVAL + ERRNO_CASE(EINVAL); +#endif + +#ifdef EIO + ERRNO_CASE(EIO); +#endif + +#ifdef EISCONN + ERRNO_CASE(EISCONN); +#endif + +#ifdef EISDIR + ERRNO_CASE(EISDIR); +#endif + +#ifdef ELOOP + ERRNO_CASE(ELOOP); +#endif + +#ifdef EMFILE + ERRNO_CASE(EMFILE); +#endif + +#ifdef EMLINK + ERRNO_CASE(EMLINK); +#endif + +#ifdef EMSGSIZE + ERRNO_CASE(EMSGSIZE); +#endif + +#ifdef EMULTIHOP + ERRNO_CASE(EMULTIHOP); +#endif + +#ifdef ENAMETOOLONG + ERRNO_CASE(ENAMETOOLONG); +#endif + +#ifdef ENETDOWN + ERRNO_CASE(ENETDOWN); +#endif + +#ifdef ENETRESET + ERRNO_CASE(ENETRESET); +#endif + +#ifdef ENETUNREACH + ERRNO_CASE(ENETUNREACH); +#endif + +#ifdef ENFILE + ERRNO_CASE(ENFILE); +#endif + +#ifdef ENOBUFS + ERRNO_CASE(ENOBUFS); +#endif + +#ifdef ENODATA + ERRNO_CASE(ENODATA); +#endif + +#ifdef ENODEV + ERRNO_CASE(ENODEV); +#endif + +#ifdef ENOENT + ERRNO_CASE(ENOENT); +#endif + +#ifdef ENOEXEC + ERRNO_CASE(ENOEXEC); +#endif + +#ifdef ENOLCK + ERRNO_CASE(ENOLCK); +#endif + +#ifdef ENOLINK + ERRNO_CASE(ENOLINK); +#endif + +#ifdef ENOMEM + ERRNO_CASE(ENOMEM); +#endif + +#ifdef ENOMSG + ERRNO_CASE(ENOMSG); +#endif + +#ifdef ENOPROTOOPT + ERRNO_CASE(ENOPROTOOPT); +#endif + +#ifdef ENOSPC + ERRNO_CASE(ENOSPC); +#endif + +#ifdef ENOSR + ERRNO_CASE(ENOSR); +#endif + +#ifdef ENOSTR + ERRNO_CASE(ENOSTR); +#endif + +#ifdef ENOSYS + ERRNO_CASE(ENOSYS); +#endif + +#ifdef ENOTCONN + ERRNO_CASE(ENOTCONN); +#endif + +#ifdef ENOTDIR + ERRNO_CASE(ENOTDIR); +#endif + +#ifdef ENOTEMPTY + ERRNO_CASE(ENOTEMPTY); +#endif + +#ifdef ENOTSOCK + ERRNO_CASE(ENOTSOCK); +#endif + +#ifdef ENOTSUP + ERRNO_CASE(ENOTSUP); +#else +# ifdef EOPNOTSUPP + ERRNO_CASE(EOPNOTSUPP); +# endif +#endif + +#ifdef ENOTTY + ERRNO_CASE(ENOTTY); +#endif + +#ifdef ENXIO + ERRNO_CASE(ENXIO); +#endif + + +#ifdef EOVERFLOW + ERRNO_CASE(EOVERFLOW); +#endif + +#ifdef EPERM + ERRNO_CASE(EPERM); +#endif + +#ifdef EPIPE + ERRNO_CASE(EPIPE); +#endif + +#ifdef EPROTO + ERRNO_CASE(EPROTO); +#endif + +#ifdef EPROTONOSUPPORT + ERRNO_CASE(EPROTONOSUPPORT); +#endif + +#ifdef EPROTOTYPE + ERRNO_CASE(EPROTOTYPE); +#endif + +#ifdef ERANGE + ERRNO_CASE(ERANGE); +#endif + +#ifdef EROFS + ERRNO_CASE(EROFS); +#endif + +#ifdef ESPIPE + ERRNO_CASE(ESPIPE); +#endif + +#ifdef ESRCH + ERRNO_CASE(ESRCH); +#endif + +#ifdef ESTALE + ERRNO_CASE(ESTALE); +#endif + +#ifdef ETIME + ERRNO_CASE(ETIME); +#endif + +#ifdef ETIMEDOUT + ERRNO_CASE(ETIMEDOUT); +#endif + +#ifdef ETXTBSY + ERRNO_CASE(ETXTBSY); +#endif + +#ifdef EXDEV + ERRNO_CASE(EXDEV); +#endif + + default: return ""; + } +} + + static inline Local ErrnoException(int errorno, const char *syscall, const char *msg = "") { + Local estring = String::NewSymbol(errno_string(errorno)); if (!msg[0]) msg = strerror(errorno); - Local e = Exception::Error(String::NewSymbol(msg)); + Local message = String::NewSymbol(msg); + + Local cons1 = String::Concat(estring, String::NewSymbol(", ")); + Local cons2 = String::Concat(cons1, message); + + Local e = Exception::Error(cons2); + Local obj = e->ToObject(); obj->Set(errno_symbol, Integer::New(errorno)); obj->Set(syscall_symbol, String::NewSymbol(syscall)); From 263813ae3e0644d5b1d5b41c3a3521594da9df5d Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Fri, 5 Feb 2010 19:34:27 -0800 Subject: [PATCH 054/105] Whitespace for node_net2.cc --- src/node_net2.cc | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/src/node_net2.cc b/src/node_net2.cc index 1e4d25044bb..5e7cfef1798 100644 --- a/src/node_net2.cc +++ b/src/node_net2.cc @@ -29,6 +29,7 @@ #include + namespace node { using namespace v8; @@ -47,13 +48,15 @@ static Persistent unix_symbol; static Persistent recv_msg_template; -#define FD_ARG(a) \ - if (!(a)->IsInt32()) { \ - return ThrowException(Exception::TypeError( \ - String::New("Bad file descriptor argument"))); \ - } \ + +#define FD_ARG(a) \ + if (!(a)->IsInt32()) { \ + return ThrowException(Exception::TypeError( \ + String::New("Bad file descriptor argument"))); \ + } \ int fd = (a)->Int32Value(); + static inline const char *errno_string(int errorno) { #define ERRNO_CASE(e) case e: return #e; switch (errorno) { @@ -394,18 +397,22 @@ static inline Local ErrnoException(int errorno, return e; } + static inline bool SetCloseOnExec(int fd) { return (fcntl(fd, F_SETFD, FD_CLOEXEC) != -1); } + static inline bool SetNonBlock(int fd) { return (fcntl(fd, F_SETFL, O_NONBLOCK) != -1); } + static inline bool SetSockFlags(int fd) { return SetNonBlock(fd) && SetCloseOnExec(fd); } + // Creates nonblocking pipe static Handle Pipe(const Arguments& args) { HandleScope scope; @@ -426,6 +433,7 @@ static Handle Pipe(const Arguments& args) { return scope.Close(a); } + // Creates nonblocking socket pair static Handle SocketPair(const Arguments& args) { HandleScope scope; @@ -450,6 +458,7 @@ static Handle SocketPair(const Arguments& args) { return scope.Close(a); } + // Creates a new non-blocking socket fd // t.socket("TCP"); // t.socket("UNIX"); @@ -597,6 +606,7 @@ static Handle Close(const Arguments& args) { return Undefined(); } + // t.shutdown(fd, "read"); -- SHUT_RD // t.shutdown(fd, "write"); -- SHUT_WR // t.shutdown(fd, "readwrite"); -- SHUT_RDWR @@ -660,6 +670,7 @@ static Handle Connect(const Arguments& args) { return Undefined(); } + static Handle GetSockName(const Arguments& args) { HandleScope scope; @@ -691,6 +702,7 @@ static Handle GetSockName(const Arguments& args) { return scope.Close(info); } + static Handle GetPeerName(const Arguments& args) { HandleScope scope; @@ -804,6 +816,7 @@ static Handle SocketError(const Arguments& args) { return scope.Close(Integer::New(error)); } + // var bytesRead = t.read(fd, buffer, offset, length); // returns null on EAGAIN or EINTR, raises an exception on all other errors // returns 0 on EOF. @@ -846,6 +859,7 @@ static Handle Read(const Arguments& args) { return scope.Close(Integer::New(bytes_read)); } + // bytesRead = t.recvMsg(fd, buffer, offset, length) // if (recvMsg.fd) { // receivedFd = recvMsg.fd; @@ -962,6 +976,7 @@ static Handle Write(const Arguments& args) { return scope.Close(Integer::New(written)); } + // var bytesWritten = t.sendFD(self.fd) // returns null on EAGAIN or EINTR, raises an exception on all other errors static Handle SendFD(const Arguments& args) { @@ -1017,6 +1032,7 @@ static Handle SendFD(const Arguments& args) { return scope.Close(Integer::New(written)); } + // Probably only works for Linux TCP sockets? // Returns the amount of data on the read queue. static Handle ToRead(const Arguments& args) { @@ -1052,7 +1068,10 @@ static Handle SetNoDelay(const Arguments& args) { } +// // G E T A D D R I N F O +// + struct resolve_request { Persistent cb; From d979a7993eb20843a8dd01ef2b596295b4d4311d Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Fri, 5 Feb 2010 19:47:16 -0800 Subject: [PATCH 055/105] Wrap syscalls with try-catch --- lib/net.js | 63 +++++++++++++++++++++++++++++++++++------------------- 1 file changed, 41 insertions(+), 22 deletions(-) diff --git a/lib/net.js b/lib/net.js index 232cf906bc6..b35674ae913 100644 --- a/lib/net.js +++ b/lib/net.js @@ -92,20 +92,25 @@ function Socket (peerInfo) { debug('recvBuffer.used ' + recvBuffer.used); var bytesRead; - if (self.type == "unix") { - bytesRead = recvMsg(self.fd, - recvBuffer, - recvBuffer.used, - recvBuffer.length - recvBuffer.used); - debug('recvMsg.fd ' + recvMsg.fd); - if (recvMsg.fd) { - self.emit('fd', recvMsg.fd); + try { + if (self.type == "unix") { + bytesRead = recvMsg(self.fd, + recvBuffer, + recvBuffer.used, + recvBuffer.length - recvBuffer.used); + debug('recvMsg.fd ' + recvMsg.fd); + if (recvMsg.fd) { + self.emit('fd', recvMsg.fd); + } + } else { + bytesRead = read(self.fd, + recvBuffer, + recvBuffer.used, + recvBuffer.length - recvBuffer.used); } - } else { - bytesRead = read(self.fd, - recvBuffer, - recvBuffer.used, - recvBuffer.length - recvBuffer.used); + } catch (e) { + self.forceClose(e); + return; } debug('bytesRead ' + bytesRead + '\n'); @@ -320,15 +325,22 @@ Socket.prototype.flush = function () { } var fdToSend = null; - if (b.isFd) { - fdToSend = parseInt(b.asciiSlice(b.sent, b.used - b.sent)); - bytesWritten = sendFD(self.fd, fdToSend); - } else { - bytesWritten = write(self.fd, - b, - b.sent, - b.used - b.sent); + + try { + if (b.isFd) { + fdToSend = parseInt(b.asciiSlice(b.sent, b.used - b.sent)); + bytesWritten = sendFD(self.fd, fdToSend); + } else { + bytesWritten = write(self.fd, + b, + b.sent, + b.used - b.sent); + } + } catch (e) { + self.forceClose(e); + return; } + if (bytesWritten === null) { // could not flush everything self._writeWatcher.start(); @@ -451,7 +463,14 @@ Socket.prototype.forceClose = function (exception) { Socket.prototype._shutdown = function () { if (this.writable) { this.writable = false; - shutdown(this.fd, 'write'); + + try { + shutdown(this.fd, 'write') + } catch (e) { + this.forceClose(e); + return; + } + if (!this.readable) this.forceClose(); } }; From 979f5889d5366e93e6fc4355e891f34098055059 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Fri, 5 Feb 2010 20:14:48 -0800 Subject: [PATCH 056/105] Optimize: Use callbacks instead of events in net2 --- lib/http2.js | 9 ++++----- lib/net.js | 32 ++++++++++++++++++++------------ 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/lib/http2.js b/lib/http2.js index ab2255bae54..8d9f6a50f68 100644 --- a/lib/http2.js +++ b/lib/http2.js @@ -387,12 +387,11 @@ function connectionListener (socket) { // we need to keep track of the order they were sent. var responses = []; - socket.addListener('dataLite', function (d, start, end) { + socket.ondata = function (d, start, end) { parser.execute(d, start, end - start); - }); + }; - // is this really needed? - socket.addListener('end', function () { + socket.onend = function () { parser.finish(); // unref the parser for easy gc freeParser(parser); @@ -402,7 +401,7 @@ function connectionListener (socket) { } else { responses[responses.length-1].closeOnFinish = true; } - }); + }; parser.socket = socket; // The following callback is issued after the headers have been read on a diff --git a/lib/net.js b/lib/net.js index b35674ae913..a5abd29379a 100644 --- a/lib/net.js +++ b/lib/net.js @@ -1,3 +1,4 @@ +var sys = require("./sys"); var debugLevel = 0; if ('NODE_DEBUG' in process.ENV) debugLevel = 1; function debug (x) { @@ -118,19 +119,23 @@ function Socket (peerInfo) { if (!recvMsg.fd && bytesRead == 0) { self.readable = false; self._readWatcher.stop(); - self.emit('end'); + + if (self._events && self._events['end']) self.emit('end'); + if (self.onend) self.onend(); + if (!self.writable) self.forceClose(); } else if (bytesRead > 0) { var start = recvBuffer.used; var end = recvBuffer.used + bytesRead; - if (self.listeners('data').length) { + + if (self._events && self._events['data']) { // emit a slice self.emit('data', recvBuffer.slice(start, end)); } - if (self.listeners('dataLite').length) { - // emit the original buffer with end points. - self.emit('dataLite', recvBuffer, start, end); - } + + // Optimization: emit the original buffer with end points + if (self.ondata) self.ondata(recvBuffer, start, end); + recvBuffer.used += bytesRead; } }; @@ -141,15 +146,17 @@ function Socket (peerInfo) { self.sendQueueSize = 0; // in bytes, not to be confused with sendQueue.length! self.sendMessageQueueSize = 0; // number of messages remaining to be sent self._doFlush = function () { - /* Socket becomes writeable on connect() but don't flush if there's - * nothing actually to write */ + // Socket becomes writeable on connect() but don't flush if there's + // nothing actually to write if ((self.sendQueueSize == 0) && (self.sendMessageQueueSize == 0)) { return; } if (self.flush()) { assert(self.sendQueueSize == 0); assert(self.sendMessageQueueSize == 0); - self.emit("drain"); + + if (self._events && self._events['drain']) self.emit("drain"); + if (self.ondrain) self.ondrain(); // Optimization } }; self._writeWatcher = ioWatchers.alloc(); @@ -302,7 +309,8 @@ Socket.prototype.sendFD = function(socketToPass) { }; -// Flushes the write buffer out. Emits "drain" if the buffer is empty. +// Flushes the write buffer out. +// Returns true if the entire buffer was flushed. Socket.prototype.flush = function () { var self = this; @@ -314,7 +322,7 @@ Socket.prototype.flush = function () { if (b == END_OF_FILE) { self._shutdown(); - break; + return false; } if (b.sent == b.used) { @@ -338,7 +346,7 @@ Socket.prototype.flush = function () { } } catch (e) { self.forceClose(e); - return; + return false; } if (bytesWritten === null) { From b6edae5671e24cc0b057096b4ab8beac083aea7b Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Mon, 8 Feb 2010 07:36:40 -0800 Subject: [PATCH 057/105] Expose errno exception creation --- lib/net.js | 7 +++---- src/node_net2.cc | 13 +++++++++++++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/lib/net.js b/lib/net.js index a5abd29379a..21e114f4109 100644 --- a/lib/net.js +++ b/lib/net.js @@ -27,6 +27,7 @@ var socketError = process.socketError; var getsockname = process.getsockname; var getaddrinfo = process.getaddrinfo; var needsLookup = process.needsLookup; +var errnoException = process.errnoException; var EINPROGRESS = process.EINPROGRESS; var ENOENT = process.ENOENT; var END_OF_FILE = 0; @@ -397,7 +398,7 @@ Socket.prototype.connect = function () { self._writeWatcher.set(self.fd, false, true); self._writeWatcher.start(); self._writeWatcher.callback = function () { - var errno = socketError(self.fd); + var errno = socketError(self.fd); if (errno == 0) { // connection established self._readWatcher.start(); @@ -406,9 +407,7 @@ Socket.prototype.connect = function () { self._writeWatcher.callback = self._doFlush; self.emit('connect'); } else if (errno != EINPROGRESS) { - var e = new Error('connection error'); - e.errno = errno; - self.forceClose(e); + self.forceClose(errnoException(errno, 'connect')); } }; } diff --git a/src/node_net2.cc b/src/node_net2.cc index 5e7cfef1798..560db1db980 100644 --- a/src/node_net2.cc +++ b/src/node_net2.cc @@ -1246,6 +1246,18 @@ static Handle NeedsLookup(const Arguments& args) { } +static Handle CreateErrnoException(const Arguments& args) { + HandleScope scope; + + int errorno = args[0]->Int32Value(); + String::Utf8Value syscall(args[1]->ToString()); + + Local exception = ErrnoException(errorno, *syscall); + + return scope.Close(exception); +} + + void InitNet2(Handle target) { HandleScope scope; @@ -1275,6 +1287,7 @@ void InitNet2(Handle target) { NODE_SET_METHOD(target, "getpeername", GetPeerName); NODE_SET_METHOD(target, "getaddrinfo", GetAddrInfo); NODE_SET_METHOD(target, "needsLookup", NeedsLookup); + NODE_SET_METHOD(target, "errnoException", CreateErrnoException); target->Set(String::NewSymbol("ENOENT"), Integer::New(ENOENT)); target->Set(String::NewSymbol("EINPROGRESS"), Integer::New(EINPROGRESS)); From 71d237e6a0c9b66a8f2a7335e3f1231b556dc066 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Fri, 26 Feb 2010 12:13:33 -0800 Subject: [PATCH 058/105] Move net2 tests into test/simple --- test/{mjsunit => }/fixtures/net-fd-passing-receiver.js | 0 test/{mjsunit => simple}/test-buffer.js | 2 +- test/{mjsunit => simple}/test-http-parser.js | 2 +- test/{mjsunit => simple}/test-net-fd-passing.js | 2 +- test/{mjsunit => simple}/test-net-pingpong.js | 2 +- 5 files changed, 4 insertions(+), 4 deletions(-) rename test/{mjsunit => }/fixtures/net-fd-passing-receiver.js (100%) rename test/{mjsunit => simple}/test-buffer.js (97%) rename test/{mjsunit => simple}/test-http-parser.js (97%) rename test/{mjsunit => simple}/test-net-fd-passing.js (97%) rename test/{mjsunit => simple}/test-net-pingpong.js (98%) diff --git a/test/mjsunit/fixtures/net-fd-passing-receiver.js b/test/fixtures/net-fd-passing-receiver.js similarity index 100% rename from test/mjsunit/fixtures/net-fd-passing-receiver.js rename to test/fixtures/net-fd-passing-receiver.js diff --git a/test/mjsunit/test-buffer.js b/test/simple/test-buffer.js similarity index 97% rename from test/mjsunit/test-buffer.js rename to test/simple/test-buffer.js index c8792e8980a..481b04b99a3 100644 --- a/test/mjsunit/test-buffer.js +++ b/test/simple/test-buffer.js @@ -1,4 +1,4 @@ -process.mixin(require("./common")); +process.mixin(require("../common")); assert = require("assert"); diff --git a/test/mjsunit/test-http-parser.js b/test/simple/test-http-parser.js similarity index 97% rename from test/mjsunit/test-http-parser.js rename to test/simple/test-http-parser.js index db148c7c118..f41e2d6f4c1 100644 --- a/test/mjsunit/test-http-parser.js +++ b/test/simple/test-http-parser.js @@ -1,4 +1,4 @@ -process.mixin(require("./common")); +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 diff --git a/test/mjsunit/test-net-fd-passing.js b/test/simple/test-net-fd-passing.js similarity index 97% rename from test/mjsunit/test-net-fd-passing.js rename to test/simple/test-net-fd-passing.js index 9b8f8882d7d..0e8620cba78 100644 --- a/test/mjsunit/test-net-fd-passing.js +++ b/test/simple/test-net-fd-passing.js @@ -1,4 +1,4 @@ -process.mixin(require("./common")); +process.mixin(require("../common")); net = require("net"); process.Buffer.prototype.toString = function () { diff --git a/test/mjsunit/test-net-pingpong.js b/test/simple/test-net-pingpong.js similarity index 98% rename from test/mjsunit/test-net-pingpong.js rename to test/simple/test-net-pingpong.js index 7017e414a6b..ded31539ecd 100644 --- a/test/mjsunit/test-net-pingpong.js +++ b/test/simple/test-net-pingpong.js @@ -1,4 +1,4 @@ -process.mixin(require("./common")); +process.mixin(require("../common")); net = require("net"); process.Buffer.prototype.toString = function () { From 16e32c8fd969847719d0b612439197c470b5eb67 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Fri, 26 Feb 2010 12:13:49 -0800 Subject: [PATCH 059/105] Allow for net reconnects --- lib/net.js | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/lib/net.js b/lib/net.js index 21e114f4109..74ed72dd3c6 100644 --- a/lib/net.js +++ b/lib/net.js @@ -71,11 +71,7 @@ function allocRecvBuffer () { recvBuffer.used = 0; } -function Socket (peerInfo) { - process.EventEmitter.call(this); - - var self = this; - +function initSocket (self) { self._readWatcher = ioWatchers.alloc(); self._readWatcher.callback = function () { // If this is the first recv (recvBuffer doesn't exist) or we've used up @@ -163,18 +159,24 @@ function Socket (peerInfo) { self._writeWatcher = ioWatchers.alloc(); self._writeWatcher.callback = self._doFlush; self.writable = false; +} + +function Socket (peerInfo) { + process.EventEmitter.call(this); if (peerInfo) { - self.fd = peerInfo.fd; - self.remoteAddress = peerInfo.remoteAddress; - self.remotePort = peerInfo.remotePort; + initSocket(this); - self._readWatcher.set(self.fd, true, false); - self._readWatcher.start(); - self.readable = true; + this.fd = peerInfo.fd; + this.remoteAddress = peerInfo.remoteAddress; + this.remotePort = peerInfo.remotePort; - self._writeWatcher.set(self.fd, false, true); - self.writable = true; + this._readWatcher.set(this.fd, true, false); + this._readWatcher.start(); + this.readable = true; + + this._writeWatcher.set(this.fd, false, true); + this.writable = true; } }; process.inherits(Socket, process.EventEmitter); @@ -376,6 +378,8 @@ Socket.prototype.flush = function () { // stream.connect(80, 'nodejs.org') - TCP connect to port 80 on nodejs.org // stream.connect('/tmp/socket') - UNIX connect to socket specified by path Socket.prototype.connect = function () { + initSocket(this); + var self = this; if (self.fd) throw new Error('Socket already opened'); From 7bd3280cc2462cf16367c65439031d2d83bced4c Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Tue, 2 Mar 2010 10:59:19 -0800 Subject: [PATCH 060/105] process.inherits -> sys.inherits in net.js --- lib/net.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/net.js b/lib/net.js index 74ed72dd3c6..8e9abd95933 100644 --- a/lib/net.js +++ b/lib/net.js @@ -179,7 +179,7 @@ function Socket (peerInfo) { this.writable = true; } }; -process.inherits(Socket, process.EventEmitter); +sys.inherits(Socket, process.EventEmitter); exports.Socket = Socket; @@ -519,7 +519,7 @@ function Server (listener) { } }; } -process.inherits(Server, process.EventEmitter); +sys.inherits(Server, process.EventEmitter); exports.Server = Server; From 00333d59d6e6e692df9bce40ee94e39f25ed8674 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Tue, 9 Mar 2010 10:33:19 -0800 Subject: [PATCH 061/105] process.inherits -> sys.inherits --- lib/http2.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/http2.js b/lib/http2.js index 8d9f6a50f68..770bd6611d0 100644 --- a/lib/http2.js +++ b/lib/http2.js @@ -429,7 +429,7 @@ function Server (requestListener, options) { //server.setOptions(options); this.addListener('request', requestListener); } -process.inherits(Server, net.Server); +sys.inherits(Server, net.Server); exports.Server = Server; exports.createServer = function (requestListener, options) { return new Server(requestListener, options); From c69d3f4a3556753fc3c4a4ac292710fa0a7b7b7f Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Tue, 9 Mar 2010 10:54:01 -0800 Subject: [PATCH 062/105] Don't use process.mixin in test-buffer.js --- test/simple/test-buffer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/simple/test-buffer.js b/test/simple/test-buffer.js index 481b04b99a3..458dcb4021f 100644 --- a/test/simple/test-buffer.js +++ b/test/simple/test-buffer.js @@ -1,4 +1,4 @@ -process.mixin(require("../common")); +require("../common"); assert = require("assert"); From 93c0c24a4ed4794572913eef3a92ed1e030c8ca9 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Tue, 9 Mar 2010 10:54:26 -0800 Subject: [PATCH 063/105] process.mixin: Return instead of continue in forEach --- src/node.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/node.js b/src/node.js index 0398ba261fb..4946d2efa6e 100644 --- a/src/node.js +++ b/src/node.js @@ -136,7 +136,7 @@ process.mixin = function() { else { // Prevent never-ending loop if (target === d.value) { - continue; + return; } if (deep && d.value && typeof d.value === "object") { From 6310e717e4f1e0468d63ebe4e77b8dbfeee22ed6 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Tue, 9 Mar 2010 11:20:58 -0800 Subject: [PATCH 064/105] Remove the 'Error: (no message)' exceptions print stack trace instead --- src/node.cc | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/node.cc b/src/node.cc index e41d4067f9e..8095ea63cd9 100644 --- a/src/node.cc +++ b/src/node.cc @@ -339,11 +339,6 @@ const char* ToCString(const v8::String::Utf8Value& value) { static void ReportException(TryCatch &try_catch, bool show_line = false) { Handle message = try_catch.Message(); - if (message.IsEmpty()) { - fprintf(stderr, "Error: (no message)\n"); - fflush(stderr); - return; - } Handle error = try_catch.Exception(); Handle stack; @@ -354,7 +349,7 @@ static void ReportException(TryCatch &try_catch, bool show_line = false) { if (raw_stack->IsString()) stack = Handle::Cast(raw_stack); } - if (show_line) { + if (show_line && !message.IsEmpty()) { // Print (filename):(line number): (message). String::Utf8Value filename(message->GetScriptResourceName()); const char* filename_string = ToCString(filename); From 264a67aed2f4ac65a9b3f6fd3c2622793e83e6ad Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Tue, 9 Mar 2010 11:59:42 -0800 Subject: [PATCH 065/105] Update net.js for new stream API --- lib/net.js | 87 ++++++++++++++++-------------- test/simple/test-net-fd-passing.js | 2 +- test/simple/test-net-pingpong.js | 8 +-- 3 files changed, 52 insertions(+), 45 deletions(-) diff --git a/lib/net.js b/lib/net.js index 8e9abd95933..656f8247651 100644 --- a/lib/net.js +++ b/lib/net.js @@ -8,6 +8,7 @@ function debug (x) { } +var Buffer = process.Buffer; var IOWatcher = process.IOWatcher; var assert = process.assert; var socket = process.socket; @@ -61,13 +62,13 @@ var ioWatchers = new FreeList("iowatcher", 100, function () { var nb = 0; var buffers = new FreeList("buffer", 100, function (l) { - return new process.Buffer(500); + return new Buffer(500); }); // Allocated on demand. var recvBuffer = null; function allocRecvBuffer () { - recvBuffer = new process.Buffer(40*1024); + recvBuffer = new Buffer(40*1024); recvBuffer.used = 0; } @@ -138,19 +139,20 @@ function initSocket (self) { }; self.readable = false; - self.sendQueue = []; // queue of buffers that need to be written to socket + self._writeQueue = []; // queue of buffers that need to be written to socket // XXX use link list? - self.sendQueueSize = 0; // in bytes, not to be confused with sendQueue.length! - self.sendMessageQueueSize = 0; // number of messages remaining to be sent + self._writeQueueSize = 0; // in bytes, not to be confused with _writeQueue.length! + self._writeMessageQueueSize = 0; // number of messages remaining to be sent + self._doFlush = function () { // Socket becomes writeable on connect() but don't flush if there's // nothing actually to write - if ((self.sendQueueSize == 0) && (self.sendMessageQueueSize == 0)) { + if ((self._writeQueueSize == 0) && (self._writeMessageQueueSize == 0)) { return; } if (self.flush()) { - assert(self.sendQueueSize == 0); - assert(self.sendMessageQueueSize == 0); + assert(self._writeQueueSize == 0); + assert(self._writeMessageQueueSize == 0); if (self._events && self._events['drain']) self.emit("drain"); if (self.ondrain) self.ondrain(); // Optimization @@ -195,19 +197,19 @@ Socket.prototype._allocateSendBuffer = function () { b.used = 0; b.sent = 0; b.isMsg = false; - this.sendQueue.push(b); + this._writeQueue.push(b); return b; }; -Socket.prototype._sendString = function (data, encoding) { +Socket.prototype._writeString = function (data, encoding) { var self = this; if (!self.writable) throw new Error('Socket is not writable'); var buffer; - if (self.sendQueue.length == 0) { + if (self._writeQueue.length == 0) { buffer = self._allocateSendBuffer(); } else { - buffer = self._sendQueueLast(); + buffer = self.__writeQueueLast(); if (buffer.used == buffer.length) { buffer = self._allocateSendBuffer(); } @@ -230,7 +232,7 @@ Socket.prototype._sendString = function (data, encoding) { charsWritten = buffer.utf8Write(data, buffer.used, buffer.length - buffer.used); - bytesWritten = process.Buffer.utf8ByteLength(data.slice(0, charsWritten)); + bytesWritten = Buffer.utf8ByteLength(data.slice(0, charsWritten)); } else { // ascii buffer.isFd = false; @@ -242,9 +244,9 @@ Socket.prototype._sendString = function (data, encoding) { buffer.used += bytesWritten; if (buffer.isFd) { - self.sendMessageQueueSize += 1; + self._writeMessageQueueSize += 1; } else { - self.sendQueueSize += bytesWritten; + self._writeQueueSize += bytesWritten; } debug('charsWritten ' + charsWritten); @@ -252,40 +254,45 @@ Socket.prototype._sendString = function (data, encoding) { // If we didn't finish, then recurse with the rest of the string. if (charsWritten < data.length) { - debug('recursive send'); - self._sendString(data.slice(charsWritten), encoding); + debug('recursive write'); + self._writeString(data.slice(charsWritten), encoding); } }; -Socket.prototype._sendQueueLast = function () { - return this.sendQueue.length > 0 ? this.sendQueue[this.sendQueue.length-1] +Socket.prototype.__writeQueueLast = function () { + return this._writeQueue.length > 0 ? this._writeQueue[this._writeQueue.length-1] : null; }; +Socket.prototype.send = function () { + throw new Error('send renamed to write'); +}; + + // Returns true if all the data was flushed to socket. Returns false if // something was queued. If data was queued, then the "drain" event will // signal when it has been finally flushed to socket. -Socket.prototype.send = function (data, encoding) { +Socket.prototype.write = function (data, encoding) { var self = this; if (!self.writable) throw new Error('Socket is not writable'); - if (self._sendQueueLast() == END_OF_FILE) { + if (self.__writeQueueLast() == END_OF_FILE) { throw new Error('socket.close() called already; cannot write.'); } if (typeof(data) == 'string') { - self._sendString(data, encoding); + self._writeString(data, encoding); } else { - // data is a process.Buffer - // walk through the sendQueue, find the first empty buffer + // data is a Buffer + // walk through the _writeQueue, find the first empty buffer //var inserted = false; data.sent = 0; data.used = data.length; - self.sendQueue.push(data); - self.sendQueueSize += data.used; + self._writeQueue.push(data); + self._writeQueueSize += data.used; } return this.flush(); }; @@ -296,7 +303,7 @@ Socket.prototype.sendFD = function(socketToPass) { if (!self.writable) throw new Error('Socket is not writable'); - if (self._sendQueueLast() == END_OF_FILE) { + if (self.__writeQueueLast() == END_OF_FILE) { throw new Error('socket.close() called already; cannot write.'); } @@ -308,7 +315,7 @@ Socket.prototype.sendFD = function(socketToPass) { throw new Error('Provided arg is not a socket'); } - return self.send(socketToPass.fd.toString(), "fd"); + return self.write(socketToPass.fd.toString(), "fd"); }; @@ -318,10 +325,10 @@ Socket.prototype.flush = function () { var self = this; var bytesWritten; - while (self.sendQueue.length) { + while (self._writeQueue.length) { if (!self.writable) throw new Error('Socket is not writable'); - var b = self.sendQueue[0]; + var b = self._writeQueue[0]; if (b == END_OF_FILE) { self._shutdown(); @@ -330,7 +337,7 @@ Socket.prototype.flush = function () { if (b.sent == b.used) { // shift! - self.sendQueue.shift(); + self._writeQueue.shift(); buffers.free(b); continue; } @@ -340,7 +347,7 @@ Socket.prototype.flush = function () { try { if (b.isFd) { fdToSend = parseInt(b.asciiSlice(b.sent, b.used - b.sent)); - bytesWritten = sendFD(self.fd, fdToSend); + bytesWritten = writeFD(self.fd, fdToSend); } else { bytesWritten = write(self.fd, b, @@ -355,16 +362,16 @@ Socket.prototype.flush = function () { if (bytesWritten === null) { // could not flush everything self._writeWatcher.start(); - assert(self.sendQueueSize > 0); + assert(self._writeQueueSize > 0); return false; } if (b.isFd) { b.sent = b.used; - self.sendMessageQueueSize -= 1; + self._writeMessageQueueSize -= 1; debug('sent fd: ' + fdToSend); } else { b.sent += bytesWritten; - self.sendQueueSize -= bytesWritten; + self._writeQueueSize -= bytesWritten; debug('bytes sent: ' + b.sent); } } @@ -446,9 +453,9 @@ Socket.prototype.forceClose = function (exception) { // recvBuffer is shared between sockets, so don't need to free it here. var b; - while (this.sendQueue.length) { - b = this.sendQueue.shift(); - if (b instanceof process.Buffer) buffers.free(b); + while (this._writeQueue.length) { + b = this._writeQueue.shift(); + if (b instanceof Buffer) buffers.free(b); } if (this._writeWatcher) { @@ -489,8 +496,8 @@ Socket.prototype._shutdown = function () { Socket.prototype.close = function () { if (this.writable) { - if (this._sendQueueLast() != END_OF_FILE) { - this.sendQueue.push(END_OF_FILE); + if (this.__writeQueueLast() != END_OF_FILE) { + this._writeQueue.push(END_OF_FILE); this.flush(); } } diff --git a/test/simple/test-net-fd-passing.js b/test/simple/test-net-fd-passing.js index 0e8620cba78..1cf577bf6af 100644 --- a/test/simple/test-net-fd-passing.js +++ b/test/simple/test-net-fd-passing.js @@ -29,7 +29,7 @@ function fdPassingTest(path, port) { var client = net.createConnection(port); client.addListener("connect", function() { - client.send(message); + client.write(message); }); client.addListener("data", function(data) { diff --git a/test/simple/test-net-pingpong.js b/test/simple/test-net-pingpong.js index ded31539ecd..560f943a515 100644 --- a/test/simple/test-net-pingpong.js +++ b/test/simple/test-net-pingpong.js @@ -25,7 +25,7 @@ function pingPongTest (port, host) { assert.equal(true, socket.readable); assert.equal(true, count <= N); if (/PING/.exec(data)) { - socket.send("PONG"); + socket.write("PONG"); } }); @@ -50,7 +50,7 @@ function pingPongTest (port, host) { client.addListener("connect", function () { assert.equal(true, client.readable); assert.equal(true, client.writable); - client.send("PING"); + client.write("PING"); }); client.addListener("data", function (data) { @@ -69,10 +69,10 @@ function pingPongTest (port, host) { } if (count < N) { - client.send("PING"); + client.write("PING"); } else { sent_final_ping = true; - client.send("PING"); + client.write("PING"); client.close(); } }); From b07f2e25f414dde014528705ddbc0823d1aeb89f Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Tue, 9 Mar 2010 12:00:06 -0800 Subject: [PATCH 066/105] Update http2 for new stream API --- benchmark/http_simple.js | 7 ++----- lib/http2.js | 41 +++++++++++++++++++++++++--------------- 2 files changed, 28 insertions(+), 20 deletions(-) diff --git a/benchmark/http_simple.js b/benchmark/http_simple.js index 9025e5d9406..2f80b43ff6a 100644 --- a/benchmark/http_simple.js +++ b/benchmark/http_simple.js @@ -1,10 +1,7 @@ path = require("path"); -libDir = path.join(path.dirname(__filename), "../lib"); -require.paths.unshift(libDir); - -var puts = require("sys").puts; -http = require("http"); +var puts = require("../lib/sys").puts; +http = require("../lib/http2"); fixed = "" for (var i = 0; i < 20*1024; i++) { diff --git a/lib/http2.js b/lib/http2.js index 770bd6611d0..8b26a3f1b61 100644 --- a/lib/http2.js +++ b/lib/http2.js @@ -111,7 +111,7 @@ function OutgoingMessage () { sys.inherits(OutgoingMessage, events.EventEmitter); exports.OutgoingMessage = OutgoingMessage; -OutgoingMessage.prototype.send = function (data, encoding) { +OutgoingMessage.prototype._send = function (data, encoding) { var length = this.output.length; if (length === 0) { @@ -139,7 +139,7 @@ OutgoingMessage.prototype.send = function (data, encoding) { this.outputEncodings.push(encoding); }; -OutgoingMessage.prototype.sendHeaderLines = function (first_line, headers) { +OutgoingMessage.prototype._sendHeaderLines = function (first_line, headers) { var sentConnectionHeader = false; var sendContentLengthHeader = false; var sendTransferEncodingHeader = false; @@ -197,19 +197,19 @@ OutgoingMessage.prototype.sendHeaderLines = function (first_line, headers) { messageHeader += CRLF; - this.send(messageHeader); + this._send(messageHeader); // wait until the first body chunk, or finish(), is sent to flush. }; -OutgoingMessage.prototype.sendBody = function (chunk, encoding) { +OutgoingMessage.prototype.write = function (chunk, encoding) { encoding = encoding || "ascii"; if (this.chunkEncoding) { - this.send(process._byteLength(chunk, encoding).toString(16)); - this.send(CRLF); - this.send(chunk, encoding); - this.send(CRLF); + this._send(process._byteLength(chunk, encoding).toString(16)); + this._send(CRLF); + this._send(chunk, encoding); + this._send(CRLF); } else { - this.send(chunk, encoding); + this._send(chunk, encoding); } if (this.flushing) { @@ -219,12 +219,17 @@ OutgoingMessage.prototype.sendBody = function (chunk, encoding) { } }; +OutgoingMessage.prototype.sendBody = function () { + throw new Error('sendBody() renamed to write()'); +}; + + OutgoingMessage.prototype.flush = function () { this.emit("flush"); }; -OutgoingMessage.prototype.finish = function () { - if (this.chunkEncoding) this.send("0\r\n\r\n"); // last chunk +OutgoingMessage.prototype.close = function () { + if (this.chunkEncoding) this._send("0\r\n\r\n"); // last chunk this.finished = true; this.flush(); }; @@ -241,10 +246,16 @@ function ServerResponse (req) { sys.inherits(ServerResponse, OutgoingMessage); exports.ServerResponse = ServerResponse; -ServerResponse.prototype.sendHeader = function (statusCode, headers) { +ServerResponse.prototype.writeHead = function (statusCode, headers) { var reason = STATUS_CODES[statusCode] || "unknown"; var status_line = "HTTP/1.1 " + statusCode.toString() + " " + reason + CRLF; - this.sendHeaderLines(status_line, headers); + this._sendHeaderLines(status_line, headers); +}; + +ServerResponse.prototype.writeHeader = ServerResponse.prototype.writeHead; + +ServerResponse.prototype.sendHeader = function () { + throw new Error('sendHeader renamed to writeHead()'); }; @@ -259,7 +270,7 @@ function ClientRequest (method, url, headers) { } this.closeOnFinish = true; - this.sendHeaderLines(method + " " + url + " HTTP/1.1\r\n", headers); + this._sendHeaderLines(method + " " + url + " HTTP/1.1\r\n", headers); } sys.inherits(ClientRequest, OutgoingMessage); exports.ClientRequest = ClientRequest; @@ -282,7 +293,7 @@ function flushMessageQueue (socket, queue) { var data = message.output.shift(); var encoding = message.outputEncodings.shift(); - socket.send(data, encoding); + socket.write(data, encoding); } if (!message.finished) break; From c1a0ade7e79067533a2d292d6d7aa3fe554eaf9c Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Tue, 9 Mar 2010 16:27:49 -0800 Subject: [PATCH 067/105] Further net2 compatibilities --- lib/net.js | 184 +++++++++++++++++---------- src/node_buffer.cc | 12 +- test/pummel/test-tcp-many-clients.js | 36 ++++-- test/pummel/test-tcp-pause.js | 6 +- test/simple/test-tcp-reconnect.js | 45 ++++--- 5 files changed, 174 insertions(+), 109 deletions(-) diff --git a/lib/net.js b/lib/net.js index 656f8247651..820a9a8eb05 100644 --- a/lib/net.js +++ b/lib/net.js @@ -1,4 +1,5 @@ var sys = require("./sys"); +var fs = require("./fs"); var debugLevel = 0; if ('NODE_DEBUG' in process.ENV) debugLevel = 1; function debug (x) { @@ -43,13 +44,13 @@ function FreeList (name, max, constructor) { } FreeList.prototype.alloc = function () { - debug("alloc " + this.name + " " + this.list.length); + //debug("alloc " + this.name + " " + this.list.length); return this.list.length ? this.list.shift() : this.constructor.apply(this, arguments); } FreeList.prototype.free = function (obj) { - debug("free " + this.name + " " + this.list.length); + //debug("free " + this.name + " " + this.list.length); if (this.list.length < this.max) { this.list.push(obj); } @@ -88,7 +89,7 @@ function initSocket (self) { allocRecvBuffer(); } - debug('recvBuffer.used ' + recvBuffer.used); + //debug('recvBuffer.used ' + recvBuffer.used); var bytesRead; try { @@ -97,7 +98,7 @@ function initSocket (self) { recvBuffer, recvBuffer.used, recvBuffer.length - recvBuffer.used); - debug('recvMsg.fd ' + recvMsg.fd); + //debug('recvMsg.fd ' + recvMsg.fd); if (recvMsg.fd) { self.emit('fd', recvMsg.fd); } @@ -112,7 +113,7 @@ function initSocket (self) { return; } - debug('bytesRead ' + bytesRead + '\n'); + //debug('bytesRead ' + bytesRead + '\n'); if (!recvMsg.fd && bytesRead == 0) { self.readable = false; @@ -125,14 +126,32 @@ function initSocket (self) { } else if (bytesRead > 0) { var start = recvBuffer.used; var end = recvBuffer.used + bytesRead; - - if (self._events && self._events['data']) { - // emit a slice - self.emit('data', recvBuffer.slice(start, end)); + + if (!self._encoding) { + if (self._events && self._events['data']) { + // emit a slice + self.emit('data', recvBuffer.slice(start, end)); + } + + // Optimization: emit the original buffer with end points + if (self.ondata) self.ondata(recvBuffer, start, end); + } else { + // TODO remove me - we should only output Buffer + + var string; + switch (self._encoding) { + case 'utf8': + string = recvBuffer.utf8Slice(start, end); + break; + case 'ascii': + string = recvBuffer.asciiSlice(start, end); + break; + default: + throw new Error('Unsupported encoding ' + self._encoding + '. Use Buffer'); + } + self.emit('data', string); } - // Optimization: emit the original buffer with end points - if (self.ondata) self.ondata(recvBuffer, start, end); recvBuffer.used += bytesRead; } @@ -173,8 +192,7 @@ function Socket (peerInfo) { this.remoteAddress = peerInfo.remoteAddress; this.remotePort = peerInfo.remotePort; - this._readWatcher.set(this.fd, true, false); - this._readWatcher.start(); + this.resume(); this.readable = true; this._writeWatcher.set(this.fd, false, true); @@ -206,6 +224,7 @@ Socket.prototype._writeString = function (data, encoding) { var self = this; if (!self.writable) throw new Error('Socket is not writable'); var buffer; + if (self._writeQueue.length == 0) { buffer = self._allocateSendBuffer(); } else { @@ -229,16 +248,12 @@ Socket.prototype._writeString = function (data, encoding) { bytesWritten = charsWritten; } else if (encoding.toLowerCase() == 'utf8') { buffer.isFd = false; - charsWritten = buffer.utf8Write(data, - buffer.used, - buffer.length - buffer.used); + charsWritten = buffer.utf8Write(data, buffer.used); bytesWritten = Buffer.utf8ByteLength(data.slice(0, charsWritten)); } else { // ascii buffer.isFd = false; - charsWritten = buffer.asciiWrite(data, - buffer.used, - buffer.length - buffer.used); + charsWritten = buffer.asciiWrite(data, buffer.used); bytesWritten = charsWritten; } @@ -249,12 +264,12 @@ Socket.prototype._writeString = function (data, encoding) { self._writeQueueSize += bytesWritten; } - debug('charsWritten ' + charsWritten); - debug('buffer.used ' + buffer.used); + //debug('charsWritten ' + charsWritten); + //debug('buffer.used ' + buffer.used); // If we didn't finish, then recurse with the rest of the string. if (charsWritten < data.length) { - debug('recursive write'); + //debug('recursive write'); self._writeString(data.slice(charsWritten), encoding); } }; @@ -270,6 +285,10 @@ Socket.prototype.send = function () { throw new Error('send renamed to write'); }; +Socket.prototype.setEncoding = function (enc) { + // TODO check values, error out on bad, and deprecation message? + this._encoding = enc.toLowerCase(); +}; // Returns true if all the data was flushed to socket. Returns false if // something was queued. If data was queued, then the "drain" event will @@ -368,11 +387,11 @@ Socket.prototype.flush = function () { if (b.isFd) { b.sent = b.used; self._writeMessageQueueSize -= 1; - debug('sent fd: ' + fdToSend); + //debug('sent fd: ' + fdToSend); } else { b.sent += bytesWritten; self._writeQueueSize -= bytesWritten; - debug('bytes sent: ' + b.sent); + //debug('bytes sent: ' + b.sent); } } if (self._writeWatcher) self._writeWatcher.stop(); @@ -380,6 +399,39 @@ Socket.prototype.flush = function () { }; +function doConnect (socket, port, host) { + try { + connect(socket.fd, port, host); + } catch (e) { + socket.forceClose(e); + } + + // Don't start the read watcher until connection is established + socket._readWatcher.set(socket.fd, true, false); + + // How to connect on POSIX: Wait for fd to become writable, then call + // socketError() if there isn't an error, we're connected. AFAIK this a + // platform independent way determining when a non-blocking connection + // is established, but I have only seen it documented in the Linux + // Manual Page connect(2) under the error code EINPROGRESS. + socket._writeWatcher.set(socket.fd, false, true); + socket._writeWatcher.start(); + socket._writeWatcher.callback = function () { + var errno = socketError(socket.fd); + if (errno == 0) { + // connection established + socket._readWatcher.start(); + socket.readable = true; + socket.writable = true; + socket._writeWatcher.callback = socket._doFlush; + socket.emit('connect'); + } else if (errno != EINPROGRESS) { + socket.forceClose(errnoException(errno, 'connect')); + } + }; +} + + // var stream = new Socket(); // stream.connect(80) - TCP connect to port 80 on the localhost // stream.connect(80, 'nodejs.org') - TCP connect to port 80 on nodejs.org @@ -390,55 +442,28 @@ Socket.prototype.connect = function () { var self = this; if (self.fd) throw new Error('Socket already opened'); - function doConnect () { - try { - connect(self.fd, arguments[0], arguments[1]); - } catch (e) { - close(self.fd); - throw e; - } - - // Don't start the read watcher until connection is established - self._readWatcher.set(self.fd, true, false); - - // How to connect on POSIX: Wait for fd to become writable, then call - // socketError() if there isn't an error, we're connected. AFAIK this a - // platform independent way determining when a non-blocking connection - // is established, but I have only seen it documented in the Linux - // Manual Page connect(2) under the error code EINPROGRESS. - self._writeWatcher.set(self.fd, false, true); - self._writeWatcher.start(); - self._writeWatcher.callback = function () { - var errno = socketError(self.fd); - if (errno == 0) { - // connection established - self._readWatcher.start(); - self.readable = true; - self.writable = true; - self._writeWatcher.callback = self._doFlush; - self.emit('connect'); - } else if (errno != EINPROGRESS) { - self.forceClose(errnoException(errno, 'connect')); - } - }; - } - if (typeof(arguments[0]) == 'string') { self.fd = socket('unix'); self.type = 'unix'; // TODO check if sockfile exists? - doConnect(arguments[0]); + doConnect(self, arguments[0]); } else { self.fd = socket('tcp'); + debug('new fd = ' + self.fd); self.type = 'tcp'; // TODO dns resolution on arguments[1] var port = arguments[0]; + var yyy = xxx++; lookupDomainName(arguments[1], function (ip) { - doConnect(port, ip); + debug('doConnect ' + self.fd + ' yyy=' + yyy); + doConnect(self, port, ip); + debug('doConnect done ' + self.fd + ' yyy=' + yyy); }); } }; +var xxx = 0; + Socket.prototype.address = function () { return getsockname(this.fd); @@ -449,8 +474,19 @@ Socket.prototype.setNoDelay = function (v) { }; +Socket.prototype.pause = function () { + this._readWatcher.stop(); +}; + +Socket.prototype.resume = function () { + if (!this.fd) throw new Error('Cannot resume() closed Socket.'); + this._readWatcher.set(this.fd, true, false); + this._readWatcher.start(); +}; + Socket.prototype.forceClose = function (exception) { // recvBuffer is shared between sockets, so don't need to free it here. + var self = this; var b; while (this._writeQueue.length) { @@ -472,8 +508,12 @@ Socket.prototype.forceClose = function (exception) { if (this.fd) { close(this.fd); + debug('close ' + this.fd); this.fd = null; - this.emit('close', exception); + process.nextTick(function () { + if (exception) self.emit('error', exception); + self.emit('close', exception ? true : false); + }); } }; @@ -517,12 +557,14 @@ function Server (listener) { self.watcher.callback = function (readable, writeable) { while (self.fd) { var peerInfo = accept(self.fd); - debug('accept: ' + JSON.stringify(peerInfo)); if (!peerInfo) return; var peer = new Socket(peerInfo); peer.type = self.type; peer.server = self; self.emit('connection', peer); + // The 'connect' event probably should be removed for server-side + // sockets. It's redundent. + peer.emit('connect'); } }; } @@ -539,7 +581,13 @@ exports.createServer = function (listener) { */ function lookupDomainName (dn, callback) { if (!needsLookup(dn)) { - callback(dn); + // Always wait until the next tick this is so people can do + // + // server.listen(8000); + // server.addListener('listening', fn); + // + // Marginally slower, but a lot fewer WTFs. + process.nextTick(function () { callback(dn); }) } else { debug("getaddrinfo 4 " + dn); getaddrinfo(dn, 4, function (r4) { @@ -589,9 +637,9 @@ Server.prototype.listen = function () { var path = arguments[0]; self.path = path; // unlink sockfile if it exists - process.fs.stat(path, function (r) { - if (r instanceof Error) { - if (r.errno == ENOENT) { + fs.stat(path, function (err, r) { + if (err) { + if (err.errno == ENOENT) { bind(self.fd, path); doListen(); } else { @@ -601,7 +649,7 @@ Server.prototype.listen = function () { if (!r.isFile()) { throw new Error("Non-file exists at " + path); } else { - process.fs.unlink(path, function (err) { + fs.unlink(path, function (err) { if (err) { throw err; } else { @@ -623,9 +671,7 @@ Server.prototype.listen = function () { self.fd = socket('tcp'); self.type = 'tcp'; var port = arguments[0]; - debug("starting tcp server on port " + port); lookupDomainName(arguments[1], function (ip) { - debug("starting tcp server on ip " + ip); bind(self.fd, port, ip); doListen(); }); @@ -648,7 +694,7 @@ Server.prototype.close = function () { self.fd = null; if (self.type === "unix") { - process.fs.unlink(self.path, function () { + fs.unlink(self.path, function () { self.emit("close"); }); } else { diff --git a/src/node_buffer.cc b/src/node_buffer.cc index d5b68aa7c56..70820dd49b3 100644 --- a/src/node_buffer.cc +++ b/src/node_buffer.cc @@ -8,6 +8,8 @@ #include +#define MIN(a,b) ((a) < (b) ? (a) : (b)) + namespace node { using namespace v8; @@ -226,7 +228,8 @@ Handle Buffer::Utf8Write(const Arguments &args) { "Not enough space in Buffer for string"))); } - int written = s->WriteUtf8((char*)p); + int written = s->WriteUtf8((char*)p, buffer->length_ - offset); + return scope.Close(Integer::New(written)); } @@ -253,12 +256,9 @@ Handle Buffer::AsciiWrite(const Arguments &args) { const char *p = buffer->data_ + offset; - if (s->Length() + offset > buffer->length_) { - return ThrowException(Exception::TypeError(String::New( - "Not enough space in Buffer for string"))); - } + size_t towrite = MIN(s->Length(), buffer->length_ - offset); - int written = s->WriteAscii((char*)p); + int written = s->WriteAscii((char*)p, 0, towrite); return scope.Close(Integer::New(written)); } diff --git a/test/pummel/test-tcp-many-clients.js b/test/pummel/test-tcp-many-clients.js index f6de67bc2d8..1c27169af4f 100644 --- a/test/pummel/test-tcp-many-clients.js +++ b/test/pummel/test-tcp-many-clients.js @@ -1,5 +1,5 @@ require("../common"); -tcp = require("tcp"); +net = require("net"); // settings var bytes = 1024*40; var concurrency = 100; @@ -13,7 +13,7 @@ for (var i = 0; i < bytes; i++) { body += "C"; } -var server = tcp.createServer(function (c) { +var server = net.createServer(function (c) { c.addListener("connect", function () { total_connections++; print("#"); @@ -24,8 +24,10 @@ var server = tcp.createServer(function (c) { server.listen(PORT); function runClient (callback) { - var client = tcp.createConnection(PORT); + var client = net.createConnection(PORT); + client.connections = 0; + client.setEncoding("utf8"); client.addListener("connect", function () { @@ -38,14 +40,25 @@ function runClient (callback) { this.recved += chunk; }); - client.addListener("end", function (had_error) { + client.addListener("end", function () { client.close(); }); + client.addListener("error", function (e) { + puts("\n\nERROOOOOr"); + throw e; + }); + client.addListener("close", function (had_error) { print("."); assert.equal(false, had_error); assert.equal(bytes, client.recved.length); + + if (client.fd) { + puts(client.fd); + } + assert.ok(!client.fd); + if (this.connections < connections_per_client) { this.connect(PORT); } else { @@ -54,13 +67,14 @@ function runClient (callback) { }); } - -var finished_clients = 0; -for (var i = 0; i < concurrency; i++) { - runClient(function () { - if (++finished_clients == concurrency) server.close(); - }); -} +server.addListener('listening', function () { + var finished_clients = 0; + for (var i = 0; i < concurrency; i++) { + runClient(function () { + if (++finished_clients == concurrency) server.close(); + }); + } +}); process.addListener("exit", function () { assert.equal(connections_per_client * concurrency, total_connections); diff --git a/test/pummel/test-tcp-pause.js b/test/pummel/test-tcp-pause.js index 71b83df9e22..adb61544013 100644 --- a/test/pummel/test-tcp-pause.js +++ b/test/pummel/test-tcp-pause.js @@ -1,8 +1,8 @@ require("../common"); -tcp = require("tcp"); +net = require("net"); N = 200; -server = tcp.createServer(function (connection) { +server = net.createServer(function (connection) { function write (j) { if (j >= N) { connection.close(); @@ -21,7 +21,7 @@ server.listen(PORT); recv = ""; chars_recved = 0; -client = tcp.createConnection(PORT); +client = net.createConnection(PORT); client.setEncoding("ascii"); client.addListener("data", function (d) { print(d); diff --git a/test/simple/test-tcp-reconnect.js b/test/simple/test-tcp-reconnect.js index 2a1ce652a54..66d93e30d63 100644 --- a/test/simple/test-tcp-reconnect.js +++ b/test/simple/test-tcp-reconnect.js @@ -1,12 +1,12 @@ require("../common"); -tcp = require("tcp"); +net = require('net'); var N = 50; var c = 0; var client_recv_count = 0; var disconnect_count = 0; -var server = tcp.createServer(function (socket) { +var server = net.createServer(function (socket) { socket.addListener("connect", function () { socket.write("hello\r\n"); }); @@ -20,33 +20,38 @@ var server = tcp.createServer(function (socket) { assert.equal(false, had_error); }); }); + server.listen(PORT); -var client = tcp.createConnection(PORT); +server.addListener('listening', function () { + puts('listening'); + var client = net.createConnection(PORT); -client.setEncoding("UTF8"); + client.setEncoding("UTF8"); -client.addListener("connect", function () { - puts("client connected."); -}); + client.addListener("connect", function () { + puts("client connected."); + }); -client.addListener("data", function (chunk) { - client_recv_count += 1; - puts("client_recv_count " + client_recv_count); - assert.equal("hello\r\n", chunk); - client.close(); -}); + client.addListener("data", function (chunk) { + client_recv_count += 1; + puts("client_recv_count " + client_recv_count); + assert.equal("hello\r\n", chunk); + client.close(); + }); -client.addListener("close", function (had_error) { - puts("disconnect"); - assert.equal(false, had_error); - if (disconnect_count++ < N) - client.connect(PORT); // reconnect - else - server.close(); + client.addListener("close", function (had_error) { + puts("disconnect"); + assert.equal(false, had_error); + if (disconnect_count++ < N) + client.connect(PORT); // reconnect + else + server.close(); + }); }); process.addListener("exit", function () { assert.equal(N+1, disconnect_count); assert.equal(N+1, client_recv_count); }); + From 0918bb2070b8bea73cfb3d36f284904b6884239c Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Tue, 9 Mar 2010 18:37:23 -0800 Subject: [PATCH 068/105] Adjust a few more tests to work with net2 --- lib/net.js | 61 ++++++++++++++++---------- test/pummel/test-tcp-pingpong-delay.js | 6 +-- test/pummel/test-tcp-pingpong.js | 8 ++-- test/pummel/test-tcp-throttle.js | 8 ++-- 4 files changed, 49 insertions(+), 34 deletions(-) diff --git a/lib/net.js b/lib/net.js index 820a9a8eb05..a7f0f35c322 100644 --- a/lib/net.js +++ b/lib/net.js @@ -73,6 +73,22 @@ function allocRecvBuffer () { recvBuffer.used = 0; } +function _doFlush () { + var socket = this.socket; + // Socket becomes writeable on connect() but don't flush if there's + // nothing actually to write + if ((socket._writeQueueSize == 0) && (socket._writeMessageQueueSize == 0)) { + return; + } + if (socket.flush()) { + assert(socket._writeQueueSize == 0); + assert(socket._writeMessageQueueSize == 0); + + if (socket._events && socket._events['drain']) socket.emit("drain"); + if (socket.ondrain) socket.ondrain(); // Optimization + } +} + function initSocket (self) { self._readWatcher = ioWatchers.alloc(); self._readWatcher.callback = function () { @@ -163,22 +179,9 @@ function initSocket (self) { self._writeQueueSize = 0; // in bytes, not to be confused with _writeQueue.length! self._writeMessageQueueSize = 0; // number of messages remaining to be sent - self._doFlush = function () { - // Socket becomes writeable on connect() but don't flush if there's - // nothing actually to write - if ((self._writeQueueSize == 0) && (self._writeMessageQueueSize == 0)) { - return; - } - if (self.flush()) { - assert(self._writeQueueSize == 0); - assert(self._writeMessageQueueSize == 0); - - if (self._events && self._events['drain']) self.emit("drain"); - if (self.ondrain) self.ondrain(); // Optimization - } - }; self._writeWatcher = ioWatchers.alloc(); - self._writeWatcher.callback = self._doFlush; + self._writeWatcher.socket = self; + self._writeWatcher.callback = _doFlush; self.writable = false; } @@ -210,6 +213,21 @@ exports.createConnection = function (port, host) { }; +Object.defineProperty(Socket.prototype, 'readyState', { + get: function () { + if (this.readable && this.writable) { + return 'open'; + } else if (this.readable && !this.writable){ + return 'readOnly'; + } else if (!this.readable && this.writable){ + return 'writeOnly'; + } else { + return 'closed'; + } + } +}); + + Socket.prototype._allocateSendBuffer = function () { var b = buffers.alloc(1024); b.used = 0; @@ -351,7 +369,7 @@ Socket.prototype.flush = function () { if (b == END_OF_FILE) { self._shutdown(); - return false; + return true; } if (b.sent == b.used) { @@ -379,19 +397,19 @@ Socket.prototype.flush = function () { } if (bytesWritten === null) { - // could not flush everything + // EAGAIN + debug('write EAGAIN'); self._writeWatcher.start(); assert(self._writeQueueSize > 0); return false; - } - if (b.isFd) { + } else if (b.isFd) { b.sent = b.used; self._writeMessageQueueSize -= 1; //debug('sent fd: ' + fdToSend); } else { b.sent += bytesWritten; self._writeQueueSize -= bytesWritten; - //debug('bytes sent: ' + b.sent); + debug('bytes sent: ' + b.sent); } } if (self._writeWatcher) self._writeWatcher.stop(); @@ -453,11 +471,8 @@ Socket.prototype.connect = function () { self.type = 'tcp'; // TODO dns resolution on arguments[1] var port = arguments[0]; - var yyy = xxx++; lookupDomainName(arguments[1], function (ip) { - debug('doConnect ' + self.fd + ' yyy=' + yyy); doConnect(self, port, ip); - debug('doConnect done ' + self.fd + ' yyy=' + yyy); }); } }; diff --git a/test/pummel/test-tcp-pingpong-delay.js b/test/pummel/test-tcp-pingpong-delay.js index b0a2a18aaf3..a2015c373e7 100644 --- a/test/pummel/test-tcp-pingpong-delay.js +++ b/test/pummel/test-tcp-pingpong-delay.js @@ -1,5 +1,5 @@ require("../common"); -tcp = require("tcp"); +net = require("net"); var tests_run = 0; @@ -10,7 +10,7 @@ function pingPongTest (port, host, on_complete) { var count = 0; var client_closed = false; - var server = tcp.createServer(function (socket) { + var server = net.createServer(function (socket) { socket.setEncoding("utf8"); socket.addListener("data", function (data) { @@ -44,7 +44,7 @@ function pingPongTest (port, host, on_complete) { }); server.listen(port, host); - var client = tcp.createConnection(port, host); + var client = net.createConnection(port, host); client.setEncoding("utf8"); diff --git a/test/pummel/test-tcp-pingpong.js b/test/pummel/test-tcp-pingpong.js index 135b9a5958d..9a3266c9673 100644 --- a/test/pummel/test-tcp-pingpong.js +++ b/test/pummel/test-tcp-pingpong.js @@ -1,5 +1,5 @@ require("../common"); -tcp = require("tcp"); +net = require("net"); var tests_run = 0; @@ -8,13 +8,13 @@ function pingPongTest (port, host, on_complete) { var count = 0; var sent_final_ping = false; - var server = tcp.createServer(function (socket) { + var server = net.createServer(function (socket) { assert.equal(true, socket.remoteAddress !== null); assert.equal(true, socket.remoteAddress !== undefined); if (host === "127.0.0.1") assert.equal(socket.remoteAddress, "127.0.0.1"); else if (host == null) - assert.equal(socket.remoteAddress, "127.0.0.1"); + assert.equal(socket.remoteAddress, "::1"); socket.setEncoding("utf8"); socket.setNoDelay(); @@ -42,7 +42,7 @@ function pingPongTest (port, host, on_complete) { }); server.listen(port, host); - var client = tcp.createConnection(port, host); + var client = net.createConnection(port, host); client.setEncoding("utf8"); diff --git a/test/pummel/test-tcp-throttle.js b/test/pummel/test-tcp-throttle.js index 2e6fdef3a15..029d3645726 100644 --- a/test/pummel/test-tcp-throttle.js +++ b/test/pummel/test-tcp-throttle.js @@ -1,6 +1,6 @@ require("../common"); -tcp = require("tcp"); -N = 60*1024; // 30kb +net = require("net"); +N = 160*1024; // 30kb puts("build big string"); var body = ""; @@ -10,7 +10,7 @@ for (var i = 0; i < N; i++) { puts("start server on port " + PORT); -server = tcp.createServer(function (connection) { +server = net.createServer(function (connection) { connection.addListener("connect", function () { assert.equal(false, connection.write(body)); connection.close(); @@ -24,7 +24,7 @@ npauses = 0; var paused = false; -client = tcp.createConnection(PORT); +client = net.createConnection(PORT); client.setEncoding("ascii"); client.addListener("data", function (d) { chars_recved += d.length; From 03f2bfe51f4051009b5f8c66114a3a44929f2ed4 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Thu, 11 Mar 2010 12:37:32 -0800 Subject: [PATCH 069/105] Make relative ref to sys in fs module. --- lib/fs.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/fs.js b/lib/fs.js index 22d4afb4430..28e52bd293d 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -1,7 +1,5 @@ -var - sys = require('sys'), - events = require('events'), - fs = require('fs'); +var sys = require('./sys'), + events = require('events'); exports.Stats = process.Stats; @@ -660,4 +658,4 @@ var FileWriteStream = exports.FileWriteStream = function(path, options) { flush(); }; -sys.inherits(FileWriteStream, events.EventEmitter); \ No newline at end of file +sys.inherits(FileWriteStream, events.EventEmitter); From 9b67962a447540e062bb25fc1c324f806f9eecd7 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Thu, 11 Mar 2010 12:38:42 -0800 Subject: [PATCH 070/105] Add timer.again method --- src/node_timer.cc | 13 +++++++++++++ src/node_timer.h | 1 + 2 files changed, 14 insertions(+) diff --git a/src/node_timer.cc b/src/node_timer.cc index 1fa470aad33..b1b6d35261a 100644 --- a/src/node_timer.cc +++ b/src/node_timer.cc @@ -27,6 +27,7 @@ Timer::Initialize (Handle target) NODE_SET_PROTOTYPE_METHOD(constructor_template, "start", Timer::Start); NODE_SET_PROTOTYPE_METHOD(constructor_template, "stop", Timer::Stop); + NODE_SET_PROTOTYPE_METHOD(constructor_template, "again", Timer::Again); constructor_template->InstanceTemplate()->SetAccessor(repeat_symbol, RepeatGetter, RepeatSetter); @@ -140,3 +141,15 @@ void Timer::Stop () { Unref(); } } + + +Handle Timer::Again(const Arguments& args) { + HandleScope scope; + Timer *timer = ObjectWrap::Unwrap(args.Holder()); + + ev_tstamp repeat = NODE_V8_UNIXTIME(args[0]); + if (repeat > 0) timer->watcher_.repeat = repeat; + ev_timer_again(EV_DEFAULT_UC_ &timer->watcher_); + + return Undefined(); +} diff --git a/src/node_timer.h b/src/node_timer.h index 0472fdb0d60..fdf2a2394f5 100644 --- a/src/node_timer.h +++ b/src/node_timer.h @@ -21,6 +21,7 @@ class Timer : ObjectWrap { static v8::Handle New (const v8::Arguments& args); static v8::Handle Start (const v8::Arguments& args); static v8::Handle Stop (const v8::Arguments& args); + static v8::Handle Again (const v8::Arguments& args); static v8::Handle RepeatGetter (v8::Local property, const v8::AccessorInfo& info); static void RepeatSetter (v8::Local property, v8::Local value, const v8::AccessorInfo& info); From 462a8f8652dafaf01f05623dd311e57e226cead4 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Thu, 11 Mar 2010 12:39:50 -0800 Subject: [PATCH 071/105] add error listener to test-net-pingpong --- test/simple/test-net-pingpong.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/simple/test-net-pingpong.js b/test/simple/test-net-pingpong.js index 560f943a515..94b983690b1 100644 --- a/test/simple/test-net-pingpong.js +++ b/test/simple/test-net-pingpong.js @@ -35,7 +35,12 @@ function pingPongTest (port, host) { socket.close(); }); + socket.addListener("error", function (e) { + throw e; + }); + socket.addListener("close", function () { + puts('server socket closed'); assert.equal(false, socket.writable); assert.equal(false, socket.readable); socket.server.close(); @@ -78,10 +83,15 @@ function pingPongTest (port, host) { }); client.addListener("close", function () { + puts('client closed'); assert.equal(N+1, count); assert.equal(true, sent_final_ping); tests_run += 1; }); + + client.addListener("error", function (e) { + throw e; + }); }); server.listen(port, host); From 4635ed7fdeef93c011d06ecbfa0030d3c0e1f9d8 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Thu, 11 Mar 2010 12:40:19 -0800 Subject: [PATCH 072/105] Add process.now Faster way to find out current time. --- src/node.cc | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/node.cc b/src/node.cc index 8095ea63cd9..b625803a3da 100644 --- a/src/node.cc +++ b/src/node.cc @@ -1,6 +1,8 @@ // Copyright 2009 Ryan Dahl #include +#include + #include #include #include @@ -533,6 +535,13 @@ static Handle SetUid(const Arguments& args) { return Undefined(); } +Handle +NowGetter (Local property, const AccessorInfo& info) +{ + HandleScope scope; + return scope.Close(Integer::New(ev_now(EV_DEFAULT_UC))); +} + v8::Handle Exit(const v8::Arguments& args) { HandleScope scope; @@ -1003,6 +1012,8 @@ static void Load(int argc, char *argv[]) { Local process_template = FunctionTemplate::New(); node::EventEmitter::Initialize(process_template); + process_template->InstanceTemplate()->SetAccessor(String::NewSymbol("now"), NowGetter, NULL); + process = Persistent::New(process_template->GetFunction()->NewInstance()); // Add a reference to the global object From ca862d75def02f3b09aa35f79c2df8bc0bd16788 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Thu, 11 Mar 2010 12:43:32 -0800 Subject: [PATCH 073/105] [net2] Add Socket.setTimeout Still seeing crashes and performance problems. --- lib/net.js | 196 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 191 insertions(+), 5 deletions(-) diff --git a/lib/net.js b/lib/net.js index a7f0f35c322..e33378c49d3 100644 --- a/lib/net.js +++ b/lib/net.js @@ -34,6 +34,167 @@ var EINPROGRESS = process.EINPROGRESS; var ENOENT = process.ENOENT; var END_OF_FILE = 0; + +// IDLE TIMEOUTS +// +// Because often many sockets will have the same idle timeout we will not +// use one timeout watcher per socket. It is too much overhead. Instead +// we'll use a single watcher for all sockets with the same timeout value +// and a linked list. This technique is described in the libev manual: +// http://pod.tst.eu/http://cvs.schmorp.de/libev/ev.pod#Be_smart_about_timeouts + + +var timeout = new (function () { + // Object containing all lists, timers + // key = time in milliseconds + // value = list + var lists = {}; + + // show the most idle socket + function peek (list) { + if (list._idlePrev == list) return null; + return list._idlePrev; + } + + + // remove the most idle socket from the list + function shift (list) { + var first = list._idlePrev; + remove(first); + return first; + } + + + // remove a socket from its list + function remove (socket) { + socket._idleNext._idlePrev = socket._idlePrev; + socket._idlePrev._idleNext = socket._idleNext; + } + + + // remove a socket from its list and place at the end. + function append (list, socket) { + remove(socket); + socket._idleNext = list._idleNext; + socket._idleNext._idlePrev = socket; + socket._idlePrev = list + list._idleNext = socket; + } + + + function normalize (msecs) { + if (!msecs || msecs <= 0) return 0; + // round up to one sec + if (msecs < 1000) return 1000; + // round down to nearest second. + return msecs - (msecs % 1000); + } + + // the main function - creates lists on demand and the watchers associated + // with them. + function insert (socket, msecs) { + socket._idleStart = process.now; + socket._idleTimeout = msecs; + + if (!msecs) return; + + var list; + + if (lists[msecs]) { + list = lists[msecs]; + } else { + list = new process.Timer(); + list._idleNext = list; + list._idlePrev = list; + + lists[msecs] = list; + + list.callback = function () { + sys.puts('timeout callback ' + msecs); + // TODO - don't stop and start the watcher all the time. + // just set its repeat + var now = process.now; + var first; + while (first = peek(list)) { + var diff = now - first._idleStart; + if (diff < msecs) { + list.again(msecs - diff); + sys.puts(msecs + ' list wait'); + return; + } else { + remove(first); + assert(first != peek(list)); + first.emit('timeout'); + first.forceClose(new Error('idle timeout')); + } + } + sys.puts(msecs + ' list empty'); + assert(list._idleNext == list); // list is empty + list.stop(); + }; + } + + if (list._idleNext == list) { + // if empty (re)start the timer + list.again(msecs); + } + + append(list, socket); + assert(list._idleNext != list); // list is not empty + } + + + var unenroll = this.unenroll = function (socket) { + socket._idleNext._idlePrev = socket._idlePrev; + socket._idlePrev._idleNext = socket._idleNext; + + var list = lists[socket._idleTimeout]; + // if empty then stop the watcher + //sys.puts('unenroll'); + if (list && list._idlePrev == list) { + //sys.puts('unenroll: list empty'); + list.stop(); + } + }; + + + // Does not start the time, just sets up the members needed. + this.enroll = function (socket, msecs) { + // if this socket was already in a list somewhere + // then we should unenroll it from that + if (socket._idleNext) unenroll(socket); + + socket._idleTimeout = msecs; + socket._idleNext = socket; + socket._idlePrev = socket; + }; + + // call this whenever the socket is active (not idle) + // it will reset its timeout. + this.active = function (socket) { + var msecs = socket._idleTimeout; + if (msecs) { + var list = lists[msecs]; + if (socket._idleNext == socket) { + insert(socket, msecs); + } else { + // inline append + socket._idleStart = process.now; + socket._idleNext._idlePrev = socket._idlePrev; + socket._idlePrev._idleNext = socket._idleNext; + socket._idleNext = list._idleNext; + socket._idleNext._idlePrev = socket; + socket._idlePrev = list + list._idleNext = socket; + } + } + }; +})(); + + + + + // This is a free list to avoid creating so many of the same object. function FreeList (name, max, constructor) { @@ -43,29 +204,33 @@ function FreeList (name, max, constructor) { this.list = []; } + FreeList.prototype.alloc = function () { //debug("alloc " + this.name + " " + this.list.length); return this.list.length ? this.list.shift() : this.constructor.apply(this, arguments); -} +}; + FreeList.prototype.free = function (obj) { //debug("free " + this.name + " " + this.list.length); if (this.list.length < this.max) { this.list.push(obj); } -} +}; var ioWatchers = new FreeList("iowatcher", 100, function () { return new IOWatcher(); }); + var nb = 0; var buffers = new FreeList("buffer", 100, function (l) { - return new Buffer(500); + return new Buffer(l); }); + // Allocated on demand. var recvBuffer = null; function allocRecvBuffer () { @@ -73,6 +238,7 @@ function allocRecvBuffer () { recvBuffer.used = 0; } + function _doFlush () { var socket = this.socket; // Socket becomes writeable on connect() but don't flush if there's @@ -90,6 +256,8 @@ function _doFlush () { } function initSocket (self) { + timeout.enroll(self, 60*1000); // default timeout, 60 seconds + self._readWatcher = ioWatchers.alloc(); self._readWatcher.callback = function () { // If this is the first recv (recvBuffer doesn't exist) or we've used up @@ -140,6 +308,9 @@ function initSocket (self) { if (!self.writable) self.forceClose(); } else if (bytesRead > 0) { + + timeout.active(self); + var start = recvBuffer.used; var end = recvBuffer.used + bytesRead; @@ -215,6 +386,7 @@ exports.createConnection = function (port, host) { Object.defineProperty(Socket.prototype, 'readyState', { get: function () { + sys.error('readyState is depricated. Use stream.readable or stream.writable'); if (this.readable && this.writable) { return 'open'; } else if (this.readable && !this.writable){ @@ -396,6 +568,9 @@ Socket.prototype.flush = function () { return false; } + timeout.active(self); + + if (bytesWritten === null) { // EAGAIN debug('write EAGAIN'); @@ -459,6 +634,8 @@ Socket.prototype.connect = function () { var self = this; if (self.fd) throw new Error('Socket already opened'); + + timeout.active(socket); if (typeof(arguments[0]) == 'string') { self.fd = socket('unix'); @@ -477,28 +654,34 @@ Socket.prototype.connect = function () { } }; -var xxx = 0; - Socket.prototype.address = function () { return getsockname(this.fd); }; + Socket.prototype.setNoDelay = function (v) { if (this.type == 'tcp') setNoDelay(this.fd, v); }; +Socket.prototype.setTimeout = function (msecs) { + timeout.enroll(this, msecs); +}; + + Socket.prototype.pause = function () { this._readWatcher.stop(); }; + Socket.prototype.resume = function () { if (!this.fd) throw new Error('Cannot resume() closed Socket.'); this._readWatcher.set(this.fd, true, false); this._readWatcher.start(); }; + Socket.prototype.forceClose = function (exception) { // recvBuffer is shared between sockets, so don't need to free it here. var self = this; @@ -521,6 +704,8 @@ Socket.prototype.forceClose = function (exception) { this._readWatcher = null; } + timeout.unenroll(this); + if (this.fd) { close(this.fd); debug('close ' + this.fd); @@ -580,6 +765,7 @@ function Server (listener) { // The 'connect' event probably should be removed for server-side // sockets. It's redundent. peer.emit('connect'); + timeout.active(peer); } }; } From 3d10852c3368b492583f16e266374710ebdd54c3 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Fri, 12 Mar 2010 12:15:25 -0800 Subject: [PATCH 074/105] Disable AsciiSliceExt Seems faster and less buggy? --- src/node_buffer.cc | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/node_buffer.cc b/src/node_buffer.cc index 70820dd49b3..975cbfe322f 100644 --- a/src/node_buffer.cc +++ b/src/node_buffer.cc @@ -77,7 +77,7 @@ static inline void blob_unref(Blob *blob) { } } - +#if 0 // When someone calls buffer.asciiSlice, data is not copied. Instead V8 // references in the underlying Blob with this ExternalAsciiStringResource. class AsciiSliceExt: public String::ExternalAsciiStringResource { @@ -89,7 +89,7 @@ class AsciiSliceExt: public String::ExternalAsciiStringResource { assert(start <= end); length_ = end - start; - assert(length_ <= parent->length()); + assert(start + length_ <= parent->length()); data_ = parent->data() + start; } @@ -108,6 +108,7 @@ class AsciiSliceExt: public String::ExternalAsciiStringResource { size_t length_; Blob *blob_; }; +#endif Handle Buffer::New(const Arguments &args) { @@ -174,11 +175,19 @@ Handle Buffer::AsciiSlice(const Arguments &args) { HandleScope scope; Buffer *parent = ObjectWrap::Unwrap(args.This()); SLICE_ARGS(args[0], args[1]) + +#if 0 AsciiSliceExt *ext = new AsciiSliceExt(parent, start, end); Local string = String::NewExternal(ext); // There should be at least two references to the blob now - the parent // and the slice. assert(parent->blob_->refs >= 2); +#endif + + const char *data = const_cast(parent->data_ + start); + Local string = String::New(data, end - start); + + return scope.Close(string); } From 177de8fefc9a06e1f54c151fc5658b41b86972a5 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Fri, 12 Mar 2010 12:20:41 -0800 Subject: [PATCH 075/105] Fix some gcc warnings --- src/node_buffer.cc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/node_buffer.cc b/src/node_buffer.cc index 975cbfe322f..cfc2b49f296 100644 --- a/src/node_buffer.cc +++ b/src/node_buffer.cc @@ -167,7 +167,7 @@ Buffer::~Buffer() { assert(blob_->refs > 0); //fprintf(stderr, "free buffer (%d refs left)\n", blob_->refs); blob_unref(blob_); - V8::AdjustAmountOfExternalAllocatedMemory(-sizeof(Buffer)); + V8::AdjustAmountOfExternalAllocatedMemory(-static_cast(sizeof(Buffer))); } @@ -196,7 +196,7 @@ Handle Buffer::Utf8Slice(const Arguments &args) { HandleScope scope; Buffer *parent = ObjectWrap::Unwrap(args.This()); SLICE_ARGS(args[0], args[1]) - const char *data = reinterpret_cast(parent->data_ + start); + const char *data = const_cast(parent->data_ + start); Local string = String::New(data, end - start); return scope.Close(string); } @@ -265,7 +265,7 @@ Handle Buffer::AsciiWrite(const Arguments &args) { const char *p = buffer->data_ + offset; - size_t towrite = MIN(s->Length(), buffer->length_ - offset); + size_t towrite = MIN((unsigned long) s->Length(), buffer->length_ - offset); int written = s->WriteAscii((char*)p, 0, towrite); return scope.Close(Integer::New(written)); @@ -290,7 +290,7 @@ Handle Buffer::Unpack(const Arguments &args) { } String::AsciiValue format(args[0]->ToString()); - int index = args[1]->IntegerValue(); + uint32_t index = args[1]->Uint32Value(); #define OUT_OF_BOUNDS ThrowException(Exception::Error(String::New("Out of bounds"))) From 836e6a3fcbe297fb22edfda521c3e6ea540b59e5 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Fri, 12 Mar 2010 12:27:41 -0800 Subject: [PATCH 076/105] Adjust object ref count after call to ev_timer_again --- src/node_timer.cc | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/node_timer.cc b/src/node_timer.cc index b1b6d35261a..1a3ed791d3d 100644 --- a/src/node_timer.cc +++ b/src/node_timer.cc @@ -147,9 +147,22 @@ Handle Timer::Again(const Arguments& args) { HandleScope scope; Timer *timer = ObjectWrap::Unwrap(args.Holder()); + int was_active = ev_is_active(&timer->watcher_); + ev_tstamp repeat = NODE_V8_UNIXTIME(args[0]); if (repeat > 0) timer->watcher_.repeat = repeat; + ev_timer_again(EV_DEFAULT_UC_ &timer->watcher_); + // ev_timer_again can start or stop the watcher. + // So we need to check what happened and adjust the ref count + // appropriately. + + if (ev_is_active(&timer->watcher_)) { + if (!was_active) timer->Ref(); + } else { + if (was_active) timer->Unref(); + } + return Undefined(); } From b94af8d6b1671da9e89da8e6f13937f41d7c673a Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Fri, 12 Mar 2010 12:34:17 -0800 Subject: [PATCH 077/105] Only print readyState deprication message once --- lib/net.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/net.js b/lib/net.js index e33378c49d3..218d66bc04b 100644 --- a/lib/net.js +++ b/lib/net.js @@ -384,9 +384,13 @@ exports.createConnection = function (port, host) { }; +var readyStateMessage; Object.defineProperty(Socket.prototype, 'readyState', { get: function () { - sys.error('readyState is depricated. Use stream.readable or stream.writable'); + if (!readyStateMessage) { + readyStateMessage = 'readyState is depricated. Use stream.readable or stream.writable'; + sys.error(readyStateMessage); + } if (this.readable && this.writable) { return 'open'; } else if (this.readable && !this.writable){ From 3adf7a3dcfd68f0347b0ab38250b003f73344dad Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Fri, 12 Mar 2010 12:38:27 -0800 Subject: [PATCH 078/105] TCP timeout opt-in instead of opt-out For speed, idle connections are super cheap. --- lib/net.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/net.js b/lib/net.js index 218d66bc04b..36fd0cc96f3 100644 --- a/lib/net.js +++ b/lib/net.js @@ -145,15 +145,17 @@ var timeout = new (function () { var unenroll = this.unenroll = function (socket) { - socket._idleNext._idlePrev = socket._idlePrev; - socket._idlePrev._idleNext = socket._idleNext; + if (socket._idleNext) { + socket._idleNext._idlePrev = socket._idlePrev; + socket._idlePrev._idleNext = socket._idleNext; - var list = lists[socket._idleTimeout]; - // if empty then stop the watcher - //sys.puts('unenroll'); - if (list && list._idlePrev == list) { - //sys.puts('unenroll: list empty'); - list.stop(); + var list = lists[socket._idleTimeout]; + // if empty then stop the watcher + //sys.puts('unenroll'); + if (list && list._idlePrev == list) { + //sys.puts('unenroll: list empty'); + list.stop(); + } } }; @@ -256,8 +258,6 @@ function _doFlush () { } function initSocket (self) { - timeout.enroll(self, 60*1000); // default timeout, 60 seconds - self._readWatcher = ioWatchers.alloc(); self._readWatcher.callback = function () { // If this is the first recv (recvBuffer doesn't exist) or we've used up From c857d65dd3e74bb34ee573bcc0ccf3840dcc2986 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Fri, 12 Mar 2010 12:43:28 -0800 Subject: [PATCH 079/105] Disable test-net-fd-passing for now --- test/{simple => disabled}/test-net-fd-passing.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/{simple => disabled}/test-net-fd-passing.js (100%) diff --git a/test/simple/test-net-fd-passing.js b/test/disabled/test-net-fd-passing.js similarity index 100% rename from test/simple/test-net-fd-passing.js rename to test/disabled/test-net-fd-passing.js From b571900d9ccf4d7f64bfa31a6920bdbdcbe45b64 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Fri, 12 Mar 2010 12:55:34 -0800 Subject: [PATCH 080/105] Fix node_timer bug; sometimes was not initializing watcher --- src/node_timer.cc | 6 ++++-- src/node_timer.h | 5 ++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/node_timer.cc b/src/node_timer.cc index 1a3ed791d3d..5ab409b048a 100644 --- a/src/node_timer.cc +++ b/src/node_timer.cc @@ -149,8 +149,10 @@ Handle Timer::Again(const Arguments& args) { int was_active = ev_is_active(&timer->watcher_); - ev_tstamp repeat = NODE_V8_UNIXTIME(args[0]); - if (repeat > 0) timer->watcher_.repeat = repeat; + if (args.Length() > 0) { + ev_tstamp repeat = NODE_V8_UNIXTIME(args[0]); + if (repeat > 0) timer->watcher_.repeat = repeat; + } ev_timer_again(EV_DEFAULT_UC_ &timer->watcher_); diff --git a/src/node_timer.h b/src/node_timer.h index fdf2a2394f5..56352912a66 100644 --- a/src/node_timer.h +++ b/src/node_timer.h @@ -15,7 +15,10 @@ class Timer : ObjectWrap { protected: static v8::Persistent constructor_template; - Timer () : ObjectWrap () { } + Timer () : ObjectWrap () { + // dummy timeout values + ev_timer_init(&watcher_, OnTimeout, 0., 1.); + } ~Timer(); static v8::Handle New (const v8::Arguments& args); From aa6eaae0aa8ba46ca2a25bd0b1e8eec5498df847 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Fri, 12 Mar 2010 13:04:33 -0800 Subject: [PATCH 081/105] Simplify Socket constructor --- lib/net.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/net.js b/lib/net.js index 36fd0cc96f3..1b2b348ee2a 100644 --- a/lib/net.js +++ b/lib/net.js @@ -356,15 +356,13 @@ function initSocket (self) { self.writable = false; } -function Socket (peerInfo) { +function Socket (fd) { process.EventEmitter.call(this); - if (peerInfo) { + if (fd) { initSocket(this); - this.fd = peerInfo.fd; - this.remoteAddress = peerInfo.remoteAddress; - this.remotePort = peerInfo.remotePort; + this.fd = fd; this.resume(); this.readable = true; @@ -758,18 +756,20 @@ function Server (listener) { self.watcher = new IOWatcher(); self.watcher.host = self; - self.watcher.callback = function (readable, writeable) { + self.watcher.callback = function () { while (self.fd) { var peerInfo = accept(self.fd); if (!peerInfo) return; - var peer = new Socket(peerInfo); - peer.type = self.type; - peer.server = self; - self.emit('connection', peer); + var s = new Socket(peerInfo.fd); + s.remoteAddress = peerInfo.remoteAddress; + s.remotePort = peerInfo.remotePort; + s.type = self.type; + s.server = self; + self.emit('connection', s); // The 'connect' event probably should be removed for server-side // sockets. It's redundent. - peer.emit('connect'); - timeout.active(peer); + s.emit('connect'); + timeout.active(s); } }; } From 96f08cf05c3bd5ee291e85e2135d8020dd7f662a Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Fri, 12 Mar 2010 18:39:02 -0800 Subject: [PATCH 082/105] Work on net2 http client --- lib/http2.js | 189 +++++++++++++++++++++++++++++++++++ lib/net.js | 18 ++-- test/simple/test-http-1.0.js | 2 +- test/simple/test-http-cat.js | 2 +- 4 files changed, 201 insertions(+), 10 deletions(-) diff --git a/lib/http2.js b/lib/http2.js index 8b26a3f1b61..c040dd22950 100644 --- a/lib/http2.js +++ b/lib/http2.js @@ -447,3 +447,192 @@ exports.createServer = function (requestListener, options) { }; + +function Client () { + net.Socket.call(this); + + var self = this; + var requests = []; + var currentRequest; + + var parser = newParser('response'); + parser.socket = self; + + self.addListener("connect", function () { + self.resetParser(); + currentRequest = requests.shift(); + currentRequest.flush(); + }); + + self.ondata = function (d, start, end) { + parser.execute(d, start, end - start); + }; + + parser.onIncoming = function (res) { + //sys.debug("incoming response!"); + + res.addListener('end', function ( ) { + //sys.debug("request complete disconnecting. readyState = " + self.readyState); + self.close(); + }); + + currentRequest.emit("response", res); + }; + + self._pushRequest = function (req) { + }; + + self.addListener("end", function () { + self.close(); + }); + + self.onend = function () { + parser.finish(); + // unref the parser for easy gc + freeParser(parser); + //sys.debug("self got end closing. readyState = " + self.readyState); + self.close(); + }; + + self.addListener("close", function (had_error) { + if (had_error) { + self.emit("error"); + return; + } + + //sys.debug("HTTP CLIENT onClose. readyState = " + self.readyState); + + // If there are more requests to handle, reconnect. + if (requests.length > 0) { + self._reconnect(); + } + }); +} +sys.inherits(Client, net.Socket); + + +exports.Client = Client; + + +exports.createClient = function (port, host) { + var client = new Client(); + client.port = port; + client.host = host; + client.connect(port, host); + return client; +}; + + +Client.prototype._reconnect = function () { + if (this.readyState != "opening") { + //sys.debug("HTTP CLIENT: reconnecting readyState = " + self.readyState); + this.connect(this.port, this.host); + } +}; + + +Client.prototype.request = function (method, url, headers) { + var self = this; + + if (typeof(url) != "string") { // assume method was omitted, shift arguments + headers = url; + url = method; + method = null; + } + var req = new ClientRequest(this, method || "GET", url, headers); + + req.addListener("flush", function () { + if (self.readyState == "closed") { + //sys.debug("HTTP CLIENT request flush. reconnect. readyState = " + self.readyState); + self._reconnect(); + return; + } + //sys.debug("self flush readyState = " + self.readyState); + if (req == currentRequest) flushMessageQueue(self, [req]); + }); + requests.push(req); + + return req; +}; + +Client.prototype.get = function () { + throw new Error("client.get(...) is now client.request('GET', ...)"); +}; + +Client.prototype.head = function () { + throw new Error("client.head(...) is now client.request('HEAD', ...)"); +}; + +Client.prototype.post = function () { + throw new Error("client.post(...) is now client.request('POST', ...)"); +}; + +Client.prototype.del = function () { + throw new Error("client.del(...) is now client.request('DELETE', ...)"); +}; + +Client.prototype.put = function () { + throw new Error("client.put(...) is now client.request('PUT', ...)"); +}; + + +exports.cat = function (url, encoding_, headers_) { + var encoding = 'utf8', + headers = {}, + callback = null; + + // parse the arguments for the various options... very ugly + if (typeof(arguments[1]) == 'string') { + encoding = arguments[1]; + if (typeof(arguments[2]) == 'object') { + headers = arguments[2]; + if (typeof(arguments[3]) == 'function') callback = arguments[3]; + } else { + if (typeof(arguments[2]) == 'function') callback = arguments[2]; + } + } else { + // didn't specify encoding + if (typeof(arguments[1]) == 'object') { + headers = arguments[1]; + callback = arguments[2]; + } else { + callback = arguments[1]; + } + } + + var url = require("url").parse(url); + + var hasHost = false; + for (var i in headers) { + if (i.toLowerCase() === "host") { + hasHost = true; + break; + } + } + if (!hasHost) headers["Host"] = url.hostname; + + var content = ""; + + var client = exports.createClient(url.port || 80, url.hostname); + var req = client.request((url.pathname || "/")+(url.search || "")+(url.hash || ""), headers); + + req.addListener('response', function (res) { + if (res.statusCode < 200 || res.statusCode >= 300) { + if (callback) callback(res.statusCode); + client.close(); + return; + } + res.setBodyEncoding(encoding); + res.addListener('data', function (chunk) { content += chunk; }); + res.addListener('end', function () { + if (callback) callback(null, content); + }); + }); + + client.addListener("error", function (err) { + // todo an error should actually be passed here... + if (callback) callback(new Error('Connection error')); + }); + + req.close(); +}; diff --git a/lib/net.js b/lib/net.js index 1b2b348ee2a..fe5fa0d85cf 100644 --- a/lib/net.js +++ b/lib/net.js @@ -632,19 +632,16 @@ function doConnect (socket, port, host) { // stream.connect(80, 'nodejs.org') - TCP connect to port 80 on nodejs.org // stream.connect('/tmp/socket') - UNIX connect to socket specified by path Socket.prototype.connect = function () { - initSocket(this); - var self = this; + initSocket(self); if (self.fd) throw new Error('Socket already opened'); + if (!self._readWatcher) throw new Error('No readWatcher'); timeout.active(socket); - if (typeof(arguments[0]) == 'string') { - self.fd = socket('unix'); - self.type = 'unix'; - // TODO check if sockfile exists? - doConnect(self, arguments[0]); - } else { + var port = parseInt(arguments[0]); + + if (port >= 0) { self.fd = socket('tcp'); debug('new fd = ' + self.fd); self.type = 'tcp'; @@ -653,6 +650,11 @@ Socket.prototype.connect = function () { lookupDomainName(arguments[1], function (ip) { doConnect(self, port, ip); }); + } else { + self.fd = socket('unix'); + self.type = 'unix'; + // TODO check if sockfile exists? + doConnect(self, arguments[0]); } }; diff --git a/test/simple/test-http-1.0.js b/test/simple/test-http-1.0.js index ceca527719f..b3f69c23cfe 100644 --- a/test/simple/test-http-1.0.js +++ b/test/simple/test-http-1.0.js @@ -1,6 +1,6 @@ require("../common"); tcp = require("tcp"); -http = require("http"); +http = require("http2"); var body = "hello world\n"; var server_response = ""; diff --git a/test/simple/test-http-cat.js b/test/simple/test-http-cat.js index e81a5c502fe..32177ee92b8 100644 --- a/test/simple/test-http-cat.js +++ b/test/simple/test-http-cat.js @@ -1,5 +1,5 @@ require("../common"); -http = require("http"); +http = require("http2"); var body = "exports.A = function() { return 'A';}"; var server = http.createServer(function (req, res) { From fdf46a65c92efc6de21c20c2d65d6883f269d0e6 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Mon, 15 Mar 2010 15:11:40 -0700 Subject: [PATCH 083/105] Use streams for stdout and stdin --- lib/net.js | 13 ++- lib/repl.js | 10 +- lib/sys.js | 8 +- src/node.cc | 12 +- src/node.js | 27 ++++- src/node_stdio.cc | 183 +++---------------------------- src/node_stdio.h | 6 +- test/simple/test-net-pingpong.js | 3 +- 8 files changed, 76 insertions(+), 186 deletions(-) diff --git a/lib/net.js b/lib/net.js index 176b6f2eeb6..3a2466dcd57 100644 --- a/lib/net.js +++ b/lib/net.js @@ -359,12 +359,13 @@ function initSocket (self) { function Socket (fd) { process.EventEmitter.call(this); - if (fd) { + this.fd = null; + + if (parseInt(fd) >= 0) { initSocket(this); this.fd = fd; - this.resume(); this.readable = true; this._writeWatcher.set(this.fd, false, true); @@ -615,7 +616,7 @@ function doConnect (socket, port, host) { var errno = socketError(socket.fd); if (errno == 0) { // connection established - socket._readWatcher.start(); + socket.resume(); socket.readable = true; socket.writable = true; socket._writeWatcher.callback = socket._doFlush; @@ -680,7 +681,7 @@ Socket.prototype.pause = function () { Socket.prototype.resume = function () { - if (!this.fd) throw new Error('Cannot resume() closed Socket.'); + if (this.fd === null) throw new Error('Cannot resume() closed Socket.'); this._readWatcher.set(this.fd, true, false); this._readWatcher.start(); }; @@ -762,16 +763,18 @@ function Server (listener) { while (self.fd) { var peerInfo = accept(self.fd); if (!peerInfo) return; + var s = new Socket(peerInfo.fd); s.remoteAddress = peerInfo.remoteAddress; s.remotePort = peerInfo.remotePort; s.type = self.type; s.server = self; + s.resume(); + self.emit('connection', s); // The 'connect' event probably should be removed for server-side // sockets. It's redundent. s.emit('connect'); - timeout.active(s); } }; } diff --git a/lib/repl.js b/lib/repl.js index ee7dd9b61c3..1608102b1f2 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -12,13 +12,17 @@ exports.scope = {}; exports.prompt = "node> "; // Can overridden with custom print functions, such as `probe` or `eyes.js` exports.writer = sys.p; + +var stdin; + exports.start = function (prompt) { if (prompt !== undefined) { exports.prompt = prompt; } - process.stdio.open(); - process.stdio.addListener("data", readline); + stdin = process.openStdin(); + stdin.setEncoding('utf8'); + stdin.addListener("data", readline); displayPrompt(); } @@ -96,7 +100,7 @@ function parseREPLKeyword (cmd) { displayPrompt(); return true; case ".exit": - process.stdio.close(); + stdin.close(); return true; case ".help": sys.puts(".break\tSometimes you get stuck in a place you can't get out... This will get you out."); diff --git a/lib/sys.js b/lib/sys.js index a828e5cef5e..aef80530e29 100644 --- a/lib/sys.js +++ b/lib/sys.js @@ -2,23 +2,23 @@ var events = require('events'); exports.print = function () { for (var i = 0, len = arguments.length; i < len; ++i) { - process.stdio.write(arguments[i]); + process.stdout.write(arguments[i]); } }; exports.puts = function () { for (var i = 0, len = arguments.length; i < len; ++i) { - process.stdio.write(arguments[i] + '\n'); + process.stdout.write(arguments[i] + '\n'); } }; exports.debug = function (x) { - process.stdio.writeError("DEBUG: " + x + "\n"); + process.binding('stdio').writeError("DEBUG: " + x + "\n"); }; exports.error = function (x) { for (var i = 0, len = arguments.length; i < len; ++i) { - process.stdio.writeError(arguments[i] + '\n'); + process.binding('stdio').writeError(arguments[i] + '\n'); } }; diff --git a/src/node.cc b/src/node.cc index c9c13542488..608b911d73f 100644 --- a/src/node.cc +++ b/src/node.cc @@ -1073,7 +1073,16 @@ static Handle Binding(const Arguments& args) { Local exports; - if (!strcmp(*module_v, "http")) { + if (!strcmp(*module_v, "stdio")) { + if (binding_cache->Has(module)) { + exports = binding_cache->Get(module)->ToObject(); + } else { + exports = Object::New(); + Stdio::Initialize(exports); + binding_cache->Set(module, exports); + } + + } else if (!strcmp(*module_v, "http")) { if (binding_cache->Has(module)) { exports = binding_cache->Get(module)->ToObject(); } else { @@ -1258,7 +1267,6 @@ static void Load(int argc, char *argv[]) { IOWatcher::Initialize(process); // io_watcher.cc IdleWatcher::Initialize(process); // idle_watcher.cc Timer::Initialize(process); // timer.cc - Stdio::Initialize(process); // stdio.cc InitNet2(process); // net2.cc InitHttpParser(process); // http_parser.cc ChildProcess::Initialize(process); // child_process.cc diff --git a/src/node.js b/src/node.js index d32626553ce..2af9ab817cf 100644 --- a/src/node.js +++ b/src/node.js @@ -106,10 +106,12 @@ process.createChildProcess = function (file, args, env) { return child; }; + process.assert = function (x, msg) { if (!(x)) throw new Error(msg || "assertion error"); }; + // From jQuery.extend in the jQuery JavaScript Library v1.3.2 // Copyright (c) 2009 John Resig // Dual licensed under the MIT and GPL licenses. @@ -119,7 +121,7 @@ var mixinMessage; process.mixin = function() { if (!mixinMessage) { mixinMessage = 'deprecation warning: process.mixin will be removed from node-core future releases.\n' - process.stdio.writeError(mixinMessage); + process.binding('stdio').writeError(mixinMessage); } // copy reference to target object var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, source; @@ -338,7 +340,7 @@ if ("NODE_DEBUG" in process.env) debugLevel = 1; function debug (x) { if (debugLevel > 0) { - process.stdio.writeError(x + "\n"); + process.binding('stdio').writeError(x + "\n"); } } @@ -781,6 +783,27 @@ Module.prototype._waitChildrenLoad = function (callback) { }; +var stdout; +process.__defineGetter__('stdout', function () { + if (stdout) return stdout; + var net = requireNative('net'); + stdout = new net.Socket(process.binding('stdio').stdoutFD); + return stdout; +}); + +var stdin; +process.openStdin = function () { + if (stdin) return stdin; + var net = requireNative('net'); + var fd = process.binding('stdio').openStdin(); + stdin = new net.Socket(fd); + process.stdout.write(stdin.fd + "\n"); + stdin.resume(); + stdin.readable = true; + return stdin; +}; + + process.exit = function (code) { process.emit("exit"); process.reallyExit(code); diff --git a/src/node_stdio.cc b/src/node_stdio.cc index 2b5b30612c1..ee1278f4bb9 100644 --- a/src/node_stdio.cc +++ b/src/node_stdio.cc @@ -8,10 +8,8 @@ #include using namespace v8; -using namespace node; +namespace node { -static Persistent stdio; -static Persistent emit; static struct coupling *stdin_coupling = NULL; static struct coupling *stdout_coupling = NULL; @@ -19,33 +17,8 @@ static struct coupling *stdout_coupling = NULL; static int stdin_fd = -1; static int stdout_fd = -1; -static evcom_reader in; -static evcom_writer out; -static enum encoding stdin_encoding; - -static void -EmitInput (Local input) -{ - HandleScope scope; - - Local argv[2] = { String::NewSymbol("data"), input }; - - emit->Call(stdio, 2, argv); -} - -static void -EmitClose (void) -{ - HandleScope scope; - - Local argv[1] = { String::NewSymbol("close") }; - - emit->Call(stdio, 1, argv); -} - - -static inline Local errno_exception(int errorno) { +static Local errno_exception(int errorno) { Local e = Exception::Error(String::NewSymbol(strerror(errorno))); Local obj = e->ToObject(); obj->Set(String::NewSymbol("errno"), Integer::New(errorno)); @@ -53,7 +26,7 @@ static inline Local errno_exception(int errorno) { } -/* STDERR IS ALWAY SYNC */ +/* STDERR IS ALWAY SYNC ALWAYS UTF8 */ static Handle WriteError (const Arguments& args) { @@ -81,84 +54,8 @@ WriteError (const Arguments& args) return Undefined(); } -static Handle -Write (const Arguments& args) -{ - HandleScope scope; - if (args.Length() == 0) { - return ThrowException(Exception::Error(String::New("Bad argument"))); - } - - enum encoding enc = UTF8; - if (args.Length() > 1) enc = ParseEncoding(args[1], UTF8); - - ssize_t len = DecodeBytes(args[0], enc); - - if (len < 0) { - Local exception = Exception::TypeError(String::New("Bad argument")); - return ThrowException(exception); - } - - char buf[len]; - ssize_t written = DecodeWrite(buf, len, args[0], enc); - - assert(written == len); - - evcom_writer_write(&out, buf, len); - - return Undefined(); -} - -static void -detach_in (evcom_reader *r) -{ - assert(r == &in); - HandleScope scope; - - EmitClose(); - - evcom_reader_detach(&in); - - if (stdin_coupling) { - coupling_destroy(stdin_coupling); - stdin_coupling = NULL; - } - - stdin_fd = -1; -} - -static void -detach_out (evcom_writer* w) -{ - assert(w == &out); - - evcom_writer_detach(&out); - if (stdout_coupling) { - coupling_destroy(stdout_coupling); - stdout_coupling = NULL; - } - stdout_fd = -1; -} - -static void -on_read (evcom_reader *r, const void *buf, size_t len) -{ - assert(r == &in); - HandleScope scope; - - if (!len) { - return; - } - - Local data = Encode(buf, len, stdin_encoding); - - EmitInput(data); -} - -static inline int -set_nonblock (int fd) -{ +static inline int SetNonblock(int fd) { int flags = fcntl(fd, F_GETFL, 0); if (flags == -1) return -1; @@ -168,20 +65,14 @@ set_nonblock (int fd) return 0; } -static Handle -Open (const Arguments& args) -{ + +static Handle OpenStdin(const Arguments& args) { HandleScope scope; if (stdin_fd >= 0) { return ThrowException(Exception::Error(String::New("stdin already open"))); } - stdin_encoding = UTF8; - if (args.Length() > 0) { - stdin_encoding = ParseEncoding(args[0]); - } - if (isatty(STDIN_FILENO)) { // XXX selecting on tty fds wont work in windows. // Must ALWAYS make a coupling on shitty platforms. @@ -190,34 +81,11 @@ Open (const Arguments& args) stdin_coupling = coupling_new_pull(STDIN_FILENO); stdin_fd = coupling_nonblocking_fd(stdin_coupling); } - set_nonblock(stdin_fd); + SetNonblock(stdin_fd); - evcom_reader_init(&in); - - in.on_read = on_read; - in.on_close = detach_in; - - evcom_reader_set(&in, stdin_fd); - evcom_reader_attach(EV_DEFAULT_ &in); - - return Undefined(); + return scope.Close(Integer::New(stdin_fd)); } -static Handle -Close (const Arguments& args) -{ - HandleScope scope; - - assert(stdio == args.Holder()); - - if (stdin_fd < 0) { - return ThrowException(Exception::Error(String::New("stdin not open"))); - } - - evcom_reader_close(&in); - - return Undefined(); -} void Stdio::Flush() { if (stdout_fd >= 0) { @@ -232,28 +100,10 @@ void Stdio::Flush() { } } -void -Stdio::Initialize (v8::Handle target) -{ + +void Stdio::Initialize(v8::Handle target) { HandleScope scope; - Local stdio_local = - EventEmitter::constructor_template->GetFunction()->NewInstance(0, NULL); - - stdio = Persistent::New(stdio_local); - - NODE_SET_METHOD(stdio, "open", Open); - NODE_SET_METHOD(stdio, "write", Write); - NODE_SET_METHOD(stdio, "writeError", WriteError); - NODE_SET_METHOD(stdio, "close", Close); - - target->Set(String::NewSymbol("stdio"), stdio); - - Local emit_v = stdio->Get(String::NewSymbol("emit")); - assert(emit_v->IsFunction()); - Local emit_f = Local::Cast(emit_v); - emit = Persistent::New(emit_f); - if (isatty(STDOUT_FILENO)) { // XXX selecting on tty fds wont work in windows. // Must ALWAYS make a coupling on shitty platforms. @@ -262,10 +112,13 @@ Stdio::Initialize (v8::Handle target) stdout_coupling = coupling_new_push(STDOUT_FILENO); stdout_fd = coupling_nonblocking_fd(stdout_coupling); } - set_nonblock(stdout_fd); + SetNonblock(stdout_fd); - evcom_writer_init(&out); - out.on_close = detach_out; - evcom_writer_set(&out, stdout_fd); - evcom_writer_attach(EV_DEFAULT_ &out); + target->Set(String::NewSymbol("stdoutFD"), Integer::New(stdout_fd)); + + NODE_SET_METHOD(target, "writeError", WriteError); + NODE_SET_METHOD(target, "openStdin", OpenStdin); } + + +} // namespace node diff --git a/src/node_stdio.h b/src/node_stdio.h index 60fa2912fd2..47639438735 100644 --- a/src/node_stdio.h +++ b/src/node_stdio.h @@ -2,9 +2,7 @@ #define node_stdio_h #include - #include -#include namespace node { @@ -14,5 +12,5 @@ public: static void Flush (); }; -} // namespace node -#endif +} // namespace node +#endif // node_stdio_h diff --git a/test/simple/test-net-pingpong.js b/test/simple/test-net-pingpong.js index 94b983690b1..ad6410c9b98 100644 --- a/test/simple/test-net-pingpong.js +++ b/test/simple/test-net-pingpong.js @@ -1,4 +1,5 @@ -process.mixin(require("../common")); +require("../common"); + net = require("net"); process.Buffer.prototype.toString = function () { From 953fa3a5f5561fd287832bc36c9bffb852b40bf1 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Mon, 15 Mar 2010 16:44:50 -0700 Subject: [PATCH 084/105] Move net2 bindings out of process --- lib/http2.js | 4 +++- lib/net.js | 53 ++++++++++++++++++++++++++++------------------------ src/node.cc | 20 ++++++++++++++++++-- 3 files changed, 50 insertions(+), 27 deletions(-) diff --git a/lib/http2.js b/lib/http2.js index 2eef63524bd..df5cbab7c67 100644 --- a/lib/http2.js +++ b/lib/http2.js @@ -2,6 +2,8 @@ var sys = require('sys'); var net = require('net'); var events = require('events'); +var HTTPParser = process.binding('http_parser').HTTPParser; + var CRLF = "\r\n"; var STATUS_CODES = exports.STATUS_CODES = { 100 : 'Continue', @@ -315,7 +317,7 @@ function newParser (type) { parser = parserFreeList.shift(); parser.reinitialize(type); } else { - parser = new process.HTTPParser(type); + parser = new HTTPParser(type); parser.onMessageBegin = function () { parser.incoming = new IncomingMessage(parser.socket); diff --git a/lib/net.js b/lib/net.js index 3a2466dcd57..8b2448373ec 100644 --- a/lib/net.js +++ b/lib/net.js @@ -1,5 +1,7 @@ var sys = require("sys"); var fs = require("fs"); +var events = require("events"); + var debugLevel = 0; if ('NODE_DEBUG' in process.ENV) debugLevel = 1; function debug (x) { @@ -8,30 +10,33 @@ function debug (x) { } } +var binding = process.binding('net'); + var Buffer = process.Buffer; var IOWatcher = process.IOWatcher; var assert = process.assert; -var socket = process.socket; -var bind = process.bind; -var connect = process.connect; -var listen = process.listen; -var accept = process.accept; -var close = process.close; -var shutdown = process.shutdown; -var read = process.read; -var recvMsg = process.recvMsg; -var sendFD = process.sendFD; -var write = process.write; -var toRead = process.toRead; -var setNoDelay = process.setNoDelay; -var socketError = process.socketError; -var getsockname = process.getsockname; -var getaddrinfo = process.getaddrinfo; -var needsLookup = process.needsLookup; -var errnoException = process.errnoException; -var EINPROGRESS = process.EINPROGRESS; -var ENOENT = process.ENOENT; + +var socket = binding.socket; +var bind = binding.bind; +var connect = binding.connect; +var listen = binding.listen; +var accept = binding.accept; +var close = binding.close; +var shutdown = binding.shutdown; +var read = binding.read; +var recvMsg = binding.recvMsg; +var sendFD = binding.sendFD; +var write = binding.write; +var toRead = binding.toRead; +var setNoDelay = binding.setNoDelay; +var socketError = binding.socketError; +var getsockname = binding.getsockname; +var getaddrinfo = binding.getaddrinfo; +var needsLookup = binding.needsLookup; +var errnoException = binding.errnoException; +var EINPROGRESS = binding.EINPROGRESS; +var ENOENT = binding.ENOENT; var END_OF_FILE = 0; @@ -357,7 +362,7 @@ function initSocket (self) { } function Socket (fd) { - process.EventEmitter.call(this); + events.EventEmitter.call(this); this.fd = null; @@ -372,7 +377,7 @@ function Socket (fd) { this.writable = true; } }; -sys.inherits(Socket, process.EventEmitter); +sys.inherits(Socket, events.EventEmitter); exports.Socket = Socket; @@ -750,7 +755,7 @@ Socket.prototype.close = function () { function Server (listener) { - process.EventEmitter.call(this); + events.EventEmitter.call(this); var self = this; if (listener) { @@ -778,7 +783,7 @@ function Server (listener) { } }; } -sys.inherits(Server, process.EventEmitter); +sys.inherits(Server, events.EventEmitter); exports.Server = Server; diff --git a/src/node.cc b/src/node.cc index 608b911d73f..78f7e595e08 100644 --- a/src/node.cc +++ b/src/node.cc @@ -1139,6 +1139,24 @@ static Handle Binding(const Arguments& args) { binding_cache->Set(module, exports); } + } else if (!strcmp(*module_v, "net")) { + if (binding_cache->Has(module)) { + exports = binding_cache->Get(module)->ToObject(); + } else { + exports = Object::New(); + InitNet2(exports); + binding_cache->Set(module, exports); + } + + } else if (!strcmp(*module_v, "http_parser")) { + if (binding_cache->Has(module)) { + exports = binding_cache->Get(module)->ToObject(); + } else { + exports = Object::New(); + InitHttpParser(exports); + binding_cache->Set(module, exports); + } + } else if (!strcmp(*module_v, "natives")) { if (binding_cache->Has(module)) { exports = binding_cache->Get(module)->ToObject(); @@ -1267,8 +1285,6 @@ static void Load(int argc, char *argv[]) { IOWatcher::Initialize(process); // io_watcher.cc IdleWatcher::Initialize(process); // idle_watcher.cc Timer::Initialize(process); // timer.cc - InitNet2(process); // net2.cc - InitHttpParser(process); // http_parser.cc ChildProcess::Initialize(process); // child_process.cc DefineConstants(process); // constants.cc From 04c06b91493762dea34d91c34f2a33ebaa919eca Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Wed, 17 Mar 2010 14:00:17 -0700 Subject: [PATCH 085/105] child process now use net.Socket --- doc/api.txt | 68 ++-- lib/child_process.js | 100 ++++++ lib/net.js | 22 +- lib/sys.js | 35 +- src/node.cc | 13 +- src/node.js | 20 +- src/node_child_process.cc | 320 +++++------------- src/node_child_process.h | 79 ++--- test/fixtures/echo.js | 11 +- test/fixtures/print-chars.js | 4 +- test/pummel/test-process-spawn-loop.js | 33 -- ...ing.js => test-child-process-buffering.js} | 11 +- test/simple/test-child-process-env.js | 13 +- test/simple/test-child-process-ipc.js | 41 +++ test/simple/test-child-process-kill.js | 38 +++ test/simple/test-child-process-spawn-loop.js | 36 ++ test/simple/test-child-process-stdin.js | 48 +++ .../simple/test-child-process-stdout-flush.js | 27 ++ test/simple/test-exec.js | 2 +- test/simple/test-http-parser.js | 5 +- test/simple/test-process-kill.js | 15 - test/simple/test-process-simple.js | 34 -- test/simple/test-stdio.js | 36 -- test/simple/test-stdout-flush.js | 29 -- 24 files changed, 519 insertions(+), 521 deletions(-) create mode 100644 lib/child_process.js delete mode 100644 test/pummel/test-process-spawn-loop.js rename test/simple/{test-process-buffering.js => test-child-process-buffering.js} (73%) create mode 100644 test/simple/test-child-process-ipc.js create mode 100644 test/simple/test-child-process-kill.js create mode 100644 test/simple/test-child-process-spawn-loop.js create mode 100644 test/simple/test-child-process-stdin.js create mode 100644 test/simple/test-child-process-stdout-flush.js delete mode 100644 test/simple/test-process-kill.js delete mode 100644 test/simple/test-process-simple.js delete mode 100644 test/simple/test-stdio.js delete mode 100644 test/simple/test-stdout-flush.js diff --git a/doc/api.txt b/doc/api.txt index def050430b4..3c611df855e 100644 --- a/doc/api.txt +++ b/doc/api.txt @@ -192,23 +192,6 @@ The default is to only recurse twice. To make it recurse indefinitely, pass in +null+ for +depth+. -+exec(command, callback)+:: -Executes the command as a child process, buffers the output and returns it -in a callback. -+ ----------------------------------------- -var sys = require("sys"); -sys.exec("ls /", function (err, stdout, stderr) { - if (err) throw err; - sys.puts(stdout); -}); ----------------------------------------- -+ -The callback gets the arguments +(err, stdout, stderr)+. On success +err+ -will be +null+. On error +err+ will be an instance of +Error+ and +err.code+ -will be the exit code of the child process. - - == Events Many objects in Node emit events: a TCP server emits an event each time @@ -399,25 +382,18 @@ Stops a interval from triggering. == Child Processes Node provides a tridirectional +popen(3)+ facility through the class -+process.ChildProcess+. It is possible to stream data through the child's +stdin+, -+stdout+, and +stderr+ in a fully non-blocking way. ++ChildProcess+ class. It is possible to stream data through the child's ++stdin+, +stdout+, and +stderr+ in a fully non-blocking way. + +To create a child process use +require("child_process").spawn()+. + +Child processes always have three streams associated with them. ++child.stdin+, +child.stdout+, and +child.stderr+. -=== +process.ChildProcess+ [cols="1,2,10",options="header"] |========================================================= | Event | Parameters |Notes - -| +"output"+ | +data+ | Each time the child process - sends data to its +stdout+, this event is - emitted. +data+ is a string. If the child - process closes its +stdout+ stream (a common - thing to do on exit), this event will be emitted - with +data === null+. - -| +"error"+ | +data+ | Identical to the +"output"+ event except for - +stderr+ instead of +stdout+. - | +"exit"+ | +code+ | This event is emitted after the child process ends. +code+ is the final exit code of the process. One can be assured that after this @@ -425,19 +401,18 @@ Node provides a tridirectional +popen(3)+ facility through the class +"error"+ callbacks will no longer be made. |========================================================= -+process.createChildProcess(command, args=[], env=process.env)+:: ++require("child_process").spawn(command, args=[], env=process.env)+:: Launches a new process with the given +command+, command line arguments, and environmental variables. For example: + ---------------------------------------- -var ls = process.createChildProcess("ls", ["-lh", "/usr"]); -ls.addListener("output", function (data) { - sys.puts(data); +// Pipe a child process output to +// parent process output +var ls = spawn("ls", ["-lh", "/usr"]); +ls.stdout.addListener("data", function (data) { + process.stdout.write(data); }); ---------------------------------------- -+ -Note, if you just want to buffer the output of a command and return it, then -+exec()+ in +/sys.js+ might be better. +child.pid+ :: @@ -459,6 +434,23 @@ Send a signal to the child process. If no argument is given, the process will be sent +"SIGTERM"+. See signal(7) for a list of available signals. ++require("child_process").exec(command, callback)+:: +High-level way to executes a command as a child process and buffer the +output and return it in a callback. ++ +---------------------------------------- +var exec = require("child_process").exec; +exec("ls /", function (err, stdout, stderr) { + if (err) throw err; + sys.puts(stdout); +}); +---------------------------------------- ++ +The callback gets the arguments +(err, stdout, stderr)+. On success +err+ +will be +null+. On error +err+ will be an instance of +Error+ and +err.code+ +will be the exit code of the child process. + + == File System diff --git a/lib/child_process.js b/lib/child_process.js new file mode 100644 index 00000000000..e3a8a30e154 --- /dev/null +++ b/lib/child_process.js @@ -0,0 +1,100 @@ +var inherits = require('sys').inherits; +var EventEmitter = require('events').EventEmitter; +var Socket = require('net').Socket; +var InternalChildProcess = process.binding('child_process').ChildProcess; + + +var spawn = exports.spawn = function (path, args, env) { + var child = new ChildProcess(); + child.spawn(path, args, env); + return child; +}; + + +exports.exec = function (command, callback) { + var child = spawn("/bin/sh", ["-c", command]); + var stdout = ""; + var stderr = ""; + + child.stdout.setEncoding('utf8'); + child.stdout.addListener("data", function (chunk) { stdout += chunk; }); + + child.stderr.setEncoding('utf8'); + child.stderr.addListener("data", function (chunk) { stderr += chunk; }); + + child.addListener("exit", function (code) { + if (code == 0) { + if (callback) callback(null, stdout, stderr); + } else { + var e = new Error("Command failed: " + stderr); + e.code = code; + if (callback) callback(e, stdout, stderr); + } + }); +}; + + +function ChildProcess () { + process.EventEmitter.call(this); + + var self = this; + + var gotCHLD = false; + var exitCode; + var internal = this._internal = new InternalChildProcess(); + + var stdin = this.stdin = new Socket(); + var stdout = this.stdout = new Socket(); + var stderr = this.stderr = new Socket(); + + stderr.onend = stdout.onend = function () { + if (gotCHLD && !stdout.readable && !stderr.readable) { + self.emit('exit', exitCode); + } + }; + + internal.onexit = function (code) { + gotCHLD = true; + exitCode = code; + if (!stdout.readable && !stderr.readable) { + self.emit('exit', exitCode); + } + }; + + this.__defineGetter__('pid', function () { return internal.pid; }); +} +inherits(ChildProcess, EventEmitter); + + +ChildProcess.prototype.kill = function (sig) { + return this._internal.kill(sig); +}; + + +ChildProcess.prototype.spawn = function (path, args, env) { + args = args || []; + env = env || process.env; + var envPairs = []; + for (var key in env) { + if (env.hasOwnProperty(key)) { + envPairs.push(key + "=" + env[key]); + } + } + + var fds = this._internal.spawn(path, args, envPairs); + + this.stdin.open(fds[0]); + this.stdin.writable = true; + this.stdin.readable = false; + + this.stdout.open(fds[1]); + this.stdout.writable = false; + this.stdout.readable = true; + this.stdout.resume(); + + this.stderr.open(fds[2]); + this.stderr.writable = false; + this.stderr.readable = true; + this.stderr.resume(); +}; + diff --git a/lib/net.js b/lib/net.js index 8b2448373ec..b563ce17b35 100644 --- a/lib/net.js +++ b/lib/net.js @@ -367,20 +367,25 @@ function Socket (fd) { this.fd = null; if (parseInt(fd) >= 0) { - initSocket(this); - - this.fd = fd; - - this.readable = true; - - this._writeWatcher.set(this.fd, false, true); - this.writable = true; + this.open(fd); } }; sys.inherits(Socket, events.EventEmitter); exports.Socket = Socket; +Socket.prototype.open = function (fd) { + initSocket(this); + + this.fd = fd; + + this.readable = true; + + this._writeWatcher.set(this.fd, false, true); + this.writable = true; +} + + exports.createConnection = function (port, host) { var s = new Socket(); s.connect(port, host); @@ -716,6 +721,7 @@ Socket.prototype.forceClose = function (exception) { timeout.unenroll(this); + // FIXME Bug when this.fd == 0 if (this.fd) { close(this.fd); debug('close ' + this.fd); diff --git a/lib/sys.js b/lib/sys.js index aef80530e29..c3b5d61304d 100644 --- a/lib/sys.js +++ b/lib/sys.js @@ -16,7 +16,7 @@ exports.debug = function (x) { process.binding('stdio').writeError("DEBUG: " + x + "\n"); }; -exports.error = function (x) { +var error = exports.error = function (x) { for (var i = 0, len = arguments.length; i < len; ++i) { process.binding('stdio').writeError(arguments[i] + '\n'); } @@ -184,7 +184,7 @@ exports.inspect = function (obj, showHidden, depth) { exports.p = function () { for (var i = 0, len = arguments.length; i < len; ++i) { - exports.error(exports.inspect(arguments[i])); + error(exports.inspect(arguments[i])); } }; @@ -207,29 +207,14 @@ exports.log = function (msg) { exports.puts(timestamp() + ' - ' + msg.toString()); } -exports.exec = function (command, callback) { - var child = process.createChildProcess("/bin/sh", ["-c", command]); - var stdout = ""; - var stderr = ""; - - child.addListener("output", function (chunk) { - if (chunk) stdout += chunk; - }); - - child.addListener("error", function (chunk) { - if (chunk) stderr += chunk; - }); - - child.addListener("exit", function (code) { - if (code == 0) { - if (callback) callback(null, stdout, stderr); - } else { - var e = new Error("Command failed: " + stderr); - e.code = code; - if (callback) callback(e, stdout, stderr); - } - }); -}; +var execWarning; +exports.exec = function () { + if (!execWarning) { + execWarning = 'sys.exec has moved to the "child_process" module. Please update your source code.' + error(execWarning); + } + return require('child_process').exec.apply(this, arguments); +} /** * Inherit the prototype methods from one constructor into another. diff --git a/src/node.cc b/src/node.cc index 78f7e595e08..d6148e22af9 100644 --- a/src/node.cc +++ b/src/node.cc @@ -1073,6 +1073,8 @@ static Handle Binding(const Arguments& args) { Local exports; + // TODO DRY THIS UP! + if (!strcmp(*module_v, "stdio")) { if (binding_cache->Has(module)) { exports = binding_cache->Get(module)->ToObject(); @@ -1157,6 +1159,15 @@ static Handle Binding(const Arguments& args) { binding_cache->Set(module, exports); } + } else if (!strcmp(*module_v, "child_process")) { + if (binding_cache->Has(module)) { + exports = binding_cache->Get(module)->ToObject(); + } else { + exports = Object::New(); + ChildProcess::Initialize(exports); + binding_cache->Set(module, exports); + } + } else if (!strcmp(*module_v, "natives")) { if (binding_cache->Has(module)) { exports = binding_cache->Get(module)->ToObject(); @@ -1165,6 +1176,7 @@ static Handle Binding(const Arguments& args) { // Explicitly define native sources. // TODO DRY/automate this? exports->Set(String::New("assert"), String::New(native_assert)); + exports->Set(String::New("child_process"),String::New(native_child_process)); exports->Set(String::New("dns"), String::New(native_dns)); exports->Set(String::New("events"), String::New(native_events)); exports->Set(String::New("file"), String::New(native_file)); @@ -1285,7 +1297,6 @@ static void Load(int argc, char *argv[]) { IOWatcher::Initialize(process); // io_watcher.cc IdleWatcher::Initialize(process); // idle_watcher.cc Timer::Initialize(process); // timer.cc - ChildProcess::Initialize(process); // child_process.cc DefineConstants(process); // constants.cc // Compile, execute the src/node.js file. (Which was included as static C diff --git a/src/node.js b/src/node.js index 2af9ab817cf..ccc4584e986 100644 --- a/src/node.js +++ b/src/node.js @@ -25,6 +25,7 @@ process.unwatchFile = removed("process.unwatchFile() has moved to fs.unwatchFile GLOBAL.node = {}; node.createProcess = removed("node.createProcess() has been changed to process.createChildProcess() update your code"); +process.createChildProcess = removed("childProcess API has changed. See doc/api.txt."); node.exec = removed("process.exec() has moved. Use require('sys') to bring it back."); node.inherits = removed("node.inherits() has moved. Use require('sys') to access it."); process.inherits = removed("process.inherits() has moved to sys.inherits."); @@ -89,24 +90,6 @@ function requireNative (id) { } -process.createChildProcess = function (file, args, env) { - var child = new process.ChildProcess(); - args = args || []; - env = env || process.env; - var envPairs = []; - for (var key in env) { - if (env.hasOwnProperty(key)) { - envPairs.push(key + "=" + env[key]); - } - } - // TODO Note envPairs is not currently used in child_process.cc. The PATH - // needs to be searched for the 'file' command if 'file' does not contain - // a '/' character. - child.spawn(file, args, envPairs); - return child; -}; - - process.assert = function (x, msg) { if (!(x)) throw new Error(msg || "assertion error"); }; @@ -797,7 +780,6 @@ process.openStdin = function () { var net = requireNative('net'); var fd = process.binding('stdio').openStdin(); stdin = new net.Socket(fd); - process.stdout.write(stdin.fd + "\n"); stdin.resume(); stdin.readable = true; return stdin; diff --git a/src/node_child_process.cc b/src/node_child_process.cc index 6c09ee0f009..870269f5fdb 100644 --- a/src/node_child_process.cc +++ b/src/node_child_process.cc @@ -15,56 +15,55 @@ namespace node { using namespace v8; -Persistent ChildProcess::constructor_template; - static Persistent pid_symbol; -static Persistent exit_symbol; -static Persistent output_symbol; -static Persistent error_symbol; +static Persistent onexit_symbol; + + +// TODO share with other modules +static inline int SetNonBlocking(int fd) { + int flags = fcntl(fd, F_GETFL, 0); + int r = fcntl(fd, F_SETFL, flags | O_NONBLOCK); + if (r != 0) { + perror("SetNonBlocking()"); + } + return r; +} + void ChildProcess::Initialize(Handle target) { HandleScope scope; Local t = FunctionTemplate::New(ChildProcess::New); - constructor_template = Persistent::New(t); - constructor_template->Inherit(EventEmitter::constructor_template); - constructor_template->InstanceTemplate()->SetInternalFieldCount(1); - constructor_template->SetClassName(String::NewSymbol("ChildProcess")); + t->InstanceTemplate()->SetInternalFieldCount(1); + t->SetClassName(String::NewSymbol("ChildProcess")); pid_symbol = NODE_PSYMBOL("pid"); - exit_symbol = NODE_PSYMBOL("exit"); - output_symbol = NODE_PSYMBOL("output"); - error_symbol = NODE_PSYMBOL("error"); + onexit_symbol = NODE_PSYMBOL("onexit"); - NODE_SET_PROTOTYPE_METHOD(constructor_template, "spawn", ChildProcess::Spawn); - NODE_SET_PROTOTYPE_METHOD(constructor_template, "write", ChildProcess::Write); - NODE_SET_PROTOTYPE_METHOD(constructor_template, "close", ChildProcess::Close); - NODE_SET_PROTOTYPE_METHOD(constructor_template, "kill", ChildProcess::Kill); + NODE_SET_PROTOTYPE_METHOD(t, "spawn", ChildProcess::Spawn); + NODE_SET_PROTOTYPE_METHOD(t, "kill", ChildProcess::Kill); - target->Set(String::NewSymbol("ChildProcess"), - constructor_template->GetFunction()); + target->Set(String::NewSymbol("ChildProcess"), t->GetFunction()); } + Handle ChildProcess::New(const Arguments& args) { HandleScope scope; - ChildProcess *p = new ChildProcess(); p->Wrap(args.Holder()); - return args.This(); } + // This is an internal function. The third argument should be an array // of key value pairs seperated with '='. Handle ChildProcess::Spawn(const Arguments& args) { HandleScope scope; - if ( args.Length() != 3 - || !args[0]->IsString() - || !args[1]->IsArray() - || !args[2]->IsArray() - ) - { + if (args.Length() != 3 || + !args[0]->IsString() || + !args[1]->IsArray() || + !args[2]->IsArray()) { return ThrowException(Exception::Error(String::New("Bad argument."))); } @@ -98,7 +97,9 @@ Handle ChildProcess::Spawn(const Arguments& args) { env[i] = strdup(*pair); } - int r = child->Spawn(argv[0], argv, env); + int fds[3]; + + int r = child->Spawn(argv[0], argv, env, fds); for (i = 0; i < argv_length; i++) free(argv[i]); delete [] argv; @@ -110,32 +111,18 @@ Handle ChildProcess::Spawn(const Arguments& args) { return ThrowException(Exception::Error(String::New("Error spawning"))); } - child->handle_->Set(pid_symbol, Integer::New(child->pid_)); + Local a = Array::New(3); - return Undefined(); + assert(fds[0] >= 0); + a->Set(0, Integer::New(fds[0])); // stdin + assert(fds[1] >= 0); + a->Set(1, Integer::New(fds[1])); // stdout + assert(fds[2] >= 0); + a->Set(2, Integer::New(fds[2])); // stderr + + return scope.Close(a); } -Handle ChildProcess::Write(const Arguments& args) { - HandleScope scope; - ChildProcess *child = ObjectWrap::Unwrap(args.Holder()); - assert(child); - - enum encoding enc = ParseEncoding(args[1]); - ssize_t len = DecodeBytes(args[0], enc); - - if (len < 0) { - Local exception = Exception::TypeError(String::New("Bad argument")); - return ThrowException(exception); - } - - char * buf = new char[len]; - ssize_t written = DecodeWrite(buf, len, args[0], enc); - assert(written == len); - int r = child->Write(buf, len); - delete [] buf; - - return r == 0 ? True() : False(); -} Handle ChildProcess::Kill(const Arguments& args) { HandleScope scope; @@ -167,160 +154,59 @@ Handle ChildProcess::Kill(const Arguments& args) { return Undefined(); } -Handle ChildProcess::Close(const Arguments& args) { - HandleScope scope; - ChildProcess *child = ObjectWrap::Unwrap(args.Holder()); - assert(child); - return child->Close() == 0 ? True() : False(); -} -void ChildProcess::reader_closed(evcom_reader *r) { - ChildProcess *child = static_cast(r->data); - if (r == &child->stdout_reader_) { - child->stdout_fd_ = -1; - } else { - assert(r == &child->stderr_reader_); - child->stderr_fd_ = -1; +void ChildProcess::Stop() { + if (ev_is_active(&child_watcher_)) { + ev_child_stop(EV_DEFAULT_UC_ &child_watcher_); + Unref(); } - evcom_reader_detach(r); - child->MaybeShutdown(); + // Don't kill the PID here. We want to allow for killing the parent + // process and reparenting to initd. This is perhaps not going the best + // technique for daemonizing, but I don't want to rule it out. + pid_ = -1; } -void ChildProcess::stdin_closed(evcom_writer *w) { - ChildProcess *child = static_cast(w->data); - assert(w == &child->stdin_writer_); - child->stdin_fd_ = -1; - evcom_writer_detach(w); - child->MaybeShutdown(); -} - -void ChildProcess::on_read(evcom_reader *r, const void *buf, size_t len) { - ChildProcess *child = static_cast(r->data); - HandleScope scope; - - bool isSTDOUT = (r == &child->stdout_reader_); - enum encoding encoding = isSTDOUT ? - child->stdout_encoding_ : child->stderr_encoding_; - - // TODO emit 'end' event instead of null. - - Local data = len ? Encode(buf, len, encoding) : Local::New(Null()); - child->Emit(isSTDOUT ? output_symbol : error_symbol, 1, &data); - child->MaybeShutdown(); -} - -ChildProcess::ChildProcess() : EventEmitter() { - evcom_reader_init(&stdout_reader_); - stdout_reader_.data = this; - stdout_reader_.on_read = on_read; - stdout_reader_.on_close = reader_closed; - - evcom_reader_init(&stderr_reader_); - stderr_reader_.data = this; - stderr_reader_.on_read = on_read; - stderr_reader_.on_close = reader_closed; - - evcom_writer_init(&stdin_writer_); - stdin_writer_.data = this; - stdin_writer_.on_close = stdin_closed; - - ev_init(&child_watcher_, ChildProcess::OnCHLD); - child_watcher_.data = this; - - stdout_fd_ = -1; - stderr_fd_ = -1; - stdin_fd_ = -1; - - stdout_encoding_ = UTF8; - stderr_encoding_ = UTF8; - - got_chld_ = false; - exit_code_ = 0; - - pid_ = 0; -} - -ChildProcess::~ChildProcess() { - Shutdown(); -} - -void ChildProcess::Shutdown() { - if (stdin_fd_ >= 0) { - evcom_writer_close(&stdin_writer_); - } - - if (stdin_fd_ >= 0) close(stdin_fd_); - if (stdout_fd_ >= 0) close(stdout_fd_); - if (stderr_fd_ >= 0) close(stderr_fd_); - - stdin_fd_ = -1; - stdout_fd_ = -1; - stderr_fd_ = -1; - - evcom_writer_detach(&stdin_writer_); - evcom_reader_detach(&stdout_reader_); - evcom_reader_detach(&stderr_reader_); - - ev_child_stop(EV_DEFAULT_UC_ &child_watcher_); - - /* XXX Kill the PID? */ - pid_ = 0; -} - -static inline int SetNonBlocking(int fd) { - int flags = fcntl(fd, F_GETFL, 0); - int r = fcntl(fd, F_SETFL, flags | O_NONBLOCK); - if (r != 0) { - perror("SetNonBlocking()"); - } - return r; -} // Note that args[0] must be the same as the "file" param. This is an // execvp() requirement. -int ChildProcess::Spawn(const char *file, char *const args[], char **env) { - assert(pid_ == 0); - assert(stdout_fd_ == -1); - assert(stderr_fd_ == -1); - assert(stdin_fd_ == -1); +// +int ChildProcess::Spawn(const char *file, + char *const args[], + char **env, + int stdio_fds[3]) { + HandleScope scope; + assert(pid_ == -1); + assert(!ev_is_active(&child_watcher_)); - int stdout_pipe[2], stdin_pipe[2], stderr_pipe[2]; + int stdin_pipe[2], stdout_pipe[2], stderr_pipe[2]; /* An implementation of popen(), basically */ - if (pipe(stdout_pipe) < 0) { + if (pipe(stdin_pipe) < 0 || + pipe(stdout_pipe) < 0 || + pipe(stderr_pipe) < 0) { perror("pipe()"); return -1; } - if (pipe(stderr_pipe) < 0) { - perror("pipe()"); - return -2; - } - - if (pipe(stdin_pipe) < 0) { - perror("pipe()"); - return -3; - } - // Save environ in the case that we get it clobbered // by the child process. char **save_our_env = environ; switch (pid_ = vfork()) { case -1: // Error. - Shutdown(); + Stop(); return -4; case 0: // Child. + close(stdin_pipe[1]); // close write end + dup2(stdin_pipe[0], STDIN_FILENO); + close(stdout_pipe[0]); // close read end dup2(stdout_pipe[1], STDOUT_FILENO); close(stderr_pipe[0]); // close read end dup2(stderr_pipe[1], STDERR_FILENO); - close(stdin_pipe[1]); // close write end - dup2(stdin_pipe[0], STDIN_FILENO); - environ = env; execvp(file, args); @@ -328,81 +214,59 @@ int ChildProcess::Spawn(const char *file, char *const args[], char **env) { _exit(127); } + // Parent. + // Restore environment. environ = save_our_env; - // Parent. - ev_child_set(&child_watcher_, pid_, 0); ev_child_start(EV_DEFAULT_UC_ &child_watcher_); - - close(stdout_pipe[1]); - stdout_fd_ = stdout_pipe[0]; - SetNonBlocking(stdout_fd_); - - close(stderr_pipe[1]); - stderr_fd_ = stderr_pipe[0]; - SetNonBlocking(stderr_fd_); + Ref(); + handle_->Set(pid_symbol, Integer::New(pid_)); close(stdin_pipe[0]); - stdin_fd_ = stdin_pipe[1]; - SetNonBlocking(stdin_fd_); + stdio_fds[0] = stdin_pipe[1]; + SetNonBlocking(stdin_pipe[1]); - evcom_reader_set(&stdout_reader_, stdout_fd_); - evcom_reader_attach(EV_DEFAULT_UC_ &stdout_reader_); + close(stdout_pipe[1]); + stdio_fds[1] = stdout_pipe[0]; + SetNonBlocking(stdout_pipe[0]); - evcom_reader_set(&stderr_reader_, stderr_fd_); - evcom_reader_attach(EV_DEFAULT_UC_ &stderr_reader_); - - evcom_writer_set(&stdin_writer_, stdin_fd_); - evcom_writer_attach(EV_DEFAULT_UC_ &stdin_writer_); - - Ref(); + close(stderr_pipe[1]); + stdio_fds[2] = stderr_pipe[0]; + SetNonBlocking(stderr_pipe[0]); return 0; } -void ChildProcess::OnCHLD(EV_P_ ev_child *watcher, int revents) { - ev_child_stop(EV_A_ watcher); - ChildProcess *child = static_cast(watcher->data); - assert(revents == EV_CHILD); - assert(child->pid_ == watcher->rpid); - assert(&child->child_watcher_ == watcher); +void ChildProcess::OnExit(int code) { + HandleScope scope; - child->got_chld_ = true; - child->exit_code_ = watcher->rstatus; + pid_ = -1; + Stop(); - if (child->stdin_fd_ >= 0) evcom_writer_close(&child->stdin_writer_); + handle_->Set(pid_symbol, Null()); - child->MaybeShutdown(); -} + Local onexit_v = handle_->Get(onexit_symbol); + assert(onexit_v->IsFunction()); + Local onexit = Local::Cast(onexit_v); -int ChildProcess::Write(const char *str, size_t len) { - if (stdin_fd_ < 0 || got_chld_) return -1; - evcom_writer_write(&stdin_writer_, str, len); - return 0; -} + TryCatch try_catch; -int ChildProcess::Close(void) { - if (stdin_fd_ < 0 || got_chld_) return -1; - evcom_writer_close(&stdin_writer_); - return 0; -} + Local argv[1]; + argv[0] = Integer::New(code); -int ChildProcess::Kill(int sig) { - if (got_chld_ || pid_ == 0) return -1; - return kill(pid_, sig); -} + onexit->Call(handle_, 1, argv); -void ChildProcess::MaybeShutdown(void) { - if (stdout_fd_ < 0 && stderr_fd_ < 0 && got_chld_) { - HandleScope scope; - Handle argv[1] = { Integer::New(exit_code_) }; - Emit(exit_symbol, 1, argv); - Shutdown(); - Unref(); + if (try_catch.HasCaught()) { + FatalException(try_catch); } } + +int ChildProcess::Kill(int sig) { + return kill(pid_, sig); +} + } // namespace node diff --git a/src/node_child_process.h b/src/node_child_process.h index b37db9f36ae..14a13ecf422 100644 --- a/src/node_child_process.h +++ b/src/node_child_process.h @@ -1,65 +1,68 @@ // Copyright 2009 Ryan Dahl -#ifndef SRC_CHILD_PROCESS_H_ -#define SRC_CHILD_PROCESS_H_ +#ifndef NODE_CHILD_PROCESS_H_ +#define NODE_CHILD_PROCESS_H_ #include -#include - +#include #include #include -#include + +// ChildProcess is a thin wrapper around ev_child. It has the extra +// functionality that it can spawn a child process with pipes connected to +// its stdin, stdout, stderr. This class is not meant to be exposed to but +// wrapped up in a more friendly EventEmitter with streams for each of the +// pipes. +// +// When the child process exits (when the parent receives SIGCHLD) the +// callback child.onexit will be called. namespace node { -class ChildProcess : EventEmitter { +class ChildProcess : ObjectWrap { public: static void Initialize(v8::Handle target); protected: - static v8::Persistent constructor_template; static v8::Handle New(const v8::Arguments& args); static v8::Handle Spawn(const v8::Arguments& args); - static v8::Handle Write(const v8::Arguments& args); - static v8::Handle Close(const v8::Arguments& args); static v8::Handle Kill(const v8::Arguments& args); - static v8::Handle PIDGetter(v8::Local _, - const v8::AccessorInfo& info); - ChildProcess(); - ~ChildProcess(); + ChildProcess() : ObjectWrap() { + ev_init(&child_watcher_, ChildProcess::on_chld); + child_watcher_.data = this; + pid_ = -1; + } - int Spawn(const char *file, char *const argv[], char **env); - int Write(const char *str, size_t len); - int Close(void); + ~ChildProcess() { + Stop(); + } + + // Returns 0 on success. stdio_fds will contain file desciptors for stdin, + // stdout, and stderr of the subprocess. stdin is writable; the other two + // are readable. + // The user of this class has responsibility to close these pipes after + // the child process exits. + int Spawn(const char *file, char *const argv[], char **env, int stdio_fds[3]); + + // Simple syscall wrapper. Does not disable the watcher. onexit will be + // called still. int Kill(int sig); private: - static void on_read(evcom_reader *r, const void *buf, size_t len); - static void reader_closed(evcom_reader *r); - static void stdin_closed(evcom_writer *w); - static void OnCHLD(EV_P_ ev_child *watcher, int revents); + void OnExit(int code); + void Stop(void); - void MaybeShutdown(void); - void Shutdown(void); - - evcom_reader stdout_reader_; - evcom_reader stderr_reader_; - evcom_writer stdin_writer_; + static void on_chld(EV_P_ ev_child *watcher, int revents) { + ChildProcess *child = static_cast(watcher->data); + assert(revents == EV_CHILD); + assert(child->pid_ == watcher->rpid); + assert(&child->child_watcher_ == watcher); + child->OnExit(watcher->rstatus); + } ev_child child_watcher_; - - int stdout_fd_; - int stderr_fd_; - int stdin_fd_; - - enum encoding stdout_encoding_; - enum encoding stderr_encoding_; - pid_t pid_; - - bool got_chld_; - int exit_code_; }; } // namespace node -#endif // SRC_CHILD_PROCESS_H_ +#endif // NODE_CHILD_PROCESS_H_ diff --git a/test/fixtures/echo.js b/test/fixtures/echo.js index 8cb96e2026f..60f3b614a03 100644 --- a/test/fixtures/echo.js +++ b/test/fixtures/echo.js @@ -1,12 +1,13 @@ require("../common"); -process.stdio.open(); print("hello world\r\n"); -process.stdio.addListener("data", function (data) { - print(data); +var stdin = process.openStdin(); + +stdin.addListener("data", function (data) { + process.stdout.write(data); }); -process.stdio.addListener("close", function () { - process.stdio.close(); +stdin.addListener("end", function () { + process.stdout.close(); }); diff --git a/test/fixtures/print-chars.js b/test/fixtures/print-chars.js index 2f8b6cf769a..13a4c3d0b3f 100644 --- a/test/fixtures/print-chars.js +++ b/test/fixtures/print-chars.js @@ -3,8 +3,8 @@ require("../common"); var n = parseInt(process.argv[2]); var s = ""; -for (var i = 0; i < n-1; i++) { +for (var i = 0; i < n; i++) { s += 'c'; } -puts(s); // \n is the nth char. +process.stdout.write(s); diff --git a/test/pummel/test-process-spawn-loop.js b/test/pummel/test-process-spawn-loop.js deleted file mode 100644 index 6b8d9d8f4bf..00000000000 --- a/test/pummel/test-process-spawn-loop.js +++ /dev/null @@ -1,33 +0,0 @@ -require("../common"); - -var N = 40; -var finished = false; - -function spawn (i) { - var child = process.createChildProcess( 'python' - , ['-c', 'print 500 * 1024 * "C"'] - ); - var output = ""; - - child.addListener("output", function(chunk) { - if (chunk) output += chunk; - }); - - child.addListener("error", function(chunk) { - if (chunk) error(chunk) - }); - - child.addListener("exit", function () { - puts(output); - if (i < N) - spawn(i+1); - else - finished = true; - }); -} - -spawn(0); - -process.addListener("exit", function () { - assert.equal(true, finished); -}); diff --git a/test/simple/test-process-buffering.js b/test/simple/test-child-process-buffering.js similarity index 73% rename from test/simple/test-process-buffering.js rename to test/simple/test-child-process-buffering.js index 78377403000..10ec8f847a2 100644 --- a/test/simple/test-process-buffering.js +++ b/test/simple/test-child-process-buffering.js @@ -1,14 +1,19 @@ require("../common"); +var spawn = require('child_process').spawn; + var pwd_called = false; function pwd (callback) { var output = ""; - var child = process.createChildProcess("pwd"); - child.addListener("output", function (s) { + var child = spawn("pwd"); + + child.stdout.setEncoding('utf8'); + child.stdout.addListener("data", function (s) { puts("stdout: " + JSON.stringify(s)); - if (s) output += s; + output += s; }); + child.addListener("exit", function (c) { puts("exit: " + c); assert.equal(0, c); diff --git a/test/simple/test-child-process-env.js b/test/simple/test-child-process-env.js index 4600fea9ef0..d6f9674d9be 100644 --- a/test/simple/test-child-process-env.js +++ b/test/simple/test-child-process-env.js @@ -1,10 +1,15 @@ require("../common"); -child = process.createChildProcess('/usr/bin/env', [], {'HELLO' : 'WORLD'}); + +var spawn = require('child_process').spawn; +child = spawn('/usr/bin/env', [], {'HELLO' : 'WORLD'}); + response = ""; -child.addListener("output", function (chunk) { - puts("stdout: " + JSON.stringify(chunk)); - if (chunk) response += chunk; +child.stdout.setEncoding('utf8'); + +child.stdout.addListener("data", function (chunk) { + puts("stdout: " + chunk); + response += chunk; }); process.addListener('exit', function () { diff --git a/test/simple/test-child-process-ipc.js b/test/simple/test-child-process-ipc.js new file mode 100644 index 00000000000..ca28462aa7b --- /dev/null +++ b/test/simple/test-child-process-ipc.js @@ -0,0 +1,41 @@ +require("../common"); + +var spawn = require('child_process').spawn; + +var path = require('path'); + +var sub = path.join(fixturesDir, 'echo.js'); + +var gotHelloWorld = false; +var gotEcho = false; + +var child = spawn(process.argv[0], [sub]); + +child.stderr.addListener("data", function (data){ + puts("parent stderr: " + data); +}); + +child.stdout.setEncoding('utf8'); + +child.stdout.addListener("data", function (data){ + puts('child said: ' + JSON.stringify(data)); + if (!gotHelloWorld) { + assert.equal("hello world\r\n", data); + gotHelloWorld = true; + child.stdin.write('echo me\r\n'); + } else { + assert.equal("echo me\r\n", data); + gotEcho = true; + child.stdin.close(); + } +}); + +child.stdout.addListener("end", function (data){ + puts('child end'); +}); + + +process.addListener('exit', function () { + assert.ok(gotHelloWorld); + assert.ok(gotEcho); +}); diff --git a/test/simple/test-child-process-kill.js b/test/simple/test-child-process-kill.js new file mode 100644 index 00000000000..d506bef8cb6 --- /dev/null +++ b/test/simple/test-child-process-kill.js @@ -0,0 +1,38 @@ +require("../common"); + +var spawn = require('child_process').spawn; + +var exitStatus = -1; +var gotStdoutEOF = false; +var gotStderrEOF = false; + +var cat = spawn("cat"); + + +cat.stdout.addListener("data", function (chunk) { + assert.ok(false); +}); + +cat.stdout.addListener("end", function () { + gotStdoutEOF = true; +}); + +cat.stderr.addListener("data", function (chunk) { + assert.ok(false); +}); + +cat.stderr.addListener("end", function () { + gotStderrEOF = true; +}); + +cat.addListener("exit", function (status) { + exitStatus = status; +}); + +cat.kill(); + +process.addListener("exit", function () { + assert.ok(exitStatus > 0); + assert.ok(gotStdoutEOF); + assert.ok(gotStderrEOF); +}); diff --git a/test/simple/test-child-process-spawn-loop.js b/test/simple/test-child-process-spawn-loop.js new file mode 100644 index 00000000000..76e4236c193 --- /dev/null +++ b/test/simple/test-child-process-spawn-loop.js @@ -0,0 +1,36 @@ +require("../common"); + +var spawn = require('child_process').spawn; + +var SIZE = 1000 * 1024; +var N = 40; +var finished = false; + +function doSpawn (i) { + var child = spawn( 'python', ['-c', 'print ' + SIZE + ' * "C"']); + var count = 0; + + child.stdout.setEncoding('ascii'); + child.stdout.addListener("data", function (chunk) { + count += chunk.length; + }); + + child.stderr.addListener("data", function (chunk) { + puts('stderr: ' + chunk); + }); + + child.addListener("exit", function () { + assert.equal(SIZE + 1, count); // + 1 for \n + if (i < N) { + doSpawn(i+1); + } else { + finished = true; + } + }); +} + +doSpawn(0); + +process.addListener("exit", function () { + assert.ok(finished); +}); diff --git a/test/simple/test-child-process-stdin.js b/test/simple/test-child-process-stdin.js new file mode 100644 index 00000000000..d60740cb715 --- /dev/null +++ b/test/simple/test-child-process-stdin.js @@ -0,0 +1,48 @@ +require("../common"); + +var spawn = require('child_process').spawn; + +var cat = spawn("cat"); +cat.stdin.write("hello"); +cat.stdin.write(" "); +cat.stdin.write("world"); +cat.stdin.close(); + +var response = ""; +var exitStatus = -1; + +var gotStdoutEOF = false; + +cat.stdout.setEncoding('utf8'); +cat.stdout.addListener("data", function (chunk) { + puts("stdout: " + chunk); + response += chunk; +}); + +cat.stdout.addListener('end', function () { + gotStdoutEOF = true; +}); + + +var gotStderrEOF = false; + +cat.stderr.addListener("data", function (chunk) { + // shouldn't get any stderr output + assert.ok(false); +}); + +cat.stderr.addListener("end", function (chunk) { + gotStderrEOF = true; +}); + + +cat.addListener("exit", function (status) { + puts("exit event"); + exitStatus = status; + assert.equal("hello world", response); +}); + +process.addListener("exit", function () { + assert.equal(0, exitStatus); + assert.equal("hello world", response); +}); diff --git a/test/simple/test-child-process-stdout-flush.js b/test/simple/test-child-process-stdout-flush.js new file mode 100644 index 00000000000..25c51186760 --- /dev/null +++ b/test/simple/test-child-process-stdout-flush.js @@ -0,0 +1,27 @@ +require("../common"); +var path = require('path'); +var spawn = require('child_process').spawn; +var sub = path.join(fixturesDir, 'print-chars.js'); + +n = 500000; + +var child = spawn(process.argv[0], [sub, n]); + +var count = 0; + +child.stderr.setEncoding('utf8'); +child.stderr.addListener("data", function (data) { + puts("parent stderr: " + data); + assert.ok(false); +}); + +child.stderr.setEncoding('utf8'); +child.stdout.addListener("data", function (data) { + count += data.length; + puts(count); +}); + +child.addListener("exit", function (data) { + assert.equal(n, count); + puts("okay"); +}); diff --git a/test/simple/test-exec.js b/test/simple/test-exec.js index d899073324c..10f537b8268 100644 --- a/test/simple/test-exec.js +++ b/test/simple/test-exec.js @@ -1,5 +1,5 @@ require("../common"); - +var exec = require('child_process').exec; success_count = 0; error_count = 0; diff --git a/test/simple/test-http-parser.js b/test/simple/test-http-parser.js index f41e2d6f4c1..6ad65100558 100644 --- a/test/simple/test-http-parser.js +++ b/test/simple/test-http-parser.js @@ -1,12 +1,13 @@ -process.mixin(require("../common")); +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 HTTPParser = process.binding('http_parser').HTTPParser; -var parser = new process.HTTPParser("request"); +var parser = new HTTPParser("request"); var buffer = new process.Buffer(1024); diff --git a/test/simple/test-process-kill.js b/test/simple/test-process-kill.js deleted file mode 100644 index 6ee9e5fa3fc..00000000000 --- a/test/simple/test-process-kill.js +++ /dev/null @@ -1,15 +0,0 @@ -require("../common"); - -var exit_status = -1; - -var cat = process.createChildProcess("cat"); - -cat.addListener("output", function (chunk) { assert.equal(null, chunk); }); -cat.addListener("error", function (chunk) { assert.equal(null, chunk); }); -cat.addListener("exit", function (status) { exit_status = status; }); - -cat.kill(); - -process.addListener("exit", function () { - assert.equal(true, exit_status > 0); -}); diff --git a/test/simple/test-process-simple.js b/test/simple/test-process-simple.js deleted file mode 100644 index dad8f44442e..00000000000 --- a/test/simple/test-process-simple.js +++ /dev/null @@ -1,34 +0,0 @@ -require("../common"); - -var cat = process.createChildProcess("cat"); - -var response = ""; -var exit_status = -1; - -cat.addListener("output", function (chunk) { - puts("stdout: " + JSON.stringify(chunk)); - if (chunk) { - response += chunk; - if (response === "hello world") { - puts("closing cat"); - cat.close(); - } - } -}); -cat.addListener("error", function (chunk) { - puts("stderr: " + JSON.stringify(chunk)); - assert.equal(null, chunk); -}); -cat.addListener("exit", function (status) { - puts("exit event"); - exit_status = status; -}); - -cat.write("hello"); -cat.write(" "); -cat.write("world"); - -process.addListener("exit", function () { - assert.equal(0, exit_status); - assert.equal("hello world", response); -}); diff --git a/test/simple/test-stdio.js b/test/simple/test-stdio.js deleted file mode 100644 index 3c33fcfe66f..00000000000 --- a/test/simple/test-stdio.js +++ /dev/null @@ -1,36 +0,0 @@ -require("../common"); -var path = require('path'); - -var sub = path.join(fixturesDir, 'echo.js'); - -var gotHelloWorld = false; -var gotEcho = false; - -var child = process.createChildProcess(process.argv[0], [sub]); - -child.addListener("error", function (data){ - puts("parent stderr: " + data); -}); - -child.addListener("output", function (data){ - if (data) { - puts('child said: ' + JSON.stringify(data)); - if (!gotHelloWorld) { - assert.equal("hello world\r\n", data); - gotHelloWorld = true; - child.write('echo me\r\n'); - } else { - assert.equal("echo me\r\n", data); - gotEcho = true; - child.close(); - } - } else { - puts('child end'); - } -}); - - -process.addListener('exit', function () { - assert.ok(gotHelloWorld); - assert.ok(gotEcho); -}); diff --git a/test/simple/test-stdout-flush.js b/test/simple/test-stdout-flush.js deleted file mode 100644 index 06864a6b876..00000000000 --- a/test/simple/test-stdout-flush.js +++ /dev/null @@ -1,29 +0,0 @@ -require("../common"); -var path = require('path'); - -var sub = path.join(fixturesDir, 'print-chars.js'); - -n = 100000; - -var child = process.createChildProcess(process.argv[0], [sub, n]); - -var count = 0; - -child.addListener("error", function (data){ - if (data) { - puts("parent stderr: " + data); - assert.ok(false); - } -}); - -child.addListener("output", function (data){ - if (data) { - count += data.length; - puts(count); - } -}); - -child.addListener("exit", function (data) { - assert.equal(n, count); - puts("okay"); -}); From 1332cafb7cedc59691f3ff60b777b3c3a2d9774c Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Wed, 17 Mar 2010 16:31:24 -0700 Subject: [PATCH 086/105] s/Socket/Stream/g --- lib/child_process.js | 8 ++--- lib/http2.js | 4 +-- lib/net.js | 72 ++++++++++++++++++++++---------------------- src/node.js | 4 +-- 4 files changed, 44 insertions(+), 44 deletions(-) diff --git a/lib/child_process.js b/lib/child_process.js index e3a8a30e154..db452110d85 100644 --- a/lib/child_process.js +++ b/lib/child_process.js @@ -1,6 +1,6 @@ var inherits = require('sys').inherits; var EventEmitter = require('events').EventEmitter; -var Socket = require('net').Socket; +var Stream = require('net').Stream; var InternalChildProcess = process.binding('child_process').ChildProcess; @@ -43,9 +43,9 @@ function ChildProcess () { var exitCode; var internal = this._internal = new InternalChildProcess(); - var stdin = this.stdin = new Socket(); - var stdout = this.stdout = new Socket(); - var stderr = this.stderr = new Socket(); + var stdin = this.stdin = new Stream(); + var stdout = this.stdout = new Stream(); + var stderr = this.stderr = new Stream(); stderr.onend = stdout.onend = function () { if (gotCHLD && !stdout.readable && !stderr.readable) { diff --git a/lib/http2.js b/lib/http2.js index df5cbab7c67..ed31454c053 100644 --- a/lib/http2.js +++ b/lib/http2.js @@ -451,7 +451,7 @@ exports.createServer = function (requestListener, options) { function Client () { - net.Socket.call(this); + net.Stream.call(this); var self = this; var requests = []; @@ -510,7 +510,7 @@ function Client () { } }); } -sys.inherits(Client, net.Socket); +sys.inherits(Client, net.Stream); exports.Client = Client; diff --git a/lib/net.js b/lib/net.js index b563ce17b35..77949e0fc84 100644 --- a/lib/net.js +++ b/lib/net.js @@ -248,7 +248,7 @@ function allocRecvBuffer () { function _doFlush () { var socket = this.socket; - // Socket becomes writeable on connect() but don't flush if there's + // Stream becomes writeable on connect() but don't flush if there's // nothing actually to write if ((socket._writeQueueSize == 0) && (socket._writeMessageQueueSize == 0)) { return; @@ -262,7 +262,7 @@ function _doFlush () { } } -function initSocket (self) { +function initStream (self) { self._readWatcher = ioWatchers.alloc(); self._readWatcher.callback = function () { // If this is the first recv (recvBuffer doesn't exist) or we've used up @@ -361,7 +361,7 @@ function initSocket (self) { self.writable = false; } -function Socket (fd) { +function Stream (fd) { events.EventEmitter.call(this); this.fd = null; @@ -370,12 +370,12 @@ function Socket (fd) { this.open(fd); } }; -sys.inherits(Socket, events.EventEmitter); -exports.Socket = Socket; +sys.inherits(Stream, events.EventEmitter); +exports.Stream = Stream; -Socket.prototype.open = function (fd) { - initSocket(this); +Stream.prototype.open = function (fd) { + initStream(this); this.fd = fd; @@ -387,14 +387,14 @@ Socket.prototype.open = function (fd) { exports.createConnection = function (port, host) { - var s = new Socket(); + var s = new Stream(); s.connect(port, host); return s; }; var readyStateMessage; -Object.defineProperty(Socket.prototype, 'readyState', { +Object.defineProperty(Stream.prototype, 'readyState', { get: function () { if (!readyStateMessage) { readyStateMessage = 'readyState is depricated. Use stream.readable or stream.writable'; @@ -413,7 +413,7 @@ Object.defineProperty(Socket.prototype, 'readyState', { }); -Socket.prototype._allocateSendBuffer = function () { +Stream.prototype._allocateSendBuffer = function () { var b = buffers.alloc(1024); b.used = 0; b.sent = 0; @@ -423,9 +423,9 @@ Socket.prototype._allocateSendBuffer = function () { }; -Socket.prototype._writeString = function (data, encoding) { +Stream.prototype._writeString = function (data, encoding) { var self = this; - if (!self.writable) throw new Error('Socket is not writable'); + if (!self.writable) throw new Error('Stream is not writable'); var buffer; if (self._writeQueue.length == 0) { @@ -478,17 +478,17 @@ Socket.prototype._writeString = function (data, encoding) { }; -Socket.prototype.__writeQueueLast = function () { +Stream.prototype.__writeQueueLast = function () { return this._writeQueue.length > 0 ? this._writeQueue[this._writeQueue.length-1] : null; }; -Socket.prototype.send = function () { +Stream.prototype.send = function () { throw new Error('send renamed to write'); }; -Socket.prototype.setEncoding = function (enc) { +Stream.prototype.setEncoding = function (enc) { // TODO check values, error out on bad, and deprecation message? this._encoding = enc.toLowerCase(); }; @@ -496,10 +496,10 @@ Socket.prototype.setEncoding = function (enc) { // Returns true if all the data was flushed to socket. Returns false if // something was queued. If data was queued, then the "drain" event will // signal when it has been finally flushed to socket. -Socket.prototype.write = function (data, encoding) { +Stream.prototype.write = function (data, encoding) { var self = this; - if (!self.writable) throw new Error('Socket is not writable'); + if (!self.writable) throw new Error('Stream is not writable'); if (self.__writeQueueLast() == END_OF_FILE) { throw new Error('socket.close() called already; cannot write.'); @@ -520,10 +520,10 @@ Socket.prototype.write = function (data, encoding) { }; // Sends a file descriptor over a unix socket -Socket.prototype.sendFD = function(socketToPass) { +Stream.prototype.sendFD = function(socketToPass) { var self = this; - if (!self.writable) throw new Error('Socket is not writable'); + if (!self.writable) throw new Error('Stream is not writable'); if (self.__writeQueueLast() == END_OF_FILE) { throw new Error('socket.close() called already; cannot write.'); @@ -533,7 +533,7 @@ Socket.prototype.sendFD = function(socketToPass) { throw new Error('FD passing only available on unix sockets'); } - if (! socketToPass instanceof Socket) { + if (! socketToPass instanceof Stream) { throw new Error('Provided arg is not a socket'); } @@ -543,12 +543,12 @@ Socket.prototype.sendFD = function(socketToPass) { // Flushes the write buffer out. // Returns true if the entire buffer was flushed. -Socket.prototype.flush = function () { +Stream.prototype.flush = function () { var self = this; var bytesWritten; while (self._writeQueue.length) { - if (!self.writable) throw new Error('Socket is not writable'); + if (!self.writable) throw new Error('Stream is not writable'); var b = self._writeQueue[0]; @@ -638,14 +638,14 @@ function doConnect (socket, port, host) { } -// var stream = new Socket(); +// var stream = new Stream(); // stream.connect(80) - TCP connect to port 80 on the localhost // stream.connect(80, 'nodejs.org') - TCP connect to port 80 on nodejs.org // stream.connect('/tmp/socket') - UNIX connect to socket specified by path -Socket.prototype.connect = function () { +Stream.prototype.connect = function () { var self = this; - initSocket(self); - if (self.fd) throw new Error('Socket already opened'); + initStream(self); + if (self.fd) throw new Error('Stream already opened'); if (!self._readWatcher) throw new Error('No readWatcher'); timeout.active(socket); @@ -670,34 +670,34 @@ Socket.prototype.connect = function () { }; -Socket.prototype.address = function () { +Stream.prototype.address = function () { return getsockname(this.fd); }; -Socket.prototype.setNoDelay = function (v) { +Stream.prototype.setNoDelay = function (v) { if (this.type == 'tcp') setNoDelay(this.fd, v); }; -Socket.prototype.setTimeout = function (msecs) { +Stream.prototype.setTimeout = function (msecs) { timeout.enroll(this, msecs); }; -Socket.prototype.pause = function () { +Stream.prototype.pause = function () { this._readWatcher.stop(); }; -Socket.prototype.resume = function () { - if (this.fd === null) throw new Error('Cannot resume() closed Socket.'); +Stream.prototype.resume = function () { + if (this.fd === null) throw new Error('Cannot resume() closed Stream.'); this._readWatcher.set(this.fd, true, false); this._readWatcher.start(); }; -Socket.prototype.forceClose = function (exception) { +Stream.prototype.forceClose = function (exception) { // recvBuffer is shared between sockets, so don't need to free it here. var self = this; @@ -734,7 +734,7 @@ Socket.prototype.forceClose = function (exception) { }; -Socket.prototype._shutdown = function () { +Stream.prototype._shutdown = function () { if (this.writable) { this.writable = false; @@ -750,7 +750,7 @@ Socket.prototype._shutdown = function () { }; -Socket.prototype.close = function () { +Stream.prototype.close = function () { if (this.writable) { if (this.__writeQueueLast() != END_OF_FILE) { this._writeQueue.push(END_OF_FILE); @@ -775,7 +775,7 @@ function Server (listener) { var peerInfo = accept(self.fd); if (!peerInfo) return; - var s = new Socket(peerInfo.fd); + var s = new Stream(peerInfo.fd); s.remoteAddress = peerInfo.remoteAddress; s.remotePort = peerInfo.remotePort; s.type = self.type; diff --git a/src/node.js b/src/node.js index ccc4584e986..5116c4de7b4 100644 --- a/src/node.js +++ b/src/node.js @@ -770,7 +770,7 @@ var stdout; process.__defineGetter__('stdout', function () { if (stdout) return stdout; var net = requireNative('net'); - stdout = new net.Socket(process.binding('stdio').stdoutFD); + stdout = new net.Stream(process.binding('stdio').stdoutFD); return stdout; }); @@ -779,7 +779,7 @@ process.openStdin = function () { if (stdin) return stdin; var net = requireNative('net'); var fd = process.binding('stdio').openStdin(); - stdin = new net.Socket(fd); + stdin = new net.Stream(fd); stdin.resume(); stdin.readable = true; return stdin; From 6db43f4c2992834274a33f818276731d4788cd7f Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Thu, 18 Mar 2010 13:21:33 -0700 Subject: [PATCH 087/105] net2 HTTPClient work --- lib/http2.js | 839 +++++++++++++------------ test/simple/test-http-client-upload.js | 4 +- 2 files changed, 452 insertions(+), 391 deletions(-) diff --git a/lib/http2.js b/lib/http2.js index ed31454c053..995a585e00e 100644 --- a/lib/http2.js +++ b/lib/http2.js @@ -4,311 +4,6 @@ var events = require('events'); var HTTPParser = process.binding('http_parser').HTTPParser; -var CRLF = "\r\n"; -var STATUS_CODES = exports.STATUS_CODES = { - 100 : 'Continue', - 101 : 'Switching Protocols', - 200 : 'OK', - 201 : 'Created', - 202 : 'Accepted', - 203 : 'Non-Authoritative Information', - 204 : 'No Content', - 205 : 'Reset Content', - 206 : 'Partial Content', - 300 : 'Multiple Choices', - 301 : 'Moved Permanently', - 302 : 'Moved Temporarily', - 303 : 'See Other', - 304 : 'Not Modified', - 305 : 'Use Proxy', - 400 : 'Bad Request', - 401 : 'Unauthorized', - 402 : 'Payment Required', - 403 : 'Forbidden', - 404 : 'Not Found', - 405 : 'Method 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', - 500 : 'Internal Server Error', - 501 : 'Not Implemented', - 502 : 'Bad Gateway', - 503 : 'Service Unavailable', - 504 : 'Gateway Time-out', - 505 : 'HTTP Version not supported' -}; - -var connectionExpression = /Connection/i; -var transferEncodingExpression = /Transfer-Encoding/i; -var closeExpression = /close/i; -var chunkExpression = /chunk/i; -var contentLengthExpression = /Content-Length/i; - - -/* Abstract base class for ServerRequest and ClientResponse. */ -function IncomingMessage (socket) { - events.EventEmitter.call(this); - - this.socket = socket; - this.httpVersion = null; - this.headers = {}; - - this.method = null; - - // response (client) only - this.statusCode = null; -} -sys.inherits(IncomingMessage, events.EventEmitter); -exports.IncomingMessage = IncomingMessage; - -IncomingMessage.prototype._parseQueryString = function () { - throw new Error("_parseQueryString is deprecated. Use require(\"querystring\") to parse query strings.\n"); -}; - -IncomingMessage.prototype.setBodyEncoding = function (enc) { - // TODO: Find a cleaner way of doing this. - this.socket.setEncoding(enc); -}; - -IncomingMessage.prototype.pause = function () { - this.socket.readPause(); -}; - -IncomingMessage.prototype.resume = function () { - this.socket.readResume(); -}; - -IncomingMessage.prototype._addHeaderLine = function (field, value) { - if (field in this.headers) { - // TODO Certain headers like 'Content-Type' should not be concatinated. - // See https://www.google.com/reader/view/?tab=my#overview-page - this.headers[field] += ", " + value; - } else { - this.headers[field] = value; - } -}; - -function OutgoingMessage () { - events.EventEmitter.call(this); - - this.output = []; - this.outputEncodings = []; - - this.closeOnFinish = false; - this.chunkEncoding = false; - this.shouldKeepAlive = true; - this.useChunkedEncodingByDefault = true; - - this.flushing = false; - - this.finished = false; -} -sys.inherits(OutgoingMessage, events.EventEmitter); -exports.OutgoingMessage = OutgoingMessage; - -OutgoingMessage.prototype._send = function (data, encoding) { - var length = this.output.length; - - if (length === 0) { - this.output.push(data); - encoding = encoding || "ascii"; - this.outputEncodings.push(encoding); - return; - } - - var lastEncoding = this.outputEncodings[length-1]; - var lastData = this.output[length-1]; - - if ((lastEncoding === encoding) || - (!encoding && data.constructor === lastData.constructor)) { - if (lastData.constructor === String) { - this.output[length-1] = lastData + data; - } else { - this.output[length-1] = lastData.concat(data); - } - return; - } - - this.output.push(data); - encoding = encoding || "ascii"; - this.outputEncodings.push(encoding); -}; - -OutgoingMessage.prototype._sendHeaderLines = function (first_line, headers) { - var sentConnectionHeader = false; - var sendContentLengthHeader = false; - var sendTransferEncodingHeader = false; - - // first_line in the case of request is: "GET /index.html HTTP/1.1\r\n" - // in the case of response it is: "HTTP/1.1 200 OK\r\n" - var messageHeader = first_line; - var field, value; - for (var i in headers) { - if (headers[i] instanceof Array) { - field = headers[i][0]; - value = headers[i][1]; - } else { - if (!headers.hasOwnProperty(i)) continue; - field = i; - value = headers[i]; - } - - messageHeader += field + ": " + value + CRLF; - - if (connectionExpression.test(field)) { - sentConnectionHeader = true; - if (closeExpression.test(value)) this.closeOnFinish = true; - - } else if (transferEncodingExpression.test(field)) { - sendTransferEncodingHeader = true; - if (chunkExpression.test(value)) this.chunkEncoding = true; - - } else if (contentLengthExpression.test(field)) { - sendContentLengthHeader = true; - - } - } - - // keep-alive logic - if (sentConnectionHeader == false) { - if (this.shouldKeepAlive && - (sendContentLengthHeader || this.useChunkedEncodingByDefault)) { - messageHeader += "Connection: keep-alive\r\n"; - } else { - this.closeOnFinish = true; - messageHeader += "Connection: close\r\n"; - } - } - - if (sendContentLengthHeader == false && sendTransferEncodingHeader == false) { - if (this.useChunkedEncodingByDefault) { - messageHeader += "Transfer-Encoding: chunked\r\n"; - this.chunkEncoding = true; - } - else { - this.closeOnFinish = true; - } - } - - messageHeader += CRLF; - - this._send(messageHeader); - // wait until the first body chunk, or finish(), is sent to flush. -}; - -OutgoingMessage.prototype.write = function (chunk, encoding) { - encoding = encoding || "ascii"; - if (this.chunkEncoding) { - this._send(process._byteLength(chunk, encoding).toString(16)); - this._send(CRLF); - this._send(chunk, encoding); - this._send(CRLF); - } else { - this._send(chunk, encoding); - } - - if (this.flushing) { - this.flush(); - } else { - this.flushing = true; - } -}; - -OutgoingMessage.prototype.sendBody = function () { - throw new Error('sendBody() renamed to write()'); -}; - - -OutgoingMessage.prototype.flush = function () { - this.emit("flush"); -}; - -OutgoingMessage.prototype.close = function () { - if (this.chunkEncoding) this._send("0\r\n\r\n"); // last chunk - this.finished = true; - this.flush(); -}; - - -function ServerResponse (req) { - OutgoingMessage.call(this); - - if (req.httpVersionMajor < 1 || req.httpVersionMinor < 1) { - this.useChunkedEncodingByDefault = false; - this.shouldKeepAlive = false; - } -} -sys.inherits(ServerResponse, OutgoingMessage); -exports.ServerResponse = ServerResponse; - -ServerResponse.prototype.writeHead = function (statusCode, headers) { - var reason = STATUS_CODES[statusCode] || "unknown"; - var status_line = "HTTP/1.1 " + statusCode.toString() + " " + reason + CRLF; - this._sendHeaderLines(status_line, headers); -}; - -ServerResponse.prototype.writeHeader = ServerResponse.prototype.writeHead; - -ServerResponse.prototype.sendHeader = function () { - throw new Error('sendHeader renamed to writeHead()'); -}; - - -function ClientRequest (method, url, headers) { - OutgoingMessage.call(this); - - this.shouldKeepAlive = false; - if (method === "GET" || method === "HEAD") { - this.useChunkedEncodingByDefault = false; - } else { - this.useChunkedEncodingByDefault = true; - } - this.closeOnFinish = true; - - this._sendHeaderLines(method + " " + url + " HTTP/1.1\r\n", headers); -} -sys.inherits(ClientRequest, OutgoingMessage); -exports.ClientRequest = ClientRequest; - -ClientRequest.prototype.finish = function (responseListener) { - this.addListener("response", responseListener); - OutgoingMessage.prototype.finish.call(this); -}; - - -/* Returns true if the message queue is finished and the socket - * should be closed. */ -function flushMessageQueue (socket, queue) { - while (queue[0]) { - var message = queue[0]; - - while (message.output.length > 0) { - if (!socket.writable) return true; - - var data = message.output.shift(); - var encoding = message.outputEncodings.shift(); - - socket.write(data, encoding); - } - - if (!message.finished) break; - - message.emit("sent"); - queue.shift(); - - if (message.closeOnFinish) return true; - } - return false; -} - - var parserFreeList = []; function newParser (type) { @@ -379,7 +74,24 @@ function newParser (type) { }; parser.onBody = function (b, start, len) { - parser.incoming.emit("data", b.slice(start, start+len)); + // TODO body encoding? + var enc = parser.incoming._encoding; + if (!enc) { + parser.incoming.emit('data', b.slice(start, start+len)); + } else { + var string; + switch (enc) { + case 'utf8': + string = b.utf8Slice(start, start+len); + break; + case 'ascii': + string = b.asciiSlice(start, start+len); + break; + default: + throw new Error('Unsupported encoding ' + self._encoding + '. Use Buffer'); + } + parser.incoming.emit('data', string); + } }; parser.onMessageComplete = function () { @@ -393,13 +105,382 @@ function freeParser (parser) { if (parserFreeList.length < 1000) parserFreeList.push(parser); } + +var CRLF = "\r\n"; +var STATUS_CODES = exports.STATUS_CODES = { + 100 : 'Continue', + 101 : 'Switching Protocols', + 200 : 'OK', + 201 : 'Created', + 202 : 'Accepted', + 203 : 'Non-Authoritative Information', + 204 : 'No Content', + 205 : 'Reset Content', + 206 : 'Partial Content', + 300 : 'Multiple Choices', + 301 : 'Moved Permanently', + 302 : 'Moved Temporarily', + 303 : 'See Other', + 304 : 'Not Modified', + 305 : 'Use Proxy', + 400 : 'Bad Request', + 401 : 'Unauthorized', + 402 : 'Payment Required', + 403 : 'Forbidden', + 404 : 'Not Found', + 405 : 'Method 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', + 500 : 'Internal Server Error', + 501 : 'Not Implemented', + 502 : 'Bad Gateway', + 503 : 'Service Unavailable', + 504 : 'Gateway Time-out', + 505 : 'HTTP Version not supported' +}; + +var connection_expression = /Connection/i; +var transfer_encoding_expression = /Transfer-Encoding/i; +var close_expression = /close/i; +var chunk_expression = /chunk/i; +var content_length_expression = /Content-Length/i; + + +/* Abstract base class for ServerRequest and ClientResponse. */ +function IncomingMessage (socket) { + events.EventEmitter.call(this); + + this.socket = socket; + this.httpVersion = null; + this.headers = {}; + + // request (server) only + this.url = ""; + + this.method = null; + + // response (client) only + this.statusCode = null; + this.client = this.socket; +} +sys.inherits(IncomingMessage, events.EventEmitter); +exports.IncomingMessage = IncomingMessage; + +IncomingMessage.prototype._parseQueryString = function () { + throw new Error("_parseQueryString is deprecated. Use require(\"querystring\") to parse query strings.\n"); +}; + +IncomingMessage.prototype.setBodyEncoding = function (enc) { + // TODO deprecation message? + this.setEncoding(enc); +}; + +IncomingMessage.prototype.setEncoding = function (enc) { + // TODO check values, error out on bad, and deprecation message? + this._encoding = enc.toLowerCase(); +}; + +IncomingMessage.prototype.pause = function () { + this.socket.pause(); +}; + +IncomingMessage.prototype.resume = function () { + this.socket.resume(); +}; + +IncomingMessage.prototype._addHeaderLine = function (field, value) { + if (field in this.headers) { + // TODO Certain headers like 'Content-Type' should not be concatinated. + // See https://www.google.com/reader/view/?tab=my#overview-page + this.headers[field] += ", " + value; + } else { + this.headers[field] = value; + } +}; + +function OutgoingMessage (socket) { + events.EventEmitter.call(this, socket); + + this.socket = socket; + + this.output = []; + this.outputEncodings = []; + + this.closeOnFinish = false; + this.chunked_encoding = false; + this.should_keep_alive = true; + this.use_chunked_encoding_by_default = true; + + this.flushing = false; + this.headWritten = false; + + this.finished = false; +} +sys.inherits(OutgoingMessage, events.EventEmitter); +exports.OutgoingMessage = OutgoingMessage; + +OutgoingMessage.prototype._send = function (data, encoding) { + var length = this.output.length; + + if (length === 0) { + this.output.push(data); + encoding = encoding || "ascii"; + this.outputEncodings.push(encoding); + return; + } + + var lastEncoding = this.outputEncodings[length-1]; + var lastData = this.output[length-1]; + + if ((lastEncoding === encoding) || + (!encoding && data.constructor === lastData.constructor)) { + if (lastData.constructor === String) { + this.output[length-1] = lastData + data; + } else { + this.output[length-1] = lastData.concat(data); + } + return; + } + + this.output.push(data); + encoding = encoding || "ascii"; + this.outputEncodings.push(encoding); +}; + +OutgoingMessage.prototype.sendHeaderLines = function (first_line, headers) { + var sent_connection_header = false; + var sent_content_length_header = false; + var sent_transfer_encoding_header = false; + + // first_line in the case of request is: "GET /index.html HTTP/1.1\r\n" + // in the case of response it is: "HTTP/1.1 200 OK\r\n" + var message_header = first_line; + var field, value; + for (var i in headers) { + if (headers[i] instanceof Array) { + field = headers[i][0]; + value = headers[i][1]; + } else { + if (!headers.hasOwnProperty(i)) continue; + field = i; + value = headers[i]; + } + + message_header += field + ": " + value + CRLF; + + if (connection_expression.test(field)) { + sent_connection_header = true; + if (close_expression.test(value)) this.closeOnFinish = true; + + } else if (transfer_encoding_expression.test(field)) { + sent_transfer_encoding_header = true; + if (chunk_expression.test(value)) this.chunked_encoding = true; + + } else if (content_length_expression.test(field)) { + sent_content_length_header = true; + + } + } + + // keep-alive logic + if (sent_connection_header == false) { + if (this.should_keep_alive && + (sent_content_length_header || this.use_chunked_encoding_by_default)) { + message_header += "Connection: keep-alive\r\n"; + } else { + this.closeOnFinish = true; + message_header += "Connection: close\r\n"; + } + } + + if (sent_content_length_header == false && sent_transfer_encoding_header == false) { + if (this.use_chunked_encoding_by_default) { + message_header += "Transfer-Encoding: chunked\r\n"; + this.chunked_encoding = true; + } + else { + this.closeOnFinish = true; + } + } + + message_header += CRLF; + + this._send(message_header); + // wait until the first body chunk, or close(), is sent to flush. +}; + + +OutgoingMessage.prototype.sendBody = function () { + throw new Error("sendBody() has been renamed to write(). " + + "The 'body' event has been renamed to 'data' and " + + "the 'complete' event has been renamed to 'end'."); +}; + + +OutgoingMessage.prototype.write = function (chunk, encoding) { + if ( (this instanceof ServerResponse) && !this.headWritten) { + throw new Error("writeHead() must be called before write()") + } + + encoding = encoding || "ascii"; + if (this.chunked_encoding) { + this._send(process._byteLength(chunk, encoding).toString(16)); + this._send(CRLF); + this._send(chunk, encoding); + this._send(CRLF); + } else { + this._send(chunk, encoding); + } + + if (this.flushing) { + this.flush(); + } else { + this.flushing = true; + } +}; + +OutgoingMessage.prototype.flush = function () { + this.emit("flush"); +}; + +OutgoingMessage.prototype.finish = function () { + throw new Error("finish() has been renamed to close()."); +}; + +OutgoingMessage.prototype.close = function () { + if (this.chunked_encoding) this._send("0\r\n\r\n"); // last chunk + this.finished = true; + this.flush(); +}; + + +function ServerResponse (req) { + OutgoingMessage.call(this, req.socket); + + if (req.httpVersionMajor < 1 || req.httpVersionMinor < 1) { + this.use_chunked_encoding_by_default = false; + this.should_keep_alive = false; + } +} +sys.inherits(ServerResponse, OutgoingMessage); +exports.ServerResponse = ServerResponse; + + +ServerResponse.prototype.writeHead = function (statusCode) { + var reasonPhrase, headers, headerIndex; + + if (typeof arguments[1] == 'string') { + reasonPhrase = arguments[1]; + headerIndex = 2; + } else { + reasonPhrase = STATUS_CODES[statusCode] || "unknown"; + headerIndex = 1; + } + + if (typeof arguments[headerIndex] == 'object') { + headers = arguments[headerIndex]; + } else { + headers = {}; + } + + var status_line = "HTTP/1.1 " + statusCode.toString() + " " + + reasonPhrase + CRLF; + this.sendHeaderLines(status_line, headers); + this.headWritten = true; +}; + +// TODO eventually remove sendHeader(), writeHeader() +ServerResponse.prototype.sendHeader = ServerResponse.prototype.writeHead; +ServerResponse.prototype.writeHeader = ServerResponse.prototype.writeHead; + +function ClientRequest (socket, method, url, headers) { + OutgoingMessage.call(this, socket); + + this.should_keep_alive = false; + if (method === "GET" || method === "HEAD") { + this.use_chunked_encoding_by_default = false; + } else { + this.use_chunked_encoding_by_default = true; + } + this.closeOnFinish = true; + + this.sendHeaderLines(method + " " + url + " HTTP/1.1\r\n", headers); +} +sys.inherits(ClientRequest, OutgoingMessage); +exports.ClientRequest = ClientRequest; + +ClientRequest.prototype.finish = function () { + throw new Error( "finish() has been renamed to close() and no longer takes " + + "a response handler as an argument. Manually add a 'response' listener " + + "to the request object." + ); +}; + +ClientRequest.prototype.close = function () { + if (arguments.length > 0) { + throw new Error( "ClientRequest.prototype.close does not take any arguments. " + + "Add a response listener manually to the request object." + ); + } + OutgoingMessage.prototype.close.call(this); +}; + + +/* Returns true if the message queue is finished and the socket + * should be closed. */ +function flushMessageQueue (socket, queue) { + while (queue[0]) { + var message = queue[0]; + + while (message.output.length > 0) { + if (!socket.writable) return true; + + var data = message.output.shift(); + var encoding = message.outputEncodings.shift(); + + socket.write(data, encoding); + } + + if (!message.finished) break; + + message.emit("sent"); + queue.shift(); + + if (message.closeOnFinish) return true; + } + return false; +} + + +function Server (requestListener) { + net.Server.call(this); + this.addListener("request", requestListener); + this.addListener("connection", connectionListener); +} +sys.inherits(Server, net.Server); + +exports.Server = Server; + +exports.createServer = function (requestListener) { + return new Server(requestListener); +}; + function connectionListener (socket) { var self = this; - var parser = newParser('request'); // An array of responses for each socket. In pipelined connections // we need to keep track of the order they were sent. var responses = []; + var parser = newParser('request'); + socket.ondata = function (d, start, end) { parser.execute(d, start, end - start); }; @@ -437,64 +518,56 @@ function connectionListener (socket) { } -function Server (requestListener, options) { - net.Server.call(this, connectionListener); - //server.setOptions(options); - this.addListener('request', requestListener); -} -sys.inherits(Server, net.Server); -exports.Server = Server; -exports.createServer = function (requestListener, options) { - return new Server(requestListener, options); -}; - - - -function Client () { +function Client ( ) { net.Stream.call(this); var self = this; + var requests = []; var currentRequest; var parser = newParser('response'); - parser.socket = self; + parser.socket = this; + + self._reconnect = function () { + if (self.readyState != "opening") { + //sys.debug("HTTP CLIENT: reconnecting readyState = " + self.readyState); + self.connect(self.port, self.host); + } + }; + + self._pushRequest = function (req) { + req.addListener("flush", function () { + /* + if (self.readyState == "closed") { + //sys.debug("HTTP CLIENT request flush. reconnect. readyState = " + self.readyState); + self._reconnect(); + return; + } + */ + //sys.debug("self flush readyState = " + self.readyState); + if (req == currentRequest) flushMessageQueue(self, [req]); + }); + requests.push(req); + }; + + this.ondata = function (d, start, end) { + parser.execute(d, start, end - start); + }; self.addListener("connect", function () { - self.resetParser(); + parser.reinitialize('response'); currentRequest = requests.shift(); currentRequest.flush(); }); - self.ondata = function (d, start, end) { - parser.execute(d, start, end - start); - }; - - parser.onIncoming = function (res) { - //sys.debug("incoming response!"); - - res.addListener('end', function ( ) { - //sys.debug("request complete disconnecting. readyState = " + self.readyState); - self.close(); - }); - - currentRequest.emit("response", res); - }; - - self._pushRequest = function (req) { - }; - self.addListener("end", function () { - self.close(); - }); - - self.onend = function () { parser.finish(); - // unref the parser for easy gc freeParser(parser); + //sys.debug("self got end closing. readyState = " + self.readyState); self.close(); - }; + }); self.addListener("close", function (had_error) { if (had_error) { @@ -509,54 +582,31 @@ function Client () { self._reconnect(); } }); -} -sys.inherits(Client, net.Stream); + parser.onIncoming = function (res) { + sys.debug("incoming response!"); + + res.addListener('end', function ( ) { + //sys.debug("request complete disconnecting. readyState = " + self.readyState); + self.close(); + }); + + currentRequest.emit("response", res); + }; +}; +sys.inherits(Client, net.Stream); exports.Client = Client; - exports.createClient = function (port, host) { - var client = new Client(); - client.port = port; - client.host = host; - client.connect(port, host); - return client; -}; + var c = new Client; + c.port = port; + c.host = host; + c.connect(port, host); + return c; +} -Client.prototype._reconnect = function () { - if (this.readyState != "opening") { - //sys.debug("HTTP CLIENT: reconnecting readyState = " + self.readyState); - this.connect(this.port, this.host); - } -}; - - -Client.prototype.request = function (method, url, headers) { - var self = this; - - if (typeof(url) != "string") { // assume method was omitted, shift arguments - headers = url; - url = method; - method = null; - } - var req = new ClientRequest(this, method || "GET", url, headers); - - req.addListener("flush", function () { - if (self.readyState == "closed") { - //sys.debug("HTTP CLIENT request flush. reconnect. readyState = " + self.readyState); - self._reconnect(); - return; - } - //sys.debug("self flush readyState = " + self.readyState); - if (req == currentRequest) flushMessageQueue(self, [req]); - }); - requests.push(req); - - return req; -}; - Client.prototype.get = function () { throw new Error("client.get(...) is now client.request('GET', ...)"); }; @@ -577,9 +627,20 @@ Client.prototype.put = function () { throw new Error("client.put(...) is now client.request('PUT', ...)"); }; +Client.prototype.request = function (method, url, headers) { + if (typeof(url) != "string") { // assume method was omitted, shift arguments + headers = url; + url = method; + method = null; + } + var req = new ClientRequest(this, method || "GET", url, headers); + this._pushRequest(req); + return req; +}; + exports.cat = function (url, encoding_, headers_) { - var encoding = 'utf8', + var encoding = 'utf8', headers = {}, callback = null; diff --git a/test/simple/test-http-client-upload.js b/test/simple/test-http-client-upload.js index 4c11bd4078c..e1d746c35d2 100644 --- a/test/simple/test-http-client-upload.js +++ b/test/simple/test-http-client-upload.js @@ -1,5 +1,5 @@ require("../common"); -http = require("http"); +http = require("http2"); var sent_body = ""; var server_req_complete = false; @@ -33,7 +33,7 @@ req.write('3\n'); puts("client finished sending request"); req.addListener('response', function(res) { - res.setBodyEncoding("utf8"); + res.setEncoding("utf8"); res.addListener('data', function(chunk) { puts(chunk); }); From 33d5c46e8cc92f5bc079b873f8baa20bee2f86ae Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Thu, 18 Mar 2010 14:01:17 -0700 Subject: [PATCH 088/105] All tests to use http2 --- test/simple/test-http-1.0.js | 4 ++-- test/simple/test-http-chunked.js | 4 ++-- test/simple/test-http-client-race.js | 2 +- test/simple/test-http-eof-on-connect.js | 8 ++++---- test/simple/test-http-malformed-request.js | 6 +++--- test/simple/test-http-proxy.js | 2 +- test/simple/test-http-server.js | 6 +++--- test/simple/test-http-wget.js | 6 +++--- test/simple/test-http.js | 2 +- test/simple/test-remote-module-loading.js | 2 +- 10 files changed, 21 insertions(+), 21 deletions(-) diff --git a/test/simple/test-http-1.0.js b/test/simple/test-http-1.0.js index b3f69c23cfe..106313d2817 100644 --- a/test/simple/test-http-1.0.js +++ b/test/simple/test-http-1.0.js @@ -1,5 +1,5 @@ require("../common"); -tcp = require("tcp"); +net = require("net"); http = require("http2"); var body = "hello world\n"; @@ -13,7 +13,7 @@ var server = http.createServer(function (req, res) { }) server.listen(PORT); -var c = tcp.createConnection(PORT); +var c = net.createConnection(PORT); c.setEncoding("utf8"); diff --git a/test/simple/test-http-chunked.js b/test/simple/test-http-chunked.js index 28d4cc6424c..4325d113dab 100644 --- a/test/simple/test-http-chunked.js +++ b/test/simple/test-http-chunked.js @@ -1,5 +1,5 @@ require("../common"); -var http = require("http"); +var http = require("http2"); var UTF8_STRING = "Il était tué"; @@ -10,7 +10,7 @@ var server = http.createServer(function(req, res) { }); server.listen(PORT); -http.cat("http://localhost:"+PORT+"/", "utf8", function (err, data) { +http.cat("http://127.0.0.1:"+PORT+"/", "utf8", function (err, data) { if (err) throw err; assert.equal(UTF8_STRING, data); server.close(); diff --git a/test/simple/test-http-client-race.js b/test/simple/test-http-client-race.js index 9076924b37e..a3824cfce1e 100644 --- a/test/simple/test-http-client-race.js +++ b/test/simple/test-http-client-race.js @@ -1,5 +1,5 @@ require("../common"); -http = require("http"); +http = require("http2"); url = require("url"); var body1_s = "1111111111111111"; diff --git a/test/simple/test-http-eof-on-connect.js b/test/simple/test-http-eof-on-connect.js index 67d4b28fe91..7838c8d141c 100644 --- a/test/simple/test-http-eof-on-connect.js +++ b/test/simple/test-http-eof-on-connect.js @@ -1,6 +1,6 @@ require("../common"); -tcp = require("tcp"); -http = require("http"); +net = require("net"); +http = require("http2"); // This is a regression test for http://github.com/ry/node/issues/#issue/44 // It is separate from test-http-malformed-request.js because it is only @@ -9,8 +9,8 @@ http = require("http"); server = http.createServer(function (req, res) {}); server.listen(PORT); -tcp.createConnection(PORT).addListener("connect", function () { - this.close(); +net.createConnection(PORT).addListener("connect", function () { + this.close(); }).addListener("close", function () { server.close(); }); diff --git a/test/simple/test-http-malformed-request.js b/test/simple/test-http-malformed-request.js index a1d4b8c91a8..98d6da780c0 100644 --- a/test/simple/test-http-malformed-request.js +++ b/test/simple/test-http-malformed-request.js @@ -1,6 +1,6 @@ require("../common"); -tcp = require("tcp"); -http = require("http"); +net = require("net"); +http = require("http2"); url = require("url"); // Make sure no exceptions are thrown when receiving malformed HTTP @@ -20,7 +20,7 @@ var s = http.createServer(function (req, res) { }); s.listen(PORT); -var c = tcp.createConnection(PORT); +var c = net.createConnection(PORT); c.addListener("connect", function () { c.write("GET /hello?foo=%99bar HTTP/1.1\r\n\r\n"); c.close(); diff --git a/test/simple/test-http-proxy.js b/test/simple/test-http-proxy.js index 20992b9dc6d..ebdf7af10f3 100644 --- a/test/simple/test-http-proxy.js +++ b/test/simple/test-http-proxy.js @@ -1,5 +1,5 @@ require("../common"); -http = require("http"); +http = require("http2"); url = require("url"); var PROXY_PORT = PORT; diff --git a/test/simple/test-http-server.js b/test/simple/test-http-server.js index fba33ba9fcb..e42d55429e9 100644 --- a/test/simple/test-http-server.js +++ b/test/simple/test-http-server.js @@ -1,6 +1,6 @@ require("../common"); -tcp = require("tcp"); -http = require("http"); +net = require("net"); +http = require("http2"); url = require("url"); qs = require("querystring"); @@ -43,7 +43,7 @@ http.createServer(function (req, res) { }).listen(PORT); -var c = tcp.createConnection(PORT); +var c = net.createConnection(PORT); c.setEncoding("utf8"); diff --git a/test/simple/test-http-wget.js b/test/simple/test-http-wget.js index 09bda325933..38a41896034 100644 --- a/test/simple/test-http-wget.js +++ b/test/simple/test-http-wget.js @@ -1,6 +1,6 @@ require("../common"); -tcp = require("tcp"); -http = require("http"); +net = require("net"); +http = require("http2"); // wget sends an HTTP/1.0 request with Connection: Keep-Alive // @@ -29,7 +29,7 @@ var server = http.createServer(function (req, res) { }) server.listen(PORT); -var c = tcp.createConnection(PORT); +var c = net.createConnection(PORT); c.setEncoding("utf8"); diff --git a/test/simple/test-http.js b/test/simple/test-http.js index c59927763ab..df0ebea4064 100644 --- a/test/simple/test-http.js +++ b/test/simple/test-http.js @@ -1,5 +1,5 @@ require("../common"); -http = require("http"); +http = require("http2"); url = require("url"); var responses_sent = 0; diff --git a/test/simple/test-remote-module-loading.js b/test/simple/test-remote-module-loading.js index 0a2af87e087..8a7edbbe27b 100644 --- a/test/simple/test-remote-module-loading.js +++ b/test/simple/test-remote-module-loading.js @@ -1,6 +1,6 @@ require("../common"); -var http = require('http'); +var http = require('http2'); var sys = require('sys'); var url = require("url"); var modulesLoaded = 0; From 916e057feab485d05888f94b00dd0fe8753753b5 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Thu, 18 Mar 2010 14:33:42 -0700 Subject: [PATCH 089/105] Add 'opening' readyState --- lib/net.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/net.js b/lib/net.js index 77949e0fc84..ed746790d15 100644 --- a/lib/net.js +++ b/lib/net.js @@ -4,10 +4,8 @@ var events = require("events"); var debugLevel = 0; if ('NODE_DEBUG' in process.ENV) debugLevel = 1; -function debug (x) { - if (debugLevel > 0) { - process.stdio.writeError(x + '\n'); - } +function debug () { + if (debugLevel > 0) sys.error.apply(this, arguments); } var binding = process.binding('net'); @@ -400,7 +398,9 @@ Object.defineProperty(Stream.prototype, 'readyState', { readyStateMessage = 'readyState is depricated. Use stream.readable or stream.writable'; sys.error(readyStateMessage); } - if (this.readable && this.writable) { + if (this._resolving) { + return 'opening'; + } else if (this.readable && this.writable) { return 'open'; } else if (this.readable && !this.writable){ return 'readOnly'; @@ -658,7 +658,9 @@ Stream.prototype.connect = function () { self.type = 'tcp'; // TODO dns resolution on arguments[1] var port = arguments[0]; + self._resolving = true; lookupDomainName(arguments[1], function (ip) { + self._resolving = false; doConnect(self, port, ip); }); } else { From 1762abcecefb41fd68692b12ceba441aaa33c7c3 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Thu, 18 Mar 2010 15:49:42 -0700 Subject: [PATCH 090/105] http2 now passes all tests --- benchmark/http_simple.js | 4 +-- lib/http2.js | 29 +++++++++++----------- test/simple/test-http-proxy.js | 45 ++++++++++++++++++++-------------- 3 files changed, 43 insertions(+), 35 deletions(-) diff --git a/benchmark/http_simple.js b/benchmark/http_simple.js index 2f80b43ff6a..6d21c38eb5e 100644 --- a/benchmark/http_simple.js +++ b/benchmark/http_simple.js @@ -1,7 +1,7 @@ path = require("path"); -var puts = require("../lib/sys").puts; -http = require("../lib/http2"); +var puts = require("sys").puts; +http = require("http2"); fixed = "" for (var i = 0; i < 20*1024; i++) { diff --git a/lib/http2.js b/lib/http2.js index 995a585e00e..a8d09d3ea8c 100644 --- a/lib/http2.js +++ b/lib/http2.js @@ -230,7 +230,7 @@ exports.OutgoingMessage = OutgoingMessage; OutgoingMessage.prototype._send = function (data, encoding) { var length = this.output.length; - if (length === 0) { + if (length === 0 || typeof data != 'string') { this.output.push(data); encoding = encoding || "ascii"; this.outputEncodings.push(encoding); @@ -242,11 +242,7 @@ OutgoingMessage.prototype._send = function (data, encoding) { if ((lastEncoding === encoding) || (!encoding && data.constructor === lastData.constructor)) { - if (lastData.constructor === String) { - this.output[length-1] = lastData + data; - } else { - this.output[length-1] = lastData.concat(data); - } + this.output[length-1] = lastData + data; return; } @@ -332,7 +328,11 @@ OutgoingMessage.prototype.write = function (chunk, encoding) { encoding = encoding || "ascii"; if (this.chunked_encoding) { - this._send(process._byteLength(chunk, encoding).toString(16)); + if (typeof chunk == 'string') { + this._send(process._byteLength(chunk, encoding).toString(16)); + } else { + this._send(chunk.length.toString(16)); + } this._send(CRLF); this._send(chunk, encoding); this._send(CRLF); @@ -531,21 +531,20 @@ function Client ( ) { self._reconnect = function () { if (self.readyState != "opening") { - //sys.debug("HTTP CLIENT: reconnecting readyState = " + self.readyState); + sys.debug("HTTP CLIENT: reconnecting readyState = " + self.readyState); self.connect(self.port, self.host); } }; self._pushRequest = function (req) { req.addListener("flush", function () { - /* if (self.readyState == "closed") { - //sys.debug("HTTP CLIENT request flush. reconnect. readyState = " + self.readyState); + sys.debug("HTTP CLIENT request flush. reconnect. readyState = " + self.readyState); self._reconnect(); return; } - */ - //sys.debug("self flush readyState = " + self.readyState); + + sys.debug("self flush readyState = " + self.readyState); if (req == currentRequest) flushMessageQueue(self, [req]); }); requests.push(req); @@ -557,7 +556,8 @@ function Client ( ) { self.addListener("connect", function () { parser.reinitialize('response'); - currentRequest = requests.shift(); + sys.puts('requests: ' + sys.inspect(requests)); + currentRequest = requests.shift() currentRequest.flush(); }); @@ -575,7 +575,7 @@ function Client ( ) { return; } - //sys.debug("HTTP CLIENT onClose. readyState = " + self.readyState); + sys.debug("HTTP CLIENT onClose. readyState = " + self.readyState); // If there are more requests to handle, reconnect. if (requests.length > 0) { @@ -602,7 +602,6 @@ exports.createClient = function (port, host) { var c = new Client; c.port = port; c.host = host; - c.connect(port, host); return c; } diff --git a/test/simple/test-http-proxy.js b/test/simple/test-http-proxy.js index ebdf7af10f3..5d57ba5310f 100644 --- a/test/simple/test-http-proxy.js +++ b/test/simple/test-http-proxy.js @@ -6,12 +6,12 @@ var PROXY_PORT = PORT; var BACKEND_PORT = PORT+1; var backend = http.createServer(function (req, res) { - // debug("backend"); + debug("backend request"); res.writeHead(200, {"content-type": "text/plain"}); res.write("hello world\n"); res.close(); }); -// debug("listen backend") +debug("listen backend") backend.listen(BACKEND_PORT); var proxy_client = http.createClient(BACKEND_PORT); @@ -25,31 +25,40 @@ var proxy = http.createServer(function (req, res) { }); proxy_res.addListener("end", function() { res.close(); - // debug("proxy res"); + debug("proxy res"); }); }); proxy_req.close(); }); -// debug("listen proxy") +debug("listen proxy") proxy.listen(PROXY_PORT); var body = ""; -var client = http.createClient(PROXY_PORT); -var req = client.request("/test"); -// debug("client req") -req.addListener('response', function (res) { - // debug("got res"); - assert.equal(200, res.statusCode); - res.setBodyEncoding("utf8"); - res.addListener('data', function (chunk) { body += chunk; }); - res.addListener('end', function () { - proxy.close(); - backend.close(); - // debug("closed both"); +nlistening = 0; +function startReq () { + nlistening++; + if (nlistening < 2) return; + + var client = http.createClient(PROXY_PORT); + var req = client.request("/test"); + debug("client req") + req.addListener('response', function (res) { + debug("got res"); + assert.equal(200, res.statusCode); + res.setBodyEncoding("utf8"); + res.addListener('data', function (chunk) { body += chunk; }); + res.addListener('end', function () { + proxy.close(); + backend.close(); + debug("closed both"); + }); }); -}); -req.close(); + req.close(); +} + +proxy.addListener('listening', startReq); +backend.addListener('listening', startReq); process.addListener("exit", function () { assert.equal(body, "hello world\n"); From 90295d9fce277a306149b185bd46a77eac818322 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Fri, 19 Mar 2010 02:24:11 -0700 Subject: [PATCH 091/105] [net2] inline write on empty write buffer for performance --- lib/net.js | 83 ++++++++++++++++++++++++++++++-- test/simple/test-net-pingpong.js | 3 ++ 2 files changed, 83 insertions(+), 3 deletions(-) diff --git a/lib/net.js b/lib/net.js index ed746790d15..c6141a5732b 100644 --- a/lib/net.js +++ b/lib/net.js @@ -501,6 +501,83 @@ Stream.prototype.write = function (data, encoding) { if (!self.writable) throw new Error('Stream is not writable'); + if (self._writeQueue && self._writeQueue.length) { + return self._writeQueued(data, encoding); // slow + + } else { + // The most common case. There is no write queue. Just push the data + // directly to the socket. + + var bytesWritten; + var buffer = data, off = 0, len = data.length; + + if (typeof data == 'string') { + encoding = (encoding || 'utf8').toLowerCase(); + var bytes = encoding == 'utf8' ? Buffer.utf8ByteLength(data) : data.length; + + //debug('write string :' + JSON.stringify(data)); + + if (!recvBuffer) allocRecvBuffer(); + + if (recvBuffer.length - recvBuffer.used < bytes) { + // not enough room - go to slow case + return self._writeQueued(data, encoding); + } + + var charsWritten; + if (encoding == 'utf8') { + recvBuffer.utf8Write(data, recvBuffer.used); + } else { + // ascii + recvBuffer.asciiWrite(data, recvBuffer.used); + } + + buffer = recvBuffer; + off = recvBuffer.used; + len = bytes; + } + + //debug('write [fd, off, len] =' + JSON.stringify([self.fd, off, len])); + + // Send the buffer. + try { + bytesWritten = write(self.fd, buffer, off, len); + } catch (e) { + self.forceClose(e); + return false; + } + + //debug('wrote ' + bytesWritten); + + // Note: if using the recvBuffer - we don't need to increase + // recvBuffer.used because it was all sent. Just reuse that space. + + if (bytesWritten == len) return true; + + //debug('write incomplete ' + bytesWritten + ' < ' + len); + + if (buffer == data) { + data.sent = bytesWritten || 0; + data.used = data.length; + } else { + // string + recvBuffer.used += bytesWritten; + data = recvBuffer.slice(off+bytesWritten, off+len+bytesWritten); + data.sent = 0; + data.used = data.length; + } + + //if (!self._writeQueue) initWriteStream(self); + self._writeQueue.push(data); + self._writeQueueSize += data.used; + return false; + } +} + +Stream.prototype._writeQueued = function (data, encoding) { + //debug('_writeQueued'); + var self = this; + if (self.__writeQueueLast() == END_OF_FILE) { throw new Error('socket.close() called already; cannot write.'); } @@ -597,7 +674,7 @@ Stream.prototype.flush = function () { } else { b.sent += bytesWritten; self._writeQueueSize -= bytesWritten; - debug('bytes sent: ' + b.sent); + //debug('bytes sent: ' + b.sent); } } if (self._writeWatcher) self._writeWatcher.stop(); @@ -654,7 +731,7 @@ Stream.prototype.connect = function () { if (port >= 0) { self.fd = socket('tcp'); - debug('new fd = ' + self.fd); + //debug('new fd = ' + self.fd); self.type = 'tcp'; // TODO dns resolution on arguments[1] var port = arguments[0]; @@ -726,7 +803,7 @@ Stream.prototype.forceClose = function (exception) { // FIXME Bug when this.fd == 0 if (this.fd) { close(this.fd); - debug('close ' + this.fd); + //debug('close ' + this.fd); this.fd = null; process.nextTick(function () { if (exception) self.emit('error', exception); diff --git a/test/simple/test-net-pingpong.js b/test/simple/test-net-pingpong.js index ad6410c9b98..860b2007c3e 100644 --- a/test/simple/test-net-pingpong.js +++ b/test/simple/test-net-pingpong.js @@ -20,6 +20,7 @@ function pingPongTest (port, host) { socket.setNoDelay(); socket.timeout = 0; + socket.setEncoding('utf8'); socket.addListener("data", function (data) { puts("server got: " + data); assert.equal(true, socket.writable); @@ -53,6 +54,7 @@ function pingPongTest (port, host) { var client = net.createConnection(port, host); + client.setEncoding('ascii'); client.addListener("connect", function () { assert.equal(true, client.readable); assert.equal(true, client.writable); @@ -106,4 +108,5 @@ pingPongTest("/tmp/pingpong.sock"); process.addListener("exit", function () { assert.equal(4, tests_run); + puts('done'); }); From e01464373f1c4dfcc62a13cacf7bc8a498ccc925 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Fri, 19 Mar 2010 11:29:14 -0700 Subject: [PATCH 092/105] Speed up test-buffer --- test/simple/test-buffer.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/simple/test-buffer.js b/test/simple/test-buffer.js index 458dcb4021f..a069face9b9 100644 --- a/test/simple/test-buffer.js +++ b/test/simple/test-buffer.js @@ -18,7 +18,7 @@ for (var i = 0; i < 1024; i++) { var asciiString = "hello world"; var offset = 100; -for (var j = 0; j < 50000; j++) { +for (var j = 0; j < 500; j++) { for (var i = 0; i < asciiString.length; i++) { b[i] = asciiString.charCodeAt(i); @@ -41,7 +41,7 @@ for (var j = 0; j < 50000; j++) { } -for (var j = 0; j < 10000; j++) { +for (var j = 0; j < 100; j++) { var slice = b.slice(100, 150); assert.equal(50, slice.length); for (var i = 0; i < 50; i++) { From f8c3b6009d4b5aeaa0f5ca6ac9ff600e61e5e651 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Fri, 19 Mar 2010 11:34:22 -0700 Subject: [PATCH 093/105] Remove send fd functionality It was broken anyway. It will go into its own class later. --- lib/net.js | 84 ++++++++---------------------------------------------- 1 file changed, 12 insertions(+), 72 deletions(-) diff --git a/lib/net.js b/lib/net.js index c6141a5732b..447c34b2583 100644 --- a/lib/net.js +++ b/lib/net.js @@ -23,8 +23,6 @@ var accept = binding.accept; var close = binding.close; var shutdown = binding.shutdown; var read = binding.read; -var recvMsg = binding.recvMsg; -var sendFD = binding.sendFD; var write = binding.write; var toRead = binding.toRead; var setNoDelay = binding.setNoDelay; @@ -248,13 +246,11 @@ function _doFlush () { var socket = this.socket; // Stream becomes writeable on connect() but don't flush if there's // nothing actually to write - if ((socket._writeQueueSize == 0) && (socket._writeMessageQueueSize == 0)) { + if (socket._writeQueueSize == 0) { return; } if (socket.flush()) { assert(socket._writeQueueSize == 0); - assert(socket._writeMessageQueueSize == 0); - if (socket._events && socket._events['drain']) socket.emit("drain"); if (socket.ondrain) socket.ondrain(); // Optimization } @@ -280,21 +276,10 @@ function initStream (self) { var bytesRead; try { - if (self.type == "unix") { - bytesRead = recvMsg(self.fd, - recvBuffer, - recvBuffer.used, - recvBuffer.length - recvBuffer.used); - //debug('recvMsg.fd ' + recvMsg.fd); - if (recvMsg.fd) { - self.emit('fd', recvMsg.fd); - } - } else { - bytesRead = read(self.fd, - recvBuffer, - recvBuffer.used, - recvBuffer.length - recvBuffer.used); - } + bytesRead = read(self.fd, + recvBuffer, + recvBuffer.used, + recvBuffer.length - recvBuffer.used); } catch (e) { self.forceClose(e); return; @@ -302,7 +287,7 @@ function initStream (self) { //debug('bytesRead ' + bytesRead + '\n'); - if (!recvMsg.fd && bytesRead == 0) { + if (bytesRead == 0) { self.readable = false; self._readWatcher.stop(); @@ -351,7 +336,6 @@ function initStream (self) { self._writeQueue = []; // queue of buffers that need to be written to socket // XXX use link list? self._writeQueueSize = 0; // in bytes, not to be confused with _writeQueue.length! - self._writeMessageQueueSize = 0; // number of messages remaining to be sent self._writeWatcher = ioWatchers.alloc(); self._writeWatcher.socket = self; @@ -442,30 +426,17 @@ Stream.prototype._writeString = function (data, encoding) { var charsWritten; var bytesWritten; - // The special encoding "fd" means that data is an integer FD and we want - // to pass the FD on the socket with sendmsg() - if (encoding == "fd") { - buffer.isFd = true; - // TODO is this OK -- does it guarantee that the fd is the only thing in the buffer? - charsWritten = buffer.asciiWrite(data, buffer.used, buffer.length - buffer.used); - bytesWritten = charsWritten; - } else if (encoding.toLowerCase() == 'utf8') { - buffer.isFd = false; + if (encoding.toLowerCase() == 'utf8') { charsWritten = buffer.utf8Write(data, buffer.used); bytesWritten = Buffer.utf8ByteLength(data.slice(0, charsWritten)); } else { // ascii - buffer.isFd = false; charsWritten = buffer.asciiWrite(data, buffer.used); bytesWritten = charsWritten; } buffer.used += bytesWritten; - if (buffer.isFd) { - self._writeMessageQueueSize += 1; - } else { - self._writeQueueSize += bytesWritten; - } + self._writeQueueSize += bytesWritten; //debug('charsWritten ' + charsWritten); //debug('buffer.used ' + buffer.used); @@ -596,28 +567,6 @@ Stream.prototype._writeQueued = function (data, encoding) { return this.flush(); }; -// Sends a file descriptor over a unix socket -Stream.prototype.sendFD = function(socketToPass) { - var self = this; - - if (!self.writable) throw new Error('Stream is not writable'); - - if (self.__writeQueueLast() == END_OF_FILE) { - throw new Error('socket.close() called already; cannot write.'); - } - - if (self.type != "unix") { - throw new Error('FD passing only available on unix sockets'); - } - - if (! socketToPass instanceof Stream) { - throw new Error('Provided arg is not a socket'); - } - - return self.write(socketToPass.fd.toString(), "fd"); -}; - - // Flushes the write buffer out. // Returns true if the entire buffer was flushed. Stream.prototype.flush = function () { @@ -644,15 +593,10 @@ Stream.prototype.flush = function () { var fdToSend = null; try { - if (b.isFd) { - fdToSend = parseInt(b.asciiSlice(b.sent, b.used - b.sent)); - bytesWritten = writeFD(self.fd, fdToSend); - } else { - bytesWritten = write(self.fd, - b, - b.sent, - b.used - b.sent); - } + bytesWritten = write(self.fd, + b, + b.sent, + b.used - b.sent); } catch (e) { self.forceClose(e); return false; @@ -667,10 +611,6 @@ Stream.prototype.flush = function () { self._writeWatcher.start(); assert(self._writeQueueSize > 0); return false; - } else if (b.isFd) { - b.sent = b.used; - self._writeMessageQueueSize -= 1; - //debug('sent fd: ' + fdToSend); } else { b.sent += bytesWritten; self._writeQueueSize -= bytesWritten; From 0c64768cb4d9d12c4815dc2f21efe3d2934d6803 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Fri, 19 Mar 2010 11:46:09 -0700 Subject: [PATCH 094/105] Don't error out when buffer.utf8Write() doesn't fit --- src/node_buffer.cc | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/node_buffer.cc b/src/node_buffer.cc index cfc2b49f296..a2402939f35 100644 --- a/src/node_buffer.cc +++ b/src/node_buffer.cc @@ -232,11 +232,6 @@ Handle Buffer::Utf8Write(const Arguments &args) { const char *p = buffer->data_ + offset; - if (s->Length() + offset > buffer->length_) { - return ThrowException(Exception::TypeError(String::New( - "Not enough space in Buffer for string"))); - } - int written = s->WriteUtf8((char*)p, buffer->length_ - offset); return scope.Close(Integer::New(written)); From 3e969f0f74a65c39453db352fcd723b61b6690bb Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Fri, 19 Mar 2010 11:46:35 -0700 Subject: [PATCH 095/105] Random net.js clean ups --- lib/net.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/lib/net.js b/lib/net.js index 447c34b2583..09d88da43d1 100644 --- a/lib/net.js +++ b/lib/net.js @@ -416,12 +416,12 @@ Stream.prototype._writeString = function (data, encoding) { buffer = self._allocateSendBuffer(); } else { buffer = self.__writeQueueLast(); - if (buffer.used == buffer.length) { + if (buffer.length - buffer.used < 64) { buffer = self._allocateSendBuffer(); } } - encoding = encoding || 'ascii'; // default to ascii since it's faster + encoding = encoding || 'utf8'; // default to utf8 var charsWritten; var bytesWritten; @@ -473,7 +473,9 @@ Stream.prototype.write = function (data, encoding) { if (!self.writable) throw new Error('Stream is not writable'); if (self._writeQueue && self._writeQueue.length) { - return self._writeQueued(data, encoding); // slow + // slow + // There is already a write queue, so let's append to it. + return self._queueWrite(data, encoding); } else { // The most common case. There is no write queue. Just push the data @@ -492,7 +494,7 @@ Stream.prototype.write = function (data, encoding) { if (recvBuffer.length - recvBuffer.used < bytes) { // not enough room - go to slow case - return self._writeQueued(data, encoding); + return self._queueWrite(data, encoding); } var charsWritten; @@ -502,7 +504,7 @@ Stream.prototype.write = function (data, encoding) { // ascii recvBuffer.asciiWrite(data, recvBuffer.used); } - + buffer = recvBuffer; off = recvBuffer.used; len = bytes; @@ -545,8 +547,8 @@ Stream.prototype.write = function (data, encoding) { } } -Stream.prototype._writeQueued = function (data, encoding) { - //debug('_writeQueued'); +Stream.prototype._queueWrite = function (data, encoding) { + //debug('_queueWrite'); var self = this; if (self.__writeQueueLast() == END_OF_FILE) { From 3a993d889704314329686695a01b58d60f0d4b85 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Fri, 19 Mar 2010 12:02:59 -0700 Subject: [PATCH 096/105] Buffer.utf8ByteLength -> Buffer.byteLength --- lib/net.js | 4 ++-- src/node_buffer.cc | 17 ++++++++++++----- src/node_buffer.h | 2 +- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/lib/net.js b/lib/net.js index 09d88da43d1..fdbe7f51f34 100644 --- a/lib/net.js +++ b/lib/net.js @@ -428,7 +428,7 @@ Stream.prototype._writeString = function (data, encoding) { if (encoding.toLowerCase() == 'utf8') { charsWritten = buffer.utf8Write(data, buffer.used); - bytesWritten = Buffer.utf8ByteLength(data.slice(0, charsWritten)); + bytesWritten = Buffer.byteLength(data.slice(0, charsWritten)); } else { // ascii charsWritten = buffer.asciiWrite(data, buffer.used); @@ -486,7 +486,7 @@ Stream.prototype.write = function (data, encoding) { if (typeof data == 'string') { encoding = (encoding || 'utf8').toLowerCase(); - var bytes = encoding == 'utf8' ? Buffer.utf8ByteLength(data) : data.length; + var bytes = Buffer.byteLength(data, encoding); //debug('write string :' + JSON.stringify(data)); diff --git a/src/node_buffer.cc b/src/node_buffer.cc index a2402939f35..b0efba427fb 100644 --- a/src/node_buffer.cc +++ b/src/node_buffer.cc @@ -331,15 +331,22 @@ Handle Buffer::Unpack(const Arguments &args) { } -// var nbytes = Buffer.utf8ByteLength("string") -Handle Buffer::Utf8ByteLength(const Arguments &args) { +// var nbytes = Buffer.byteLength("string", "utf8") +Handle Buffer::ByteLength(const Arguments &args) { HandleScope scope; + if (!args[0]->IsString()) { return ThrowException(Exception::TypeError(String::New( "Argument must be a string"))); } + Local s = args[0]->ToString(); - return scope.Close(Integer::New(s->Utf8Length())); + enum encoding e = ParseEncoding(args[1], UTF8); + + Local length = + Integer::New(e == UTF8 ? s->Utf8Length() : s->Length()); + + return scope.Close(length); } @@ -365,8 +372,8 @@ void Buffer::Initialize(Handle target) { NODE_SET_PROTOTYPE_METHOD(constructor_template, "unpack", Buffer::Unpack); NODE_SET_METHOD(constructor_template->GetFunction(), - "utf8ByteLength", - Buffer::Utf8ByteLength); + "byteLength", + Buffer::ByteLength); target->Set(String::NewSymbol("Buffer"), constructor_template->GetFunction()); } diff --git a/src/node_buffer.h b/src/node_buffer.h index 78059be7efe..569943eb26e 100644 --- a/src/node_buffer.h +++ b/src/node_buffer.h @@ -49,7 +49,7 @@ class Buffer : public ObjectWrap { static v8::Handle Utf8Slice(const v8::Arguments &args); static v8::Handle AsciiWrite(const v8::Arguments &args); static v8::Handle Utf8Write(const v8::Arguments &args); - static v8::Handle Utf8ByteLength(const v8::Arguments &args); + static v8::Handle ByteLength(const v8::Arguments &args); static v8::Handle Unpack(const v8::Arguments &args); int AsciiWrite(char *string, int offset, int length); From 776c3e2b29f379c89187e3527d5f81757fdbc923 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Fri, 19 Mar 2010 12:08:57 -0700 Subject: [PATCH 097/105] Add note about Buffer abstraction --- lib/net.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/net.js b/lib/net.js index fdbe7f51f34..b963fc7c393 100644 --- a/lib/net.js +++ b/lib/net.js @@ -10,8 +10,16 @@ function debug () { var binding = process.binding('net'); - +// Note about Buffer interface: +// I'm attempting to do the simplest possible interface to abstracting raw +// memory allocation. This might turn out to be too simple - it seems that +// I always use a buffer.used member to keep track of how much I've filled. +// Perhaps giving the Buffer a file-like interface with a head (which would +// represent buffer.used) that can be seeked around would be easier. I'm not +// yet convinced that every use-case can be fit into that abstraction, so +// waiting to implement it until I get more experience with this. var Buffer = process.Buffer; + var IOWatcher = process.IOWatcher; var assert = process.assert; From 4278f35e892935787afb1db18ca8811fba0d18f7 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Fri, 19 Mar 2010 18:36:22 -0700 Subject: [PATCH 098/105] Add support for Buffer to fs.write --- src/node_file.cc | 126 +++++++++++++++++++++++++------ test/pummel/test-tcp-pingpong.js | 2 + 2 files changed, 103 insertions(+), 25 deletions(-) diff --git a/src/node_file.cc b/src/node_file.cc index 4efdc06a288..5ca5416506c 100644 --- a/src/node_file.cc +++ b/src/node_file.cc @@ -1,5 +1,6 @@ // Copyright 2009 Ryan Dahl #include +#include #include #include @@ -84,7 +85,7 @@ static int After(eio_req *req) { argv[1] = BuildStatsObject(s); break; } - + case EIO_READLINK: { argc = 2; @@ -131,7 +132,7 @@ static int After(eio_req *req) { } } - if (req->type == EIO_WRITE) { + if (req->type == EIO_WRITE && req->int3 == 1) { assert(req->ptr2); delete [] reinterpret_cast(req->ptr2); } @@ -447,43 +448,118 @@ static Handle Open(const Arguments& args) { } } -/* node.fs.write(fd, data, position, enc, callback) - * Wrapper for write(2). - * - * 0 fd integer. file descriptor - * 1 data the data to write (string = utf8, array = raw) - * 2 position if integer, position to write at in the file. - * if null, write from the current position - * 3 encoding - */ +// write(fd, data, position, enc, callback) +// Wrapper for write(2). +// +// 0 fd integer. file descriptor +// 1 buffer the data to write +// 2 offset where in the buffer to start from +// 3 length how much to write +// 4 position if integer, position to write at in the file. +// if null, write from the current position +// +// - OR - +// +// 0 fd integer. file descriptor +// 1 string the data to write +// 2 position if integer, position to write at in the file. +// if null, write from the current position +// 3 encoding static Handle Write(const Arguments& args) { HandleScope scope; - if (args.Length() < 4 || !args[0]->IsInt32()) { + if (!args[0]->IsInt32()) { return THROW_BAD_ARGS; } int fd = args[0]->Int32Value(); - off_t offset = args[2]->IsNumber() ? args[2]->IntegerValue() : -1; - enum encoding enc = ParseEncoding(args[3]); - ssize_t len = DecodeBytes(args[1], enc); - if (len < 0) { - Local exception = Exception::TypeError(String::New("Bad argument")); - return ThrowException(exception); + off_t pos; + ssize_t len; + char * buf; + ssize_t written; + + Local cb; + bool legacy; + + if (Buffer::HasInstance(args[1])) { + legacy = false; + // buffer + // + // 0 fd integer. file descriptor + // 1 buffer the data to write + // 2 offset where in the buffer to start from + // 3 length how much to write + // 4 position if integer, position to write at in the file. + // if null, write from the current position + + Buffer * buffer = ObjectWrap::Unwrap(args[1]->ToObject()); + + size_t off = args[2]->Int32Value(); + if (off >= buffer->length()) { + return ThrowException(Exception::Error( + String::New("Offset is out of bounds"))); + } + + len = args[3]->Int32Value(); + if (off + len > buffer->length()) { + return ThrowException(Exception::Error( + String::New("Length is extends beyond buffer"))); + } + + pos = args[4]->IsNumber() ? args[4]->IntegerValue() : -1; + + buf = (char*)buffer->data() + off; + + cb = args[5]; + + } else { + legacy = true; + // legacy interface.. args[1] is a string + + pos = args[2]->IsNumber() ? args[2]->IntegerValue() : -1; + + enum encoding enc = ParseEncoding(args[3]); + + len = DecodeBytes(args[1], enc); + if (len < 0) { + Local exception = Exception::TypeError(String::New("Bad argument")); + return ThrowException(exception); + } + + buf = new char[len]; + written = DecodeWrite(buf, len, args[1], enc); + assert(written == len); + + cb = args[4]; } - char * buf = new char[len]; - ssize_t written = DecodeWrite(buf, len, args[1], enc); - assert(written == len); + if (cb->IsFunction()) { + // WARNING: HACK AHEAD, PROCEED WITH CAUTION + // Normally here I would do + // ASYNC_CALL(write, cb, fd, buf, len, pos) + // however, I'm trying to support a legacy interface; where in the + // String version a buffer is allocated to encode into. In the After() + // function it is freed. However in the other version, we just increase + // the reference count to a buffer. We have to let After() know which + // version is being done so it can know if it needs to free 'buf' or + // not. We do that by using req->int3. + // req->int3 == 1 legacy, String version. Need to free `buf`. + // req->int3 == 0 Buffer version. Don't do anything. + eio_req *req = eio_write(fd, buf, len, pos, + EIO_PRI_DEFAULT, + After, + cb_persist(cb)); + assert(req); + req->int3 = legacy ? 1 : 0; + ev_ref(EV_DEFAULT_UC); + return Undefined(); - if (args[4]->IsFunction()) { - ASYNC_CALL(write, args[4], fd, buf, len, offset) } else { - if (offset < 0) { + if (pos < 0) { written = write(fd, buf, len); } else { - written = pwrite(fd, buf, len, offset); + written = pwrite(fd, buf, len, pos); } if (written < 0) return ThrowException(errno_exception(errno)); return scope.Close(Integer::New(written)); diff --git a/test/pummel/test-tcp-pingpong.js b/test/pummel/test-tcp-pingpong.js index 9a3266c9673..73d36c82d9d 100644 --- a/test/pummel/test-tcp-pingpong.js +++ b/test/pummel/test-tcp-pingpong.js @@ -52,6 +52,8 @@ function pingPongTest (port, host, on_complete) { }); client.addListener("data", function (data) { + puts('client got: ' + data); + assert.equal("PONG", data); count += 1; From b80f6e9ed16f32920e81f9acf749d7bf9faf1320 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Fri, 19 Mar 2010 19:22:04 -0700 Subject: [PATCH 099/105] http2 now default --- benchmark/http_simple.js | 2 +- lib/http.js | 397 ++++++++++-------- lib/{http2.js => http_old.js} | 397 ++++++++---------- src/node.cc | 2 +- .../test-event-emitter-modify-in-emit.js | 2 +- test/simple/test-file-read-stream.js | 2 +- test/simple/test-file-write-stream.js | 2 +- test/simple/test-http-1.0.js | 2 +- test/simple/test-http-cat.js | 2 +- test/simple/test-http-chunked.js | 2 +- test/simple/test-http-client-race.js | 2 +- test/simple/test-http-client-upload.js | 2 +- test/simple/test-http-eof-on-connect.js | 2 +- test/simple/test-http-malformed-request.js | 2 +- test/simple/test-http-proxy.js | 2 +- test/simple/test-http-server.js | 2 +- test/simple/test-http-wget.js | 2 +- test/simple/test-http.js | 2 +- test/simple/test-remote-module-loading.js | 2 +- 19 files changed, 414 insertions(+), 414 deletions(-) rename lib/{http2.js => http_old.js} (63%) diff --git a/benchmark/http_simple.js b/benchmark/http_simple.js index 6d21c38eb5e..40515779928 100644 --- a/benchmark/http_simple.js +++ b/benchmark/http_simple.js @@ -1,7 +1,7 @@ path = require("path"); var puts = require("sys").puts; -http = require("http2"); +http = require("http"); fixed = "" for (var i = 0; i < 20*1024; i++) { diff --git a/lib/http.js b/lib/http.js index 832df6b2cb2..a8d09d3ea8c 100644 --- a/lib/http.js +++ b/lib/http.js @@ -1,11 +1,110 @@ var sys = require('sys'); +var net = require('net'); var events = require('events'); -// FIXME: The TCP binding isn't actually used here, but it needs to be -// loaded before the http binding. -process.binding('tcp'); +var HTTPParser = process.binding('http_parser').HTTPParser; + +var parserFreeList = []; + +function newParser (type) { + var parser; + if (parserFreeList.length) { + parser = parserFreeList.shift(); + parser.reinitialize(type); + } else { + parser = new HTTPParser(type); + + parser.onMessageBegin = function () { + parser.incoming = new IncomingMessage(parser.socket); + parser.field = null; + parser.value = null; + }; + + // Only servers will get URL events. + parser.onURL = function (b, start, len) { + var slice = b.asciiSlice(start, start+len); + if (parser.incoming.url) { + parser.incoming.url += slice; + } else { + // Almost always will branch here. + parser.incoming.url = slice; + } + }; + + parser.onHeaderField = function (b, start, len) { + var slice = b.asciiSlice(start, start+len).toLowerCase(); + if (parser.value) { + parser.incoming._addHeaderLine(parser.field, parser.value); + parser.field = null; + parser.value = null; + } + if (parser.field) { + parser.field += slice; + } else { + parser.field = slice; + } + }; + + parser.onHeaderValue = function (b, start, len) { + var slice = b.asciiSlice(start, start+len); + if (parser.value) { + parser.value += slice; + } else { + parser.value = slice; + } + }; + + parser.onHeadersComplete = function (info) { + if (parser.field && parser.value) { + parser.incoming._addHeaderLine(parser.field, parser.value); + } + + parser.incoming.httpVersionMajor = info.versionMajor; + parser.incoming.httpVersionMinor = info.versionMinor; + + if (info.method) { + // server only + parser.incoming.method = info.method; + } else { + // client only + parser.incoming.statusCode = info.statusCode; + } + + parser.onIncoming(parser.incoming, info.shouldKeepAlive); + }; + + parser.onBody = function (b, start, len) { + // TODO body encoding? + var enc = parser.incoming._encoding; + if (!enc) { + parser.incoming.emit('data', b.slice(start, start+len)); + } else { + var string; + switch (enc) { + case 'utf8': + string = b.utf8Slice(start, start+len); + break; + case 'ascii': + string = b.asciiSlice(start, start+len); + break; + default: + throw new Error('Unsupported encoding ' + self._encoding + '. Use Buffer'); + } + parser.incoming.emit('data', string); + } + }; + + parser.onMessageComplete = function () { + parser.incoming.emit("end"); + }; + } + return parser; +} + +function freeParser (parser) { + if (parserFreeList.length < 1000) parserFreeList.push(parser); +} -var http = process.binding('http'); var CRLF = "\r\n"; var STATUS_CODES = exports.STATUS_CODES = { @@ -56,10 +155,10 @@ var content_length_expression = /Content-Length/i; /* Abstract base class for ServerRequest and ClientResponse. */ -function IncomingMessage (connection) { +function IncomingMessage (socket) { events.EventEmitter.call(this); - this.connection = connection; + this.socket = socket; this.httpVersion = null; this.headers = {}; @@ -70,7 +169,7 @@ function IncomingMessage (connection) { // response (client) only this.statusCode = null; - this.client = this.connection; + this.client = this.socket; } sys.inherits(IncomingMessage, events.EventEmitter); exports.IncomingMessage = IncomingMessage; @@ -80,16 +179,21 @@ IncomingMessage.prototype._parseQueryString = function () { }; IncomingMessage.prototype.setBodyEncoding = function (enc) { - // TODO: Find a cleaner way of doing this. - this.connection.setEncoding(enc); + // TODO deprecation message? + this.setEncoding(enc); +}; + +IncomingMessage.prototype.setEncoding = function (enc) { + // TODO check values, error out on bad, and deprecation message? + this._encoding = enc.toLowerCase(); }; IncomingMessage.prototype.pause = function () { - this.connection.pause(); + this.socket.pause(); }; IncomingMessage.prototype.resume = function () { - this.connection.resume(); + this.socket.resume(); }; IncomingMessage.prototype._addHeaderLine = function (field, value) { @@ -102,10 +206,10 @@ IncomingMessage.prototype._addHeaderLine = function (field, value) { } }; -function OutgoingMessage (connection) { - events.EventEmitter.call(this, connection); +function OutgoingMessage (socket) { + events.EventEmitter.call(this, socket); - this.connection = connection; + this.socket = socket; this.output = []; this.outputEncodings = []; @@ -126,7 +230,7 @@ exports.OutgoingMessage = OutgoingMessage; OutgoingMessage.prototype._send = function (data, encoding) { var length = this.output.length; - if (length === 0) { + if (length === 0 || typeof data != 'string') { this.output.push(data); encoding = encoding || "ascii"; this.outputEncodings.push(encoding); @@ -138,11 +242,7 @@ OutgoingMessage.prototype._send = function (data, encoding) { if ((lastEncoding === encoding) || (!encoding && data.constructor === lastData.constructor)) { - if (lastData.constructor === String) { - this.output[length-1] = lastData + data; - } else { - this.output[length-1] = lastData.concat(data); - } + this.output[length-1] = lastData + data; return; } @@ -228,7 +328,11 @@ OutgoingMessage.prototype.write = function (chunk, encoding) { encoding = encoding || "ascii"; if (this.chunked_encoding) { - this._send(process._byteLength(chunk, encoding).toString(16)); + if (typeof chunk == 'string') { + this._send(process._byteLength(chunk, encoding).toString(16)); + } else { + this._send(chunk.length.toString(16)); + } this._send(CRLF); this._send(chunk, encoding); this._send(CRLF); @@ -259,7 +363,7 @@ OutgoingMessage.prototype.close = function () { function ServerResponse (req) { - OutgoingMessage.call(this, req.connection); + OutgoingMessage.call(this, req.socket); if (req.httpVersionMajor < 1 || req.httpVersionMinor < 1) { this.use_chunked_encoding_by_default = false; @@ -297,8 +401,8 @@ ServerResponse.prototype.writeHead = function (statusCode) { ServerResponse.prototype.sendHeader = ServerResponse.prototype.writeHead; ServerResponse.prototype.writeHeader = ServerResponse.prototype.writeHead; -function ClientRequest (connection, method, url, headers) { - OutgoingMessage.call(this, connection); +function ClientRequest (socket, method, url, headers) { + OutgoingMessage.call(this, socket); this.should_keep_alive = false; if (method === "GET" || method === "HEAD") { @@ -330,85 +434,19 @@ ClientRequest.prototype.close = function () { }; -function createIncomingMessageStream (connection, incoming_listener) { - var incoming, field, value; - - connection.addListener("messageBegin", function () { - incoming = new IncomingMessage(connection); - field = null; - value = null; - }); - - // Only servers will get URL events. - connection.addListener("url", function (data) { - incoming.url += data; - }); - - connection.addListener("headerField", function (data) { - if (value) { - incoming._addHeaderLine(field, value); - field = null; - value = null; - } - if (field) { - field += data; - } else { - field = data; - } - }); - - connection.addListener("headerValue", function (data) { - if (value) { - value += data; - } else { - value = data; - } - }); - - connection.addListener("headerComplete", function (info) { - if (field && value) { - incoming._addHeaderLine(field, value); - } - - incoming.httpVersion = info.httpVersion; - incoming.httpVersionMajor = info.versionMajor; - incoming.httpVersionMinor = info.versionMinor; - - if (info.method) { - // server only - incoming.method = info.method; - } else { - // client only - incoming.statusCode = info.statusCode; - } - - incoming_listener(incoming, info.should_keep_alive); - }); - - connection.addListener("body", function (chunk) { - incoming.emit('data', chunk); - }); - - connection.addListener("messageComplete", function () { - incoming.emit('end'); - }); -} - -/* Returns true if the message queue is finished and the connection +/* Returns true if the message queue is finished and the socket * should be closed. */ -function flushMessageQueue (connection, queue) { +function flushMessageQueue (socket, queue) { while (queue[0]) { var message = queue[0]; while (message.output.length > 0) { - if (connection.readyState !== "open" && connection.readyState !== "writeOnly") { - return true; - } + if (!socket.writable) return true; var data = message.output.shift(); var encoding = message.outputEncodings.shift(); - connection.write(data, encoding); + socket.write(data, encoding); } if (!message.finished) break; @@ -422,152 +460,173 @@ function flushMessageQueue (connection, queue) { } -exports.createServer = function (requestListener, options) { - var server = new http.Server(); - //server.setOptions(options); - server.addListener("request", requestListener); - server.addListener("connection", connectionListener); - return server; +function Server (requestListener) { + net.Server.call(this); + this.addListener("request", requestListener); + this.addListener("connection", connectionListener); +} +sys.inherits(Server, net.Server); + +exports.Server = Server; + +exports.createServer = function (requestListener) { + return new Server(requestListener); }; -function connectionListener (connection) { - // An array of responses for each connection. In pipelined connections +function connectionListener (socket) { + var self = this; + // An array of responses for each socket. In pipelined connections // we need to keep track of the order they were sent. var responses = []; - connection.resetParser(); + var parser = newParser('request'); + + socket.ondata = function (d, start, end) { + parser.execute(d, start, end - start); + }; + + socket.onend = function () { + parser.finish(); + // unref the parser for easy gc + freeParser(parser); - // is this really needed? - connection.addListener("end", function () { if (responses.length == 0) { - connection.close(); + socket.close(); } else { responses[responses.length-1].closeOnFinish = true; } - }); + }; - - createIncomingMessageStream(connection, function (incoming, should_keep_alive) { + parser.socket = socket; + // The following callback is issued after the headers have been read on a + // new message. In this callback we setup the response object and pass it + // to the user. + parser.onIncoming = function (incoming, shouldKeepAlive) { var req = incoming; - var res = new ServerResponse(req); - res.should_keep_alive = should_keep_alive; - res.addListener("flush", function () { - if (flushMessageQueue(connection, responses)) { - connection.close(); + + res.shouldKeepAlive = shouldKeepAlive; + res.addListener('flush', function () { + if (flushMessageQueue(socket, responses)) { + socket.close(); } }); responses.push(res); - connection.server.emit("request", req, res); - }); + self.emit('request', req, res); + }; } -exports.createClient = function (port, host) { - var client = new http.Client(); - var secure_credentials={ secure : false }; +function Client ( ) { + net.Stream.call(this); + + var self = this; var requests = []; var currentRequest; - client.tcpSetSecure = client.setSecure; - client.setSecure = function(format_type, ca_certs, crl_list, private_key, certificate) { - secure_credentials.secure = true; - secure_credentials.format_type = format_type; - secure_credentials.ca_certs = ca_certs; - secure_credentials.crl_list = crl_list; - secure_credentials.private_key = private_key; - secure_credentials.certificate = certificate; - } + var parser = newParser('response'); + parser.socket = this; - client._reconnect = function () { - if (client.readyState != "opening") { - //sys.debug("HTTP CLIENT: reconnecting readyState = " + client.readyState); - client.connect(port, host); - if (secure_credentials.secure) { - client.tcpSetSecure(secure_credentials.format_type, - secure_credentials.ca_certs, - secure_credentials.crl_list, - secure_credentials.private_key, - secure_credentials.certificate); - } + self._reconnect = function () { + if (self.readyState != "opening") { + sys.debug("HTTP CLIENT: reconnecting readyState = " + self.readyState); + self.connect(self.port, self.host); } }; - client._pushRequest = function (req) { + self._pushRequest = function (req) { req.addListener("flush", function () { - if (client.readyState == "closed") { - //sys.debug("HTTP CLIENT request flush. reconnect. readyState = " + client.readyState); - client._reconnect(); + if (self.readyState == "closed") { + sys.debug("HTTP CLIENT request flush. reconnect. readyState = " + self.readyState); + self._reconnect(); return; } - //sys.debug("client flush readyState = " + client.readyState); - if (req == currentRequest) flushMessageQueue(client, [req]); + + sys.debug("self flush readyState = " + self.readyState); + if (req == currentRequest) flushMessageQueue(self, [req]); }); requests.push(req); }; - client.addListener("connect", function () { - client.resetParser(); - currentRequest = requests.shift(); + this.ondata = function (d, start, end) { + parser.execute(d, start, end - start); + }; + + self.addListener("connect", function () { + parser.reinitialize('response'); + sys.puts('requests: ' + sys.inspect(requests)); + currentRequest = requests.shift() currentRequest.flush(); }); - client.addListener("end", function () { - //sys.debug("client got end closing. readyState = " + client.readyState); - client.close(); + self.addListener("end", function () { + parser.finish(); + freeParser(parser); + + //sys.debug("self got end closing. readyState = " + self.readyState); + self.close(); }); - client.addListener("close", function (had_error) { + self.addListener("close", function (had_error) { if (had_error) { - client.emit("error"); + self.emit("error"); return; } - //sys.debug("HTTP CLIENT onClose. readyState = " + client.readyState); + sys.debug("HTTP CLIENT onClose. readyState = " + self.readyState); // If there are more requests to handle, reconnect. if (requests.length > 0) { - client._reconnect(); + self._reconnect(); } }); - createIncomingMessageStream(client, function (res) { - //sys.debug("incoming response!"); + parser.onIncoming = function (res) { + sys.debug("incoming response!"); res.addListener('end', function ( ) { - //sys.debug("request complete disconnecting. readyState = " + client.readyState); - client.close(); + //sys.debug("request complete disconnecting. readyState = " + self.readyState); + self.close(); }); currentRequest.emit("response", res); - }); - - return client; + }; }; +sys.inherits(Client, net.Stream); -http.Client.prototype.get = function () { +exports.Client = Client; + +exports.createClient = function (port, host) { + var c = new Client; + c.port = port; + c.host = host; + return c; +} + + +Client.prototype.get = function () { throw new Error("client.get(...) is now client.request('GET', ...)"); }; -http.Client.prototype.head = function () { +Client.prototype.head = function () { throw new Error("client.head(...) is now client.request('HEAD', ...)"); }; -http.Client.prototype.post = function () { +Client.prototype.post = function () { throw new Error("client.post(...) is now client.request('POST', ...)"); }; -http.Client.prototype.del = function () { +Client.prototype.del = function () { throw new Error("client.del(...) is now client.request('DELETE', ...)"); }; -http.Client.prototype.put = function () { +Client.prototype.put = function () { throw new Error("client.put(...) is now client.request('PUT', ...)"); }; -http.Client.prototype.request = function (method, url, headers) { +Client.prototype.request = function (method, url, headers) { if (typeof(url) != "string") { // assume method was omitted, shift arguments headers = url; url = method; @@ -580,7 +639,7 @@ http.Client.prototype.request = function (method, url, headers) { exports.cat = function (url, encoding_, headers_) { - var encoding = 'utf8', + var encoding = 'utf8', headers = {}, callback = null; diff --git a/lib/http2.js b/lib/http_old.js similarity index 63% rename from lib/http2.js rename to lib/http_old.js index a8d09d3ea8c..832df6b2cb2 100644 --- a/lib/http2.js +++ b/lib/http_old.js @@ -1,110 +1,11 @@ var sys = require('sys'); -var net = require('net'); var events = require('events'); -var HTTPParser = process.binding('http_parser').HTTPParser; - -var parserFreeList = []; - -function newParser (type) { - var parser; - if (parserFreeList.length) { - parser = parserFreeList.shift(); - parser.reinitialize(type); - } else { - parser = new HTTPParser(type); - - parser.onMessageBegin = function () { - parser.incoming = new IncomingMessage(parser.socket); - parser.field = null; - parser.value = null; - }; - - // Only servers will get URL events. - parser.onURL = function (b, start, len) { - var slice = b.asciiSlice(start, start+len); - if (parser.incoming.url) { - parser.incoming.url += slice; - } else { - // Almost always will branch here. - parser.incoming.url = slice; - } - }; - - parser.onHeaderField = function (b, start, len) { - var slice = b.asciiSlice(start, start+len).toLowerCase(); - if (parser.value) { - parser.incoming._addHeaderLine(parser.field, parser.value); - parser.field = null; - parser.value = null; - } - if (parser.field) { - parser.field += slice; - } else { - parser.field = slice; - } - }; - - parser.onHeaderValue = function (b, start, len) { - var slice = b.asciiSlice(start, start+len); - if (parser.value) { - parser.value += slice; - } else { - parser.value = slice; - } - }; - - parser.onHeadersComplete = function (info) { - if (parser.field && parser.value) { - parser.incoming._addHeaderLine(parser.field, parser.value); - } - - parser.incoming.httpVersionMajor = info.versionMajor; - parser.incoming.httpVersionMinor = info.versionMinor; - - if (info.method) { - // server only - parser.incoming.method = info.method; - } else { - // client only - parser.incoming.statusCode = info.statusCode; - } - - parser.onIncoming(parser.incoming, info.shouldKeepAlive); - }; - - parser.onBody = function (b, start, len) { - // TODO body encoding? - var enc = parser.incoming._encoding; - if (!enc) { - parser.incoming.emit('data', b.slice(start, start+len)); - } else { - var string; - switch (enc) { - case 'utf8': - string = b.utf8Slice(start, start+len); - break; - case 'ascii': - string = b.asciiSlice(start, start+len); - break; - default: - throw new Error('Unsupported encoding ' + self._encoding + '. Use Buffer'); - } - parser.incoming.emit('data', string); - } - }; - - parser.onMessageComplete = function () { - parser.incoming.emit("end"); - }; - } - return parser; -} - -function freeParser (parser) { - if (parserFreeList.length < 1000) parserFreeList.push(parser); -} +// FIXME: The TCP binding isn't actually used here, but it needs to be +// loaded before the http binding. +process.binding('tcp'); +var http = process.binding('http'); var CRLF = "\r\n"; var STATUS_CODES = exports.STATUS_CODES = { @@ -155,10 +56,10 @@ var content_length_expression = /Content-Length/i; /* Abstract base class for ServerRequest and ClientResponse. */ -function IncomingMessage (socket) { +function IncomingMessage (connection) { events.EventEmitter.call(this); - this.socket = socket; + this.connection = connection; this.httpVersion = null; this.headers = {}; @@ -169,7 +70,7 @@ function IncomingMessage (socket) { // response (client) only this.statusCode = null; - this.client = this.socket; + this.client = this.connection; } sys.inherits(IncomingMessage, events.EventEmitter); exports.IncomingMessage = IncomingMessage; @@ -179,21 +80,16 @@ IncomingMessage.prototype._parseQueryString = function () { }; IncomingMessage.prototype.setBodyEncoding = function (enc) { - // TODO deprecation message? - this.setEncoding(enc); -}; - -IncomingMessage.prototype.setEncoding = function (enc) { - // TODO check values, error out on bad, and deprecation message? - this._encoding = enc.toLowerCase(); + // TODO: Find a cleaner way of doing this. + this.connection.setEncoding(enc); }; IncomingMessage.prototype.pause = function () { - this.socket.pause(); + this.connection.pause(); }; IncomingMessage.prototype.resume = function () { - this.socket.resume(); + this.connection.resume(); }; IncomingMessage.prototype._addHeaderLine = function (field, value) { @@ -206,10 +102,10 @@ IncomingMessage.prototype._addHeaderLine = function (field, value) { } }; -function OutgoingMessage (socket) { - events.EventEmitter.call(this, socket); +function OutgoingMessage (connection) { + events.EventEmitter.call(this, connection); - this.socket = socket; + this.connection = connection; this.output = []; this.outputEncodings = []; @@ -230,7 +126,7 @@ exports.OutgoingMessage = OutgoingMessage; OutgoingMessage.prototype._send = function (data, encoding) { var length = this.output.length; - if (length === 0 || typeof data != 'string') { + if (length === 0) { this.output.push(data); encoding = encoding || "ascii"; this.outputEncodings.push(encoding); @@ -242,7 +138,11 @@ OutgoingMessage.prototype._send = function (data, encoding) { if ((lastEncoding === encoding) || (!encoding && data.constructor === lastData.constructor)) { - this.output[length-1] = lastData + data; + if (lastData.constructor === String) { + this.output[length-1] = lastData + data; + } else { + this.output[length-1] = lastData.concat(data); + } return; } @@ -328,11 +228,7 @@ OutgoingMessage.prototype.write = function (chunk, encoding) { encoding = encoding || "ascii"; if (this.chunked_encoding) { - if (typeof chunk == 'string') { - this._send(process._byteLength(chunk, encoding).toString(16)); - } else { - this._send(chunk.length.toString(16)); - } + this._send(process._byteLength(chunk, encoding).toString(16)); this._send(CRLF); this._send(chunk, encoding); this._send(CRLF); @@ -363,7 +259,7 @@ OutgoingMessage.prototype.close = function () { function ServerResponse (req) { - OutgoingMessage.call(this, req.socket); + OutgoingMessage.call(this, req.connection); if (req.httpVersionMajor < 1 || req.httpVersionMinor < 1) { this.use_chunked_encoding_by_default = false; @@ -401,8 +297,8 @@ ServerResponse.prototype.writeHead = function (statusCode) { ServerResponse.prototype.sendHeader = ServerResponse.prototype.writeHead; ServerResponse.prototype.writeHeader = ServerResponse.prototype.writeHead; -function ClientRequest (socket, method, url, headers) { - OutgoingMessage.call(this, socket); +function ClientRequest (connection, method, url, headers) { + OutgoingMessage.call(this, connection); this.should_keep_alive = false; if (method === "GET" || method === "HEAD") { @@ -434,19 +330,85 @@ ClientRequest.prototype.close = function () { }; -/* Returns true if the message queue is finished and the socket +function createIncomingMessageStream (connection, incoming_listener) { + var incoming, field, value; + + connection.addListener("messageBegin", function () { + incoming = new IncomingMessage(connection); + field = null; + value = null; + }); + + // Only servers will get URL events. + connection.addListener("url", function (data) { + incoming.url += data; + }); + + connection.addListener("headerField", function (data) { + if (value) { + incoming._addHeaderLine(field, value); + field = null; + value = null; + } + if (field) { + field += data; + } else { + field = data; + } + }); + + connection.addListener("headerValue", function (data) { + if (value) { + value += data; + } else { + value = data; + } + }); + + connection.addListener("headerComplete", function (info) { + if (field && value) { + incoming._addHeaderLine(field, value); + } + + incoming.httpVersion = info.httpVersion; + incoming.httpVersionMajor = info.versionMajor; + incoming.httpVersionMinor = info.versionMinor; + + if (info.method) { + // server only + incoming.method = info.method; + } else { + // client only + incoming.statusCode = info.statusCode; + } + + incoming_listener(incoming, info.should_keep_alive); + }); + + connection.addListener("body", function (chunk) { + incoming.emit('data', chunk); + }); + + connection.addListener("messageComplete", function () { + incoming.emit('end'); + }); +} + +/* Returns true if the message queue is finished and the connection * should be closed. */ -function flushMessageQueue (socket, queue) { +function flushMessageQueue (connection, queue) { while (queue[0]) { var message = queue[0]; while (message.output.length > 0) { - if (!socket.writable) return true; + if (connection.readyState !== "open" && connection.readyState !== "writeOnly") { + return true; + } var data = message.output.shift(); var encoding = message.outputEncodings.shift(); - socket.write(data, encoding); + connection.write(data, encoding); } if (!message.finished) break; @@ -460,173 +422,152 @@ function flushMessageQueue (socket, queue) { } -function Server (requestListener) { - net.Server.call(this); - this.addListener("request", requestListener); - this.addListener("connection", connectionListener); -} -sys.inherits(Server, net.Server); - -exports.Server = Server; - -exports.createServer = function (requestListener) { - return new Server(requestListener); +exports.createServer = function (requestListener, options) { + var server = new http.Server(); + //server.setOptions(options); + server.addListener("request", requestListener); + server.addListener("connection", connectionListener); + return server; }; -function connectionListener (socket) { - var self = this; - // An array of responses for each socket. In pipelined connections +function connectionListener (connection) { + // An array of responses for each connection. In pipelined connections // we need to keep track of the order they were sent. var responses = []; - var parser = newParser('request'); - - socket.ondata = function (d, start, end) { - parser.execute(d, start, end - start); - }; - - socket.onend = function () { - parser.finish(); - // unref the parser for easy gc - freeParser(parser); + connection.resetParser(); + // is this really needed? + connection.addListener("end", function () { if (responses.length == 0) { - socket.close(); + connection.close(); } else { responses[responses.length-1].closeOnFinish = true; } - }; + }); - parser.socket = socket; - // The following callback is issued after the headers have been read on a - // new message. In this callback we setup the response object and pass it - // to the user. - parser.onIncoming = function (incoming, shouldKeepAlive) { + + createIncomingMessageStream(connection, function (incoming, should_keep_alive) { var req = incoming; - var res = new ServerResponse(req); - res.shouldKeepAlive = shouldKeepAlive; - res.addListener('flush', function () { - if (flushMessageQueue(socket, responses)) { - socket.close(); + var res = new ServerResponse(req); + res.should_keep_alive = should_keep_alive; + res.addListener("flush", function () { + if (flushMessageQueue(connection, responses)) { + connection.close(); } }); responses.push(res); - self.emit('request', req, res); - }; + connection.server.emit("request", req, res); + }); } -function Client ( ) { - net.Stream.call(this); - - var self = this; +exports.createClient = function (port, host) { + var client = new http.Client(); + var secure_credentials={ secure : false }; var requests = []; var currentRequest; - var parser = newParser('response'); - parser.socket = this; + client.tcpSetSecure = client.setSecure; + client.setSecure = function(format_type, ca_certs, crl_list, private_key, certificate) { + secure_credentials.secure = true; + secure_credentials.format_type = format_type; + secure_credentials.ca_certs = ca_certs; + secure_credentials.crl_list = crl_list; + secure_credentials.private_key = private_key; + secure_credentials.certificate = certificate; + } - self._reconnect = function () { - if (self.readyState != "opening") { - sys.debug("HTTP CLIENT: reconnecting readyState = " + self.readyState); - self.connect(self.port, self.host); + client._reconnect = function () { + if (client.readyState != "opening") { + //sys.debug("HTTP CLIENT: reconnecting readyState = " + client.readyState); + client.connect(port, host); + if (secure_credentials.secure) { + client.tcpSetSecure(secure_credentials.format_type, + secure_credentials.ca_certs, + secure_credentials.crl_list, + secure_credentials.private_key, + secure_credentials.certificate); + } } }; - self._pushRequest = function (req) { + client._pushRequest = function (req) { req.addListener("flush", function () { - if (self.readyState == "closed") { - sys.debug("HTTP CLIENT request flush. reconnect. readyState = " + self.readyState); - self._reconnect(); + if (client.readyState == "closed") { + //sys.debug("HTTP CLIENT request flush. reconnect. readyState = " + client.readyState); + client._reconnect(); return; } - - sys.debug("self flush readyState = " + self.readyState); - if (req == currentRequest) flushMessageQueue(self, [req]); + //sys.debug("client flush readyState = " + client.readyState); + if (req == currentRequest) flushMessageQueue(client, [req]); }); requests.push(req); }; - this.ondata = function (d, start, end) { - parser.execute(d, start, end - start); - }; - - self.addListener("connect", function () { - parser.reinitialize('response'); - sys.puts('requests: ' + sys.inspect(requests)); - currentRequest = requests.shift() + client.addListener("connect", function () { + client.resetParser(); + currentRequest = requests.shift(); currentRequest.flush(); }); - self.addListener("end", function () { - parser.finish(); - freeParser(parser); - - //sys.debug("self got end closing. readyState = " + self.readyState); - self.close(); + client.addListener("end", function () { + //sys.debug("client got end closing. readyState = " + client.readyState); + client.close(); }); - self.addListener("close", function (had_error) { + client.addListener("close", function (had_error) { if (had_error) { - self.emit("error"); + client.emit("error"); return; } - sys.debug("HTTP CLIENT onClose. readyState = " + self.readyState); + //sys.debug("HTTP CLIENT onClose. readyState = " + client.readyState); // If there are more requests to handle, reconnect. if (requests.length > 0) { - self._reconnect(); + client._reconnect(); } }); - parser.onIncoming = function (res) { - sys.debug("incoming response!"); + createIncomingMessageStream(client, function (res) { + //sys.debug("incoming response!"); res.addListener('end', function ( ) { - //sys.debug("request complete disconnecting. readyState = " + self.readyState); - self.close(); + //sys.debug("request complete disconnecting. readyState = " + client.readyState); + client.close(); }); currentRequest.emit("response", res); - }; + }); + + return client; }; -sys.inherits(Client, net.Stream); -exports.Client = Client; - -exports.createClient = function (port, host) { - var c = new Client; - c.port = port; - c.host = host; - return c; -} - - -Client.prototype.get = function () { +http.Client.prototype.get = function () { throw new Error("client.get(...) is now client.request('GET', ...)"); }; -Client.prototype.head = function () { +http.Client.prototype.head = function () { throw new Error("client.head(...) is now client.request('HEAD', ...)"); }; -Client.prototype.post = function () { +http.Client.prototype.post = function () { throw new Error("client.post(...) is now client.request('POST', ...)"); }; -Client.prototype.del = function () { +http.Client.prototype.del = function () { throw new Error("client.del(...) is now client.request('DELETE', ...)"); }; -Client.prototype.put = function () { +http.Client.prototype.put = function () { throw new Error("client.put(...) is now client.request('PUT', ...)"); }; -Client.prototype.request = function (method, url, headers) { +http.Client.prototype.request = function (method, url, headers) { if (typeof(url) != "string") { // assume method was omitted, shift arguments headers = url; url = method; @@ -639,7 +580,7 @@ Client.prototype.request = function (method, url, headers) { exports.cat = function (url, encoding_, headers_) { - var encoding = 'utf8', + var encoding = 'utf8', headers = {}, callback = null; diff --git a/src/node.cc b/src/node.cc index 42b7ff77bf4..3fb9f804369 100644 --- a/src/node.cc +++ b/src/node.cc @@ -1190,7 +1190,7 @@ static Handle Binding(const Arguments& args) { exports->Set(String::New("file"), String::New(native_file)); exports->Set(String::New("fs"), String::New(native_fs)); exports->Set(String::New("http"), String::New(native_http)); - exports->Set(String::New("http2"), String::New(native_http2)); + exports->Set(String::New("http_old"), String::New(native_http_old)); exports->Set(String::New("ini"), String::New(native_ini)); exports->Set(String::New("mjsunit"), String::New(native_mjsunit)); exports->Set(String::New("multipart"), String::New(native_multipart)); diff --git a/test/simple/test-event-emitter-modify-in-emit.js b/test/simple/test-event-emitter-modify-in-emit.js index 1dd94ba7127..81bba7c89e3 100644 --- a/test/simple/test-event-emitter-modify-in-emit.js +++ b/test/simple/test-event-emitter-modify-in-emit.js @@ -35,4 +35,4 @@ e.addListener("foo", callback1); e.addListener("foo", callback2); assert.equal(2, e.listeners("foo").length) e.removeAllListeners("foo") -assert.equal(0, e.listeners("foo").length) \ No newline at end of file +assert.equal(0, e.listeners("foo").length) diff --git a/test/simple/test-file-read-stream.js b/test/simple/test-file-read-stream.js index df57387947b..3ac308b9a26 100644 --- a/test/simple/test-file-read-stream.js +++ b/test/simple/test-file-read-stream.js @@ -60,4 +60,4 @@ process.addListener('exit', function() { for (var k in callbacks) { assert.equal(0, callbacks[k], k+' count off by '+callbacks[k]); } -}); \ No newline at end of file +}); diff --git a/test/simple/test-file-write-stream.js b/test/simple/test-file-write-stream.js index 579455c7d91..df045998cd7 100644 --- a/test/simple/test-file-write-stream.js +++ b/test/simple/test-file-write-stream.js @@ -59,4 +59,4 @@ process.addListener('exit', function() { for (var k in callbacks) { assert.equal(0, callbacks[k], k+' count off by '+callbacks[k]); } -}); \ No newline at end of file +}); diff --git a/test/simple/test-http-1.0.js b/test/simple/test-http-1.0.js index 106313d2817..105acdcd8ba 100644 --- a/test/simple/test-http-1.0.js +++ b/test/simple/test-http-1.0.js @@ -1,6 +1,6 @@ require("../common"); net = require("net"); -http = require("http2"); +http = require("http"); var body = "hello world\n"; var server_response = ""; diff --git a/test/simple/test-http-cat.js b/test/simple/test-http-cat.js index 32177ee92b8..e81a5c502fe 100644 --- a/test/simple/test-http-cat.js +++ b/test/simple/test-http-cat.js @@ -1,5 +1,5 @@ require("../common"); -http = require("http2"); +http = require("http"); var body = "exports.A = function() { return 'A';}"; var server = http.createServer(function (req, res) { diff --git a/test/simple/test-http-chunked.js b/test/simple/test-http-chunked.js index 4325d113dab..a838235472d 100644 --- a/test/simple/test-http-chunked.js +++ b/test/simple/test-http-chunked.js @@ -1,5 +1,5 @@ require("../common"); -var http = require("http2"); +var http = require("http"); var UTF8_STRING = "Il était tué"; diff --git a/test/simple/test-http-client-race.js b/test/simple/test-http-client-race.js index a3824cfce1e..9076924b37e 100644 --- a/test/simple/test-http-client-race.js +++ b/test/simple/test-http-client-race.js @@ -1,5 +1,5 @@ require("../common"); -http = require("http2"); +http = require("http"); url = require("url"); var body1_s = "1111111111111111"; diff --git a/test/simple/test-http-client-upload.js b/test/simple/test-http-client-upload.js index e1d746c35d2..b4c0f61acba 100644 --- a/test/simple/test-http-client-upload.js +++ b/test/simple/test-http-client-upload.js @@ -1,5 +1,5 @@ require("../common"); -http = require("http2"); +http = require("http"); var sent_body = ""; var server_req_complete = false; diff --git a/test/simple/test-http-eof-on-connect.js b/test/simple/test-http-eof-on-connect.js index 7838c8d141c..1f2f2e5121e 100644 --- a/test/simple/test-http-eof-on-connect.js +++ b/test/simple/test-http-eof-on-connect.js @@ -1,6 +1,6 @@ require("../common"); net = require("net"); -http = require("http2"); +http = require("http"); // This is a regression test for http://github.com/ry/node/issues/#issue/44 // It is separate from test-http-malformed-request.js because it is only diff --git a/test/simple/test-http-malformed-request.js b/test/simple/test-http-malformed-request.js index 98d6da780c0..47bcfca3249 100644 --- a/test/simple/test-http-malformed-request.js +++ b/test/simple/test-http-malformed-request.js @@ -1,6 +1,6 @@ require("../common"); net = require("net"); -http = require("http2"); +http = require("http"); url = require("url"); // Make sure no exceptions are thrown when receiving malformed HTTP diff --git a/test/simple/test-http-proxy.js b/test/simple/test-http-proxy.js index 5d57ba5310f..348851d6bab 100644 --- a/test/simple/test-http-proxy.js +++ b/test/simple/test-http-proxy.js @@ -1,5 +1,5 @@ require("../common"); -http = require("http2"); +http = require("http"); url = require("url"); var PROXY_PORT = PORT; diff --git a/test/simple/test-http-server.js b/test/simple/test-http-server.js index e42d55429e9..c7deeb8baff 100644 --- a/test/simple/test-http-server.js +++ b/test/simple/test-http-server.js @@ -1,6 +1,6 @@ require("../common"); net = require("net"); -http = require("http2"); +http = require("http"); url = require("url"); qs = require("querystring"); diff --git a/test/simple/test-http-wget.js b/test/simple/test-http-wget.js index 38a41896034..1d7f3412630 100644 --- a/test/simple/test-http-wget.js +++ b/test/simple/test-http-wget.js @@ -1,6 +1,6 @@ require("../common"); net = require("net"); -http = require("http2"); +http = require("http"); // wget sends an HTTP/1.0 request with Connection: Keep-Alive // diff --git a/test/simple/test-http.js b/test/simple/test-http.js index df0ebea4064..c59927763ab 100644 --- a/test/simple/test-http.js +++ b/test/simple/test-http.js @@ -1,5 +1,5 @@ require("../common"); -http = require("http2"); +http = require("http"); url = require("url"); var responses_sent = 0; diff --git a/test/simple/test-remote-module-loading.js b/test/simple/test-remote-module-loading.js index 8a7edbbe27b..0a2af87e087 100644 --- a/test/simple/test-remote-module-loading.js +++ b/test/simple/test-remote-module-loading.js @@ -1,6 +1,6 @@ require("../common"); -var http = require('http2'); +var http = require('http'); var sys = require('sys'); var url = require("url"); var modulesLoaded = 0; From ac684f358391400a4533fc81fa7c522090aec884 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Fri, 19 Mar 2010 20:33:09 -0700 Subject: [PATCH 100/105] Add legacy 'binary' encoding/decoding methods to Buffer --- lib/http.js | 5 ++++- lib/net.js | 11 ++++++++--- src/node_buffer.cc | 44 ++++++++++++++++++++++++++++++++++++++++++++ src/node_buffer.h | 2 ++ 4 files changed, 58 insertions(+), 4 deletions(-) diff --git a/lib/http.js b/lib/http.js index a8d09d3ea8c..7ff1f8eec06 100644 --- a/lib/http.js +++ b/lib/http.js @@ -87,8 +87,11 @@ function newParser (type) { case 'ascii': string = b.asciiSlice(start, start+len); break; + case 'binary': + string = b.binarySlice(start, start+len); + break; default: - throw new Error('Unsupported encoding ' + self._encoding + '. Use Buffer'); + throw new Error('Unsupported encoding ' + enc + '. Use Buffer'); } parser.incoming.emit('data', string); } diff --git a/lib/net.js b/lib/net.js index b963fc7c393..72618e14966 100644 --- a/lib/net.js +++ b/lib/net.js @@ -429,18 +429,23 @@ Stream.prototype._writeString = function (data, encoding) { } } - encoding = encoding || 'utf8'; // default to utf8 + encoding = (encoding || 'utf8').toLowerCase(); // default to utf8 var charsWritten; var bytesWritten; - if (encoding.toLowerCase() == 'utf8') { + + if (encoding == 'utf8') { charsWritten = buffer.utf8Write(data, buffer.used); bytesWritten = Buffer.byteLength(data.slice(0, charsWritten)); - } else { + } else if (encoding == 'ascii') { // ascii charsWritten = buffer.asciiWrite(data, buffer.used); bytesWritten = charsWritten; + } else { + // binary + charsWritten = buffer.binaryWrite(data, buffer.used); + bytesWritten = charsWritten; } buffer.used += bytesWritten; diff --git a/src/node_buffer.cc b/src/node_buffer.cc index b0efba427fb..c16ead365e5 100644 --- a/src/node_buffer.cc +++ b/src/node_buffer.cc @@ -171,6 +171,20 @@ Buffer::~Buffer() { } +Handle Buffer::BinarySlice(const Arguments &args) { + HandleScope scope; + Buffer *parent = ObjectWrap::Unwrap(args.This()); + SLICE_ARGS(args[0], args[1]) + + const char *data = const_cast(parent->data_ + start); + //Local string = String::New(data, end - start); + + Local b = Encode(data, end - start, BINARY); + + return scope.Close(b); +} + + Handle Buffer::AsciiSlice(const Arguments &args) { HandleScope scope; Buffer *parent = ObjectWrap::Unwrap(args.This()); @@ -267,6 +281,34 @@ Handle Buffer::AsciiWrite(const Arguments &args) { } +Handle Buffer::BinaryWrite(const Arguments &args) { + HandleScope scope; + + Buffer *buffer = ObjectWrap::Unwrap(args.This()); + + if (!args[0]->IsString()) { + return ThrowException(Exception::TypeError(String::New( + "Argument must be a string"))); + } + + Local s = args[0]->ToString(); + + size_t offset = args[1]->Int32Value(); + + if (offset >= buffer->length_) { + return ThrowException(Exception::TypeError(String::New( + "Offset is out of bounds"))); + } + + char *p = (char*)buffer->data_ + offset; + + size_t towrite = MIN((unsigned long) s->Length(), buffer->length_ - offset); + + int written = DecodeWrite(p, towrite, s, BINARY); + return scope.Close(Integer::New(written)); +} + + // buffer.unpack(format, index); // Starting at 'index', unpacks binary from the buffer into an array. // 'format' is a string @@ -361,6 +403,7 @@ void Buffer::Initialize(Handle target) { constructor_template->SetClassName(String::NewSymbol("Buffer")); // copy free + NODE_SET_PROTOTYPE_METHOD(constructor_template, "binarySlice", Buffer::BinarySlice); NODE_SET_PROTOTYPE_METHOD(constructor_template, "asciiSlice", Buffer::AsciiSlice); NODE_SET_PROTOTYPE_METHOD(constructor_template, "slice", Buffer::Slice); // TODO NODE_SET_PROTOTYPE_METHOD(t, "utf16Slice", Utf16Slice); @@ -369,6 +412,7 @@ void Buffer::Initialize(Handle target) { NODE_SET_PROTOTYPE_METHOD(constructor_template, "utf8Write", Buffer::Utf8Write); NODE_SET_PROTOTYPE_METHOD(constructor_template, "asciiWrite", Buffer::AsciiWrite); + NODE_SET_PROTOTYPE_METHOD(constructor_template, "binaryWrite", Buffer::BinaryWrite); NODE_SET_PROTOTYPE_METHOD(constructor_template, "unpack", Buffer::Unpack); NODE_SET_METHOD(constructor_template->GetFunction(), diff --git a/src/node_buffer.h b/src/node_buffer.h index 569943eb26e..9f497c5419f 100644 --- a/src/node_buffer.h +++ b/src/node_buffer.h @@ -45,8 +45,10 @@ class Buffer : public ObjectWrap { static v8::Persistent constructor_template; static v8::Handle New(const v8::Arguments &args); static v8::Handle Slice(const v8::Arguments &args); + static v8::Handle BinarySlice(const v8::Arguments &args); static v8::Handle AsciiSlice(const v8::Arguments &args); static v8::Handle Utf8Slice(const v8::Arguments &args); + static v8::Handle BinaryWrite(const v8::Arguments &args); static v8::Handle AsciiWrite(const v8::Arguments &args); static v8::Handle Utf8Write(const v8::Arguments &args); static v8::Handle ByteLength(const v8::Arguments &args); From 025116f8d01e2ff07af79d3f8bfaf9cd1b734740 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Fri, 19 Mar 2010 20:50:29 -0700 Subject: [PATCH 101/105] Move Buffer into own module --- lib/buffer.js | 20 ++++++++++++++++++++ lib/net.js | 2 +- src/node.cc | 11 ++++++++++- test/disabled/test-net-fd-passing.js | 4 ---- test/fixtures/net-fd-passing-receiver.js | 5 ----- test/simple/test-buffer.js | 5 +++-- test/simple/test-http-parser.js | 3 ++- test/simple/test-net-pingpong.js | 4 ---- 8 files changed, 36 insertions(+), 18 deletions(-) create mode 100644 lib/buffer.js diff --git a/lib/buffer.js b/lib/buffer.js new file mode 100644 index 00000000000..6b570eed049 --- /dev/null +++ b/lib/buffer.js @@ -0,0 +1,20 @@ +var Buffer = process.binding('buffer').Buffer; + +exports.Buffer = Buffer; + +Buffer.prototype.toString = function () { + return this.utf8Slice(0, this.length); +}; + +Buffer.prototype.toJSON = function () { + return this.utf8Slice(0, this.length); + /* + var s = ""; + for (var i = 0; i < this.length; i++) { + s += this[i].toString(16) + " "; + } + return s; + */ +}; + + diff --git a/lib/net.js b/lib/net.js index 72618e14966..b5951acb322 100644 --- a/lib/net.js +++ b/lib/net.js @@ -18,7 +18,7 @@ var binding = process.binding('net'); // represent buffer.used) that can be seeked around would be easier. I'm not // yet convinced that every use-case can be fit into that abstraction, so // waiting to implement it until I get more experience with this. -var Buffer = process.Buffer; +var Buffer = require('buffer').Buffer; var IOWatcher = process.IOWatcher; var assert = process.assert; diff --git a/src/node.cc b/src/node.cc index 3fb9f804369..5f98e7de7ab 100644 --- a/src/node.cc +++ b/src/node.cc @@ -1176,6 +1176,15 @@ static Handle Binding(const Arguments& args) { binding_cache->Set(module, exports); } + } else if (!strcmp(*module_v, "buffer")) { + if (binding_cache->Has(module)) { + exports = binding_cache->Get(module)->ToObject(); + } else { + exports = Object::New(); + Buffer::Initialize(exports); + binding_cache->Set(module, exports); + } + } else if (!strcmp(*module_v, "natives")) { if (binding_cache->Has(module)) { exports = binding_cache->Get(module)->ToObject(); @@ -1184,6 +1193,7 @@ static Handle Binding(const Arguments& args) { // Explicitly define native sources. // TODO DRY/automate this? exports->Set(String::New("assert"), String::New(native_assert)); + exports->Set(String::New("buffer"), String::New(native_buffer)); exports->Set(String::New("child_process"),String::New(native_child_process)); exports->Set(String::New("dns"), String::New(native_dns)); exports->Set(String::New("events"), String::New(native_events)); @@ -1301,7 +1311,6 @@ static void Load(int argc, char *argv[]) { // Initialize the C++ modules..................filename of module - Buffer::Initialize(process); // buffer.cc IOWatcher::Initialize(process); // io_watcher.cc IdleWatcher::Initialize(process); // idle_watcher.cc Timer::Initialize(process); // timer.cc diff --git a/test/disabled/test-net-fd-passing.js b/test/disabled/test-net-fd-passing.js index 1cf577bf6af..3d16ff62d37 100644 --- a/test/disabled/test-net-fd-passing.js +++ b/test/disabled/test-net-fd-passing.js @@ -1,10 +1,6 @@ process.mixin(require("../common")); net = require("net"); -process.Buffer.prototype.toString = function () { - return this.utf8Slice(0, this.length); -}; - var tests_run = 0; function fdPassingTest(path, port) { diff --git a/test/fixtures/net-fd-passing-receiver.js b/test/fixtures/net-fd-passing-receiver.js index be29a287233..bcaf9e116ca 100644 --- a/test/fixtures/net-fd-passing-receiver.js +++ b/test/fixtures/net-fd-passing-receiver.js @@ -1,11 +1,6 @@ process.mixin(require("../common")); net = require("net"); -process.Buffer.prototype.toString = function () { - return this.utf8Slice(0, this.length); -}; - - path = process.ARGV[2]; greeting = process.ARGV[3]; diff --git a/test/simple/test-buffer.js b/test/simple/test-buffer.js index a069face9b9..d175c4dfd0d 100644 --- a/test/simple/test-buffer.js +++ b/test/simple/test-buffer.js @@ -1,8 +1,9 @@ require("../common"); assert = require("assert"); +var Buffer = require('buffer').Buffer; -var b = new process.Buffer(1024); +var b = new Buffer(1024); puts("b.length == " + b.length); assert.equal(1024, b.length); @@ -52,7 +53,7 @@ for (var j = 0; j < 100; j++) { // unpack -var b = new process.Buffer(10); +var b = new Buffer(10); b[0] = 0x00; b[1] = 0x01; b[2] = 0x03; diff --git a/test/simple/test-http-parser.js b/test/simple/test-http-parser.js index 6ad65100558..e524cd009dd 100644 --- a/test/simple/test-http-parser.js +++ b/test/simple/test-http-parser.js @@ -9,7 +9,8 @@ var HTTPParser = process.binding('http_parser').HTTPParser; var parser = new HTTPParser("request"); -var buffer = new process.Buffer(1024); +var Buffer = require('buffer').Buffer; +var buffer = new Buffer(1024); var request = "GET /hello HTTP/1.1\r\n\r\n"; diff --git a/test/simple/test-net-pingpong.js b/test/simple/test-net-pingpong.js index 860b2007c3e..ed8cfebf877 100644 --- a/test/simple/test-net-pingpong.js +++ b/test/simple/test-net-pingpong.js @@ -2,10 +2,6 @@ require("../common"); net = require("net"); -process.Buffer.prototype.toString = function () { - return this.utf8Slice(0, this.length); -}; - var tests_run = 0; function pingPongTest (port, host) { From 663269f6870ca6bf55303f2b1e297e684f9fe768 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Fri, 19 Mar 2010 21:22:11 -0700 Subject: [PATCH 102/105] old tcp module is now tcp_old --- lib/tcp.js | 31 ++++++++----------------------- lib/tcp_old.js | 26 ++++++++++++++++++++++++++ src/node.cc | 1 + 3 files changed, 35 insertions(+), 23 deletions(-) create mode 100644 lib/tcp_old.js diff --git a/lib/tcp.js b/lib/tcp.js index a27a32761a8..64080be87b6 100644 --- a/lib/tcp.js +++ b/lib/tcp.js @@ -1,26 +1,11 @@ -var tcp = process.binding('tcp'); +var net = require('net'); +var sys = require('sys'); -var TLS_STATUS_CODES = { - 1 : 'JS_GNUTLS_CERT_VALIDATED', - 0 : 'JS_GNUTLS_CERT_UNDEFINED', +var warning; +if (!warning) { + warning = "The 'tcp' module is now called 'net'. Otherwise it should have a similar interface."; + sys.error(warning); } -TLS_STATUS_CODES[-100] = 'JS_GNUTLS_CERT_SIGNER_NOT_FOUND'; -TLS_STATUS_CODES[-101] = 'JS_GNUTLS_CERT_SIGNER_NOT_CA'; -TLS_STATUS_CODES[-102] = 'JS_GNUTLS_CERT_INVALID'; -TLS_STATUS_CODES[-103] = 'JS_GNUTLS_CERT_NOT_ACTIVATED'; -TLS_STATUS_CODES[-104] = 'JS_GNUTLS_CERT_EXPIRED'; -TLS_STATUS_CODES[-105] = 'JS_GNUTLS_CERT_REVOKED'; -TLS_STATUS_CODES[-106] = 'JS_GNUTLS_CERT_DOES_NOT_MATCH_HOSTNAME'; -exports.createServer = function (on_connection, options) { - var server = new tcp.Server(); - server.addListener("connection", on_connection); - //server.setOptions(options); - return server; -}; - -exports.createConnection = function (port, host) { - var connection = new tcp.Connection(); - connection.connect(port, host); - return connection; -}; +exports.createServer = net.createServer; +exports.createConnection = net.createConnection; diff --git a/lib/tcp_old.js b/lib/tcp_old.js new file mode 100644 index 00000000000..a27a32761a8 --- /dev/null +++ b/lib/tcp_old.js @@ -0,0 +1,26 @@ +var tcp = process.binding('tcp'); + +var TLS_STATUS_CODES = { + 1 : 'JS_GNUTLS_CERT_VALIDATED', + 0 : 'JS_GNUTLS_CERT_UNDEFINED', +} +TLS_STATUS_CODES[-100] = 'JS_GNUTLS_CERT_SIGNER_NOT_FOUND'; +TLS_STATUS_CODES[-101] = 'JS_GNUTLS_CERT_SIGNER_NOT_CA'; +TLS_STATUS_CODES[-102] = 'JS_GNUTLS_CERT_INVALID'; +TLS_STATUS_CODES[-103] = 'JS_GNUTLS_CERT_NOT_ACTIVATED'; +TLS_STATUS_CODES[-104] = 'JS_GNUTLS_CERT_EXPIRED'; +TLS_STATUS_CODES[-105] = 'JS_GNUTLS_CERT_REVOKED'; +TLS_STATUS_CODES[-106] = 'JS_GNUTLS_CERT_DOES_NOT_MATCH_HOSTNAME'; + +exports.createServer = function (on_connection, options) { + var server = new tcp.Server(); + server.addListener("connection", on_connection); + //server.setOptions(options); + return server; +}; + +exports.createConnection = function (port, host) { + var connection = new tcp.Connection(); + connection.connect(port, host); + return connection; +}; diff --git a/src/node.cc b/src/node.cc index 5f98e7de7ab..50fd399885d 100644 --- a/src/node.cc +++ b/src/node.cc @@ -1210,6 +1210,7 @@ static Handle Binding(const Arguments& args) { exports->Set(String::New("repl"), String::New(native_repl)); exports->Set(String::New("sys"), String::New(native_sys)); exports->Set(String::New("tcp"), String::New(native_tcp)); + exports->Set(String::New("tcp_old"), String::New(native_tcp_old)); exports->Set(String::New("uri"), String::New(native_uri)); exports->Set(String::New("url"), String::New(native_url)); exports->Set(String::New("utils"), String::New(native_utils)); From 139c91e892b0f9dcf9015f81486e614eaf26d906 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Fri, 19 Mar 2010 21:25:29 -0700 Subject: [PATCH 103/105] Support old 'binary' encoding in net.js --- lib/net.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/net.js b/lib/net.js index b5951acb322..81073be7ca3 100644 --- a/lib/net.js +++ b/lib/net.js @@ -329,6 +329,9 @@ function initStream (self) { case 'ascii': string = recvBuffer.asciiSlice(start, end); break; + case 'binary': + string = recvBuffer.binarySlice(start, end); + break; default: throw new Error('Unsupported encoding ' + self._encoding + '. Use Buffer'); } @@ -513,9 +516,12 @@ Stream.prototype.write = function (data, encoding) { var charsWritten; if (encoding == 'utf8') { recvBuffer.utf8Write(data, recvBuffer.used); - } else { + } else if (encoding == 'ascii') { // ascii recvBuffer.asciiWrite(data, recvBuffer.used); + } else { + // binary + recvBuffer.binaryWrite(data, recvBuffer.used); } buffer = recvBuffer; From ebe2721cbfdb2a9b7ed177d45b3c7eed92f18837 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Fri, 19 Mar 2010 21:49:00 -0700 Subject: [PATCH 104/105] Don't use sys.debug, it gets preprocessed out Problem introduced in b29f78772ec183b4e4aee0f67f95c599a7d8060b --- lib/http.js | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/lib/http.js b/lib/http.js index 7ff1f8eec06..5ec1c6e984d 100644 --- a/lib/http.js +++ b/lib/http.js @@ -1,3 +1,12 @@ +var debugLevel = 0; +if ("NODE_DEBUG" in process.env) debugLevel = 1; + +function debug (x) { + if (debugLevel > 0) { + process.binding('stdio').writeError(x + "\n"); + } +} + var sys = require('sys'); var net = require('net'); var events = require('events'); @@ -534,7 +543,7 @@ function Client ( ) { self._reconnect = function () { if (self.readyState != "opening") { - sys.debug("HTTP CLIENT: reconnecting readyState = " + self.readyState); + debug("HTTP CLIENT: reconnecting readyState = " + self.readyState); self.connect(self.port, self.host); } }; @@ -542,12 +551,12 @@ function Client ( ) { self._pushRequest = function (req) { req.addListener("flush", function () { if (self.readyState == "closed") { - sys.debug("HTTP CLIENT request flush. reconnect. readyState = " + self.readyState); + debug("HTTP CLIENT request flush. reconnect. readyState = " + self.readyState); self._reconnect(); return; } - sys.debug("self flush readyState = " + self.readyState); + debug("self flush readyState = " + self.readyState); if (req == currentRequest) flushMessageQueue(self, [req]); }); requests.push(req); @@ -568,7 +577,7 @@ function Client ( ) { parser.finish(); freeParser(parser); - //sys.debug("self got end closing. readyState = " + self.readyState); + debug("self got end closing. readyState = " + self.readyState); self.close(); }); @@ -578,7 +587,7 @@ function Client ( ) { return; } - sys.debug("HTTP CLIENT onClose. readyState = " + self.readyState); + debug("HTTP CLIENT onClose. readyState = " + self.readyState); // If there are more requests to handle, reconnect. if (requests.length > 0) { @@ -587,10 +596,10 @@ function Client ( ) { }); parser.onIncoming = function (res) { - sys.debug("incoming response!"); + debug("incoming response!"); res.addListener('end', function ( ) { - //sys.debug("request complete disconnecting. readyState = " + self.readyState); + debug("request complete disconnecting. readyState = " + self.readyState); self.close(); }); From 04001fb591ee9adbe343d4b23aac48abce0809e2 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Fri, 19 Mar 2010 21:51:04 -0700 Subject: [PATCH 105/105] Don't deprecate readyState, yet --- lib/net.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/net.js b/lib/net.js index 81073be7ca3..8a433da3817 100644 --- a/lib/net.js +++ b/lib/net.js @@ -386,13 +386,8 @@ exports.createConnection = function (port, host) { }; -var readyStateMessage; Object.defineProperty(Stream.prototype, 'readyState', { get: function () { - if (!readyStateMessage) { - readyStateMessage = 'readyState is depricated. Use stream.readable or stream.writable'; - sys.error(readyStateMessage); - } if (this._resolving) { return 'opening'; } else if (this.readable && this.writable) {