Remove error codes from file on_completion callbacks. Use file.onError.
authorRyan <ry@tinyclouds.org>
Mon, 25 May 2009 11:17:35 +0000 (13:17 +0200)
committerRyan <ry@tinyclouds.org>
Mon, 25 May 2009 11:17:35 +0000 (13:17 +0200)
The error codes still remain for the two general file system operations:
rename and stat.

Additionally I've removed the actionQueue for file system operations. They
are sent directly into the thread pool.

src/file.cc
src/file.js
test/test-file-open.js
website/node.html

index c014a70..c8f6fae 100644 (file)
 using namespace v8;
 using namespace node;
 
-#define FD_SYMBOL v8::String::NewSymbol("fd")
-#define ACTION_QUEUE_SYMBOL v8::String::NewSymbol("_actionQueue")
-#define ENCODING_SYMBOL v8::String::NewSymbol("encoding")
+#define FD_SYMBOL           String::NewSymbol("fd")
+#define ACTION_QUEUE_SYMBOL String::NewSymbol("_actionQueue")
+#define ENCODING_SYMBOL     String::NewSymbol("encoding")
+#define CALLBACK_SYMBOL     String::NewSymbol("callbaccallback")
+#define POLL_ACTIONS_SYMBOL String::NewSymbol("_pollActions")
 
-#define UTF8_SYMBOL v8::String::NewSymbol("utf8")
-#define RAW_SYMBOL v8::String::NewSymbol("raw")
-
-static void
-InitActionQueue (Handle<Object> handle)
-{
-  handle->Set(ACTION_QUEUE_SYMBOL, Array::New());
-}
-
-// This is the file system object which contains methods
-// for accessing the file system (like rename, mkdir, etC). 
-// In javascript it is called "File".
-static Persistent<Object> fs;
+#define UTF8_SYMBOL         String::NewSymbol("utf8")
+#define RAW_SYMBOL          String::NewSymbol("raw")
 
 void
 File::Initialize (Handle<Object> target)
 {
-  if (!fs.IsEmpty())
-    return;
-
   HandleScope scope;
 
-  fs = Persistent<Object>::New(target);
-  InitActionQueue(fs);
-
-
   // file system methods
-  NODE_SET_METHOD(fs, "_ffi_rename", FileSystem::Rename);
-  NODE_SET_METHOD(fs, "_ffi_stat", FileSystem::Stat);
-  NODE_SET_METHOD(fs, "strerror", FileSystem::StrError);
+  NODE_SET_METHOD(target, "rename",   FileSystem::Rename);
+  NODE_SET_METHOD(target, "stat",     FileSystem::Stat);
+  NODE_SET_METHOD(target, "strerror", FileSystem::StrError);
 
-  fs->Set(String::NewSymbol("STDIN_FILENO"), Integer::New(STDIN_FILENO));
-  fs->Set(String::NewSymbol("STDOUT_FILENO"), Integer::New(STDOUT_FILENO));
-  fs->Set(String::NewSymbol("STDERR_FILENO"), Integer::New(STDERR_FILENO));
+  target->Set(String::NewSymbol("STDIN_FILENO"), Integer::New(STDIN_FILENO));
+  target->Set(String::NewSymbol("STDOUT_FILENO"), Integer::New(STDOUT_FILENO));
+  target->Set(String::NewSymbol("STDERR_FILENO"), Integer::New(STDERR_FILENO));
 
 
   Local<FunctionTemplate> file_template = FunctionTemplate::New(File::New);
   file_template->InstanceTemplate()->SetInternalFieldCount(1);
+
   // file methods
   NODE_SET_PROTOTYPE_METHOD(file_template, "_ffi_open", File::Open);
   NODE_SET_PROTOTYPE_METHOD(file_template, "_ffi_close", File::Close);
   NODE_SET_PROTOTYPE_METHOD(file_template, "_ffi_write", File::Write);
   NODE_SET_PROTOTYPE_METHOD(file_template, "_ffi_read", File::Read);
   file_template->InstanceTemplate()->SetAccessor(ENCODING_SYMBOL, File::GetEncoding, File::SetEncoding);
-  fs->Set(String::NewSymbol("File"), file_template->GetFunction());
+  target->Set(String::NewSymbol("File"), file_template->GetFunction());
 }
 
 Handle<Value>
