Browse Source

Upgrade http parser, change node as needed.

The latest version of http-parser is a bit more stringent EOF semantics.
v0.7.4-release
Ryan Dahl 15 years ago
parent
commit
b893859c34
  1. 8
      deps/http_parser/LICENSE
  2. 27
      deps/http_parser/README.md
  3. 5493
      deps/http_parser/http_parser.c
  4. 47
      deps/http_parser/http_parser.h
  5. 206
      deps/http_parser/http_parser.rl
  6. 99
      deps/http_parser/test.c
  7. 7
      lib/http.js
  8. 42
      src/http.cc
  9. 1
      src/http.h
  10. 22
      test/mjsunit/test-http-malformed-request.js
  11. 16
      test/mjsunit/test-http-proxy.js
  12. 16
      test/mjsunit/test-http-server.js

8
deps/http_parser/LICENSE

@ -1,4 +1,5 @@
Copyright 2009, Ryan Lienhart Dahl. All rights reserved. Copyright 2009, Ryan Lienhart Dahl. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the deal in the Software without restriction, including without limitation the
@ -17,12 +18,9 @@ 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 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE. IN THE SOFTWARE.
http_parser is based on Zed Shaw's Mongrel. Mongrel's license is as follows. http_parser is based on Zed Shaw's Mongrel. Mongrel's license is as follows.
-- CUT ---- CUT ---- CUT ---- CUT ---- CUT ---- CUT ---- CUT ---- CUT -- ---- 8< ---- 8< ---- 8< ---- 8< ---- 8< ---- 8< ---- 8< ---- 8< ----
Mongrel Web Server (Mongrel) is copyrighted free software by Zed A. Shaw Mongrel Web Server (Mongrel) is copyrighted free software by Zed A. Shaw
<zedshaw at zedshaw dot com> and contributors. You can redistribute it <zedshaw at zedshaw dot com> and contributors. You can redistribute it
and/or modify it under either the terms of the GPL2 or the conditions below: and/or modify it under either the terms of the GPL2 or the conditions below:
@ -76,4 +74,4 @@ and/or modify it under either the terms of the GPL2 or the conditions below:
IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. PURPOSE.
-- CUT ---- CUT ---- CUT ---- CUT ---- CUT ---- CUT ---- CUT ---- CUT -- ---- 8< ---- 8< ---- 8< ---- 8< ---- 8< ---- 8< ---- 8< ---- 8< ----

27
deps/http_parser/README.md

