http: parse the status message in a http response.
authorCam Swords <cam.swords@gmail.com>
Sun, 22 Sep 2013 14:06:58 +0000 (00:06 +1000)
committerFedor Indutny <fedor.indutny@gmail.com>
Fri, 20 Dec 2013 13:55:08 +0000 (17:55 +0400)
doc/api/http.markdown
lib/_http_common.js
lib/_http_incoming.js
src/env.h
src/node_http_parser.cc
test/simple/test-http-parser.js
test/simple/test-http-response-status-message.js [new file with mode: 0644]

index 182711a..d06e78f 100644 (file)
@@ -1011,6 +1011,12 @@ you can use the `require('querystring').parse` function, or pass
 
 The 3-digit HTTP response status code. E.G. `404`.
 
+### message.statusMessage
+
+**Only valid for response obtained from `http.ClientRequest`.**
+
+The HTTP response status message (reason phrase). E.G. `OK` or `Internal Server Error`.
+
 ### message.socket
 
 The `net.Socket` object associated with the connection.
index 7d89b86..b599547 100644 (file)
@@ -97,7 +97,7 @@ function parserOnHeadersComplete(info) {
   } else {
     // client only
     parser.incoming.statusCode = info.statusCode;
-    // CHECKME dead code? we're always a request parser
+    parser.incoming.statusMessage = info.statusMessage;
   }
 
   parser.incoming.upgrade = info.upgrade;
index 2f66400..650b054 100644 (file)
@@ -64,6 +64,7 @@ function IncomingMessage(socket) {
 
   // response (client) only
   this.statusCode = null;
+  this.statusMessage = null;
   this.client = this.socket;
 
   // flag for backwards compatibility grossness.
index 5cf414b..6db4a08 100644 (file)
--- a/src/env.h
+++ b/src/env.h
@@ -119,6 +119,7 @@ namespace node {
   V(smalloc_p_string, "_smalloc_p")                                           \
   V(sni_context_string, "sni_context")                                        \
   V(status_code_string, "statusCode")                                         \
+  V(status_message_string, "statusMessage")                                   \
   V(subject_string, "subject")                                                \
   V(subjectaltname_string, "subjectaltname")                                  \
   V(syscall_string, "syscall")                                                \
index 37840d8..738358b 100644 (file)
@@ -181,6 +181,7 @@ class Parser : public BaseObject {
   HTTP_CB(on_message_begin) {
     num_fields_ = num_values_ = 0;
     url_.Reset();
+    status_message_.Reset();
     return 0;
   }
 
@@ -191,6 +192,12 @@ class Parser : public BaseObject {
   }
 
 
+  HTTP_DATA_CB(on_status) {
+    status_message_.Update(at, length);
+    return 0;
+  }
+
+
   HTTP_DATA_CB(on_header_field) {
     if (num_fields_ == num_values_) {
       // start of new field name
@@ -259,6 +266,8 @@ class Parser : public BaseObject {
     if (parser_.type == HTTP_RESPONSE) {
       message_info->Set(env()->status_code_string(),
                         Integer::New(parser_.status_code, node_isolate));
+      message_info->Set(env()->status_message_string(),
+                        status_message_.ToString());
     }
 
     // VERSION
@@ -349,6 +358,7 @@ class Parser : public BaseObject {
 
   void Save() {
     url_.Save();
+    status_message_.Save();
 
     for (int i = 0; i < num_fields_; i++) {
       fields_[i].Save();
@@ -515,6 +525,7 @@ class Parser : public BaseObject {
   void Init(enum http_parser_type type) {
     http_parser_init(&parser_, type);
     url_.Reset();
+    status_message_.Reset();
     num_fields_ = 0;
     num_values_ = 0;
     have_flushed_ = false;
@@ -526,6 +537,7 @@ class Parser : public BaseObject {
   StringPtr fields_[32];  // header fields
   StringPtr values_[32];  // header values
   StringPtr url_;
+  StringPtr status_message_;
   int num_fields_;
   int num_values_;
   bool have_flushed_;
@@ -540,6 +552,7 @@ class Parser : public BaseObject {
 const struct http_parser_settings Parser::settings = {
   Parser::on_message_begin,
   Parser::on_url,
+  Parser::on_status,
   Parser::on_header_field,
   Parser::on_header_value,
   Parser::on_headers_complete,
index 7536f77..a7fba0f 100644 (file)
@@ -143,6 +143,7 @@ function expectBody(expected) {
     assert.equal(info.versionMajor, 1);
     assert.equal(info.versionMinor, 1);
     assert.equal(info.statusCode, 200);
+    assert.equal(info.statusMessage, "OK");
   });
 
   parser[kOnBody] = mustCall(function(buf, start, len) {
@@ -169,6 +170,7 @@ function expectBody(expected) {
     assert.equal(info.versionMajor, 1);
     assert.equal(info.versionMinor, 0);
     assert.equal(info.statusCode, 200);
+    assert.equal(info.statusMessage, "Connection established");
     assert.deepEqual(info.headers || parser.headers, []);
   });
 
diff --git a/test/simple/test-http-response-status-message.js b/test/simple/test-http-response-status-message.js
new file mode 100644 (file)
index 0000000..3b39110
--- /dev/null
@@ -0,0 +1,78 @@
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+var common = require('../common');
+var assert = require('assert');
+var http = require('http');
+var net = require('net');
+
+var testsComplete = 0;
+
+var testCases = [
+  { path: "/200", statusMessage: "OK", response: 'HTTP/1.1 200 OK\r\n\r\n' },
+  { path: "/500", statusMessage: "Internal Server Error", response: 'HTTP/1.1 500 Internal Server Error\r\n\r\n' },
+  { path: "/302", statusMessage: "Moved Temporarily", response: 'HTTP/1.1 302 Moved Temporarily\r\n\r\n' },
+  { path: "/missing", statusMessage: "", response: 'HTTP/1.1 200 \r\n\r\n' },
+  { path: "/missing-no-space", statusMessage: "", response: 'HTTP/1.1 200\r\n\r\n' }
+];
+testCases.findByPath = function(path) {
+  var matching = this.filter(function(testCase) { return testCase.path === path; });
+  if (matching.length === 0) { throw "failed to find test case with path " + path; }
+  return matching[0];
+};
+
+var server = net.createServer(function(connection) {
+  connection.on('data', function(data) {
+    var path = data.toString().match(/GET (.*) HTTP.1.1/)[1];
+    var testCase = testCases.findByPath(path);
+
+    connection.write(testCase.response);
+    connection.end();
+  });
+});
+
+var runTest = function(testCaseIndex) {
+  var testCase = testCases[testCaseIndex];
+
+  http.get({ port: common.PORT, path: testCase.path }, function(response) {
+    console.log('client: expected status message: ' + testCase.statusMessage);
+    console.log('client: actual status message: ' + response.statusMessage);
+    assert.equal(testCase.statusMessage, response.statusMessage);
+
+    response.on('end', function() {
+      testsComplete++;
+
+      if (testCaseIndex + 1 < testCases.length) {
+        runTest(testCaseIndex + 1);
+      } else {
+        server.close();
+      }
+    });
+
+    response.resume();
+  });
+};
+
+server.listen(common.PORT, function() { runTest(0); });
+
+process.on('exit', function() {
+  assert.equal(testCases.length, testsComplete);
+});