Browse Source

New http-parser

No longer based on Ragel, but hand-written.

Had to add HTTPConnection.resetParser() because the parser is stricter and
will error out when you try to give it a message after the previous had
"Connection: close". The HTTP client was doing that. Thus we reset the
parser manually after each new connection.
v0.7.4-release
Ryan Dahl 15 years ago
parent
commit
7719ce33db
  1. 4
      LICENSE
  2. 79
      deps/http_parser/LICENSE
  3. 19
      deps/http_parser/LICENSE-MIT
  4. 40
      deps/http_parser/Makefile
  5. 51
      deps/http_parser/README.md
  6. 7395
      deps/http_parser/http_parser.c
  7. 171
      deps/http_parser/http_parser.h
  8. 502
      deps/http_parser/http_parser.rl
  9. 685
      deps/http_parser/test.c
  10. 7
      lib/http.js
  11. 65
      src/node_http.cc
  12. 21
      src/node_http.h

4
LICENSE

@ -26,9 +26,7 @@ are:
Michael Tokarev <mjt@corpit.ru>. Released under the GNU Lesser General
Public License version 2.1.
Additionally deps/http_parser is based on Zed Shaw's Mongrel. Mongrel is
copyrighted by Zed Shaw and distributed under GPL2 or a permissive open
licence. See deps/http_parser/LICENCE for more information.
Other external libraries are my own and all use the same license as Node.
Node's license follows:

79
deps/http_parser/LICENSE

@ -1,79 +0,0 @@
Copyright 2009, Ryan Lienhart Dahl. All rights reserved.
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.
http_parser is based on Zed Shaw's Mongrel. Mongrel's license is as follows.
-- CUT ---- CUT ---- CUT ---- CUT ---- CUT ---- CUT ---- CUT ---- CUT --
Mongrel Web Server (Mongrel) is copyrighted free software by Zed A. Shaw
<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:
1. You may make and give away verbatim copies of the source form of the
software without restriction, provided that you duplicate all of the
original copyright notices and associated disclaimers.
2. You may modify your copy of the software in any way, provided that
you do at least ONE of the following:
a) place your modifications in the Public Domain or otherwise make them
Freely Available, such as by posting said modifications to Usenet or an
equivalent medium, or by allowing the author to include your
modifications in the software.
b) use the modified software only within your corporation or
organization.
c) rename any non-standard executables so the names do not conflict with
standard executables, which must also be provided.
d) make other distribution arrangements with the author.
3. You may distribute the software in object code or executable
form, provided that you do at least ONE of the following:
a) distribute the executables and library files of the software,
together with instructions (in the manual page or equivalent) on where
to get the original distribution.
b) accompany the distribution with the machine-readable source of the
software.
c) give non-standard executables non-standard names, with
instructions on where to get the original software distribution.
d) make other distribution arrangements with the author.
4. You may modify and include the part of the software into any other
software (possibly commercial). But some files in the distribution
are not written by the author, so that they are not under this terms.
5. The scripts and library files supplied as input to or produced as
output from the software do not automatically fall under the
copyright of the software, but belong to whomever generated them,
and may be sold commercially, and may be aggregated with this
software.
6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE.
-- CUT ---- CUT ---- CUT ---- CUT ---- CUT ---- CUT ---- CUT ---- CUT --

19
deps/http_parser/LICENSE-MIT

@ -0,0 +1,19 @@
Copyright 2009 Ryan Dahl <ry@tinyclouds.org>
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.

40
deps/http_parser/Makefile