@ -43,15 +43,29 @@ When data is received on the socket execute the parser and check for errors.
char buf[len]; char buf[len];
ssize_t recved; ssize_t recved;
recved = read(fd, buf, len); recved = recv(fd, buf, len, 0);
if (recved != 0) // handle error
if (recved < 0) {
/* Handle error. */
}
/* Start up / continue the parser.
* Note we pass the recved==0 to http_parser_execute to signal
* that EOF has been recieved.
*/
http_parser_execute(parser, buf, recved); http_parser_execute(parser, buf, recved);
if (http_parser_has_error(parser)) { if (http_parser_has_error(parser)) {
// handle error. usually just close the connection /* Handle error. Usually just close the connection. */
} }
HTTP needs to know where the end of the stream is. For example, sometimes
servers send responses without Content-Length and expect the client to
consume input (for the body) until EOF. To tell http_parser about EOF, give
`0` as the third parameter to `http_parser_execute()`. Callbacks and errors
can still be encountered during an EOF, so one must still be prepared
to receive them.
Scalar valued message information such as `status_code`, `method`, and the Scalar valued message information such as `status_code`, `method`, and the
HTTP version are stored in the parser structure. This data is only HTTP version are stored in the parser structure. This data is only
temporarlly stored in `http_parser` and gets reset on each new message. If temporarlly stored in `http_parser` and gets reset on each new message. If
@ -129,3 +143,10 @@ Releases
* [0.1](http://s3.amazonaws.com/four.livejournal/20090427/http_parser-0.1.tar.gz) * [0.1](http://s3.amazonaws.com/four.livejournal/20090427/http_parser-0.1.tar.gz)
The source repo is at [github](http://github.com/ry/http-parser). The source repo is at [github](http://github.com/ry/http-parser).
Bindings
--------
* [Ruby](http://github.com/yakischloba/http-parser-ffi)
* [Lua](http://github.com/phoenixsol/lua-http-parser)

5493
deps/http_parser/http_parser.c

File diff suppressed because it is too large

47
deps/http_parser/http_parser.h

@ -28,6 +28,9 @@
extern "C" { extern "C" {
#endif #endif
#ifdef _MSC_VER
# include <stddef.h>
#endif
#include <sys/types.h> #include <sys/types.h>
typedef struct http_parser http_parser; typedef struct http_parser http_parser;
@ -58,36 +61,20 @@ typedef int (*http_cb) (http_parser*);
#define HTTP_TRACE 0x1000 #define HTTP_TRACE 0x1000
#define HTTP_UNLOCK 0x2000 #define HTTP_UNLOCK 0x2000
/* Transfer Encodings */
#define HTTP_IDENTITY 0x01
#define HTTP_CHUNKED 0x02
enum http_parser_type { HTTP_REQUEST, HTTP_RESPONSE }; enum http_parser_type { HTTP_REQUEST, HTTP_RESPONSE };
#define HTTP_VERSION_OTHER 0x00
#define HTTP_VERSION_11 0x01
#define HTTP_VERSION_10 0x02
#define HTTP_VERSION_09 0x04
struct http_parser { struct http_parser {
/** PRIVATE **/ /** PRIVATE **/
int cs; int cs;
enum http_parser_type type; enum http_parser_type type;
size_t chunk_size; size_t chunk_size;
char flags;
/**
XXX
do this so no other code has to change, but make the field only 1 byte wide
instead of 2 (on x86/x86_64).
doing this not only shrinks the sizeof this struct by a byte but it ALSO
makes wrapping this in FFI way easier.
*/
union {
struct {
unsigned eating:1;
unsigned error:1;
};
struct {
unsigned char _flags;
};
};
size_t body_read; size_t body_read;
@ -107,11 +94,9 @@ struct http_parser {
/** READ-ONLY **/ /** READ-ONLY **/
unsigned short status_code; /* responses only */ unsigned short status_code; /* responses only */
unsigned short method; /* requests only */ unsigned short method; /* requests only */
short transfer_encoding; short version;
unsigned short version_major;
unsigned short version_minor;
short keep_alive; short keep_alive;
size_t content_length; ssize_t content_length;
/** PUBLIC **/ /** PUBLIC **/
void *data; /* A pointer to get hook to the "connection" or "socket" object */ void *data; /* A pointer to get hook to the "connection" or "socket" object */
@ -138,11 +123,17 @@ struct http_parser {
*/ */
void http_parser_init (http_parser *parser, enum http_parser_type); void http_parser_init (http_parser *parser, enum http_parser_type);
size_t http_parser_execute (http_parser *parser, const char *data, size_t len); void http_parser_execute (http_parser *parser, const char *data, size_t len);
int http_parser_has_error (http_parser *parser); int http_parser_has_error (http_parser *parser);
int http_parser_should_keep_alive (http_parser *parser); static inline int
http_parser_should_keep_alive (http_parser *parser)
{
if (parser->keep_alive == -1) return (parser->version == HTTP_VERSION_11);
return parser->keep_alive;
}
#ifdef __cplusplus #ifdef __cplusplus
} }

206
deps/http_parser/http_parser.rl

@ -26,6 +26,12 @@
#include <limits.h> #include <limits.h>
#include <assert.h> #include <assert.h>
/* parser->flags */
#define EATING 0x01
#define ERROR 0x02
#define CHUNKED 0x04
#define EAT_FOREVER 0x10
static int unhex[] = {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 static int unhex[] = {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
@ -35,12 +41,14 @@ static int unhex[] = {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1 ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
}; };
#define TRUE 1
#define FALSE 0
#define MIN(a,b) (a < b ? a : b)
#define NULL (void*)(0)
#define MAX_FIELD_SIZE 80*1024 #undef MIN
#define MIN(a,b) ((a) < (b) ? (a) : (b))
#undef NULL
#define NULL ((void*)(0))
#define MAX_FIELD_SIZE (80*1024)
#define REMAINING (unsigned long)(pe - p) #define REMAINING (unsigned long)(pe - p)
#define CALLBACK(FOR) \ #define CALLBACK(FOR) \
@ -48,34 +56,36 @@ do { \
if (parser->FOR##_mark) { \ if (parser->FOR##_mark) { \
parser->FOR##_size += p - parser->FOR##_mark; \ parser->FOR##_size += p - parser->FOR##_mark; \
if (parser->FOR##_size > MAX_FIELD_SIZE) { \ if (parser->FOR##_size > MAX_FIELD_SIZE) { \
parser->error = TRUE; \ parser->flags |= ERROR; \
return 0; \ return; \
} \ } \
if (parser->on_##FOR) { \ if (parser->on_##FOR) { \
callback_return_value = parser->on_##FOR(parser, \ callback_return_value = parser->on_##FOR(parser, \
parser->FOR##_mark, \ parser->FOR##_mark, \
p - parser->FOR##_mark); \ p - parser->FOR##_mark); \
} \ } \
if (callback_return_value != 0) { \
parser->flags |= ERROR; \
return; \
} \
} \ } \
} while(0) } while(0)
#define RESET_PARSER(parser) \ #define RESET_PARSER(parser) \
parser->chunk_size = 0; \ parser->chunk_size = 0; \
parser->eating = 0; \ parser->flags = 0; \
parser->header_field_mark = NULL; \ parser->header_field_mark = NULL; \
parser->header_value_mark = NULL; \ parser->header_value_mark = NULL; \
parser->query_string_mark = NULL; \ parser->query_string_mark = NULL; \
parser->path_mark = NULL; \ parser->path_mark = NULL; \
parser->uri_mark = NULL; \ parser->uri_mark = NULL; \
parser->fragment_mark = NULL; \ parser->fragment_mark = NULL; \
parser->status_code = 0; \ parser->status_code = 0; \
parser->method = 0; \ parser->method = 0; \
parser->transfer_encoding = HTTP_IDENTITY; \ parser->version = HTTP_VERSION_OTHER; \
parser->version_major = 0; \ parser->keep_alive = -1; \
parser->version_minor = 0; \ parser->content_length = -1; \
parser->keep_alive = -1; \ parser->body_read = 0
parser->content_length = 0; \
parser->body_read = 0;
#define END_REQUEST \ #define END_REQUEST \
do { \ do { \
@ -97,12 +107,12 @@ do { \
parser->body_read += tmp; \ parser->body_read += tmp; \
parser->chunk_size -= tmp; \ parser->chunk_size -= tmp; \
if (0 == parser->chunk_size) { \ if (0 == parser->chunk_size) { \
parser->eating = FALSE; \ parser->flags &= ~EATING; \
if (parser->transfer_encoding == HTTP_IDENTITY) { \ if (!(parser->flags & CHUNKED)) { \
END_REQUEST; \ END_REQUEST; \
} \ } \
} else { \ } else { \
parser->eating = TRUE; \ parser->flags |= EATING; \
} \ } \
} \ } \
} while (0) } while (0)
@ -142,60 +152,36 @@ do { \
action header_field { action header_field {
CALLBACK(header_field); CALLBACK(header_field);
if (callback_return_value != 0) {
parser->error = TRUE;
return 0;
}
parser->header_field_mark = NULL; parser->header_field_mark = NULL;
parser->header_field_size = 0; parser->header_field_size = 0;
} }
action header_value { action header_value {
CALLBACK(header_value); CALLBACK(header_value);
if (callback_return_value != 0) {
parser->error = TRUE;
return 0;
}
parser->header_value_mark = NULL; parser->header_value_mark = NULL;
parser->header_value_size = 0; parser->header_value_size = 0;
} }
action request_uri { action request_uri {
CALLBACK(uri); CALLBACK(uri);
if (callback_return_value != 0) {
parser->error = TRUE;
return 0;
}
parser->uri_mark = NULL; parser->uri_mark = NULL;
parser->uri_size = 0; parser->uri_size = 0;
} }
action fragment { action fragment {
CALLBACK(fragment); CALLBACK(fragment);
if (callback_return_value != 0) {
parser->error = TRUE;
return 0;
}
parser->fragment_mark = NULL; parser->fragment_mark = NULL;
parser->fragment_size = 0; parser->fragment_size = 0;
} }
action query_string { action query_string {
CALLBACK(query_string); CALLBACK(query_string);
if (callback_return_value != 0) {
parser->error = TRUE;
return 0;
}
parser->query_string_mark = NULL; parser->query_string_mark = NULL;
parser->query_string_size = 0; parser->query_string_size = 0;
} }
action request_path { action request_path {
CALLBACK(path); CALLBACK(path);
if (callback_return_value != 0) {
parser->error = TRUE;
return 0;
}
parser->path_mark = NULL; parser->path_mark = NULL;
parser->path_size = 0; parser->path_size = 0;
} }
@ -204,8 +190,8 @@ do { \
if(parser->on_headers_complete) { if(parser->on_headers_complete) {
callback_return_value = parser->on_headers_complete(parser); callback_return_value = parser->on_headers_complete(parser);
if (callback_return_value != 0) { if (callback_return_value != 0) {
parser->error = TRUE; parser->flags |= ERROR;
return 0; return;
} }
} }
} }
@ -214,16 +200,17 @@ do { \
if(parser->on_message_begin) { if(parser->on_message_begin) {
callback_return_value = parser->on_message_begin(parser); callback_return_value = parser->on_message_begin(parser);
if (callback_return_value != 0) { if (callback_return_value != 0) {
parser->error = TRUE; parser->flags |= ERROR;
return 0; return;
} }
} }
} }
action content_length { action content_length {
if (parser->content_length == -1) parser->content_length = 0;
if (parser->content_length > INT_MAX) { if (parser->content_length > INT_MAX) {
parser->error = TRUE; parser->flags |= ERROR;
return 0; return;
} }
parser->content_length *= 10; parser->content_length *= 10;
parser->content_length += *p - '0'; parser->content_length += *p - '0';
@ -234,21 +221,14 @@ do { \
parser->status_code += *p - '0'; parser->status_code += *p - '0';
} }
action use_identity_encoding { parser->transfer_encoding = HTTP_IDENTITY; } action use_chunked_encoding { parser->flags |= CHUNKED; }
action use_chunked_encoding { parser->transfer_encoding = HTTP_CHUNKED; }
action set_keep_alive { parser->keep_alive = TRUE; } action set_keep_alive { parser->keep_alive = 1; }
action set_not_keep_alive { parser->keep_alive = FALSE; } action set_not_keep_alive { parser->keep_alive = 0; }
action version_major { action version_11 { parser->version = HTTP_VERSION_11; }
parser->version_major *= 10; action version_10 { parser->version = HTTP_VERSION_10; }
parser->version_major += *p - '0'; action version_09 { parser->version = HTTP_VERSION_09; }
}
action version_minor {
parser->version_minor *= 10;
parser->version_minor += *p - '0';
}
action add_to_chunk_size { action add_to_chunk_size {
parser->chunk_size *= 16; parser->chunk_size *= 16;
@ -258,8 +238,8 @@ do { \
action skip_chunk_data { action skip_chunk_data {
SKIP_BODY(MIN(parser->chunk_size, REMAINING)); SKIP_BODY(MIN(parser->chunk_size, REMAINING));
if (callback_return_value != 0) { if (callback_return_value != 0) {
parser->error = TRUE; parser->flags |= ERROR;
return 0; return;
} }
fhold; fhold;
@ -280,18 +260,32 @@ do { \
} }
action body_logic { action body_logic {
if (parser->transfer_encoding == HTTP_CHUNKED) { if (parser->flags & CHUNKED) {
fnext ChunkedBody; fnext ChunkedBody;
} else { } else {
/* this is pretty stupid. i'd prefer to combine this with skip_chunk_data */ /* this is pretty stupid. i'd prefer to combine this with
parser->chunk_size = parser->content_length; * skip_chunk_data */
if (parser->content_length < 0) {
/* If we didn't get a content length; if not keep-alive
* just read body until EOF */
if (!http_parser_should_keep_alive(parser)) {
parser->flags |= EAT_FOREVER;
parser->chunk_size = REMAINING;
} else {
/* Otherwise, if keep-alive, then assume the message
* has no body. */
parser->chunk_size = parser->content_length = 0;
}
} else {
parser->chunk_size = parser->content_length;
}
p += 1; p += 1;
SKIP_BODY(MIN(REMAINING, parser->content_length)); SKIP_BODY(MIN(REMAINING, parser->chunk_size));
if (callback_return_value != 0) { if (callback_return_value != 0) {
parser->error = TRUE; parser->flags |= ERROR;
return 0; return;
} }
fhold; fhold;
@ -342,7 +336,11 @@ do { \
| "UNLOCK" %{ parser->method = HTTP_UNLOCK; } | "UNLOCK" %{ parser->method = HTTP_UNLOCK; }
); # Not allowing extension methods ); # Not allowing extension methods
HTTP_Version = "HTTP/" digit $version_major "." digit $version_minor; HTTP_Version = "HTTP/" ( "1.1" %version_11
| "1.0" %version_10
| "0.9" %version_09
| (digit "." digit)
);
scheme = ( alpha | digit | "+" | "-" | "." )* ; scheme = ( alpha | digit | "+" | "-" | "." )* ;
absolute_uri = (scheme ":" (uchar | reserved )*); absolute_uri = (scheme ":" (uchar | reserved )*);
@ -369,7 +367,7 @@ do { \
| "close"i %set_not_keep_alive | "close"i %set_not_keep_alive
) )
) )
| ("Transfer-Encoding"i %use_chunked_encoding hsep "identity" %use_identity_encoding) | ("Transfer-Encoding"i hsep "chunked"i %use_chunked_encoding)
| (Field_Name hsep Field_Value) | (Field_Name hsep Field_Value)
) :> CRLF; ) :> CRLF;
@ -387,11 +385,11 @@ do { \
chunk_ext_val = token*; chunk_ext_val = token*;
chunk_ext_name = token*; chunk_ext_name = token*;
chunk_extension = ( ";" " "* chunk_ext_name ("=" chunk_ext_val)? )*; chunk_extension = ( ";" " "* chunk_ext_name ("=" chunk_ext_val)? )*;
last_chunk = "0"+ chunk_extension CRLF; last_chunk = "0"+ ( chunk_extension | " "+) CRLF;
chunk_size = (xdigit* [1-9a-fA-F] xdigit*) $add_to_chunk_size; chunk_size = (xdigit* [1-9a-fA-F] xdigit* ) $add_to_chunk_size;
chunk_end = CRLF; chunk_end = CRLF;
chunk_body = any >skip_chunk_data; chunk_body = any >skip_chunk_data;
chunk_begin = chunk_size chunk_extension CRLF; chunk_begin = chunk_size ( chunk_extension | " "+ ) CRLF;
chunk = chunk_begin chunk_body chunk_end; chunk = chunk_begin chunk_body chunk_end;
ChunkedBody := chunk* last_chunk trailing_headers CRLF @end_chunked_body; ChunkedBody := chunk* last_chunk trailing_headers CRLF @end_chunked_body;
@ -421,7 +419,6 @@ http_parser_init (http_parser *parser, enum http_parser_type type)
%% write init; %% write init;
parser->cs = cs; parser->cs = cs;
parser->type = type; parser->type = type;
parser->error = 0;
parser->on_message_begin = NULL; parser->on_message_begin = NULL;
parser->on_path = NULL; parser->on_path = NULL;
@ -438,23 +435,39 @@ http_parser_init (http_parser *parser, enum http_parser_type type)
} }
/** exec **/ /** exec **/
size_t void
http_parser_execute (http_parser *parser, const char *buffer, size_t len) http_parser_execute (http_parser *parser, const char *buffer, size_t len)
{ {
size_t tmp; // REMOVE ME this is extremely hacky size_t tmp; // REMOVE ME this is extremely hacky
int callback_return_value = 0; int callback_return_value = 0;
const char *p, *pe; const char *p, *pe, *eof;
int cs = parser->cs; int cs = parser->cs;
p = buffer; p = buffer;
pe = buffer+len; pe = buffer+len;
eof = len ? NULL : pe;
if (0 < parser->chunk_size && parser->eating) { if (parser->flags & EAT_FOREVER) {
if (len == 0) {
if (parser->on_message_complete) {
callback_return_value = parser->on_message_complete(parser);
if (callback_return_value != 0) parser->flags |= ERROR;
}
} else {
if (parser->on_body) {
callback_return_value = parser->on_body(parser, p, len);
if (callback_return_value != 0) parser->flags |= ERROR;
}
}
return;
}
if (0 < parser->chunk_size && (parser->flags & EATING)) {
/* eat body */ /* eat body */
SKIP_BODY(MIN(len, parser->chunk_size)); SKIP_BODY(MIN(len, parser->chunk_size));
if (callback_return_value != 0) { if (callback_return_value != 0) {
parser->error = TRUE; parser->flags |= ERROR;
return 0; return;
} }
} }
@ -477,26 +490,11 @@ http_parser_execute (http_parser *parser, const char *buffer, size_t len)
CALLBACK(uri); CALLBACK(uri);
assert(p <= pe && "buffer overflow after parsing execute"); assert(p <= pe && "buffer overflow after parsing execute");
return(p - buffer);
} }
int int
http_parser_has_error (http_parser *parser) http_parser_has_error (http_parser *parser)
{ {
if (parser->error) return TRUE; if (parser->flags & ERROR) return 1;
return parser->cs == http_parser_error; return parser->cs == http_parser_error;
} }
int
http_parser_should_keep_alive (http_parser *parser)
{
if (parser->keep_alive == -1)
if (parser->version_major == 1)
return (parser->version_minor != 0);
else if (parser->version_major == 0)
return FALSE;
else
return TRUE;
else
return parser->keep_alive;
}

99
deps/http_parser/test.c

@ -359,6 +359,45 @@ const struct message responses[] =
"</BODY></HTML>\r\n" "</BODY></HTML>\r\n"
} }
, {.name= "no content-length response"
,.type= HTTP_RESPONSE
,.raw= "HTTP/1.1 200 OK\r\n"
"Date: Tue, 04 Aug 2009 07:59:32 GMT\r\n"
"Server: Apache\r\n"
"X-Powered-By: Servlet/2.5 JSP/2.1\r\n"
"Content-Type: text/xml; charset=utf-8\r\n"
"Connection: close\r\n"
"\r\n"
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\">\n"
" <SOAP-ENV:Body>\n"
" <SOAP-ENV:Fault>\n"
" <faultcode>SOAP-ENV:Client</faultcode>\n"
" <faultstring>Client Error</faultstring>\n"
" </SOAP-ENV:Fault>\n"
" </SOAP-ENV:Body>\n"
"</SOAP-ENV:Envelope>"
,.should_keep_alive= FALSE
,.status_code= 200
,.num_headers= 5
,.headers=
{ { "Date", "Tue, 04 Aug 2009 07:59:32 GMT" }
, { "Server", "Apache" }
, { "X-Powered-By", "Servlet/2.5 JSP/2.1" }
, { "Content-Type", "text/xml; charset=utf-8" }
, { "Connection", "close" }
}
,.body= "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\">\n"
" <SOAP-ENV:Body>\n"
" <SOAP-ENV:Fault>\n"
" <faultcode>SOAP-ENV:Client</faultcode>\n"
" <faultstring>Client Error</faultstring>\n"
" </SOAP-ENV:Fault>\n"
" </SOAP-ENV:Body>\n"
"</SOAP-ENV:Envelope>"
}
, {.name= "404 no headers no body" , {.name= "404 no headers no body"
,.type= HTTP_RESPONSE ,.type= HTTP_RESPONSE
,.raw= "HTTP/1.1 404 Not Found\r\n\r\n" ,.raw= "HTTP/1.1 404 Not Found\r\n\r\n"
@ -377,7 +416,34 @@ const struct message responses[] =
,.num_headers= 0 ,.num_headers= 0
,.headers= {} ,.headers= {}
,.body= "" ,.body= ""
} }
, {.name="200 trailing space on chunked body"
,.type= HTTP_RESPONSE
,.raw= "HTTP/1.1 200 OK\r\n"
"Content-Type: text/plain\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n"
"25 \r\n"
"This is the data in the first chunk\r\n"
"\r\n"
"1C\r\n"
"and this is the second one\r\n"
"\r\n"
"0 \r\n"
"\r\n"
,.should_keep_alive= TRUE
,.status_code= 200
,.num_headers= 2
,.headers=
{ {"Content-Type", "text/plain" }
, {"Transfer-Encoding", "chunked" }
}
,.body =
"This is the data in the first chunk\r\n"
"and this is the second one\r\n"
}
, {.name= NULL } /* sentinel */ , {.name= NULL } /* sentinel */
}; };
@ -543,12 +609,14 @@ parse_messages (int message_count, const struct message *input_messages[])
} }
// Parse the stream // Parse the stream
size_t traversed = 0;
parser_init(HTTP_REQUEST); parser_init(HTTP_REQUEST);
traversed = http_parser_execute(&parser, total, length); http_parser_execute(&parser, total, length);
assert(!http_parser_has_error(&parser));
http_parser_execute(&parser, NULL, 0);
assert(!http_parser_has_error(&parser)); assert(!http_parser_has_error(&parser));
assert(num_messages == message_count); assert(num_messages == message_count);
for (i = 0; i < message_count; i++) { for (i = 0; i < message_count; i++) {
@ -560,11 +628,14 @@ parse_messages (int message_count, const struct message *input_messages[])
void void
test_message (const struct message *message) test_message (const struct message *message)
{ {
size_t traversed = 0;
parser_init(message->type); parser_init(message->type);
traversed = http_parser_execute(&parser, message->raw, strlen(message->raw)); http_parser_execute(&parser, message->raw, strlen(message->raw));
assert(!http_parser_has_error(&parser)); assert(!http_parser_has_error(&parser));
http_parser_execute(&parser, NULL, 0);
assert(!http_parser_has_error(&parser));
assert(num_messages == 1); assert(num_messages == 1);
message_eq(0, message); message_eq(0, message);
@ -573,10 +644,10 @@ test_message (const struct message *message)
void void
test_error (const char *buf) test_error (const char *buf)
{ {
size_t traversed = 0;
parser_init(HTTP_REQUEST); parser_init(HTTP_REQUEST);
traversed = http_parser_execute(&parser, buf, strlen(buf)); http_parser_execute(&parser, buf, strlen(buf));
http_parser_execute(&parser, NULL, 0);
assert(http_parser_has_error(&parser)); assert(http_parser_has_error(&parser));
} }
@ -595,12 +666,14 @@ test_multiple3 (const struct message *r1, const struct message *r2, const struct
strcat(total, r2->raw); strcat(total, r2->raw);
strcat(total, r3->raw); strcat(total, r3->raw);
size_t traversed = 0;
parser_init(HTTP_REQUEST); parser_init(HTTP_REQUEST);
traversed = http_parser_execute(&parser, total, strlen(total)); http_parser_execute(&parser, total, strlen(total));
assert(!http_parser_has_error(&parser) );
http_parser_execute(&parser, NULL, 0);
assert(!http_parser_has_error(&parser) );
assert(! http_parser_has_error(&parser) );
assert(num_messages == 3); assert(num_messages == 3);
message_eq(0, r1); message_eq(0, r1);
message_eq(1, r2); message_eq(1, r2);
@ -659,16 +732,16 @@ test_scan (const struct message *r1, const struct message *r2, const struct mess
*/ */
http_parser_execute(&parser, buf1, buf1_len); http_parser_execute(&parser, buf1, buf1_len);
assert(!http_parser_has_error(&parser)); assert(!http_parser_has_error(&parser));
http_parser_execute(&parser, buf2, buf2_len); http_parser_execute(&parser, buf2, buf2_len);
assert(!http_parser_has_error(&parser)); assert(!http_parser_has_error(&parser));
http_parser_execute(&parser, buf3, buf3_len); http_parser_execute(&parser, buf3, buf3_len);
assert(!http_parser_has_error(&parser));
assert(! http_parser_has_error(&parser)); http_parser_execute(&parser, NULL, 0);
assert(!http_parser_has_error(&parser));
assert(3 == num_messages); assert(3 == num_messages);

7
lib/http.js

@ -333,7 +333,7 @@ function ClientRequest (method, uri, headers) {
} }
this.closeOnFinish = true; this.closeOnFinish = true;
this.sendHeaderLines(method + " " + uri + " HTTP/1.1\r\n", headers); this.sendHeaderLines(method + " " + uri + " HTTP/1.0\r\n", headers);
} }
node.inherits(ClientRequest, OutgoingMessage); node.inherits(ClientRequest, OutgoingMessage);
@ -476,7 +476,6 @@ function connectionListener (connection) {
} }
}); });
createIncomingMessageStream(connection, function (incoming, should_keep_alive) { createIncomingMessageStream(connection, function (incoming, should_keep_alive) {
var req = incoming; var req = incoming;
@ -507,7 +506,9 @@ exports.createClient = function (port, host) {
return; return;
} }
//sys.debug("client flush readyState = " + client.readyState); //sys.debug("client flush readyState = " + client.readyState);
if (req == requests[0]) flushMessageQueue(client, [req]); if (req == requests[0]) {
if(flushMessageQueue(client, [req])) client.close();
}
}); });
requests.push(req); requests.push(req);
}; };

