Binary HTTP bodies for both requests and responses.
authorRyan <ry@tinyclouds.org>
Thu, 7 May 2009 10:15:01 +0000 (12:15 +0200)
committerRyan <ry@tinyclouds.org>
Thu, 7 May 2009 10:15:01 +0000 (12:15 +0200)
src/http.cc
src/http.js

index bef2638..8aeb1fa 100644 (file)
@@ -4,19 +4,22 @@
 
 #include <assert.h>
 #include <stdio.h>
+#include <strings.h>
 
-#define ON_MESSAGE_SYMBOL String::NewSymbol("onMessage")
-#define MESSAGE_HANDLER_SYMBOL String::NewSymbol("messageHandler")
-#define ON_HEADERS_COMPLETE_SYMBOL String::NewSymbol("onHeadersComplete")
-#define ON_BODY_SYMBOL String::NewSymbol("onBody")
-#define ON_MESSAGE_COMPLETE_SYMBOL String::NewSymbol("onMessageComplete")
+#define ENCODING_SYMBOL             String::NewSymbol("encoding")
 
-#define ON_PATH_SYMBOL String::NewSymbol("onPath")
-#define ON_QUERY_STRING_SYMBOL String::NewSymbol("onQueryString")
-#define ON_URI_SYMBOL String::NewSymbol("onURI")
-#define ON_FRAGMENT_SYMBOL String::NewSymbol("onFragment")
-#define ON_HEADER_FIELD_SYMBOL String::NewSymbol("onHeaderField")
-#define ON_HEADER_VALUE_SYMBOL String::NewSymbol("onHeaderValue")
+#define MESSAGE_HANDLER_SYMBOL      String::NewSymbol("messageHandler")
+
+#define ON_MESSAGE_SYMBOL           String::NewSymbol("onMessage")
+#define ON_PATH_SYMBOL              String::NewSymbol("onPath")
+#define ON_QUERY_STRING_SYMBOL      String::NewSymbol("onQueryString")
+#define ON_URI_SYMBOL               String::NewSymbol("onURI")
+#define ON_FRAGMENT_SYMBOL          String::NewSymbol("onFragment")
+#define ON_HEADER_FIELD_SYMBOL      String::NewSymbol("onHeaderField")
+#define ON_HEADER_VALUE_SYMBOL      String::NewSymbol("onHeaderValue")
+#define ON_HEADERS_COMPLETE_SYMBOL  String::NewSymbol("onHeadersComplete")
+#define ON_BODY_SYMBOL              String::NewSymbol("onBody")
+#define ON_MESSAGE_COMPLETE_SYMBOL  String::NewSymbol("onMessageComplete")
 
 #define STATUS_CODE_SYMBOL String::NewSymbol("status_code")
 #define HTTP_VERSION_SYMBOL String::NewSymbol("http_version")