@@ -95,32 +80,15 @@ CallTopCallback (Handle<Object> handle, const int argc, Handle<Value> argv[])
 {
   HandleScope scope;
 
-  Local<Value> queue_value = handle->Get(ACTION_QUEUE_SYMBOL);
-  assert(queue_value->IsArray());
-
-  Local<Array> queue = Local<Array>::Cast(queue_value);
-  Local<Value> top_value = queue->Get(Integer::New(0));
-  if (top_value->IsObject()) {
-    Local<Object> top = top_value->ToObject();
-    Local<Value> callback_value = top->Get(String::NewSymbol("callback"));
-    if (callback_value->IsFunction()) {
-      Handle<Function> callback = Handle<Function>::Cast(callback_value);
-
-      TryCatch try_catch;
-      callback->Call(handle, argc, argv);
-      if(try_catch.HasCaught()) {
-        node::fatal_exception(try_catch);
-        return;
-      }
-    }
-  }
-
   // poll_actions
-  Local<Value> poll_actions_value = handle->Get(String::NewSymbol("_pollActions"));
+  Local<Value> poll_actions_value = handle->Get(POLL_ACTIONS_SYMBOL);
   assert(poll_actions_value->IsFunction());  
   Handle<Function> poll_actions = Handle<Function>::Cast(poll_actions_value);
 
-  poll_actions->Call(handle, 0, NULL);
+  TryCatch try_catch;
+  poll_actions->Call(handle, argc, argv);
+  if(try_catch.HasCaught())
+    node::fatal_exception(try_catch);
 }
 
 Handle<Value>
@@ -134,6 +102,13 @@ FileSystem::Rename (const Arguments& args)
   String::Utf8Value path(args[0]->ToString());
   String::Utf8Value new_path(args[1]->ToString());
 
+  Persistent<Function> *callback = NULL;
+  if (args[2]->IsFunction()) {
+    Local<Function> l = Local<Function>::Cast(args[2]);
+    callback = new Persistent<Function>();
+    *callback = Persistent<Function>::New(l);
+  }
+
   node::eio_warmup();
   eio_rename(*path, *new_path, EIO_PRI_DEFAULT, AfterRename, NULL);
 
@@ -147,7 +122,18 @@ FileSystem::AfterRename (eio_req *req)
   const int argc = 1;
   Local<Value> argv[argc];
   argv[0] = Integer::New(req->errorno);
-  CallTopCallback(fs, argc, argv);
+
+  if (req->data) {
+    Persistent<Function> *callback = reinterpret_cast<Persistent<Function>*>(req->data);
+
+    TryCatch try_catch;
+    (*callback)->Call(Context::GetCurrent()->Global(), argc, argv);
+    if(try_catch.HasCaught())
+      node::fatal_exception(try_catch);
+
+    free(callback);
+  }
+
   return 0;
 }
 
@@ -161,8 +147,15 @@ FileSystem::Stat (const Arguments& args)
 
   String::Utf8Value path(args[0]->ToString());
 
+  Persistent<Function> *callback = NULL;
+  if (args[1]->IsFunction()) {
+    Local<Function> l = Local<Function>::Cast(args[1]);
+    callback = new Persistent<Function>();
+    *callback = Persistent<Function>::New(l);
+  }
+
   node::eio_warmup();
-  eio_stat(*path, EIO_PRI_DEFAULT, AfterStat, NULL);
+  eio_stat(*path, EIO_PRI_DEFAULT, AfterStat, callback);
 
   return Undefined();
 }
@@ -180,7 +173,7 @@ FileSystem::AfterStat (eio_req *req)
   argv[1] = stats;
 
   if (req->result == 0) {
-    struct stat *s = static_cast<struct stat*>(req->ptr2);
+    struct stat *s = reinterpret_cast<struct stat*>(req->ptr2);
 
     /* ID of device containing file */
     stats->Set(NODE_SYMBOL("dev"), Integer::New(s->st_dev));
@@ -210,8 +203,17 @@ FileSystem::AfterStat (eio_req *req)
     stats->Set(NODE_SYMBOL("ctime"), Date::New(1000*static_cast<double>(s->st_ctime)));
   }
 