42
src/http.cc

@ -66,6 +66,21 @@ HTTPConnection::OnReceive (const void *buf, size_t len)
if (http_parser_has_error(&parser_)) ForceClose(); if (http_parser_has_error(&parser_)) ForceClose();
} }
void
HTTPConnection::OnEOF ()
{
HandleScope scope;
assert(attached_);
http_parser_execute(&parser_, NULL, 0);
if (http_parser_has_error(&parser_)) {
ForceClose();
} else {
Emit("eof", 0, NULL);
}
}
int int
HTTPConnection::on_message_begin (http_parser *parser) HTTPConnection::on_message_begin (http_parser *parser)
{ {
@ -211,14 +226,25 @@ HTTPConnection::on_headers_complete (http_parser *parser)
Integer::New(connection->parser_.status_code)); Integer::New(connection->parser_.status_code));
// VERSION // VERSION
char version[10]; Local<String> version;
snprintf( version switch (connection->parser_.version) {
, 10 case HTTP_VERSION_OTHER:
, "%d.%d" version = String::NewSymbol("Other");
, connection->parser_.version_major break;
, connection->parser_.version_minor
); case HTTP_VERSION_09:
message_info->Set(HTTP_VERSION_SYMBOL, String::New(version)); version = String::NewSymbol("0.9");
break;
case HTTP_VERSION_10:
version = String::NewSymbol("1.0");
break;
case HTTP_VERSION_11:
version = String::NewSymbol("1.1");
break;
}
message_info->Set(HTTP_VERSION_SYMBOL, version);
message_info->Set(SHOULD_KEEP_ALIVE_SYMBOL, message_info->Set(SHOULD_KEEP_ALIVE_SYMBOL,
http_parser_should_keep_alive(&connection->parser_) ? True() : False()); http_parser_should_keep_alive(&connection->parser_) ? True() : False());