@@ -180,7 +183,7 @@ HTTPConnection::on_headers_complete (http_parser *parser)
 int
 HTTPConnection::on_body (http_parser *parser, const char *buf, size_t len)
 {
-  if(len == 0) return 0;
+  assert(len != 0);
 
   HTTPConnection *connection = static_cast<HTTPConnection*> (parser->data);
   HandleScope scope;
@@ -193,11 +196,24 @@ HTTPConnection::on_body (http_parser *parser, const char *buf, size_t len)
   if (on_body_v->IsFunction() == false) return 0;
   Handle<Function> on_body = Handle<Function>::Cast(on_body_v);
 
-  Handle<Value> argv[1];
+  /* Look at the value of message_handler.encoding to decide how to
+   * send the body chunk. This is rather sloppy and unnecesary. FIXME
+   */
+  enum encoding encoding = RAW;
+  Local<Value> encoding_v = message_handler->Get(ENCODING_SYMBOL);
+  if (encoding_v->IsString()) {
+    Local<String> encoding_string = encoding_v->ToString();
+    char buf[5]; // need enough room for "utf8" or "raw"
+    encoding_string->WriteAscii(buf, 0, 4);
+    buf[4] = '\0';
+    if(strcasecmp(buf, "utf8") == 0)
+      encoding = UTF8;
+  }
 
+  Handle<Value> argv[1];
   // TODO each message should have their encoding. 
   // don't look at the conneciton for encoding
-  if(connection->encoding_ == UTF8) {
+  if(encoding == UTF8) {
     // utf8 encoding
     Handle<String> chunk = String::New((const char*)buf, len);
     argv[0] = chunk;
index ebf74ca..e335006 100644 (file)
@@ -20,76 +20,109 @@ node.http.Server = function (RequestHandler, options) {
        * are writing to responses out of order! HTTP requires that responses
        * are returned in the same order the requests come.
        */
-      this.output = "";
 
+      var output = [];
+
+      function toRaw(string) {
+        var a = [];
+        for (var i = 0; i < string.length; i++)
+          a.push(string.charCodeAt(i));
+        return a;
+      }
+
+      // The send method appends data onto the output array. The deal is,
+      // the data is either an array of integer, representing binary or it
+      // is a string in which case it's UTF8 encoded. 
+      // Two things to considered:
+      // - we should be able to send mixed encodings.
+      // - we don't want to call connection.send("smallstring") because that
+      //   is wasteful. *I think* its rather faster to concat inside of JS
+      // Thus I attempt to concat as much as possible.  
       function send (data) {
-        if (responses[0] === this) {
-          connection.send(data);
-        } else {
-          this.output += data;
+        if (output.length == 0) {
+          output.push(data);
+          return;
         }
+
+        var li = output.length-1;
+
+        if (data.constructor == String && output[li].constructor == String) {
+          output[li] += data;
+          return;
+        }
+
+        if (data.constructor == Array && output[li].constructor == Array) {
+          output[li] = output[li].concat(data);
+          return;
+        }
+
+        // If the string is small enough, just convert it to binary
+        if (data.constructor == String 
+            && data.length < 128
+            && output[li].constructor == Array) 
+        {
+          output[li] = output[li].concat(toRaw(data));
+          return;
+        }
+
+        output.push(data);
+      };
+
+      this.flush = function () {
+        if (responses.length > 0 && responses[0] === this)
+          while (output.length > 0)
+            connection.send(output.shift());
       };
 
       this.sendStatus = function (status, reason) {
         // XXX http/1.0 until i get the keep-alive logic below working.
-        this.output += "HTTP/1.0 " + status.toString() + " " + reason + "\r\n";
+        send("HTTP/1.0 " + status.toString() + " " + reason + "\r\n");
       };
 
       var chunked_encoding = false;
       var connection_close = false;
 
       this.sendHeader = function (field, value) {
-        this.output += field + ": " + value.toString() + "\r\n";
+        send(field + ": " + value.toString() + "\r\n");
+
         if (/Connection/i.exec(field) && /close/i.exec(value))
           connection_close = true;
+
         else if (/Transfer-Encoding/i.exec(field) && /chunk/i.exec(value))
           chunked_encoding = true;           
-
       };
 
       var bodyBegan = false;
 
-      function toRaw(string) {
-        var a = [];
-        for (var i = 0; i < string.length; i++)
-          a.push(string.charCodeAt(i));
-        return i;
-      }
-
-      function chunkEncode (chunk) {
-        var hexlen = chunk.length.toString(16);
-        if (chunk.constructor === String)
-          return hexlen + "\r\n" + chunk + "\r\n";
-        // er..
-      }
-
       this.sendBody = function (chunk) {
         if (bodyBegan === false) {
-          this.output += "\r\n";
+          send("\r\n");
           bodyBegan = true;
         }
 
-        if (chunked_encoding)
-          this.output += chunkEncode(chunk)
-        else
-          this.output += chunk;
-
-        if (responses[0] === this) {
-          connection.send(this.output);
-          this.output = "";
+        if (chunked_encoding) {
+          send(chunk.length.toString(16));
+          send("\r\n");
+          send(chunk);
+          send("\r\n");
+        } else {
+          send(chunk);
         }
+
+        this.flush();
       };
 
       var finished = false;
       this.finish = function () {
         if (chunked_encoding)
-          this.output += "0\r\n\r\n"; // last chunk
+          send("0\r\n\r\n"); // last chunk
 
         this.finished = true;
 
         while (responses.length > 0 && responses[0].finished) {
-          var res = responses.shift();
-          connection.send(res.output);
+          var res = responses[0];
+          res.flush();
+          responses.shift();
         }
 
         if (responses.length == 0) {
@@ -104,7 +137,7 @@ node.http.Server = function (RequestHandler, options) {
 
     this.onMessage = function ( ) {
       var res = new Response();
-      var req = new RequestHandler(res);
+      var req = new RequestHandler(res, connection);
 
       this.encoding = req.encoding || "raw";