@ -1,27 +1,31 @@
#OPT=-O0 -g -Wall -Wextra -Werror
OPT=-O2
OPT_DEBUG=-O0 -g -Wall -Wextra -Werror
OPT_FAST=-O3 -DHTTP_PARSER_STRICT=0
http_parser_g.o: http_parser.c http_parser.h Makefile
gcc $(OPT_DEBUG) -c http_parser.c
test_g: http_parser_g.o test.c
gcc $(OPT_DEBUG) http_parser.o test.c -o $@
test-run: test_g
./test_g
test: http_parser.o test.c
gcc $(OPT) http_parser.o test.c -o $@
http_parser.o: http_parser.c http_parser.h Makefile
gcc $(OPT) -c http_parser.c
gcc $(OPT_FAST) -c http_parser.c
http_parser.c: http_parser.rl Makefile
ragel -s -G2 http_parser.rl -o $@
test: http_parser.o test.c
gcc $(OPT_FAST) http_parser.o test.c -o $@
tags: http_parser.rl http_parser.h test.c
test-run-timed: test
while(true) do time ./test > /dev/null; done
tags: http_parser.c http_parser.h test.c
ctags $^
clean:
rm -f *.o http_parser.c test http_parser.tar
package: http_parser.c
@rm -rf /tmp/http_parser && mkdir /tmp/http_parser && \
cp LICENSE README.md Makefile http_parser.c http_parser.rl \
http_parser.h test.c /tmp/http_parser && \
cd /tmp && \
tar -cf http_parser.tar http_parser/
@echo /tmp/http_parser.tar
rm -f *.o test test_g http_parser.tar
.PHONY: clean package
.PHONY: clean package test-run test-run-timed

51
deps/http_parser/README.md