1
src/http.h

@ -36,6 +36,7 @@ protected:
} }
void OnReceive (const void *buf, size_t len); void OnReceive (const void *buf, size_t len);
void OnEOF ();
static int on_message_begin (http_parser *parser); static int on_message_begin (http_parser *parser);
static int on_uri (http_parser *parser, const char *at, size_t length); static int on_uri (http_parser *parser, const char *at, size_t length);

22
test/mjsunit/test-http-malformed-request.js

@ -7,26 +7,30 @@ http = require("/http.js");
port = 9999; port = 9999;
nrequests_completed = 0; nrequests_completed = 0;
nrequests_expected = 1; nrequests_expected = 2;
var s = http.createServer(function (req, res) { var server = http.createServer(function (req, res) {
puts("req: " + JSON.stringify(req.uri)); puts("req: " + JSON.stringify(req.uri));
res.sendHeader(200, {"Content-Type": "text/plain"}); res.sendHeader(200, {"Content-Type": "text/plain"});
res.sendBody("Hello World"); res.sendBody("Hello World");
res.finish(); res.finish();
if (++nrequests_completed == nrequests_expected) s.close(); if (++nrequests_completed == nrequests_expected) server.close();
puts("nrequests_completed: " + nrequests_completed);
}); });
s.listen(port); server.listen(port);
var c = tcp.createConnection(port); tcp.createConnection(port).addListener("connect", function () {
c.addListener("connect", function () { this.send("GET /hello?foo=%99bar HTTP/1.1\r\nConnection: close\r\n\r\n");
c.send("GET /hello?foo=%99bar HTTP/1.1\r\n\r\n"); this.close();
c.close();
}); });
// TODO add more! tcp.createConnection(port).addListener("connect", function () {
this.send("GET /with_\"stupid\"_quotes?in_the=\"uri\" HTTP/1.1\r\nConnection: close\r\n\r\n");
this.close();
});
process.addListener("exit", function () { process.addListener("exit", function () {
assertEquals(nrequests_expected, nrequests_completed); assertEquals(nrequests_expected, nrequests_completed);

16
test/mjsunit/test-http-proxy.js

@ -5,12 +5,12 @@ var PROXY_PORT = 8869;
var BACKEND_PORT = 8870; var BACKEND_PORT = 8870;
var backend = http.createServer(function (req, res) { var backend = http.createServer(function (req, res) {
// debug("backend"); debug("backend");
res.sendHeader(200, {"content-type": "text/plain"}); res.sendHeader(200, {"content-type": "text/plain"});
res.sendBody("hello world\n"); res.sendBody("hello world\n");
res.finish(); res.finish();
}); });
// debug("listen backend") debug("listen backend")
backend.listen(BACKEND_PORT); backend.listen(BACKEND_PORT);
var proxy_client = http.createClient(BACKEND_PORT); var proxy_client = http.createClient(BACKEND_PORT);
@ -24,30 +24,30 @@ var proxy = http.createServer(function (req, res) {
}); });
proxy_res.addListener("complete", function() { proxy_res.addListener("complete", function() {
res.finish(); res.finish();
// debug("proxy res"); debug("proxy res");
}); });
}); });
}); });
// debug("listen proxy") debug("listen proxy")
proxy.listen(PROXY_PORT); proxy.listen(PROXY_PORT);
var body = ""; var body = "";
var client = http.createClient(PROXY_PORT); var client = http.createClient(PROXY_PORT);
var req = client.get("/test"); var req = client.get("/test");
// debug("client req") debug("client req")
req.finish(function (res) { req.finish(function (res) {
// debug("got res"); debug("got res: " + JSON.stringify(res.headers));
assertEquals(200, res.statusCode); assertEquals(200, res.statusCode);
res.setBodyEncoding("utf8"); res.setBodyEncoding("utf8");
res.addListener("body", function (chunk) { body += chunk; }); res.addListener("body", function (chunk) { body += chunk; });
res.addListener("complete", function () { res.addListener("complete", function () {
proxy.close(); proxy.close();
backend.close(); backend.close();
// debug("closed both"); debug("closed both");
}); });
}); });
process.addListener("exit", function () { process.addListener("exit", function () {
assertEquals(body, "hello world\n"); assertEquals("hello world\n", body);
}); });