-  CallTopCallback(fs, argc, argv);
-    
+  if (req->data) {
+    Persistent<Function> *callback = reinterpret_cast<Persistent<Function>*>(req->data);
+
+    TryCatch try_catch;
+    (*callback)->Call(Context::GetCurrent()->Global(), argc, argv);
+    if(try_catch.HasCaught())
+      node::fatal_exception(try_catch);
+
+    free(callback);
+  }
+
   return 0;
 }
 
@@ -237,7 +239,7 @@ File::File (Handle<Object> handle)
 {
   HandleScope scope;
   encoding_ = RAW;
-  InitActionQueue(handle);
+  handle->Set(ACTION_QUEUE_SYMBOL, Array::New());
 }
 
 File::~File ()
@@ -277,7 +279,7 @@ File::Close (const Arguments& args)
 int
 File::AfterClose (eio_req *req)
 {
-  File *file = static_cast<File*>(req->data);
+  File *file = reinterpret_cast<File*>(req->data);
 
   if (req->result == 0) {
     file->handle_->Delete(FD_SYMBOL);
@@ -338,7 +340,7 @@ File::Open (const Arguments& args)
 int
 File::AfterOpen (eio_req *req)
 {
-  File *file = static_cast<File*>(req->data);
+  File *file = reinterpret_cast<File*>(req->data);
   HandleScope scope;
 
   if(req->result >= 0) {
@@ -364,6 +366,8 @@ File::Write (const Arguments& args)
 
   File *file = NODE_UNWRAP(File, args.Holder());
 
+  off_t pos = args[1]->IntegerValue();
+
   char *buf = NULL; 
   size_t length = 0;
 
@@ -371,14 +375,14 @@ File::Write (const Arguments& args)
     // utf8 encoding
     Local<String> string = args[0]->ToString();
     length = string->Utf8Length();
-    buf = static_cast<char*>(malloc(length));
+    buf = reinterpret_cast<char*>(malloc(length));
     string->WriteUtf8(buf, length);
     
   } else if (args[0]->IsArray()) {
     // raw encoding
     Local<Array> array = Local<Array>::Cast(args[0]);
     length = array->Length();
-    buf = static_cast<char*>(malloc(length));
+    buf = reinterpret_cast<char*>(malloc(length));
     for (unsigned int i = 0; i < length; i++) {
       Local<Value> int_value = array->Get(Integer::New(i));
       buf[i] = int_value->Int32Value();
@@ -389,8 +393,6 @@ File::Write (const Arguments& args)
     return Undefined();
   }
 
-  off_t pos = args[1]->IntegerValue();
-
   if (file->handle_->Has(FD_SYMBOL) == false) {
     printf("trying to write to a bad fd!\n");  
     return Undefined();
@@ -408,9 +410,9 @@ File::Write (const Arguments& args)
 int
 File::AfterWrite (eio_req *req)
 {
-  File *file = static_cast<File*>(req->data);
+  File *file = reinterpret_cast<File*>(req->data);
 
-  //char *buf = static_cast<char*>(req->ptr2);
+  //char *buf = reinterpret_cast<char*>(req->ptr2);
   free(req->ptr2);
   ssize_t written = req->result;
 
@@ -429,20 +431,22 @@ File::AfterWrite (eio_req *req)
 Handle<Value>
 File::Read (const Arguments& args)
 {
-  if (args.Length() < 1) return Undefined();
-  if (!args[0]->IsNumber()) return Undefined();
-  if (!args[1]->IsNumber()) return Undefined();
-
   HandleScope scope;
+
+  if (args.Length() < 1 || !args[0]->IsNumber() || !args[1]->IsNumber()) {
+    return ThrowException(String::New("Bad parameter for _ffi_read()"));
+  }
+
   File *file = NODE_UNWRAP(File, args.Holder());
-  size_t length = args[0]->IntegerValue();
-  off_t pos = args[1]->IntegerValue();
+  size_t len = args[0]->IntegerValue();
+  off_t pos  = args[1]->IntegerValue();
 
   int fd = file->GetFD();
+  assert(fd >= 0);
 
-  // NOTE: NULL pointer tells eio to allocate it itself
+  // NOTE: 2nd param: NULL pointer tells eio to allocate it itself
   node::eio_warmup();
-  eio_read(fd, NULL, length, pos, EIO_PRI_DEFAULT, File::AfterRead, file);
+  eio_read(fd, NULL, len, pos, EIO_PRI_DEFAULT, File::AfterRead, file);
 
   file->Attach();
   return Undefined();
@@ -451,32 +455,33 @@ File::Read (const Arguments& args)
 int
 File::AfterRead (eio_req *req)
 {
-  File *file = static_cast<File*>(req->data);
+  File *file = reinterpret_cast<File*>(req->data);
   HandleScope scope;
 
   const int argc = 2;
   Local<Value> argv[argc];
   argv[0] = Integer::New(req->errorno);
 
-  char *buf = static_cast<char*>(req->ptr2);
+  char *buf = reinterpret_cast<char*>(req->ptr2);
 
-  if(req->result == 0) { 
+  if (req->result == 0) { 
     // eof 
     argv[1] = Local<Value>::New(Null());
   } else {
-    size_t length = req->result;
+    size_t len = req->result;
     if (file->encoding_ == UTF8) {
       // utf8 encoding
       argv[1] = String::New(buf, req->result);
     } else {
       // raw encoding
-      Local<Array> array = Array::New(length);
-      for (unsigned int i = 0; i < length; i++) {
+      Local<Array> array = Array::New(len);
+      for (unsigned int i = 0; i < len; i++) {
         array->Set(Integer::New(i), Integer::New(buf[i]));
       }
       argv[1] = array;
     }
   }
+
   CallTopCallback(file->handle_, argc, argv);
 
   file->Detach();
index e645863..a4caafc 100644 (file)
@@ -1,13 +1,5 @@
-node.fs.rename = function (file1, file2, callback) {
-  this._addAction("rename", [file1, file2], callback);
-};
-
-node.fs.stat = function (path, callback) {
-  this._addAction("stat", [path], callback);
-};
-
 node.fs.exists = function (path, callback) {
-  this._addAction("stat", [path], function (status) {
+  node.fs.stat(path, function (status) {
     callback(status == 0);
   });
 }
@@ -16,16 +8,17 @@ node.fs.cat = function (path, encoding, callback) {
   var file = new node.fs.File();
   file.encoding = encoding;
 
-  var content = "";
-  if (file.encoding == "raw") content = [];
+  file.onError = function (method, errno, msg) {
+    callback(-1);
+  };
 
+  var content = (file.encoding == "raw" ? "" : []);
   var pos = 0;
   var chunkSize = 10*1024;
 
   function readChunk () {
-    file.read(chunkSize, pos, function (status, chunk) {
+    file.read(chunkSize, pos, function (chunk) {
       if (chunk) {
-
         if (chunk.constructor == String)
           content += chunk;
         else
@@ -40,22 +33,11 @@ node.fs.cat = function (path, encoding, callback) {
     });
   }
 
-  file.open(path, "r", function (status) {
-    if (status == 0) 
-      readChunk();
-    else
-      callback (status);
+  file.open(path, "r", function () {
+    readChunk();
   });
 }
 
-node.fs.File.prototype.puts = function (data, callback) {
-  this.write(data + "\n", -1, callback);
-};
-
-node.fs.File.prototype.print = function (data, callback) {
-  this.write(data, -1, callback);
-};
-
 node.fs.File.prototype.open = function (path, mode, callback) {
   this._addAction("open", [path, mode], callback);
 };
@@ -64,12 +46,20 @@ node.fs.File.prototype.close = function (callback) {
   this._addAction("close", [], callback);
 };
 
+node.fs.File.prototype.read = function (length, pos, callback) {
+  this._addAction("read", [length, pos], callback);
+};
+
 node.fs.File.prototype.write = function (buf, pos, callback) {
   this._addAction("write", [buf, pos], callback);
 };
 
-node.fs.File.prototype.read = function (length, pos, callback) {
-  this._addAction("read", [length, pos], callback);
+node.fs.File.prototype.print = function (data, callback) {
+  this.write(data, -1, callback);
+};
+
+node.fs.File.prototype.puts = function (data, callback) {
+  this.write(data + "\n", -1, callback);
 };
 
 // Some explanation of the File binding.
@@ -84,38 +74,65 @@ node.fs.File.prototype.read = function (length, pos, callback) {
 // the member _actionQueue = []
 //
 // Any of the methods called on a file are put into this queue. When they
-// reach the head of the queue they will be executed. C++ calles the
+// reach the head of the queue they will be executed. C++ calls the
 // method _pollActions each time it becomes idle. If there is no action
-// currently being executed then _pollActions will not be called. Thus when
-// actions are added to an empty _actionQueue, they should be immediately
+// currently being executed then _pollActions will not be called. When
+// actions are added to an empty _actionQueue, they will be immediately
 // executed.
 //
-// When an action has completed, the C++ side is going to look at the first
+// When an action has completed, the C++ side is looks at the first
 // element of _actionQueue in order to get a handle on the callback
 // function. Only after that completion callback has been made can the
 // action be shifted out of the queue.
 // 
-// See File::CallTopCallback() in file.cc to see the other side of the
-// binding.
-node.fs._addAction = node.fs.File.prototype._addAction = function (method, args, callback) {
-  this._actionQueue.push({ method: method 
-                         , callback: callback
-                         , args: args
-                         });
+// See File::CallTopCallback() in file.cc for the other side of the binding.
+
+
+node.fs.File.prototype._addAction = function (method, args, callback) {
+  // This adds a method to the queue. 
+  var action = { method: method 
+               , callback: callback
+               , args: args
+               };
+  this._actionQueue.push(action);
+
+  // If the queue was empty, immediately call the method.
   if (this._actionQueue.length == 1) this._act();
-}
+};
 
-node.fs._act = node.fs.File.prototype._act = function () {
+node.fs.File.prototype._act = function () {
+  // peek at the head of the queue
   var action = this._actionQueue[0];
-  if (action)
-    // TODO FIXME what if the action throws an error?
+  if (action) {
+    // execute the c++ version of the method. the c++ version 
+    // is gotten by appending "_ffi_" to the method name.
     this["_ffi_" + action.method].apply(this, action.args);
+  }
 };
 
 // called from C++ after each action finishes
 // (i.e. when it returns from the thread pool)
-node.fs._pollActions = node.fs.File.prototype._pollActions = function () {
+node.fs.File.prototype._pollActions = function () {
+  var action = this._actionQueue[0];
+
+  var errno = arguments[0]; 
+
+  if (errno < 0) {
+    if (this.onError)
+      this.onError(action.method, errno, node.fs.strerror(errno));
+    this._actionQueue = []; // empty the queue.
+    return;
+  }
+
+  var rest = [];
+  for (var i = 1; i < arguments.length; i++)
+    rest.push(arguments[i]);
+
+  if (action.callback)
+    action.callback.apply(this, rest);
+
   this._actionQueue.shift();
+
   this._act();
 };
 
index 2e3f7dd..98da2f7 100644 (file)
@@ -7,8 +7,11 @@ function onLoad () {
   var x = node.path.join(fixtures, "x.txt");
 
   file = new node.fs.File;
-  file.open(x, "r", function (status) {
-    assertTrue(status == 0);
+  file.onError = function (method, errno, msg) {
+    assertTrue(false); 
+  };
+
+  file.open(x, "r", function () {
     assert_count += 1;
     file.close();
   });
index 4626146..213d8d1 100644 (file)
@@ -80,11 +80,11 @@ a:hover { text-decoration: underline; }
   <ol>
     <li><a href="#benchmarks">Benchmarks</a></li>
     <li><a href="#download">Download</a></li>
-    <li><a href="#install">Build</a></li>
+    <li><a href="#build">Build</a></li>
     <li><a href="#api">API</a>
       <ol>
         <li><a href="#timers">Timers</a>
-        <li><a href="#files">File System I/O</a>
+        <li><a href="#files">File I/O</a>
         <li><a href="#tcp">TCP</a>
           <ol>
           <li><a href="#tcp_server">Server</a>
@@ -166,12 +166,15 @@ always have a capital first letter.
 
 <dl>
   <dt><code class="sh_javascript">puts(string, callback)</code></dt>
-  <dd>Outputs the <code>string</code> and a trailing new-line to <code>stdout</code>.
+  <dd>
+      Alias for <code class="sh_javascript">stdout.puts()</code>.
+
+      Outputs the <code>string</code> and a trailing new-line to <code>stdout</code>.
   <p>The <code>callback</code> argument is optional and mostly useless: it will
     notify the user when the operation has completed. Everything in node is
     asynchronous; <code>puts()</code> is no exception. This might seem ridiculous
-    but, if for example, one is piping their output into an NFS'd file,
-    <code>printf()</code> will block.
+    but, if for example, one is piping <code>stdout</code> into an NFS file,
+    <code>printf()</code> will block from network latency.
     There is an internal queue for <code>puts()</code> output, so you can be assured that
     output will be displayed in the order it was called. 
   </dd>
@@ -204,45 +207,80 @@ always have a capital first letter.
   <dd> Stops a interval from triggering.  </dd>
 </dl>
 
-<h3 id="files">node.File</h3>
+<h3 id="files">node.fs</h3>
 
-<p> File system I/O has always been tricky because there are not any portable
-non-blocking ways to do it. To get around this, Node uses <a
+<p>Because there are not non-blocking ways to do it, asynchronous file I/O is
+tricky.  Node handles file I/O by employing <a
 href="http://software.schmorp.de/pkg/libeio.html">an internal thread pool</a>
-to execute file system calls asynchronously.  
-
-
-<p>All file I/O calls are rather thin wrappers around standard POSIX functions.
-All calls have an optional last callback parameter 
+to execute file system calls.  
 
+<p>Internal request queues exist for each file object so that multiple commands
+can be issued at once without worry that they will be executed out-of-order.
+Thus the following is safe:
 
-
-
-<p>Internal request queues exist for each file object so multiple commands can
-be issued at once without worry that they will reach the file system out of
-order.  Thus the following is safe:
 <pre class="sh_javascript">
-var file = new node.File();
+var file = new node.fs.File();
 file.open("/tmp/blah", "w+");
-file.write("hello ");
+file.write("hello");
 file.write("world");
 file.close();</pre>
-Additionally there is a process-wide queue for all commands which operate on
-the file system directory structure (like <code>rename</code> and
-<code>unlink</code>). It's important to understand that all of these request queues are
-distinct. If, for example, you do
+
+<p>
+It's important to understand that the request queues are local to a single file.
+If one does
 <pre class="sh_javascript">fileA.write("hello");
 fileB.write("world");</pre>
-it could be that
-first <code>fileB</code> gets written to and then <code>fileA</code> gets written to. 
-So if a certain operation order is needed involving multiple files, use the
+it could be that <code>fileB</code> gets written to before <code>fileA</code>
+is written to. 
+If a certain operation order is needed involving multiple files, use the
 completion callbacks:
 <pre class="sh_javascript">fileA.write("hello", function () {
   fileB.write("world");
 });</pre>
 
 <dl>
-  <dt><code class="sh_javascript">node.File.rename()</code></dt>
+  <dt><code class="sh_javascript">new node.fs.File</code></dt>
+  <dd>Creates a new file object. </dd>
+
+  <dt><code class="sh_javascript">file.open(path, mode, on_completion)</code></dt>
+  <dd>Opens the file at <code>path</code>. 
+  <p><code>mode</code> is a string: 
+    <code>"r"</code>  open for reading and writing.
+    <code>"r+"</code> open for only reading.
+    <code>"w"</code> create a new file for reading and writing; if it
+    already exists truncate it. 
+    <code>"w+"</code> create a new file for writing only; if it already
+    exists truncate it. 
+    <code>"a"</code> create a new file for writing and reading. Writes
+    append to the end of the file. 
+    <code>"a+"</code> 
+  <p>The <code>on_completion</code> is a callback that is made without
+  arguments when the operation completes. It is optional
+  If an error occurred the <code>on_completion</code> callback will not be
+  called, but the <code>file.onError</code> will be called.
+  </dd>
+
+  <dt><code class="sh_javascript">file.read(length, position, on_completion)</code></dt>
+  <dd>
+  </dd>
+
+  <dt><code class="sh_javascript">file.write(data, position, on_completion)</code></dt>
+  <dd>
+  </dd>
+
+  <dt><code class="sh_javascript">file.close(on_completion)</code></dt>
+  <dd>
+  </dd>
+</dl>
+
+<h4>File System Operations</h4>
+
+<dl>
+  <dt><code class="sh_javascript">node.fs.rename(path1, path2, on_completion)</code></dt>
+  <dd> 
+  </dd>
+
+  <dt><code class="sh_javascript">node.fs.stat(path1, on_completion)</code></dt>
   <dd> 
   </dd>
 </dl>
@@ -250,6 +288,10 @@ completion callbacks:
 
 <h3 id="tcp"><code>node.tcp</code></h3>
 
+<h4 id="tcp_server"><code>node.tcp.Server</code></h4>
+
+<h4 id="tcp_connection"><code>node.tcp.Connection</code></h4>
+
 <h3 id="http"><code>node.http</code></h3>
 
 <p> Node provides a web server and client interface. The interface is rather
@@ -410,10 +452,11 @@ res.sendHeader(200, [ ["Content-Length", body.length]
 
 <h4 id="http_client"><code class="sh_javascript">node.http.Client</code></h4>
 
-<p> An HTTP client is constructed with a server address as its argument, then
-the user issues one or more requests.  Depending on the server connected to,
-the client might pipeline the requests or reestablish the connection after each
-connection. <i>Currently the client does not pipeline requests.</i>
+<p> An HTTP client is constructed with a server address as its argument, the
+returned handle is then used to issue one or more requests.  Depending on the
+server connected to, the client might pipeline the requests or reestablish the
+connection after each connection. 
+<i>Currently the implementation does not pipeline requests.</i>
 
 <p> Example of connecting to <code>google.com</code>
 <pre class="sh_javascript">
@@ -441,7 +484,9 @@ request is issued.
   <dt><code class="sh_javascript">client.post(path, request_headers);</code></dt>
   <dt><code class="sh_javascript">client.del(path, request_headers);</code></dt>
   <dt><code class="sh_javascript">client.put(path, request_headers);</code></dt>
-  <dd> Issues a request. 
+  <dd> Issues a request; if necessary establishes connection. 
+
+  <p>
     <code>request_headers</code> is optional. 
     <code>request_headers</code> should be an array of 2-element arrays. 
     Additional request headers might be added internally by Node.
@@ -450,15 +495,18 @@ request is issued.
     <p>Important: the request is not complete. This method only sends the
 header of the request. One needs to call <code>req.finish()</code> to finalize
 the request and retrieve the response.  (This sounds convoluted but it provides
-a chance for the user to stream a body to the server with
-<code>req.sendBody</code>.  <code>GET</code> and <code>HEAD</code> requests
-normally are without bodies but HTTP does not forbid it, so neither do we.)
+a chance for the user to stream a body to the server with <code
+class="sh_javascript">req.sendBody()</code>.) 
+
+    <p><i> <code>GET</code> and
+<code>HEAD</code> requests normally are without bodies but HTTP does not forbid
+it, so neither do we.</i>
 
 </dl>
 
 <h4 id="http_client_request"><code class="sh_javascript">node.http.ClientRequest</code></h4>
 
-<p>This object created internally and returned from the request methods of a
+<p>This object is created internally and returned from the request methods of a
 <code>node.http.Client</code>. It represents an <i>in-progress</i> request
 whose header has already been sent. 
 
@@ -500,7 +548,7 @@ read.
 
 <dl>
   <dt><code class="sh_javascript">res.statusCode</code></dt>
-  <dd>The 3-digit HTTP response status code. (E.G. <code class="sh_javascript">404</code>.)</dd>
+  <dd>The 3-digit HTTP response status code. E.G. <code class="sh_javascript">404</code>.</dd>
 
   <dt><code class="sh_javascript">res.httpVersion</code></dt>
   <dd>The HTTP version of the connected-to server. Probably either 
@@ -513,7 +561,7 @@ read.
 
   <dt><code class="sh_javascript">res.onBody</code></dt>
   <dd>Callback. Should be set by the user to be informed of when a piece
-  of the message body is received.
+  of the response body is received.
   A chunk of the body is given as the single argument. The transfer-encoding
   has been removed.