@ -5,13 +5,13 @@ This is a parser for HTTP messages written in C. It parses both requests
and responses. The parser is designed to be used in performance HTTP
applications. It does not make any allocations, it does not buffer data, and
it can be interrupted at anytime. It only requires about 128 bytes of data
per message stream (in a web server that is per connection).
per message stream (in a web server that is per connection).
Features:
* No dependencies
* No dependencies
* Parses both requests and responses.
* Handles keep-alive streams.
* Handles perstent streams.
* Decodes chunked encoding.
* Extracts the following data from a message
* header fields and values
@ -32,32 +32,47 @@ using `http_parser_init()` and set the callbacks. That might look something
like this:
http_parser *parser = malloc(sizeof(http_parser));
http_parser_init(parser, HTTP_REQUEST);
http_parser_init(parser);
parser->on_path = my_path_callback;
parser->on_header_field = my_header_field_callback;
/* ... */
parser->data = my_socket;
When data is received on the socket execute the parser and check for errors.
size_t len = 80*1024;
size_t len = 80*1024, nparsed;
char buf[len];
ssize_t recved;
recved = read(fd, buf, len);
if (recved != 0) // handle error
recved = recv(fd, buf, len, 0);
http_parser_execute(parser, buf, recved);
if (recved < 0) {
/* Handle error. */
}
/* Start up / continue the parser.
* Note we pass the recved==0 to http_parse_requests to signal
* that EOF has been recieved.
*/
nparsed = http_parse_requests(parser, buf, recved);
if (http_parser_has_error(parser)) {
// handle error. usually just close the connection
if (nparsed != recved) {
/* 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_parse_requests()`. 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
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
this information is needed later, copy it out of the structure during the
`headers_complete` callback.
The parser decodes the transfer-encoding for both requests and responses
transparently. That is, a chunked encoding is decoded before being sent to
the on_body callback.
@ -70,7 +85,7 @@ parser, for example, would not want such a feature.
Callbacks
---------
During the `http_parser_execute()` call, the callbacks set in `http_parser`
During the `http_parse_requests()` call, the callbacks set in `http_parser`
will be executed. The parser maintains state and never looks behind, so
buffering the data is not necessary. If you need to save certain data for
later usage, you can do that from the callbacks.
@ -93,7 +108,7 @@ Reading headers may be a tricky task if you read/parse headers partially.
Basically, you need to remember whether last header callback was field or value
and apply following logic:
/* on_header_field and on_header_value shortened to on_h_*
(on_header_field and on_header_value shortened to on_h_*)
------------------------ ------------ --------------------------------------------
| State (prev. callback) | Callback | Description/action |
------------------------ ------------ --------------------------------------------
@ -113,19 +128,9 @@ and apply following logic:
| value | on_h_value | Value continues. Reallocate value buffer |
| | | and append callback data to it |
------------------------ ------------ --------------------------------------------
*/
See examples of reading in headers:
* [partial example](http://gist.github.com/155877) in C
* [from http-parser tests](http://github.com/ry/http-parser/blob/37a0ff8928fb0d83cec0d0d8909c5a4abcd221af/test.c#L403) in C
* [from Node library](http://github.com/ry/node/blob/842eaf446d2fdcb33b296c67c911c32a0dabc747/src/http.js#L284) in Javascript
Releases
--------
* [0.2](http://s3.amazonaws.com/four.livejournal/20090807/http_parser-0.2.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).

7395
deps/http_parser/http_parser.c

File diff suppressed because it is too large

171
deps/http_parser/http_parser.h

@ -1,40 +1,48 @@
/* Copyright (c) 2008, 2009 Ryan Dahl (ry@tinyclouds.org)
* Based on Zed Shaw's Mongrel, copyright (c) Zed A. Shaw
*
* All rights reserved.
/* Copyright 2009 Ryan Dahl <ry@tinyclouds.org>
*
* 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:
* 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 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.
* 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.
*/
#ifndef http_parser_h
#define http_parser_h
#ifdef __cplusplus
extern "C" {
#endif
#endif
#include <sys/types.h>
#ifdef _MSC_VER
# include <stddef.h>
#endif
#include <sys/types.h>
/* Compile with -DHTTP_PARSER_STRICT=0 to make less checks, but run
* faster
*/
#ifndef HTTP_PARSER_STRICT
# define HTTP_PARSER_STRICT 1
#else
# define HTTP_PARSER_STRICT 0
#endif
typedef struct http_parser http_parser;
/* Callbacks should return non-zero to indicate an error. The parse will
* then halt execution.
*
* then halt execution.
*
* http_data_cb does not return data chunks. It will be call arbitrarally
* many times for each string. E.G. you might get 10 callbacks for "on_path"
* each providing just a few characters more data.
@ -43,75 +51,43 @@ typedef int (*http_data_cb) (http_parser*, const char *at, size_t length);
typedef int (*http_cb) (http_parser*);
/* Request Methods */
#define HTTP_COPY 0x0001
#define HTTP_DELETE 0x0002
#define HTTP_GET 0x0004
#define HTTP_HEAD 0x0008
#define HTTP_LOCK 0x0010
#define HTTP_MKCOL 0x0020
#define HTTP_MOVE 0x0040
#define HTTP_OPTIONS 0x0080
#define HTTP_POST 0x0100
#define HTTP_PROPFIND 0x0200
#define HTTP_PROPPATCH 0x0400
#define HTTP_PUT 0x0800
#define HTTP_TRACE 0x1000
#define HTTP_UNLOCK 0x2000
/* Transfer Encodings */
#define HTTP_IDENTITY 0x01
#define HTTP_CHUNKED 0x02
enum http_parser_type { HTTP_REQUEST, HTTP_RESPONSE };
enum http_method
{ HTTP_DELETE = 0x0002
, HTTP_GET = 0x0004
, HTTP_HEAD = 0x0008
, HTTP_POST = 0x0100
, HTTP_PUT = 0x0800
};
struct http_parser {
/** PRIVATE **/
int cs;
enum http_parser_type type;
size_t chunk_size;
/**
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;
const char *header_field_mark;
size_t header_field_size;
const char *header_value_mark;
size_t header_value_size;
const char *query_string_mark;
size_t query_string_size;
const char *path_mark;
size_t path_size;
const char *uri_mark;
size_t uri_size;
const char *fragment_mark;
size_t fragment_size;
unsigned short state;
unsigned short header_state;
size_t header_index;
char flags;
ssize_t body_read;
ssize_t content_length;
const char *header_field_mark;
size_t header_field_size;
const char *header_value_mark;
size_t header_value_size;
const char *query_string_mark;
size_t query_string_size;
const char *path_mark;
size_t path_size;
const char *url_mark;
size_t url_size;
const char *fragment_mark;
size_t fragment_size;
/** READ-ONLY **/
unsigned short status_code; /* responses only */
unsigned short method; /* requests only */
short transfer_encoding;
unsigned short version_major;
unsigned short version_minor;
short keep_alive;
size_t content_length;
enum http_method method; /* requests only */
unsigned short http_major;
unsigned short http_minor;
/** PUBLIC **/
void *data; /* A pointer to get hook to the "connection" or "socket" object */
@ -123,7 +99,7 @@ struct http_parser {
/* requests only */
http_data_cb on_path;
http_data_cb on_query_string;
http_data_cb on_uri;
http_data_cb on_url;
http_data_cb on_fragment;
http_data_cb on_header_field;
@ -133,18 +109,17 @@ struct http_parser {
http_cb on_message_complete;
};
/* Initializes an http_parser structure. The second argument specifies if
* it will be parsing requests or responses.
void http_parser_init(http_parser *parser);
size_t http_parse_requests(http_parser *parser, const char *data, size_t len);
size_t http_parse_responses(http_parser *parser, const char *data, size_t len);
/* Call this in the on_headers_complete or on_message_complete callback to
* determine if this will be the last message on the connection.
* If you are the server, respond with the "Connection: close" header
* if you are the client, close the connection.
*/
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);
int http_parser_has_error (http_parser *parser);
int http_parser_should_keep_alive (http_parser *parser);
int http_should_keep_alive(http_parser *parser);
#ifdef __cplusplus
}
#endif
#endif
#endif

502
deps/http_parser/http_parser.rl

@ -1,502 +0,0 @@
/* Copyright (c) 2008, 2009 Ryan Dahl (ry@tinyclouds.org)
* Based on Zed Shaw's Mongrel, copyright (c) Zed A. Shaw
*
* All rights reserved.
*
* 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.
*/
#include "http_parser.h"
#include <limits.h>
#include <assert.h>
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
, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-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,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
};
#define TRUE 1
#define FALSE 0
#define MIN(a,b) (a < b ? a : b)
#define NULL (void*)(0)
#define MAX_FIELD_SIZE 80*1024
#define REMAINING (unsigned long)(pe - p)
#define CALLBACK(FOR) \
do { \
if (parser->FOR##_mark) { \
parser->FOR##_size += p - parser->FOR##_mark; \
if (parser->FOR##_size > MAX_FIELD_SIZE) { \
parser->error = TRUE; \
return 0; \
} \
if (parser->on_##FOR) { \
callback_return_value = parser->on_##FOR(parser, \
parser->FOR##_mark, \
p - parser->FOR##_mark); \
} \
} \
} while(0)
#define RESET_PARSER(parser) \
parser->chunk_size = 0; \
parser->eating = 0; \
parser->header_field_mark = NULL; \
parser->header_value_mark = NULL; \
parser->query_string_mark = NULL; \
parser->path_mark = NULL; \
parser->uri_mark = NULL; \
parser->fragment_mark = NULL; \
parser->status_code = 0; \
parser->method = 0; \
parser->transfer_encoding = HTTP_IDENTITY; \
parser->version_major = 0; \
parser->version_minor = 0; \
parser->keep_alive = -1; \
parser->content_length = 0; \
parser->body_read = 0;
#define END_REQUEST \
do { \
if (parser->on_message_complete) { \
callback_return_value = \
parser->on_message_complete(parser); \
} \
RESET_PARSER(parser); \
} while (0)
#define SKIP_BODY(nskip) \
do { \
tmp = (nskip); \
if (parser->on_body && tmp > 0) { \
callback_return_value = parser->on_body(parser, p, tmp); \
} \
if (callback_return_value == 0) { \
p += tmp; \
parser->body_read += tmp; \
parser->chunk_size -= tmp; \
if (0 == parser->chunk_size) { \
parser->eating = FALSE; \
if (parser->transfer_encoding == HTTP_IDENTITY) { \
END_REQUEST; \
} \
} else { \
parser->eating = TRUE; \
} \
} \
} while (0)
%%{
machine http_parser;
action mark_header_field {
parser->header_field_mark = p;
parser->header_field_size = 0;
}
action mark_header_value {
parser->header_value_mark = p;
parser->header_value_size = 0;
}
action mark_fragment {
parser->fragment_mark = p;
parser->fragment_size = 0;
}
action mark_query_string {
parser->query_string_mark = p;
parser->query_string_size = 0;
}
action mark_request_path {
parser->path_mark = p;
parser->path_size = 0;
}
action mark_request_uri {
parser->uri_mark = p;
parser->uri_size = 0;
}
action header_field {
CALLBACK(header_field);
if (callback_return_value != 0) {
parser->error = TRUE;
return 0;
}
parser->header_field_mark = NULL;
parser->header_field_size = 0;
}
action header_value {
CALLBACK(header_value);
if (callback_return_value != 0) {
parser->error = TRUE;
return 0;
}
parser->header_value_mark = NULL;
parser->header_value_size = 0;
}
action request_uri {
CALLBACK(uri);
if (callback_return_value != 0) {
parser->error = TRUE;
return 0;
}
parser->uri_mark = NULL;
parser->uri_size = 0;
}
action fragment {
CALLBACK(fragment);
if (callback_return_value != 0) {
parser->error = TRUE;
return 0;
}
parser->fragment_mark = NULL;
parser->fragment_size = 0;
}
action query_string {
CALLBACK(query_string);
if (callback_return_value != 0) {
parser->error = TRUE;
return 0;
}
parser->query_string_mark = NULL;
parser->query_string_size = 0;
}
action request_path {
CALLBACK(path);
if (callback_return_value != 0) {
parser->error = TRUE;
return 0;
}
parser->path_mark = NULL;
parser->path_size = 0;
}
action headers_complete {
if(parser->on_headers_complete) {
callback_return_value = parser->on_headers_complete(parser);
if (callback_return_value != 0) {
parser->error = TRUE;
return 0;
}
}
}
action begin_message {
if(parser->on_message_begin) {
callback_return_value = parser->on_message_begin(parser);
if (callback_return_value != 0) {
parser->error = TRUE;
return 0;
}
}
}
action content_length {
if (parser->content_length > INT_MAX) {
parser->error = TRUE;
return 0;
}
parser->content_length *= 10;
parser->content_length += *p - '0';
}
action status_code {
parser->status_code *= 10;
parser->status_code += *p - '0';
}
action use_identity_encoding { parser->transfer_encoding = HTTP_IDENTITY; }
action use_chunked_encoding { parser->transfer_encoding = HTTP_CHUNKED; }
action set_keep_alive { parser->keep_alive = TRUE; }
action set_not_keep_alive { parser->keep_alive = FALSE; }
action version_major {
parser->version_major *= 10;
parser->version_major += *p - '0';
}
action version_minor {
parser->version_minor *= 10;
parser->version_minor += *p - '0';
}
action add_to_chunk_size {
parser->chunk_size *= 16;
parser->chunk_size += unhex[(int)*p];
}
action skip_chunk_data {
SKIP_BODY(MIN(parser->chunk_size, REMAINING));
if (callback_return_value != 0) {
parser->error = TRUE;
return 0;
}
fhold;
if (parser->chunk_size > REMAINING) {
fbreak;
} else {
fgoto chunk_end;
}
}
action end_chunked_body {
END_REQUEST;
if (parser->type == HTTP_REQUEST) {
fnext Requests;
} else {
fnext Responses;
}
}
action body_logic {
if (parser->transfer_encoding == HTTP_CHUNKED) {
fnext ChunkedBody;
} else {
/* this is pretty stupid. i'd prefer to combine this with skip_chunk_data */
parser->chunk_size = parser->content_length;
p += 1;
SKIP_BODY(MIN(REMAINING, parser->content_length));
if (callback_return_value != 0) {
parser->error = TRUE;
return 0;
}
fhold;
if(parser->chunk_size > REMAINING) {
fbreak;
}
}
}
CRLF = "\r\n";
# character types
CTL = (cntrl | 127);
safe = ("$" | "-" | "_" | ".");
extra = ("!" | "*" | "'" | "(" | ")" | ",");
reserved = (";" | "/" | "?" | ":" | "@" | "&" | "=" | "+");
unsafe = (CTL | " " | "\"" | "#" | "%" | "<" | ">");
national = any -- (alpha | digit | reserved | extra | safe | unsafe);
unreserved = (alpha | digit | safe | extra | national);
escape = ("%" xdigit xdigit);
uchar = (unreserved | escape | "\"");
pchar = (uchar | ":" | "@" | "&" | "=" | "+");
tspecials = ("(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\\" | "\""
| "/" | "[" | "]" | "?" | "=" | "{" | "}" | " " | "\t");
# elements
token = (ascii -- (CTL | tspecials));
quote = "\"";
# qdtext = token -- "\"";
# quoted_pair = "\" ascii;
# quoted_string = "\"" (qdtext | quoted_pair )* "\"";
# headers
Method = ( "COPY" %{ parser->method = HTTP_COPY; }
| "DELETE" %{ parser->method = HTTP_DELETE; }
| "GET" %{ parser->method = HTTP_GET; }
| "HEAD" %{ parser->method = HTTP_HEAD; }
| "LOCK" %{ parser->method = HTTP_LOCK; }
| "MKCOL" %{ parser->method = HTTP_MKCOL; }
| "MOVE" %{ parser->method = HTTP_MOVE; }
| "OPTIONS" %{ parser->method = HTTP_OPTIONS; }
| "POST" %{ parser->method = HTTP_POST; }
| "PROPFIND" %{ parser->method = HTTP_PROPFIND; }
| "PROPPATCH" %{ parser->method = HTTP_PROPPATCH; }
| "PUT" %{ parser->method = HTTP_PUT; }
| "TRACE" %{ parser->method = HTTP_TRACE; }
| "UNLOCK" %{ parser->method = HTTP_UNLOCK; }
); # Not allowing extension methods
HTTP_Version = "HTTP/" digit $version_major "." digit $version_minor;
scheme = ( alpha | digit | "+" | "-" | "." )* ;
absolute_uri = (scheme ":" (uchar | reserved )*);
path = ( pchar+ ( "/" pchar* )* ) ;
query = ( uchar | reserved )* >mark_query_string %query_string ;
param = ( pchar | "/" )* ;
params = ( param ( ";" param )* ) ;
rel_path = ( path? (";" params)? ) ;
absolute_path = ( "/"+ rel_path ) >mark_request_path %request_path ("?" query)?;
Request_URI = ( "*" | absolute_uri | absolute_path ) >mark_request_uri %request_uri;
Fragment = ( uchar | reserved )* >mark_fragment %fragment;
field_name = ( token -- ":" )+;
Field_Name = field_name >mark_header_field %header_field;
field_value = ((any - " ") any*)?;
Field_Value = field_value >mark_header_value %header_value;
hsep = ":" " "*;
header = (field_name hsep field_value) :> CRLF;
Header = ( ("Content-Length"i hsep digit+ $content_length)
| ("Connection"i hsep
( "Keep-Alive"i %set_keep_alive
| "close"i %set_not_keep_alive
)
)
| ("Transfer-Encoding"i %use_chunked_encoding hsep "identity" %use_identity_encoding)
| (Field_Name hsep Field_Value)
) :> CRLF;
Headers = (Header)* :> CRLF @headers_complete;
Request_Line = ( Method " " Request_URI ("#" Fragment)? " " HTTP_Version CRLF ) ;
StatusCode = (digit digit digit) $status_code;
ReasonPhrase = ascii* -- ("\r" | "\n");
StatusLine = HTTP_Version " " StatusCode (" " ReasonPhrase)? CRLF;
# chunked message
trailing_headers = header*;
#chunk_ext_val = token | quoted_string;
chunk_ext_val = token*;
chunk_ext_name = token*;
chunk_extension = ( ";" " "* chunk_ext_name ("=" chunk_ext_val)? )*;
last_chunk = "0"+ chunk_extension CRLF;
chunk_size = (xdigit* [1-9a-fA-F] xdigit*) $add_to_chunk_size;
chunk_end = CRLF;
chunk_body = any >skip_chunk_data;
chunk_begin = chunk_size chunk_extension CRLF;
chunk = chunk_begin chunk_body chunk_end;
ChunkedBody := chunk* last_chunk trailing_headers CRLF @end_chunked_body;
Request = (Request_Line Headers) >begin_message @body_logic;
Response = (StatusLine Headers) >begin_message @body_logic;
Requests := Request*;
Responses := Response*;
main := any >{
fhold;
if (parser->type == HTTP_REQUEST) {
fgoto Requests;
} else {
fgoto Responses;
}
};
}%%
%% write data;
void
http_parser_init (http_parser *parser, enum http_parser_type type)
{
int cs = 0;
%% write init;
parser->cs = cs;
parser->type = type;
parser->error = 0;
parser->on_message_begin = NULL;
parser->on_path = NULL;
parser->on_query_string = NULL;
parser->on_uri = NULL;
parser->on_fragment = NULL;
parser->on_header_field = NULL;
parser->on_header_value = NULL;
parser->on_headers_complete = NULL;
parser->on_body = NULL;
parser->on_message_complete = NULL;
RESET_PARSER(parser);
}
/** exec **/
size_t
http_parser_execute (http_parser *parser, const char *buffer, size_t len)
{
size_t tmp; // REMOVE ME this is extremely hacky
int callback_return_value = 0;
const char *p, *pe;
int cs = parser->cs;
p = buffer;
pe = buffer+len;
if (0 < parser->chunk_size && parser->eating) {
/* eat body */
SKIP_BODY(MIN(len, parser->chunk_size));
if (callback_return_value != 0) {
parser->error = TRUE;
return 0;
}
}
if (parser->header_field_mark) parser->header_field_mark = buffer;
if (parser->header_value_mark) parser->header_value_mark = buffer;
if (parser->fragment_mark) parser->fragment_mark = buffer;
if (parser->query_string_mark) parser->query_string_mark = buffer;
if (parser->path_mark) parser->path_mark = buffer;
if (parser->uri_mark) parser->uri_mark = buffer;
%% write exec;
parser->cs = cs;
CALLBACK(header_field);
CALLBACK(header_value);
CALLBACK(fragment);
CALLBACK(query_string);
CALLBACK(path);
CALLBACK(uri);
assert(p <= pe && "buffer overflow after parsing execute");
return(p - buffer);
}
int
http_parser_has_error (http_parser *parser)
{
if (parser->error) return TRUE;
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;
}

685
deps/http_parser/test.c

File diff suppressed because it is too large

7
lib/http.js

@ -363,7 +363,7 @@ function createIncomingMessageStream (connection, incoming_listener) {
});
// Only servers will get URI events.
connection.addListener("uri", function (data) {
connection.addListener("url", function (data) {
incoming.uri.full += data;
});
@ -441,7 +441,7 @@ function flushMessageQueue (connection, queue) {
while (message.output.length > 0) {
if (connection.readyState !== "open" && connection.readyState !== "writeOnly") {
return false;
return true;
}
var data = message.output.shift();
@ -474,6 +474,8 @@ function connectionListener (connection) {
// we need to keep track of the order they were sent.
var responses = [];
connection.resetParser();
// is this really needed?
connection.addListener("eof", function () {
if (responses.length == 0) {
@ -520,6 +522,7 @@ exports.createClient = function (port, host) {
};
client.addListener("connect", function () {
client.resetParser();
requests[0].flush();
});

65
src/node_http.cc

@ -25,12 +25,14 @@ HTTPConnection::Initialize (Handle<Object> target)
client_constructor_template->Inherit(Connection::constructor_template);
client_constructor_template->InstanceTemplate()->SetInternalFieldCount(1);
client_constructor_template->SetClassName(String::NewSymbol("Client"));
NODE_SET_PROTOTYPE_METHOD(client_constructor_template, "resetParser", ResetParser);
target->Set(String::NewSymbol("Client"), client_constructor_template->GetFunction());
t = FunctionTemplate::New(NewServer);
server_constructor_template = Persistent<FunctionTemplate>::New(t);
server_constructor_template->Inherit(Connection::constructor_template);
server_constructor_template->InstanceTemplate()->SetInternalFieldCount(1);
NODE_SET_PROTOTYPE_METHOD(server_constructor_template, "resetParser", ResetParser);
server_constructor_template->SetClassName(String::NewSymbol("ServerSideConnection"));
}
@ -56,12 +58,46 @@ HTTPConnection::NewServer (const Arguments& args)
return args.This();
}
Handle<Value> HTTPConnection::ResetParser(const Arguments& args) {
HandleScope scope;
HTTPConnection *connection = ObjectWrap::Unwrap<HTTPConnection>(args.Holder());
connection->ResetParser();
return Undefined();
}
void
HTTPConnection::OnReceive (const void *buf, size_t len)
{
HandleScope scope;
assert(attached_);
size_t nparsed;
if (type_ == HTTP_REQUEST) {
nparsed = http_parse_requests(&parser_, static_cast<const char*>(buf), len);
} else {
nparsed = http_parse_responses(&parser_, static_cast<const char*>(buf), len);
}
if (nparsed != len) {
ForceClose();
}
}
void
HTTPConnection::OnEOF ()
{
HandleScope scope;
assert(attached_);
http_parser_execute(&parser_, static_cast<const char*>(buf), len);
if (http_parser_has_error(&parser_)) ForceClose();
printf("(node) HTTP EOF!\n");
if (type_ == HTTP_REQUEST) {
http_parse_requests(&parser_, NULL, 0);
} else {
http_parse_responses(&parser_, NULL, 0);
}
Emit("eof", 0, NULL);
}
int
@ -83,13 +119,13 @@ HTTPConnection::on_message_complete (http_parser *parser)
}
int
HTTPConnection::on_uri (http_parser *parser, const char *buf, size_t len)
HTTPConnection::on_url (http_parser *parser, const char *buf, size_t len)
{
HandleScope scope;
HTTPConnection *connection = static_cast<HTTPConnection*>(parser->data);
assert(connection->attached_);
Local<Value> argv[1] = { String::New(buf, len) };
connection->Emit("uri", 1, argv);
connection->Emit("url", 1, argv);
return 0;
}
@ -170,20 +206,11 @@ GetMethod (int method)
{
const char *s;
switch (method) {
case HTTP_COPY: s = "COPY"; break;
case HTTP_DELETE: s = "DELETE"; break;
case HTTP_GET: s = "GET"; break;
case HTTP_HEAD: s = "HEAD"; break;
case HTTP_LOCK: s = "LOCK"; break;
case HTTP_MKCOL: s = "MKCOL"; break;
case HTTP_MOVE: s = "MOVE"; break;
case HTTP_OPTIONS: s = "OPTIONS"; break;
case HTTP_POST: s = "POST"; break;
case HTTP_PROPFIND: s = "PROPFIND"; break;
case HTTP_PROPPATCH: s = "PROPPATCH"; break;
case HTTP_PUT: s = "PUT"; break;
case HTTP_TRACE: s = "TRACE"; break;
case HTTP_UNLOCK: s = "UNLOCK"; break;
}
HandleScope scope;
Local<String> method = String::NewSymbol(s);
@ -200,26 +227,28 @@ HTTPConnection::on_headers_complete (http_parser *parser)
Local<Object> message_info = Object::New();
// METHOD
if (connection->parser_.type == HTTP_REQUEST)
if (connection->type_ == HTTP_REQUEST) {
message_info->Set(METHOD_SYMBOL, GetMethod(connection->parser_.method));
}
// STATUS
if (connection->parser_.type == HTTP_RESPONSE)
if (connection->type_ == HTTP_RESPONSE) {
message_info->Set(STATUS_CODE_SYMBOL,
Integer::New(connection->parser_.status_code));
}
// VERSION
char version[10];
snprintf( version
, 10
, "%d.%d"
, connection->parser_.version_major
, connection->parser_.version_minor
, connection->parser_.http_major
, connection->parser_.http_minor
);
message_info->Set(HTTP_VERSION_SYMBOL, String::New(version));
message_info->Set(SHOULD_KEEP_ALIVE_SYMBOL,
http_parser_should_keep_alive(&connection->parser_) ? True() : False());
http_should_keep_alive(&connection->parser_) ? True() : False());
Local<Value> argv[1] = { message_info };

21
src/node_http.h

@ -7,6 +7,8 @@
namespace node {
enum http_connection_type { HTTP_RESPONSE, HTTP_REQUEST };
class HTTPConnection : public Connection {
public:
static void Initialize (v8::Handle<v8::Object> target);
@ -17,13 +19,19 @@ public:
protected:
static v8::Handle<v8::Value> NewClient (const v8::Arguments& args);
static v8::Handle<v8::Value> NewServer (const v8::Arguments& args);
static v8::Handle<v8::Value> ResetParser(const v8::Arguments& args);
HTTPConnection (enum http_parser_type type)
HTTPConnection (enum http_connection_type t)
: Connection()
{
http_parser_init (&parser_, type);
type_ = t;
ResetParser();
}
void ResetParser() {
http_parser_init (&parser_);
parser_.on_message_begin = on_message_begin;
parser_.on_uri = on_uri;
parser_.on_url = on_url;
parser_.on_path = on_path;
parser_.on_fragment = on_fragment;
parser_.on_query_string = on_query_string;
@ -36,9 +44,10 @@ protected:
}
void OnReceive (const void *buf, size_t len);
void OnEOF ();
static int on_message_begin (http_parser *parser);
static int on_uri (http_parser *parser, const char *at, size_t length);
static int on_url (http_parser *parser, const char *at, size_t length);
static int on_query_string (http_parser *parser, const char *at, size_t length);
static int on_path (http_parser *parser, const char *at, size_t length);
static int on_fragment (http_parser *parser, const char *at, size_t length);
@ -49,7 +58,9 @@ protected:
static int on_message_complete (http_parser *parser);
http_parser parser_;
enum http_connection_type type_; // should probably use subclass
// but going to refactor this all soon
// so won't worry about it.
friend class HTTPServer;
};

Loading…
Cancel
Save