16
test/mjsunit/test-http-server.js

@ -13,6 +13,12 @@ http.createServer(function (req, res) {
res.id = request_number; res.id = request_number;
req.id = request_number++; req.id = request_number++;
puts("server got request " + req.id);
req.addListener("complete", function () {
puts("request complete " + req.id);
});
if (req.id == 0) { if (req.id == 0) {
assertEquals("GET", req.method); assertEquals("GET", req.method);
assertEquals("/hello", req.uri.path); assertEquals("/hello", req.uri.path);
@ -24,10 +30,11 @@ http.createServer(function (req, res) {
assertEquals("POST", req.method); assertEquals("POST", req.method);
assertEquals("/quit", req.uri.path); assertEquals("/quit", req.uri.path);
this.close(); this.close();
//puts("server closed"); puts("server closed");
} }
setTimeout(function () { setTimeout(function () {
puts("send response " + req.id);
res.sendHeader(200, {"Content-Type": "text/plain"}); res.sendHeader(200, {"Content-Type": "text/plain"});
res.sendBody(req.uri.path); res.sendBody(req.uri.path);
res.finish(); res.finish();
@ -40,7 +47,8 @@ var c = tcp.createConnection(port);
c.setEncoding("utf8"); c.setEncoding("utf8");
c.addListener("connect", function () { c.addListener("connect", function () {
c.send( "GET /hello?hello=world&foo=b==ar HTTP/1.1\r\n\r\n" ); puts("client connected. sending first request");
c.send("GET /hello?hello=world&foo=b==ar HTTP/1.1\r\n\r\n" );
requests_sent += 1; requests_sent += 1;
}); });
@ -48,7 +56,9 @@ c.addListener("receive", function (chunk) {
server_response += chunk; server_response += chunk;
if (requests_sent == 1) { if (requests_sent == 1) {
puts("send request 2");
c.send("POST /quit HTTP/1.1\r\n\r\n"); c.send("POST /quit HTTP/1.1\r\n\r\n");
puts("close client");
c.close(); c.close();
assertEquals(c.readyState, "readOnly"); assertEquals(c.readyState, "readOnly");
requests_sent += 1; requests_sent += 1;
@ -56,10 +66,12 @@ c.addListener("receive", function (chunk) {
}); });
c.addListener("eof", function () { c.addListener("eof", function () {
puts("client got eof");
client_got_eof = true; client_got_eof = true;
}); });
c.addListener("close", function () { c.addListener("close", function () {
puts("client closed");
assertEquals(c.readyState, "closed"); assertEquals(c.readyState, "closed");
}); });

Loading…
Cancel
Save