Browse Source

Begin refactor of http.cc. Remove libebb add http_parser.

And most of http.cc was deleted.
v0.7.4-release
Ryan 16 years ago
parent
commit
5a071ad72f
  1. 75
      deps/http_parser/README.md
  2. 5776
      deps/http_parser/http_parser.c
  3. 127
      deps/http_parser/http_parser.h
  4. 424
      deps/http_parser/http_parser.rl
  5. 748
      deps/http_parser/test.c
  6. 5
      deps/libebb/.gitignore
  7. 21
      deps/libebb/LICENSE
  8. 7
      deps/libebb/README
  9. 38
      deps/libebb/config.mk
  10. BIN
      deps/libebb/doc/icon.png
  11. 240
      deps/libebb/doc/index.html
  12. 798
      deps/libebb/ebb.c
  13. 120
      deps/libebb/ebb.h
  14. 117
      deps/libebb/ebb_request_parser.h
  15. 413
      deps/libebb/ebb_request_parser.rl
  16. 12
      deps/libebb/examples/ca-cert.pem
  17. 15
      deps/libebb/examples/ca-key.pem
  18. 101
      deps/libebb/examples/hello_world.c
  19. 412
      deps/libebb/rbtree.c
  20. 54
      deps/libebb/rbtree.h
  21. 60
      deps/libebb/test_examples.rb
  22. 108
      deps/libebb/test_rbtree.c
  23. 746
      deps/libebb/test_request_parser.c
  24. 35
      ragel.py
  25. 686
      src/http.cc
  26. 13
      src/http.h
  27. 23
      src/net.cc
  28. 25
      src/net.h
  29. 2
      src/node.cc
  30. 2
      src/node.h
  31. 25
      wscript

75
deps/http_parser/README.md

@ -0,0 +1,75 @@
HTTP Parser
===========
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 100 bytes of data
per message stream (in a web server that is per connection).
Features:
* No dependencies
* Parses both requests and responses.
* Handles keep-alive streams.
* Decodes chunked encoding.
* Extracts the following data from a message
* header fields and values
* content-length
* request method
* response status code
* transfer-encoding
* http version
* request path, query string, fragment
* message body
Usage
-----
One `http_parser` object is used per TCP connection. Initialize the struct
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);
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;
char buf[len];
ssize_t recved;
recved = read(fd, buf, len);
if (recved != 0) // handle error
http_parser_execute(parser, buf, recved);
if (http_parser_has_error(parser)) {
// handle error. usually just close the connection
}
During the `http_parser_execute()` 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. (You can also `read()` into
a heap allocated buffer to avoid copying memory around if this fits your
application.)
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.
It does not decode the content-encoding (gzip). Not all HTTP applications
need to inspect the body. Decoding gzip is non-neglagable amount of
processing (and requires making allocations). HTTP proxies using this
parser, for example, would not want such a feature.
Releases
--------
* [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).

5776
deps/http_parser/http_parser.c

File diff suppressed because it is too large

127
deps/http_parser/http_parser.h

@ -0,0 +1,127 @@
/* Copyright (c) 2008 Ryan Dahl (ry@tinyclouds.org)
* All rights reserved.
*
* This parser is based on code from Zed Shaw's Mongrel.
* Copyright (c) 2005 Zed A. Shaw
*
* 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.
*/
#ifndef http_parser_h
#define http_parser_h
#ifdef __cplusplus
extern "C" {
#endif
#include <sys/types.h>
typedef struct http_parser http_parser;
/* Callbacks should return non-zero to indicate an error. The parse will
* 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.
*/
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 };
struct http_parser {
/** PRIVATE **/
int cs;
enum http_parser_type type;
size_t chunk_size;
unsigned eating:1;
size_t body_read;
const char *header_field_mark;
const char *header_value_mark;
const char *query_string_mark;
const char *path_mark;
const char *uri_mark;
const char *fragment_mark;
/** 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;
/** PUBLIC **/
void *data; /* A pointer to get hook to the "connection" or "socket" object */
/* an ordered list of callbacks */
http_cb on_message_begin;
/* requests only */
http_data_cb on_path;
http_data_cb on_query_string;
http_data_cb on_uri;
http_data_cb on_fragment;
http_data_cb on_header_field;
http_data_cb on_header_value;
http_cb on_headers_complete;
http_data_cb on_body;
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, 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);
#ifdef __cplusplus
}
#endif
#endif

424
deps/http_parser/http_parser.rl

@ -0,0 +1,424 @@
/* Copyright (c) 2008, 2009 Ryan Dahl (ry@tinyclouds.org)
*
* Based on Zed Shaw's Mongrel.
* Copyright (c) 2005 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 <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 REMAINING (pe - p)
#define CALLBACK(FOR) \
if (parser->FOR##_mark && parser->on_##FOR) { \
callback_return_value = \
parser->on_##FOR(parser, parser->FOR##_mark, p - parser->FOR##_mark); \
}
#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; }
action mark_header_value { parser->header_value_mark = p; }
action mark_fragment { parser->fragment_mark = p; }
action mark_query_string { parser->query_string_mark = p; }
action mark_request_path { parser->path_mark = p; }
action mark_request_uri { parser->uri_mark = p; }
action header_field {
CALLBACK(header_field);
if (callback_return_value != 0) fbreak;
parser->header_field_mark = NULL;
}
action header_value {
CALLBACK(header_value);
if (callback_return_value != 0) fbreak;
parser->header_value_mark = NULL;
}
action request_uri {
CALLBACK(uri);
if (callback_return_value != 0) fbreak;
parser->uri_mark = NULL;
}
action fragment {
CALLBACK(fragment);
if (callback_return_value != 0) fbreak;
parser->fragment_mark = NULL;
}
action query_string {
CALLBACK(query_string);
if (callback_return_value != 0) fbreak;
parser->query_string_mark = NULL;
}
action request_path {
CALLBACK(path);
if (callback_return_value != 0) fbreak;
parser->path_mark = NULL;
}
action headers_complete {
if(parser->on_headers_complete) {
callback_return_value = parser->on_headers_complete(parser);
if (callback_return_value != 0) fbreak;
}
}
action begin_message {
if(parser->on_message_begin) {
callback_return_value = parser->on_message_begin(parser);
if (callback_return_value != 0) fbreak;
}
}
action content_length {
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) fbreak;
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) fbreak;
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->on_message_begin = NULL;
parser->on_path = NULL;
parser->on_query_string = NULL;
parser->on_uri = 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) goto out;
}
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);
out:
assert(p <= pe && "buffer overflow after parsing execute");
return(p - buffer);
}
int
http_parser_has_error (http_parser *parser)
{
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;
}

748
deps/http_parser/test.c

@ -0,0 +1,748 @@
#include "http_parser.h"
#include <stdlib.h>
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#undef TRUE
#define TRUE 1
#undef FALSE
#define FALSE 0
#define MAX_HEADERS 10
#define MAX_ELEMENT_SIZE 500
static http_parser parser;
struct message {
const char *name; // for debugging purposes
const char *raw;
enum http_parser_type type;
int method;
int status_code;
char request_path[MAX_ELEMENT_SIZE];
char request_uri[MAX_ELEMENT_SIZE];
char fragment[MAX_ELEMENT_SIZE];
char query_string[MAX_ELEMENT_SIZE];
char body[MAX_ELEMENT_SIZE];
int num_headers;
enum { NONE=0, FIELD, VALUE } last_header_element;
char headers [MAX_HEADERS][2][MAX_ELEMENT_SIZE];
int should_keep_alive;
};
static struct message messages[5];
static int num_messages;
/* * R E Q U E S T S * */
const struct message requests[] =
#define CURL_GET 0
{ {.name= "curl get"
,.type= HTTP_REQUEST
,.raw= "GET /test HTTP/1.1\r\n"
"User-Agent: curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1\r\n"
"Host: 0.0.0.0=5000\r\n"
"Accept: */*\r\n"
"\r\n"
,.should_keep_alive= TRUE
,.method= HTTP_GET
,.query_string= ""
,.fragment= ""
,.request_path= "/test"
,.request_uri= "/test"
,.num_headers= 3
,.headers=
{ { "User-Agent", "curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1" }
, { "Host", "0.0.0.0=5000" }
, { "Accept", "*/*" }
}
,.body= ""
}
#define FIREFOX_GET 1
, {.name= "firefox get"
,.type= HTTP_REQUEST
,.raw= "GET /favicon.ico HTTP/1.1\r\n"
"Host: 0.0.0.0=5000\r\n"
"User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0\r\n"
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
"Accept-Language: en-us,en;q=0.5\r\n"
"Accept-Encoding: gzip,deflate\r\n"
"Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n"
"Keep-Alive: 300\r\n"
"Connection: keep-alive\r\n"
"\r\n"
,.should_keep_alive= TRUE
,.method= HTTP_GET
,.query_string= ""
,.fragment= ""
,.request_path= "/favicon.ico"
,.request_uri= "/favicon.ico"
,.num_headers= 8
,.headers=
{ { "Host", "0.0.0.0=5000" }
, { "User-Agent", "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0" }
, { "Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" }
, { "Accept-Language", "en-us,en;q=0.5" }
, { "Accept-Encoding", "gzip,deflate" }
, { "Accept-Charset", "ISO-8859-1,utf-8;q=0.7,*;q=0.7" }
, { "Keep-Alive", "300" }
, { "Connection", "keep-alive" }
}
,.body= ""
}
#define DUMBFUCK 2
, {.name= "dumbfuck"
,.type= HTTP_REQUEST
,.raw= "GET /dumbfuck HTTP/1.1\r\n"
"aaaaaaaaaaaaa:++++++++++\r\n"
"\r\n"
,.should_keep_alive= TRUE
,.method= HTTP_GET
,.query_string= ""
,.fragment= ""
,.request_path= "/dumbfuck"
,.request_uri= "/dumbfuck"
,.num_headers= 1
,.headers=
{ { "aaaaaaaaaaaaa", "++++++++++" }
}
,.body= ""
}
#define FRAGMENT_IN_URI 3
, {.name= "fragment in uri"
,.type= HTTP_REQUEST
,.raw= "GET /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n"
"\r\n"
,.should_keep_alive= TRUE
,.method= HTTP_GET
,.query_string= "page=1"
,.fragment= "posts-17408"
,.request_path= "/forums/1/topics/2375"
/* XXX request uri does not include fragment? */
,.request_uri= "/forums/1/topics/2375?page=1"
,.num_headers= 0
,.body= ""
}
#define GET_NO_HEADERS_NO_BODY 4
, {.name= "get no headers no body"
,.type= HTTP_REQUEST
,.raw= "GET /get_no_headers_no_body/world HTTP/1.1\r\n"
"\r\n"
,.should_keep_alive= TRUE
,.method= HTTP_GET
,.query_string= ""
,.fragment= ""
,.request_path= "/get_no_headers_no_body/world"
,.request_uri= "/get_no_headers_no_body/world"
,.num_headers= 0
,.body= ""
}
#define GET_ONE_HEADER_NO_BODY 5
, {.name= "get one header no body"
,.type= HTTP_REQUEST
,.raw= "GET /get_one_header_no_body HTTP/1.1\r\n"
"Accept: */*\r\n"
"\r\n"
,.should_keep_alive= TRUE
,.method= HTTP_GET
,.query_string= ""
,.fragment= ""
,.request_path= "/get_one_header_no_body"
,.request_uri= "/get_one_header_no_body"
,.num_headers= 1
,.headers=
{ { "Accept" , "*/*" }
}
,.body= ""
}
#define GET_FUNKY_CONTENT_LENGTH 6
, {.name= "get funky content length body hello"
,.type= HTTP_REQUEST
,.raw= "GET /get_funky_content_length_body_hello HTTP/1.0\r\n"
"conTENT-Length: 5\r\n"
"\r\n"
"HELLO"
,.should_keep_alive= FALSE
,.method= HTTP_GET
,.query_string= ""
,.fragment= ""
,.request_path= "/get_funky_content_length_body_hello"
,.request_uri= "/get_funky_content_length_body_hello"
,.num_headers= 1
,.headers=
{ { "conTENT-Length" , "5" }
}
,.body= "HELLO"
}
#define POST_IDENTITY_BODY_WORLD 7
, {.name= "post identity body world"
,.type= HTTP_REQUEST
,.raw= "POST /post_identity_body_world?q=search#hey HTTP/1.1\r\n"
"Accept: */*\r\n"
"Transfer-Encoding: identity\r\n"
"Content-Length: 5\r\n"
"\r\n"
"World"
,.should_keep_alive= TRUE
,.method= HTTP_POST
,.query_string= "q=search"
,.fragment= "hey"
,.request_path= "/post_identity_body_world"
,.request_uri= "/post_identity_body_world?q=search"
,.num_headers= 3
,.headers=
{ { "Accept", "*/*" }
, { "Transfer-Encoding", "identity" }
, { "Content-Length", "5" }
}
,.body= "World"
}
#define POST_CHUNKED_ALL_YOUR_BASE 8
, {.name= "post - chunked body: all your base are belong to us"
,.type= HTTP_REQUEST
,.raw= "POST /post_chunked_all_your_base HTTP/1.1\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n"
"1e\r\nall your base are belong to us\r\n"
"0\r\n"
"\r\n"
,.should_keep_alive= TRUE
,.method= HTTP_POST
,.query_string= ""
,.fragment= ""
,.request_path= "/post_chunked_all_your_base"
,.request_uri= "/post_chunked_all_your_base"
,.num_headers= 1
,.headers=
{ { "Transfer-Encoding" , "chunked" }
}
,.body= "all your base are belong to us"
}
#define TWO_CHUNKS_MULT_ZERO_END 9
, {.name= "two chunks ; triple zero ending"
,.type= HTTP_REQUEST
,.raw= "POST /two_chunks_mult_zero_end HTTP/1.1\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n"
"5\r\nhello\r\n"
"6\r\n world\r\n"
"000\r\n"
"\r\n"
,.should_keep_alive= TRUE
,.method= HTTP_POST
,.query_string= ""
,.fragment= ""
,.request_path= "/two_chunks_mult_zero_end"
,.request_uri= "/two_chunks_mult_zero_end"
,.num_headers= 1
,.headers=
{ { "Transfer-Encoding", "chunked" }
}
,.body= "hello world"
}
#define CHUNKED_W_TRAILING_HEADERS 10
, {.name= "chunked with trailing headers. blech."
,.type= HTTP_REQUEST
,.raw= "POST /chunked_w_trailing_headers HTTP/1.1\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n"
"5\r\nhello\r\n"
"6\r\n world\r\n"
"0\r\n"
"Vary: *\r\n"
"Content-Type: text/plain\r\n"
"\r\n"
,.should_keep_alive= TRUE
,.method= HTTP_POST
,.query_string= ""
,.fragment= ""
,.request_path= "/chunked_w_trailing_headers"
,.request_uri= "/chunked_w_trailing_headers"
,.num_headers= 1
,.headers=
{ { "Transfer-Encoding", "chunked" }
}
,.body= "hello world"
}
#define CHUNKED_W_BULLSHIT_AFTER_LENGTH 11
, {.name= "with bullshit after the length"
,.type= HTTP_REQUEST
,.raw= "POST /chunked_w_bullshit_after_length HTTP/1.1\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n"
"5; ihatew3;whatthefuck=aretheseparametersfor\r\nhello\r\n"
"6; blahblah; blah\r\n world\r\n"
"0\r\n"
"\r\n"
,.should_keep_alive= TRUE
,.method= HTTP_POST
,.query_string= ""
,.fragment= ""
,.request_path= "/chunked_w_bullshit_after_length"
,.request_uri= "/chunked_w_bullshit_after_length"
,.num_headers= 1
,.headers=
{ { "Transfer-Encoding", "chunked" }
}
,.body= "hello world"
}
, {.name= NULL } /* sentinel */
};
/* * R E S P O N S E S * */
const struct message responses[] =
{ {.name= "google 301"
,.type= HTTP_RESPONSE
,.raw= "HTTP/1.1 301 Moved Permanently\r\n"
"Location: http://www.google.com/\r\n"
"Content-Type: text/html; charset=UTF-8\r\n"
"Date: Sun, 26 Apr 2009 11:11:49 GMT\r\n"
"Expires: Tue, 26 May 2009 11:11:49 GMT\r\n"
"Cache-Control: public, max-age=2592000\r\n"
"Server: gws\r\n"
"Content-Length: 219\r\n"
"\r\n"
"<HTML><HEAD><meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\">\n"
"<TITLE>301 Moved</TITLE></HEAD><BODY>\n"
"<H1>301 Moved</H1>\n"
"The document has moved\n"
"<A HREF=\"http://www.google.com/\">here</A>.\r\n"
"</BODY></HTML>\r\n"
,.should_keep_alive= TRUE
,.status_code= 301
,.num_headers= 7
,.headers=
{ { "Location", "http://www.google.com/" }
, { "Content-Type", "text/html; charset=UTF-8" }
, { "Date", "Sun, 26 Apr 2009 11:11:49 GMT" }
, { "Expires", "Tue, 26 May 2009 11:11:49 GMT" }
, { "Cache-Control", "public, max-age=2592000" }
, { "Server", "gws" }
, { "Content-Length", "219" }
}
,.body= "<HTML><HEAD><meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\">\n"
"<TITLE>301 Moved</TITLE></HEAD><BODY>\n"
"<H1>301 Moved</H1>\n"
"The document has moved\n"
"<A HREF=\"http://www.google.com/\">here</A>.\r\n"
"</BODY></HTML>\r\n"
}
, {.name= "404 no headers no body"
,.type= HTTP_RESPONSE
,.raw= "HTTP/1.1 404 Not Found\r\n\r\n"
,.should_keep_alive= TRUE
,.status_code= 404
,.num_headers= 0
,.headers= {}
,.body= ""
}
, {.name= NULL } /* sentinel */
};
int
request_path_cb (http_parser *_, const char *p, size_t len)
{
strncat(messages[num_messages].request_path, p, len);
return 0;
}
int
request_uri_cb (http_parser *_, const char *p, size_t len)
{
strncat(messages[num_messages].request_uri, p, len);
return 0;
}
int
query_string_cb (http_parser *_, const char *p, size_t len)
{
strncat(messages[num_messages].query_string, p, len);
return 0;
}
int
fragment_cb (http_parser *_, const char *p, size_t len)
{
strncat(messages[num_messages].fragment, p, len);
return 0;
}
int
header_field_cb (http_parser *_, const char *p, size_t len)
{
struct message *m = &messages[num_messages];
if (m->last_header_element != FIELD)
m->num_headers++;
strncat(m->headers[m->num_headers-1][0], p, len);
m->last_header_element = FIELD;
return 0;
}
int
header_value_cb (http_parser *_, const char *p, size_t len)
{
struct message *m = &messages[num_messages];
strncat(m->headers[m->num_headers-1][1], p, len);
m->last_header_element = VALUE;
return 0;
}
int
body_cb (http_parser *_, const char *p, size_t len)
{
strncat(messages[num_messages].body, p, len);
// printf("body_cb: '%s'\n", requests[num_messages].body);
return 0;
}
int
message_complete_cb (http_parser *parser)
{
messages[num_messages].method = parser->method;
messages[num_messages].status_code = parser->status_code;
num_messages++;
return 0;
}
int
message_begin_cb (http_parser *_)
{
return 0;
}
void
parser_init (enum http_parser_type type)
{
num_messages = 0;
http_parser_init(&parser, type);
memset(&messages, 0, sizeof messages);
parser.on_message_begin = message_begin_cb;
parser.on_header_field = header_field_cb;
parser.on_header_value = header_value_cb;
parser.on_path = request_path_cb;
parser.on_uri = request_uri_cb;
parser.on_fragment = fragment_cb;
parser.on_query_string = query_string_cb;
parser.on_body = body_cb;
parser.on_headers_complete = NULL;
parser.on_message_complete = message_complete_cb;
}
void
message_eq (int index, const struct message *expected)
{
int i;
struct message *m = &messages[index];
assert(m->method == expected->method);
assert(m->status_code == expected->status_code);
assert(0 == strcmp(m->body, expected->body));
assert(0 == strcmp(m->fragment, expected->fragment));
assert(0 == strcmp(m->query_string, expected->query_string));
assert(0 == strcmp(m->request_path, expected->request_path));
assert(0 == strcmp(m->request_uri, expected->request_uri));
assert(m->num_headers == expected->num_headers);
for (i = 0; i < m->num_headers; i++) {
assert(0 == strcmp(m->headers[i][0], expected->headers[i][0]));
assert(0 == strcmp(m->headers[i][1], expected->headers[i][1]));
}
}
void
parse_messages (int message_count, const struct message *input_messages[])
{
// Concat the input messages
size_t length = 0;
int i;
for (i = 0; i < message_count; i++) {
length += strlen(input_messages[i]->raw);
}
char total[length + 1];
total[0] = '\0';
for (i = 0; i < message_count; i++) {
strcat(total, input_messages[i]->raw);
}
// Parse the stream
size_t traversed = 0;
parser_init(HTTP_REQUEST);
traversed = http_parser_execute(&parser, total, length);
assert(!http_parser_has_error(&parser));
assert(num_messages == message_count);
for (i = 0; i < message_count; i++) {
message_eq(i, input_messages[i]);
}
}
void
test_message (const struct message *message)
{
size_t traversed = 0;
parser_init(message->type);
traversed = http_parser_execute(&parser, message->raw, strlen(message->raw));
assert(!http_parser_has_error(&parser));
assert(num_messages == 1);
message_eq(0, message);
}
void
test_error (const char *buf)
{
size_t traversed = 0;
parser_init(HTTP_REQUEST);
traversed = http_parser_execute(&parser, buf, strlen(buf));
assert(http_parser_has_error(&parser));
}
void
test_multiple3 (const struct message *r1, const struct message *r2, const struct message *r3)
{
char total[ strlen(r1->raw)
+ strlen(r2->raw)
+ strlen(r3->raw)
+ 1
];
total[0] = '\0';
strcat(total, r1->raw);
strcat(total, r2->raw);
strcat(total, r3->raw);
size_t traversed = 0;
parser_init(HTTP_REQUEST);
traversed = http_parser_execute(&parser, total, strlen(total));
assert(! http_parser_has_error(&parser) );
assert(num_messages == 3);
message_eq(0, r1);
message_eq(1, r2);
message_eq(2, r3);
}
/* SCAN through every possible breaking to make sure the
* parser can handle getting the content in any chunks that
* might come from the socket
*/
void
test_scan (const struct message *r1, const struct message *r2, const struct message *r3)
{
char total[80*1024] = "\0";
char buf1[80*1024] = "\0";
char buf2[80*1024] = "\0";
char buf3[80*1024] = "\0";
strcat(total, r1->raw);
strcat(total, r2->raw);
strcat(total, r3->raw);
int total_len = strlen(total);
int total_ops = (total_len - 1) * (total_len - 2) / 2;
int ops = 0 ;
int i,j;
for (j = 2; j < total_len; j ++ ) {
for (i = 1; i < j; i ++ ) {
if (ops % 1000 == 0) {
printf("\b\b\b\b%3.0f%%", 100 * (float)ops /(float)total_ops);
fflush(stdout);
}
ops += 1;
parser_init(HTTP_REQUEST);
int buf1_len = i;
strncpy(buf1, total, buf1_len);
buf1[buf1_len] = 0;
int buf2_len = j - i;
strncpy(buf2, total+i, buf2_len);
buf2[buf2_len] = 0;
int buf3_len = total_len - j;
strncpy(buf3, total+j, buf3_len);
buf3[buf3_len] = 0;
/*
printf("buf1: %s - %d\n", buf1, buf1_len);
printf("buf2: %s - %d \n", buf2, buf2_len );
printf("buf3: %s - %d\n\n", buf3, buf3_len);
*/
http_parser_execute(&parser, buf1, buf1_len);
assert(!http_parser_has_error(&parser));
http_parser_execute(&parser, buf2, buf2_len);
assert(!http_parser_has_error(&parser));
http_parser_execute(&parser, buf3, buf3_len);
assert(! http_parser_has_error(&parser));
assert(3 == num_messages);
message_eq(0, r1);
message_eq(1, r2);
message_eq(2, r3);
}
}
printf("\b\b\b\b100%\n");
}
int
main (void)
{
int i, j, k;
printf("sizeof(http_parser) = %d\n", sizeof(http_parser));
int request_count;
for (request_count = 0; requests[request_count].name; request_count++);
int response_count;
for (response_count = 0; responses[response_count].name; response_count++);
//// RESPONSES
for (i = 0; i < response_count; i++) {
test_message(&responses[i]);
}
puts("responses okay");
/// REQUESTS
test_error("hello world");
test_error("GET / HTP/1.1\r\n\r\n");
const char *dumbfuck2 =
"GET / HTTP/1.1\r\n"
"X-SSL-Bullshit: -----BEGIN CERTIFICATE-----\r\n"
"\tMIIFbTCCBFWgAwIBAgICH4cwDQYJKoZIhvcNAQEFBQAwcDELMAkGA1UEBhMCVUsx\r\n"
"\tETAPBgNVBAoTCGVTY2llbmNlMRIwEAYDVQQLEwlBdXRob3JpdHkxCzAJBgNVBAMT\r\n"
"\tAkNBMS0wKwYJKoZIhvcNAQkBFh5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMu\r\n"
"\tdWswHhcNMDYwNzI3MTQxMzI4WhcNMDcwNzI3MTQxMzI4WjBbMQswCQYDVQQGEwJV\r\n"
"\tSzERMA8GA1UEChMIZVNjaWVuY2UxEzARBgNVBAsTCk1hbmNoZXN0ZXIxCzAJBgNV\r\n"
"\tBAcTmrsogriqMWLAk1DMRcwFQYDVQQDEw5taWNoYWVsIHBhcmQYJKoZIhvcNAQEB\r\n"
"\tBQADggEPADCCAQoCggEBANPEQBgl1IaKdSS1TbhF3hEXSl72G9J+WC/1R64fAcEF\r\n"
"\tW51rEyFYiIeZGx/BVzwXbeBoNUK41OK65sxGuflMo5gLflbwJtHBRIEKAfVVp3YR\r\n"
"\tgW7cMA/s/XKgL1GEC7rQw8lIZT8RApukCGqOVHSi/F1SiFlPDxuDfmdiNzL31+sL\r\n"
"\t0iwHDdNkGjy5pyBSB8Y79dsSJtCW/iaLB0/n8Sj7HgvvZJ7x0fr+RQjYOUUfrePP\r\n"
"\tu2MSpFyf+9BbC/aXgaZuiCvSR+8Snv3xApQY+fULK/xY8h8Ua51iXoQ5jrgu2SqR\r\n"
"\twgA7BUi3G8LFzMBl8FRCDYGUDy7M6QaHXx1ZWIPWNKsCAwEAAaOCAiQwggIgMAwG\r\n"
"\tA1UdEwEB/wQCMAAwEQYJYIZIAYb4QgHTTPAQDAgWgMA4GA1UdDwEB/wQEAwID6DAs\r\n"
"\tBglghkgBhvhCAQ0EHxYdVUsgZS1TY2llbmNlIFVzZXIgQ2VydGlmaWNhdGUwHQYD\r\n"
"\tVR0OBBYEFDTt/sf9PeMaZDHkUIldrDYMNTBZMIGaBgNVHSMEgZIwgY+AFAI4qxGj\r\n"
"\tloCLDdMVKwiljjDastqooXSkcjBwMQswCQYDVQQGEwJVSzERMA8GA1UEChMIZVNj\r\n"
"\taWVuY2UxEjAQBgNVBAsTCUF1dGhvcml0eTELMAkGA1UEAxMCQ0ExLTArBgkqhkiG\r\n"
"\t9w0BCQEWHmNhLW9wZXJhdG9yQGdyaWQtc3VwcG9ydC5hYy51a4IBADApBgNVHRIE\r\n"
"\tIjAggR5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMudWswGQYDVR0gBBIwEDAO\r\n"
"\tBgwrBgEEAdkvAQEBAQYwPQYJYIZIAYb4QgEEBDAWLmh0dHA6Ly9jYS5ncmlkLXN1\r\n"
"\tcHBvcnQuYWMudmT4sopwqlBWsvcHViL2NybC9jYWNybC5jcmwwPQYJYIZIAYb4QgEDBDAWLmh0\r\n"
"\tdHA6Ly9jYS5ncmlkLXN1cHBvcnQuYWMudWsvcHViL2NybC9jYWNybC5jcmwwPwYD\r\n"
"\tVR0fBDgwNjA0oDKgMIYuaHR0cDovL2NhLmdyaWQt5hYy51ay9wdWIv\r\n"
"\tY3JsL2NhY3JsLmNybDANBgkqhkiG9w0BAQUFAAOCAQEAS/U4iiooBENGW/Hwmmd3\r\n"
"\tXCy6Zrt08YjKCzGNjorT98g8uGsqYjSxv/hmi0qlnlHs+k/3Iobc3LjS5AMYr5L8\r\n"
"\tUO7OSkgFFlLHQyC9JzPfmLCAugvzEbyv4Olnsr8hbxF1MbKZoQxUZtMVu29wjfXk\r\n"
"\thTeApBv7eaKCWpSp7MCbvgzm74izKhu3vlDk9w6qVrxePfGgpKPqfHiOoGhFnbTK\r\n"
"\twTC6o2xq5y0qZ03JonF7OJspEd3I5zKY3E+ov7/ZhW6DqT8UFvsAdjvQbXyhV8Eu\r\n"
"\tYhixw1aKEPzNjNowuIseVogKOLXxWI5vAi5HgXdS0/ES5gDGsABo4fqovUKlgop3\r\n"
"\tRA==\r\n"
"\t-----END CERTIFICATE-----\r\n"
"\r\n";
test_error(dumbfuck2);
// no content-length
// error if there is a body without content length
const char *bad_get_no_headers_no_body = "GET /bad_get_no_headers_no_body/world HTTP/1.1\r\n"
"Accept: */*\r\n"
"\r\n"
"HELLO";
test_error(bad_get_no_headers_no_body);
/* TODO sending junk and large headers gets rejected */
/* check to make sure our predefined requests are okay */
for (i = 0; requests[i].name; i++) {
test_message(&requests[i]);
}
for (i = 0; i < request_count; i++) {
for (j = 0; j < request_count; j++) {
for (k = 0; k < request_count; k++) {
//printf("%d %d %d\n", i, j, k);
test_multiple3(&requests[i], &requests[j], &requests[k]);
}
}
}
printf("request scan 1/3 ");
test_scan( &requests[GET_NO_HEADERS_NO_BODY]
, &requests[GET_ONE_HEADER_NO_BODY]
, &requests[GET_NO_HEADERS_NO_BODY]
);
printf("request scan 2/3 ");
test_scan( &requests[GET_FUNKY_CONTENT_LENGTH]
, &requests[POST_IDENTITY_BODY_WORLD]
, &requests[POST_CHUNKED_ALL_YOUR_BASE]
);
printf("request scan 3/3 ");
test_scan( &requests[TWO_CHUNKS_MULT_ZERO_END]
, &requests[CHUNKED_W_TRAILING_HEADERS]
, &requests[CHUNKED_W_BULLSHIT_AFTER_LENGTH]
);
puts("requests okay");
return 0;
}

5
deps/libebb/.gitignore

@ -1,5 +0,0 @@
*.o
examples/hello_world
test_request_parser
ebb_request_parser.c
tags

21
deps/libebb/LICENSE

@ -1,21 +0,0 @@
Copyright (c) 2008 Ryan Dahl (ry@ndahl.us)
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.

7
deps/libebb/README

@ -1,7 +0,0 @@
see doc/index.html and examples/hello_world.c for explanation
webpage: http://tinyclouds.org/libebb/
git repository: http://github.com/ry/libebb/tree/master
To build libebb please edit config.mk to reflect your system's parameters.

38
deps/libebb/config.mk

@ -1,38 +0,0 @@
PREFIX = $(HOME)/local/libebb
# libev
EVINC = $(HOME)/local/libev/include
EVLIB = $(HOME)/local/libev/lib
EVLIBS = -L${EVLIB} -lev
# GnuTLS, comment if you don't want it (necessary for HTTPS)
GNUTLSLIB = /usr/lib
GNUTLSINC = /usr/include
GNUTLSLIBS = -L${GNUTLSLIB} -lgnutls
GNUTLSFLAGS = -DHAVE_GNUTLS
# includes and libs
INCS = -I${EVINC} -I${GNUTLSINC}
LIBS = ${EVLIBS} ${GNUTLSLIBS} -lefence
# flags
CPPFLAGS = -DVERSION=\"$(VERSION)\" ${GNUTLSFLAGS}
CFLAGS = -O2 -g -Wall ${INCS} ${CPPFLAGS} -fPIC
LDFLAGS = -s ${LIBS}
LDOPT = -shared
SUFFIX = so
SONAME = -Wl,-soname,$(OUTPUT_LIB)
# Solaris
#CFLAGS = -fast ${INCS} -DVERSION=\"$(VERSION)\" -fPIC
#LDFLAGS = ${LIBS}
#SONAME =
# Darwin
# LDOPT = -dynamiclib
# SUFFIX = dylib
# SONAME = -current_version $(VERSION) -compatibility_version $(VERSION)
# compiler and linker
CC = cc
RANLIB = ranlib

BIN
deps/libebb/doc/icon.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

240
deps/libebb/doc/index.html

@ -1,240 +0,0 @@
<html>
<style>
body {
background: #fff;
color: #2e3436;
font-size: 12pt;
line-height: 16pt;
/* font-family: Palatino; */
margin: 3em 0 3em 3em;
}
code, pre {
}
#contents {
max-width: 40em;
}
ul {
padding-left: 0;
}
li {
margin-top: 0.5em;
margin-bottom: 0.5em;
}
p {
text-align: left;
}
img {
float: left;
margin: 0 1em 1em 0;
}
p { clear: both; }
</style>
<body> <div id="contents">
<img src="icon.png"/>
<h1>libebb</h1>
<p>
libebb is a lightweight HTTP server library for C. It lays the
foundation for writing a web server by providing the socket juggling
and request parsing. By implementing the HTTP/1.1 grammar provided in
RFC2612, libebb understands most most valid HTTP/1.1 connections
(persistent, pipelined, and chunked requests included) and rejects
invalid or malicious requests. libebb supports SSL over HTTP.
</p>
<p>
The library embraces a minimalistic single-threaded evented design.
No control is removed from the user. For example, all allocations are
done through callbacks so that the user might implement in optimal
ways for their specific application. By design libebb is not
thread-safe and all provided callbacks must not block. libebb uses
the <a href="http://libev.schmorp.de/bench.html">high-performance</a>
libev event loop, but does not control it. The user of the library may
start and stop the loop at will, they may attach thier own watchers.
</p>
<p>
libebb depends on POSIX sockets, libev, and optionally GnuTLS.
</p>
<p>
libebb is in the early stages of development and probably contains
many bugs. The API is subject to radical changes. If you're
interested <a href="http://github.com/ry/libebb/tree/master">checkout
the source code</a> and <a
href="http://groups.google.com/group/ebbebb">join the mailing
list</a>. A release will be made when it proves stable.
</p>
<p>libebb is released under <a
href="http://www.gnu.org/licenses/license-list.html#X11License">the
X11 license</a>.</p>
<h2>Usage</h2>
<p>
libebb is a simple API, mostly it is providing callbacks. There are
two types of callbacks that one will work with:
</p>
<ul>
<li>callbacks to allocate and initialize data for libebb. These are
named <code>new_*</code> like <code>new_connection</code> and
<code>new_request</code></li>
<li>callbacks which happen on an event and might provide a pointer to
a chunk of data. These are named <code>on_*</code> like
<code>on_body</code> and <code>on_close</code>.</li>
</ul>
<p>
In libebb there are three important classes: <code>ebb_server</code>,
<code>ebb_connection</code>, and <code>ebb_request</code>.
Each server has many peer connections. Each peer connection may have many
requests.
There are two additional classes <code>ebb_buf</code> and <code>ebb_request_parser</code>
which may or may not be useful.
</p>
<h3><code>ebb_server</code></h3>
<p>
<code>ebb_server</code> represents a single web server listening on a
single port. The user must allocate the structure themselves, then
call <code>ebb_server_init()</code> and provide a libev event loop.
<code>ebb_server_set_secure()</code> will make the server understand
HTTPS connections.
</p>
<p>
After initialized the <code>ebb_server_listen_on_port()</code> can be
called to open the server up to new connections. libebb does not
control the event loop, it is the user's responsibility to start the
event loop (using <code>ev_loop()</code>) after
<code>ebb_server_listen_on_port()</code> is called.
</p>
<p>
To accept connections you must provide the new server with a callback
called <code>new_connection</code>. This callback must return an allocated
and initialized <code>ebb_connection</code> structure.
To set this callback do
</p>
<pre>my_server-&gt;new_connection = my_new_connection_callback;</pre>
<p>
Additional documentation can be found in <code>ebb.h</code>
</p>
<h3><code>ebb_connection</code></h3>
<p>
This structure contains information and callbacks for a single client
connection. It is allocated and initialized through the
<code>new_connection</code> callback in <code>ebb_server</code>.
To initialize a newly allocated <code>ebb_connection</code> use
<code>ebb_connection_init()</code>.
</p>
<p>
After <code>ebb_connection_init()</code> is called a number of
callbacks can be set: <code>new_request</code>, <code>new_buf</code>,
<code>on_timeout</code>, and <code>on_close</code>.
</p>
<p>
When an <code>ebb_connection</code> is returned to an
<code>ebb_server</code>, data is immediately data is read from the
socket. This data must be stored somewhere. Because libebb is
agnostic about allocation decisions, it passes this off to the user in
the form of a callback: <code>connection-&gt;new_buf</code>. This
callback returns a newly allocated and initialized
<code>ebb_buf</code> structure. How much libebb attempts to read from
the socket is determined by how large the returned
<code>ebb_buf</code> structure is. Using <code>new_buf</code> is
optional. By default libebb reads data into a static buffer
(allocated at compile time), writing over it on each read. In many
web server using the static buffer will be sufficent because callbacks
made during the parsing will buffer the data elsewhere. Providing a
<code>new_buf</code> callback is necessary only if you want to save
the raw data coming from the socket.
</p>
<p>
The <code>new_request</code> callback is called at the beginning of a
request. It must return a newly allocated and initialized
<code>ebb_request</code> structure. Because HTTP/1.1 supports <a
href="http://en.wikipedia.org/wiki/HTTP_persistent_connection">peristant</a>
connections, there may be many requests per connection.
</p>
<p>
You may access the file descriptor for the client socket inside the
<code>ebb_connection</code> structure. Writing the response, in valid
HTTP, is the user's responsibility. Remember, requests must be
returned to client in the same order that they were received.
</p>
<p>
A convience function, <coe>ebb_connection_write</code>, is provided
which will write a single string to the peer. You may use
this function or you may write to the file descriptor directly.
</p>
<p>
To close a peer connection use
<code>ebb_connnection_schedule_close()</code>. Because SSL may require
some additional communication to close the connection properly, the
file descriptor cannot be closed immediately. The
<code>on_close</code> callback will be made when the peer socket is
finally closed.
<em>Only once <code>on_close</code> is called may the
user free the <code>ebb_connection</code> structure.</em>
</p>
<h3><code>ebb_request</code></h3>
<p>
This structure provides information about a request. For example,
<code>request-&gt;method == EBB_POST</code> would mean the method of
the request is <code>POST</code>. There are also many callbacks
which can be set to handle data from a request as it is parsed.
</p>
<p>
The <code>on_uri</code> callback and all other
<code>ebb_element_cb</code> callbacks provide pointers to request
data. The annoying thing is they do not necessarily provide a
complete string. This is because the reads from the socket may not
contain an entire request and for efficency libebb does not attempt to
buffer the data. Theoretically, one might receive an
<code>on_uri</code> callback 10 times, each providing just a single
character of the request's URI. See <code>ebb_request_parser.h</code> for
a full list of callbacks that you may provide. (If you don't set them,
they are ignored.)
</p>
<p>
The <code>on_complete</code> callback is called at the end of
each request.
<em>Only once <code>on_complete</code> is called may the
user free the <code>ebb_request</code> structure.</em>
</p>
<h2>Example</h2>
<p>
A simple example is provided in <code>examples/hello_world.c</code>.
</p>
</div></body>
</html>

798
deps/libebb/ebb.c

@ -1,798 +0,0 @@
/* This file is part of libebb.
*
* Copyright (c) 2008 Ryan Dahl (ry@ndahl.us)
* 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 <assert.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/tcp.h> /* TCP_NODELAY */
#include <netinet/in.h> /* inet_ntoa */
#include <arpa/inet.h> /* inet_ntoa */
#include <unistd.h>
#include <stdio.h> /* perror */
#include <errno.h> /* perror */
#include <stdlib.h> /* for the default methods */
#include <ev.h>
#include "ebb.h"
#include "ebb_request_parser.h"
#ifdef HAVE_GNUTLS
# include <gnutls/gnutls.h>
# include "rbtree.h" /* for session_cache */
#endif
#ifndef TRUE
# define TRUE 1
#endif
#ifndef FALSE
# define FALSE 0
#endif
#ifndef MIN
# define MIN(a,b) (a < b ? a : b)
#endif
#define error(FORMAT, ...) fprintf(stderr, "error: " FORMAT "\n", ##__VA_ARGS__)
#define CONNECTION_HAS_SOMETHING_TO_WRITE (connection->to_write != NULL)
static void
set_nonblock (int fd)
{
int flags = fcntl(fd, F_GETFL, 0);
int r = fcntl(fd, F_SETFL, flags | O_NONBLOCK);
assert(0 <= r && "Setting socket non-block failed!");
}
static ssize_t
nosigpipe_push(void *data, const void *buf, size_t len)
{
int fd = (int)data;
int flags = 0;
#ifdef MSG_NOSIGNAL
flags = MSG_NOSIGNAL;
#endif
return send(fd, buf, len, flags);
}
static void
close_connection(ebb_connection *connection)
{
#ifdef HAVE_GNUTLS
if(connection->server->secure)
ev_io_stop(connection->server->loop, &connection->handshake_watcher);
#endif
ev_io_stop(connection->server->loop, &connection->read_watcher);
ev_io_stop(connection->server->loop, &connection->write_watcher);
ev_timer_stop(connection->server->loop, &connection->timeout_watcher);
if(0 > close(connection->fd))
error("problem closing connection fd");
connection->open = FALSE;
if(connection->on_close)
connection->on_close(connection);
/* No access to the connection past this point!
* The user is allowed to free in the callback
*/
}
#ifdef HAVE_GNUTLS
#define GNUTLS_NEED_WRITE (gnutls_record_get_direction(connection->session) == 1)
#define GNUTLS_NEED_READ (gnutls_record_get_direction(connection->session) == 0)
#define EBB_MAX_SESSION_KEY 32
#define EBB_MAX_SESSION_VALUE 512
struct session_cache {
struct rbtree_node_t node;
gnutls_datum_t key;
gnutls_datum_t value;
char key_storage[EBB_MAX_SESSION_KEY];
char value_storage[EBB_MAX_SESSION_VALUE];
};
static int
session_cache_compare (void *left, void *right)
{
gnutls_datum_t *left_key = left;
gnutls_datum_t *right_key = right;
if(left_key->size < right_key->size)
return -1;
else if(left_key->size > right_key->size)
return 1;
else
return memcmp( left_key->data
, right_key->data
, MIN(left_key->size, right_key->size)
);
}
static int
session_cache_store(void *data, gnutls_datum_t key, gnutls_datum_t value)
{
rbtree tree = data;
if( tree == NULL
|| key.size > EBB_MAX_SESSION_KEY
|| value.size > EBB_MAX_SESSION_VALUE
) return -1;
struct session_cache *cache = gnutls_malloc(sizeof(struct session_cache));
memcpy (cache->key_storage, key.data, key.size);
cache->key.size = key.size;
cache->key.data = (void*)cache->key_storage;
memcpy (cache->value_storage, value.data, value.size);
cache->value.size = value.size;
cache->value.data = (void*)cache->value_storage;
cache->node.key = &cache->key;
cache->node.value = &cache;
rbtree_insert(tree, (rbtree_node)cache);
//printf("session_cache_store\n");
return 0;
}
static gnutls_datum_t
session_cache_retrieve (void *data, gnutls_datum_t key)
{
rbtree tree = data;
gnutls_datum_t res = { NULL, 0 };
struct session_cache *cache = rbtree_lookup(tree, &key);
if(cache == NULL)
return res;
res.size = cache->value.size;
res.data = gnutls_malloc (res.size);
if(res.data == NULL)
return res;
memcpy(res.data, cache->value.data, res.size);
//printf("session_cache_retrieve\n");
return res;
}
static int
session_cache_remove (void *data, gnutls_datum_t key)
{
rbtree tree = data;
if(tree == NULL)
return -1;
struct session_cache *cache = (struct session_cache *)rbtree_delete(tree, &key);
if(cache == NULL)
return -1;
gnutls_free(cache);
//printf("session_cache_remove\n");
return 0;
}
static void
on_handshake(struct ev_loop *loop ,ev_io *watcher, int revents)
{
ebb_connection *connection = watcher->data;
//printf("on_handshake\n");
assert(ev_is_active(&connection->timeout_watcher));
assert(!ev_is_active(&connection->read_watcher));
assert(!ev_is_active(&connection->write_watcher));
if(EV_ERROR & revents) {
error("on_handshake() got error event, closing connection.n");
goto error;
}
int r = gnutls_handshake(connection->session);
if(r < 0) {
if(gnutls_error_is_fatal(r)) goto error;
if(r == GNUTLS_E_INTERRUPTED || r == GNUTLS_E_AGAIN)
ev_io_set( watcher
, connection->fd
, (GNUTLS_NEED_WRITE ? EV_WRITE : EV_READ)
);
return;
}
ebb_connection_reset_timeout(connection);
ev_io_stop(loop, watcher);
ev_io_start(loop, &connection->read_watcher);
if(CONNECTION_HAS_SOMETHING_TO_WRITE)
ev_io_start(loop, &connection->write_watcher);
return;
error:
close_connection(connection);
}
#endif /* HAVE_GNUTLS */
/* Internal callback
* called by connection->timeout_watcher
*/
static void
on_timeout(struct ev_loop *loop, ev_timer *watcher, int revents)
{
ebb_connection *connection = watcher->data;
assert(watcher == &connection->timeout_watcher);
//printf("on_timeout\n");
/* if on_timeout returns true, we don't time out */
if(connection->on_timeout) {
int r = connection->on_timeout(connection);
if(r == EBB_AGAIN) {
ebb_connection_reset_timeout(connection);
return;
}
}
ebb_connection_schedule_close(connection);
}
/* Internal callback
* called by connection->read_watcher
*/
static void
on_readable(struct ev_loop *loop, ev_io *watcher, int revents)
{
ebb_connection *connection = watcher->data;
char recv_buffer[TCP_MAXWIN];
ssize_t recved;
//printf("on_readable\n");
// TODO -- why is this broken?
//assert(ev_is_active(&connection->timeout_watcher));
assert(watcher == &connection->read_watcher);
if(EV_ERROR & revents) {
error("on_readable() got error event, closing connection.");
goto error;
}
#ifdef HAVE_GNUTLS
assert(!ev_is_active(&connection->handshake_watcher));
if(connection->server->secure) {
recved = gnutls_record_recv( connection->session
, recv_buffer
, TCP_MAXWIN
);
if(recved <= 0) {
if(gnutls_error_is_fatal(recved)) goto error;
if( (recved == GNUTLS_E_INTERRUPTED || recved == GNUTLS_E_AGAIN)
&& GNUTLS_NEED_WRITE
) ev_io_start(loop, &connection->write_watcher);
return;
}
} else {
#endif /* HAVE_GNUTLS */
recved = recv(connection->fd, recv_buffer, TCP_MAXWIN, 0);
if(recved <= 0) goto error;
#ifdef HAVE_GNUTLS
}
#endif /* HAVE_GNUTLS */
ebb_connection_reset_timeout(connection);
ebb_request_parser_execute(&connection->parser, recv_buffer, recved);
/* parse error? just drop the client. screw the 400 response */
if(ebb_request_parser_has_error(&connection->parser)) goto error;
return;
error:
ebb_connection_schedule_close(connection);
}
/* Internal callback
* called by connection->write_watcher
*/
static void
on_writable(struct ev_loop *loop, ev_io *watcher, int revents)
{
ebb_connection *connection = watcher->data;
ssize_t sent;
//printf("on_writable\n");
assert(CONNECTION_HAS_SOMETHING_TO_WRITE);
assert(connection->written <= connection->to_write_len);
// TODO -- why is this broken?
//assert(ev_is_active(&connection->timeout_watcher));
assert(watcher == &connection->write_watcher);
if(connection->to_write == 0)
goto stop_writing;
#ifdef HAVE_GNUTLS
assert(!ev_is_active(&connection->handshake_watcher));
if(connection->server->secure) {
sent = gnutls_record_send( connection->session
, connection->to_write + connection->written
, connection->to_write_len - connection->written
);
if(sent < 0) {
if(gnutls_error_is_fatal(sent)) goto error;
if( (sent == GNUTLS_E_INTERRUPTED || sent == GNUTLS_E_AGAIN)
&& GNUTLS_NEED_READ
) ev_io_stop(loop, watcher);
return;
}
} else {
#endif /* HAVE_GNUTLS */
sent = nosigpipe_push( (void*)connection->fd
, connection->to_write + connection->written
, connection->to_write_len - connection->written
);
if(sent < 0) goto error;
if(sent == 0) return;
#ifdef HAVE_GNUTLS
}
#endif /* HAVE_GNUTLS */
ebb_connection_reset_timeout(connection);
connection->written += sent;
if(connection->written == connection->to_write_len) {
goto stop_writing;
}
return;
stop_writing:
ev_io_stop(loop, watcher);
connection->to_write = NULL;
if(connection->after_write_cb)
connection->after_write_cb(connection);
return;
error:
error("close connection on write.");
ebb_connection_schedule_close(connection);
}
#ifdef HAVE_GNUTLS
static void
on_goodbye_tls(struct ev_loop *loop, ev_io *watcher, int revents)
{
ebb_connection *connection = watcher->data;
assert(watcher == &connection->goodbye_tls_watcher);
if(EV_ERROR & revents) {
error("on_goodbye() got error event, closing connection.");
goto die;
}
int r = gnutls_bye(connection->session, GNUTLS_SHUT_RDWR);
if(r < 0) {
if(gnutls_error_is_fatal(r)) goto die;
if(r == GNUTLS_E_INTERRUPTED || r == GNUTLS_E_AGAIN)
ev_io_set( watcher
, connection->fd
, (GNUTLS_NEED_WRITE ? EV_WRITE : EV_READ)
);
return;
}
die:
ev_io_stop(loop, watcher);
if(connection->session)
gnutls_deinit(connection->session);
close_connection(connection);
}
#endif /* HAVE_GNUTLS*/
static void
on_goodbye(struct ev_loop *loop, ev_timer *watcher, int revents)
{
ebb_connection *connection = watcher->data;
assert(watcher == &connection->goodbye_watcher);
close_connection(connection);
}
static ebb_request*
new_request_wrapper(void *data)
{
ebb_connection *connection = data;
if(connection->new_request)
return connection->new_request(connection);
return NULL;
}
/* Internal callback
* Called by server->connection_watcher.
*/
static void
on_connection(struct ev_loop *loop, ev_io *watcher, int revents)
{
ebb_server *server = watcher->data;
//printf("on connection!\n");
assert(server->listening);
assert(server->loop == loop);
assert(&server->connection_watcher == watcher);
if(EV_ERROR & revents) {
error("on_connection() got error event, closing server.");
ebb_server_unlisten(server);
return;
}
struct sockaddr_in addr; // connector's address information
socklen_t addr_len = sizeof(addr);
int fd = accept( server->fd
, (struct sockaddr*) & addr
, & addr_len
);
if(fd < 0) {
perror("accept()");
return;
}
ebb_connection *connection = NULL;
if(server->new_connection)
connection = server->new_connection(server, &addr);
if(connection == NULL) {
close(fd);
return;
}
set_nonblock(fd);
connection->fd = fd;
connection->open = TRUE;
connection->server = server;
memcpy(&connection->sockaddr, &addr, addr_len);
if(server->port[0] != '\0')
connection->ip = inet_ntoa(connection->sockaddr.sin_addr);
#ifdef SO_NOSIGPIPE
int arg = 1;
setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &arg, sizeof(int));
#endif
#ifdef HAVE_GNUTLS
if(server->secure) {
gnutls_init(&connection->session, GNUTLS_SERVER);
gnutls_transport_set_lowat(connection->session, 0);
gnutls_set_default_priority(connection->session);
gnutls_credentials_set(connection->session, GNUTLS_CRD_CERTIFICATE, connection->server->credentials);
gnutls_transport_set_ptr(connection->session, (gnutls_transport_ptr) fd);
gnutls_transport_set_push_function(connection->session, nosigpipe_push);
gnutls_db_set_ptr (connection->session, &server->session_cache);
gnutls_db_set_store_function (connection->session, session_cache_store);
gnutls_db_set_retrieve_function (connection->session, session_cache_retrieve);
gnutls_db_set_remove_function (connection->session, session_cache_remove);
}
ev_io_set(&connection->handshake_watcher, connection->fd, EV_READ | EV_WRITE);
#endif /* HAVE_GNUTLS */
/* Note: not starting the write watcher until there is data to be written */
ev_io_set(&connection->write_watcher, connection->fd, EV_WRITE);
ev_io_set(&connection->read_watcher, connection->fd, EV_READ);
/* XXX: seperate error watcher? */
ev_timer_again(loop, &connection->timeout_watcher);
#ifdef HAVE_GNUTLS
if(server->secure) {
ev_io_start(loop, &connection->handshake_watcher);
return;
}
#endif
ev_io_start(loop, &connection->read_watcher);
}
/**
* Begin the server listening on a file descriptor. This DOES NOT start the
* event loop. Start the event loop after making this call.
*/
int
ebb_server_listen_on_fd(ebb_server *server, const int fd)
{
assert(server->listening == FALSE);
if (listen(fd, EBB_MAX_CONNECTIONS) < 0) {
perror("listen()");
return -1;
}
set_nonblock(fd); /* XXX superfluous? */
server->fd = fd;
server->listening = TRUE;
ev_io_set (&server->connection_watcher, server->fd, EV_READ);
ev_io_start (server->loop, &server->connection_watcher);
return server->fd;
}
/**
* Begin the server listening on a file descriptor This DOES NOT start the
* event loop. Start the event loop after making this call.
*/
int
ebb_server_listen_on_port(ebb_server *server, const int port)
{
int fd = -1;
struct linger ling = {0, 0};
struct sockaddr_in addr;
int flags = 1;
if ((fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket()");
goto error;
}
flags = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *)&flags, sizeof(flags));
setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (void *)&flags, sizeof(flags));
setsockopt(fd, SOL_SOCKET, SO_LINGER, (void *)&ling, sizeof(ling));
/* XXX: Sending single byte chunks in a response body? Perhaps there is a
* need to enable the Nagel algorithm dynamically. For now disabling.
*/
setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (void *)&flags, sizeof(flags));
/* the memset call clears nonstandard fields in some impementations that
* otherwise mess things up.
*/
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("bind()");
goto error;
}
int ret = ebb_server_listen_on_fd(server, fd);
if (ret >= 0) {
sprintf(server->port, "%d", port);
}
return ret;
error:
if(fd > 0) close(fd);
return -1;
}
/**
* Stops the server. Will not accept new connections. Does not drop
* existing connections.
*/
void
ebb_server_unlisten(ebb_server *server)
{
if(server->listening) {
ev_io_stop(server->loop, &server->connection_watcher);
close(server->fd);
server->port[0] = '\0';
server->listening = FALSE;
}
}
/**
* Initialize an ebb_server structure. After calling ebb_server_init set
* the callback server->new_connection and, optionally, callback data
* server->data. The new connection MUST be initialized with
* ebb_connection_init before returning it to the server.
*
* @param server the server to initialize
* @param loop a libev loop
*/
void
ebb_server_init(ebb_server *server, struct ev_loop *loop)
{
server->loop = loop;
server->listening = FALSE;
server->port[0] = '\0';
server->fd = -1;
server->connection_watcher.data = server;
ev_init (&server->connection_watcher, on_connection);
server->secure = FALSE;
#ifdef HAVE_GNUTLS
rbtree_init(&server->session_cache, session_cache_compare);
server->credentials = NULL;
#endif
server->new_connection = NULL;
server->data = NULL;
}
#ifdef HAVE_GNUTLS
/* similar to server_init.
*
* the user of secure server might want to set additional callbacks from
* GNUTLS. In particular
* gnutls_global_set_mem_functions()
* gnutls_global_set_log_function()
* Also see the note above ebb_connection_init() about setting gnutls cache
* access functions
*
* cert_file: the filename of a PEM certificate file
*
* key_file: the filename of a private key. Currently only PKCS-1 encoded
* RSA and DSA private keys are accepted.
*/
int
ebb_server_set_secure (ebb_server *server, const char *cert_file, const char *key_file)
{
server->secure = TRUE;
gnutls_global_init();
gnutls_certificate_allocate_credentials(&server->credentials);
/* todo gnutls_certificate_free_credentials */
int r = gnutls_certificate_set_x509_key_file( server->credentials
, cert_file
, key_file
, GNUTLS_X509_FMT_PEM
);
if(r < 0) {
error("loading certificates");
return -1;
}
return 1;
}
#endif /* HAVE_GNUTLS */
/**
* Initialize an ebb_connection structure. After calling this function you
* must setup callbacks for the different actions the server can take. See
* server.h for which callbacks are availible.
*
* This should be called immediately after allocating space for a new
* ebb_connection structure. Most likely, this will only be called within
* the ebb_server->new_connection callback which you supply.
*
* If using SSL do consider setting
* gnutls_db_set_retrieve_function (connection->session, _);
* gnutls_db_set_remove_function (connection->session, _);
* gnutls_db_set_store_function (connection->session, _);
* gnutls_db_set_ptr (connection->session, _);
* To provide a better means of storing SSL session caches. libebb provides
* only a simple default implementation.
*
* @param connection the connection to initialize
* @param timeout the timeout in seconds
*/
void
ebb_connection_init(ebb_connection *connection)
{
connection->fd = -1;
connection->server = NULL;
connection->ip = NULL;
connection->open = FALSE;
ebb_request_parser_init( &connection->parser );
connection->parser.data = connection;
connection->parser.new_request = new_request_wrapper;
ev_init (&connection->write_watcher, on_writable);
connection->write_watcher.data = connection;
connection->to_write = NULL;
ev_init(&connection->read_watcher, on_readable);
connection->read_watcher.data = connection;
#ifdef HAVE_GNUTLS
connection->handshake_watcher.data = connection;
ev_init(&connection->handshake_watcher, on_handshake);
ev_init(&connection->goodbye_tls_watcher, on_goodbye_tls);
connection->goodbye_tls_watcher.data = connection;
connection->session = NULL;
#endif /* HAVE_GNUTLS */
ev_timer_init(&connection->goodbye_watcher, on_goodbye, 0., 0.);
connection->goodbye_watcher.data = connection;
ev_timer_init(&connection->timeout_watcher, on_timeout, 0., EBB_DEFAULT_TIMEOUT);
connection->timeout_watcher.data = connection;
connection->new_request = NULL;
connection->on_timeout = NULL;
connection->on_close = NULL;
connection->data = NULL;
}
void
ebb_connection_schedule_close (ebb_connection *connection)
{
#ifdef HAVE_GNUTLS
if(connection->server->secure) {
ev_io_set(&connection->goodbye_tls_watcher, connection->fd, EV_READ | EV_WRITE);
ev_io_start(connection->server->loop, &connection->goodbye_tls_watcher);
return;
}
#endif
ev_timer_start(connection->server->loop, &connection->goodbye_watcher);
}
/*
* Resets the timeout to stay alive for another connection->timeout seconds
*/
void
ebb_connection_reset_timeout(ebb_connection *connection)
{
ev_timer_again(connection->server->loop, &connection->timeout_watcher);
}
/**
* Writes a string to the socket. This is actually sets a watcher
* which may take multiple iterations to write the entire string.
*
* This can only be called once at a time. If you call it again
* while the connection is writing another buffer the ebb_connection_write
* will return FALSE and ignore the request.
*/
int
ebb_connection_write (ebb_connection *connection, const char *buf, size_t len, ebb_after_write_cb cb)
{
if(ev_is_active(&connection->write_watcher))
return FALSE;
assert(!CONNECTION_HAS_SOMETHING_TO_WRITE);
connection->to_write = buf;
connection->to_write_len = len;
connection->written = 0;
connection->after_write_cb = cb;
ev_io_start(connection->server->loop, &connection->write_watcher);
return TRUE;
}

120
deps/libebb/ebb.h

@ -1,120 +0,0 @@
/* This file is part of libebb.
*
* Copyright (c) 2008 Ryan Dahl (ry@ndahl.us)
* 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.
*/
#ifndef EBB_H
#define EBB_H
#include <sys/socket.h>
#include <netinet/in.h>
#include <ev.h>
#ifdef HAVE_GNUTLS
# include <gnutls/gnutls.h>
# include "rbtree.h" /* for ebb_server.session_cache */
#endif
#include "ebb_request_parser.h"
#define EBB_MAX_CONNECTIONS 1024
#define EBB_DEFAULT_TIMEOUT 30.0
#define EBB_AGAIN 0
#define EBB_STOP 1
typedef struct ebb_server ebb_server;
typedef struct ebb_connection ebb_connection;
typedef void (*ebb_after_write_cb) (ebb_connection *connection);
typedef void (*ebb_connection_cb)(ebb_connection *connection, void *data);
struct ebb_server {
int fd; /* ro */
struct sockaddr_in sockaddr; /* ro */
socklen_t socklen; /* ro */
char port[6]; /* ro */
struct ev_loop *loop; /* ro */
unsigned listening:1; /* ro */
unsigned secure:1; /* ro */
#ifdef HAVE_GNUTLS
gnutls_certificate_credentials_t credentials; /* private */
struct rbtree_t session_cache; /* private */
#endif
ev_io connection_watcher; /* private */
/* Public */
/* Allocates and initializes an ebb_connection. NULL by default. */
ebb_connection* (*new_connection) (ebb_server*, struct sockaddr_in*);
void *data;
};
struct ebb_connection {
int fd; /* ro */
struct sockaddr_in sockaddr; /* ro */
socklen_t socklen; /* ro */
ebb_server *server; /* ro */
char *ip; /* ro */
unsigned open:1; /* ro */
const char *to_write; /* ro */
size_t to_write_len; /* ro */
size_t written; /* ro */
ebb_after_write_cb after_write_cb; /* ro */
ebb_request_parser parser; /* private */
ev_io write_watcher; /* private */
ev_io read_watcher; /* private */
ev_timer timeout_watcher; /* private */
ev_timer goodbye_watcher; /* private */
#ifdef HAVE_GNUTLS
ev_io handshake_watcher; /* private */
gnutls_session_t session; /* private */
ev_io goodbye_tls_watcher; /* private */
#endif
/* Public */
ebb_request* (*new_request) (ebb_connection*);
/* Returns EBB_STOP or EBB_AGAIN. NULL by default. */
int (*on_timeout) (ebb_connection*);
void (*on_close) (ebb_connection*);
void *data;
};
void ebb_server_init (ebb_server *server, struct ev_loop *loop);
#ifdef HAVE_GNUTLS
int ebb_server_set_secure (ebb_server *server, const char *cert_file,
const char *key_file);
#endif
int ebb_server_listen_on_port (ebb_server *server, const int port);
int ebb_server_listen_on_fd (ebb_server *server, const int sfd);
void ebb_server_unlisten (ebb_server *server);
void ebb_connection_init (ebb_connection *);
void ebb_connection_schedule_close (ebb_connection *);
void ebb_connection_reset_timeout (ebb_connection *);
int ebb_connection_write (ebb_connection *, const char *buf, size_t len, ebb_after_write_cb);
#endif

117
deps/libebb/ebb_request_parser.h

@ -1,117 +0,0 @@
/* This file is part of the libebb web server library
*
* Copyright (c) 2008 Ryan Dahl (ry@ndahl.us)
* All rights reserved.
*
* This parser is based on code from Zed Shaw's Mongrel.
* Copyright (c) 2005 Zed A. Shaw
*
* 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.
*/
#ifndef ebb_request_parser_h
#define ebb_request_parser_h
#ifdef __cplusplus
extern "C" {
#endif
#include <sys/types.h>
typedef struct ebb_request ebb_request;
typedef struct ebb_request_parser ebb_request_parser;
typedef void (*ebb_header_cb)(ebb_request*, const char *at, size_t length, int header_index);
typedef void (*ebb_element_cb)(ebb_request*, const char *at, size_t length);
#define EBB_MAX_MULTIPART_BOUNDARY_LEN 20
/* HTTP Methods */
#define EBB_COPY 0x00000001
#define EBB_DELETE 0x00000002
#define EBB_GET 0x00000004
#define EBB_HEAD 0x00000008
#define EBB_LOCK 0x00000010
#define EBB_MKCOL 0x00000020
#define EBB_MOVE 0x00000040
#define EBB_OPTIONS 0x00000080
#define EBB_POST 0x00000100
#define EBB_PROPFIND 0x00000200
#define EBB_PROPPATCH 0x00000400
#define EBB_PUT 0x00000800
#define EBB_TRACE 0x00001000
#define EBB_UNLOCK 0x00002000
/* Transfer Encodings */
#define EBB_IDENTITY 0x00000001
#define EBB_CHUNKED 0x00000002
struct ebb_request {
int method;
int transfer_encoding; /* ro */
int expect_continue; /* ro */
unsigned int version_major; /* ro */
unsigned int version_minor; /* ro */
int number_of_headers; /* ro */
int keep_alive; /* private - use ebb_request_should_keep_alive */
size_t content_length; /* ro - 0 if unknown */
size_t body_read; /* ro */
/* Public - ordered list of callbacks */
ebb_element_cb on_path;
ebb_element_cb on_query_string;
ebb_element_cb on_uri;
ebb_element_cb on_fragment;
ebb_header_cb on_header_field;
ebb_header_cb on_header_value;
void (*on_headers_complete)(ebb_request *);
ebb_element_cb on_body;
void (*on_complete)(ebb_request *);
void *data;
};
struct ebb_request_parser {
int cs; /* private */
size_t chunk_size; /* private */
unsigned eating:1; /* private */
ebb_request *current_request; /* ro */
const char *header_field_mark;
const char *header_value_mark;
const char *query_string_mark;
const char *path_mark;
const char *uri_mark;
const char *fragment_mark;
/* Public */
ebb_request* (*new_request)(void*);
void *data;
};
void ebb_request_parser_init(ebb_request_parser *parser);
size_t ebb_request_parser_execute(ebb_request_parser *parser, const char *data, size_t len);
int ebb_request_parser_has_error(ebb_request_parser *parser);
int ebb_request_parser_is_finished(ebb_request_parser *parser);
void ebb_request_init(ebb_request *);
int ebb_request_should_keep_alive(ebb_request *request);
#define ebb_request_has_body(request) \
(request->transfer_encoding == EBB_CHUNKED || request->content_length > 0 )
#ifdef __cplusplus
}
#endif
#endif

413
deps/libebb/ebb_request_parser.rl

@ -1,413 +0,0 @@
/* This file is part of the libebb web server library
*
* Copyright (c) 2008 Ryan Dahl (ry@ndahl.us)
* All rights reserved.
*
* This parser is based on code from Zed Shaw's Mongrel.
* Copyright (c) 2005 Zed A. Shaw
*
* 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 "ebb_request_parser.h"
#include <stdio.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 REMAINING (pe - p)
#define CURRENT (parser->current_request)
#define CONTENT_LENGTH (parser->current_request->content_length)
#define CALLBACK(FOR) \
if(parser->FOR##_mark && CURRENT->on_##FOR) { \
CURRENT->on_##FOR( CURRENT \
, parser->FOR##_mark \
, p - parser->FOR##_mark \
); \
}
#define HEADER_CALLBACK(FOR) \
if(parser->FOR##_mark && CURRENT->on_##FOR) { \
CURRENT->on_##FOR( CURRENT \
, parser->FOR##_mark \
, p - parser->FOR##_mark \
, CURRENT->number_of_headers \
); \
}
#define END_REQUEST \
if(CURRENT->on_complete) \
CURRENT->on_complete(CURRENT); \
CURRENT = NULL;
%%{
machine ebb_request_parser;
action mark_header_field { parser->header_field_mark = p; }
action mark_header_value { parser->header_value_mark = p; }
action mark_fragment { parser->fragment_mark = p; }
action mark_query_string { parser->query_string_mark = p; }
action mark_request_path { parser->path_mark = p; }
action mark_request_uri { parser->uri_mark = p; }
action write_field {
HEADER_CALLBACK(header_field);
parser->header_field_mark = NULL;
}
action write_value {
HEADER_CALLBACK(header_value);
parser->header_value_mark = NULL;
}
action request_uri {
CALLBACK(uri);
parser->uri_mark = NULL;
}
action fragment {
CALLBACK(fragment);
parser->fragment_mark = NULL;
}
action query_string {
CALLBACK(query_string);
parser->query_string_mark = NULL;
}
action request_path {
CALLBACK(path);
parser->path_mark = NULL;
}
action content_length {
CURRENT->content_length *= 10;
CURRENT->content_length += *p - '0';
}
action use_identity_encoding { CURRENT->transfer_encoding = EBB_IDENTITY; }
action use_chunked_encoding { CURRENT->transfer_encoding = EBB_CHUNKED; }
action set_keep_alive { CURRENT->keep_alive = TRUE; }
action set_not_keep_alive { CURRENT->keep_alive = FALSE; }
action expect_continue {
CURRENT->expect_continue = TRUE;
}
action trailer {
/* not implemenetd yet. (do requests even have trailing headers?) */
}
action version_major {
CURRENT->version_major *= 10;
CURRENT->version_major += *p - '0';
}
action version_minor {
CURRENT->version_minor *= 10;
CURRENT->version_minor += *p - '0';
}
action end_header_line {
CURRENT->number_of_headers++;
}
action end_headers {
if(CURRENT->on_headers_complete)
CURRENT->on_headers_complete(CURRENT);
}
action add_to_chunk_size {
parser->chunk_size *= 16;
parser->chunk_size += unhex[(int)*p];
}
action skip_chunk_data {
skip_body(&p, parser, MIN(parser->chunk_size, REMAINING));
fhold;
if(parser->chunk_size > REMAINING) {
fbreak;
} else {
fgoto chunk_end;
}
}
action end_chunked_body {
END_REQUEST;
fnext main;
}
action start_req {
assert(CURRENT == NULL);
CURRENT = parser->new_request(parser->data);
}
action body_logic {
if(CURRENT->transfer_encoding == EBB_CHUNKED) {
fnext ChunkedBody;
} else {
/* this is pretty stupid. i'd prefer to combine this with skip_chunk_data */
parser->chunk_size = CURRENT->content_length;
p += 1;
skip_body(&p, parser, MIN(REMAINING, CURRENT->content_length));
fhold;
if(parser->chunk_size > REMAINING) {
fbreak;
}
}
}
#
##
###
#### HTTP/1.1 STATE MACHINE
###
## RequestHeaders and character types are from
# Zed Shaw's beautiful Mongrel parser.
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" %{ CURRENT->method = EBB_COPY; }
| "DELETE" %{ CURRENT->method = EBB_DELETE; }
| "GET" %{ CURRENT->method = EBB_GET; }
| "HEAD" %{ CURRENT->method = EBB_HEAD; }
| "LOCK" %{ CURRENT->method = EBB_LOCK; }
| "MKCOL" %{ CURRENT->method = EBB_MKCOL; }
| "MOVE" %{ CURRENT->method = EBB_MOVE; }
| "OPTIONS" %{ CURRENT->method = EBB_OPTIONS; }
| "POST" %{ CURRENT->method = EBB_POST; }
| "PROPFIND" %{ CURRENT->method = EBB_PROPFIND; }
| "PROPPATCH" %{ CURRENT->method = EBB_PROPPATCH; }
| "PUT" %{ CURRENT->method = EBB_PUT; }
| "TRACE" %{ CURRENT->method = EBB_TRACE; }
| "UNLOCK" %{ CURRENT->method = EBB_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 %write_field;
field_value = ((any - " ") any*)?;
Field_Value = field_value >mark_header_value %write_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)
# | ("Expect"i hsep "100-continue"i %expect_continue)
# | ("Trailer"i hsep field_value %trailer)
| (Field_Name hsep Field_Value)
) :> CRLF;
Request_Line = ( Method " " Request_URI ("#" Fragment)? " " HTTP_Version CRLF ) ;
RequestHeader = Request_Line (Header %end_header_line)* :> CRLF @end_headers;
# 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 = RequestHeader >start_req @body_logic;
main := Request*; # sequence of requests (for keep-alive)
}%%
%% write data;
static void
skip_body(const char **p, ebb_request_parser *parser, size_t nskip) {
if(CURRENT->on_body && nskip > 0) {
CURRENT->on_body(CURRENT, *p, nskip);
}
CURRENT->body_read += nskip;
parser->chunk_size -= nskip;
*p += nskip;
if(0 == parser->chunk_size) {
parser->eating = FALSE;
if(CURRENT->transfer_encoding == EBB_IDENTITY) {
END_REQUEST;
}
} else {
parser->eating = TRUE;
}
}
void ebb_request_parser_init(ebb_request_parser *parser)
{
int cs = 0;
%% write init;
parser->cs = cs;
parser->chunk_size = 0;
parser->eating = 0;
parser->current_request = NULL;
parser->header_field_mark = parser->header_value_mark =
parser->query_string_mark = parser->path_mark =
parser->uri_mark = parser->fragment_mark = NULL;
parser->new_request = NULL;
}
/** exec **/
size_t ebb_request_parser_execute(ebb_request_parser *parser, const char *buffer, size_t len)
{
const char *p, *pe;
int cs = parser->cs;
assert(parser->new_request && "undefined callback");
p = buffer;
pe = buffer+len;
if(0 < parser->chunk_size && parser->eating) {
/* eat body */
size_t eat = MIN(len, parser->chunk_size);
skip_body(&p, parser, eat);
}
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;
HEADER_CALLBACK(header_field);
HEADER_CALLBACK(header_value);
CALLBACK(fragment);
CALLBACK(query_string);
CALLBACK(path);
CALLBACK(uri);
assert(p <= pe && "buffer overflow after parsing execute");
return(p - buffer);
}
int ebb_request_parser_has_error(ebb_request_parser *parser)
{
return parser->cs == ebb_request_parser_error;
}
int ebb_request_parser_is_finished(ebb_request_parser *parser)
{
return parser->cs == ebb_request_parser_first_final;
}
void ebb_request_init(ebb_request *request)
{
request->expect_continue = FALSE;
request->body_read = 0;
request->content_length = 0;
request->version_major = 0;
request->version_minor = 0;
request->number_of_headers = 0;
request->transfer_encoding = EBB_IDENTITY;
request->keep_alive = -1;
request->on_complete = NULL;
request->on_headers_complete = NULL;
request->on_body = NULL;
request->on_header_field = NULL;
request->on_header_value = NULL;
request->on_uri = NULL;
request->on_fragment = NULL;
request->on_path = NULL;
request->on_query_string = NULL;
}
int ebb_request_should_keep_alive(ebb_request *request)
{
if(request->keep_alive == -1)
if(request->version_major == 1)
return (request->version_minor != 0);
else if(request->version_major == 0)
return FALSE;
else
return TRUE;
else
return request->keep_alive;
}

12
deps/libebb/examples/ca-cert.pem

@ -1,12 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIBzDCCATegAwIBAgIESIuNVTALBgkqhkiG9w0BAQUwADAeFw0wODA3MjYyMDQ3
MTlaFw0xMTA0MjIyMDQ3MjVaMAAwgZwwCwYJKoZIhvcNAQEBA4GMADCBiAKBgOm9
l/FoXbTIcEusk/QlS5YrlR04+oWIbSdZIf3GJBEWEUPljDxAX96qHsTcaVnGK+EP
keU4cIZvdY+hzbqa5cc1j2/9IeJNejL8gpQ/ocyMM69yq5Ib2F8K4mGWm1xr30hU
bYpY5D0MrZ1b0HtYFVc8KVAr0ADGG+pye0P9c3B/AgMBAAGjWjBYMAwGA1UdEwEB
/wQCMAAwFAYDVR0RBA0wC4IJbG9jYWxob3N0MBMGA1UdJQQMMAoGCCsGAQUFBwMB
MB0GA1UdDgQWBBTYgVB7kJnnm+jgX9DgrapzGfUmxjALBgkqhkiG9w0BAQUDgYEA
GkadA2H8CAzU3w4oCGZu9Ry9Tj/9Agw1XMFKvoJuG7VLPk7+B25JvNFVsmpROLxO
0TJ6mIU2hz5/rLvEfTBGQ+DYtbsjIxCz1fD7R5c1kKBtA0d0u8mY8pTlPNlxFPSW
3ymx5DB2zyDa/HuX6m6/VmzMYmA0vp7Dp1cl+pA9Nhs=
-----END CERTIFICATE-----

15
deps/libebb/examples/ca-key.pem

@ -1,15 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQDpvZfxaF20yHBLrJP0JUuWK5UdOPqFiG0nWSH9xiQRFhFD5Yw8
QF/eqh7E3GlZxivhD5HlOHCGb3WPoc26muXHNY9v/SHiTXoy/IKUP6HMjDOvcquS
G9hfCuJhlptca99IVG2KWOQ9DK2dW9B7WBVXPClQK9AAxhvqcntD/XNwfwIDAQAB
AoGAJqo3LTbfcV1KvinhG5zjwQaalwfq4RXtQHoNFmalZrIozvt01C6t7S5lApmX
T8NpVMR3lNxeOM7NOqJAXuLqqVVqk81YEYuMx6E4gB/Ifl7jVZk1jstmLILhh59D
pXrlpzvvm5X2hVsI7lp/YGAvtdLS1iVy37bGgmQWfCeeZiECQQDtZLfcJb4oE1yR
ZfLOcPDlBCw02wGMNFpAjwbspf/du3Yn3ONWHVfhSCCcCe262h9PLblL97LoB+gF
OHOlM9JvAkEA/A+U3/p9pwL4L742pxZP62/rmk6p5mZFIykk2jIUTpdilXBWBlcT
OjgjnpquZpwnaClmvgFpkzdhIPF7Nq4K8QJBAITVKagOmnOUOeTF1fI78h9DkXTV
4uzP0nxzS52ZWS16Gqg9ihuCecz97flB+Prn2EMWw6tFY58/5U0ehF85OxMCQQDF
08TYdVSg+6emcPeb89sNwW18UjjuZ13j1qrhxWRCunXZK62YlEa27tCl7mjqh6w2
CChm/9zIejJ1FJHLvJVBAkBj63ZbwggMYkxuj60jIBbNrEtDx9y7zM0sXkiJqcKp
5uGtJNafG+yZrLAHE6/b4aqUOtGsCGsiZpT9ms7CoaVr
-----END RSA PRIVATE KEY-----

101
deps/libebb/examples/hello_world.c

@ -1,101 +0,0 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <ev.h>
#include "ebb.h"
#define MSG ("HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 12\r\n\r\nhello world\n")
static int c = 0;
struct hello_connection {
unsigned int responses_to_write;
};
void on_close(ebb_connection *connection)
{
free(connection->data);
free(connection);
}
static void continue_responding(ebb_connection *connection)
{
int r;
struct hello_connection *connection_data = connection->data;
//printf("response complete \n");
if(--connection_data->responses_to_write > 0) {
/* write another response */
r = ebb_connection_write(connection, MSG, sizeof MSG, continue_responding);
assert(r);
} else {
ebb_connection_schedule_close(connection);
}
}
static void request_complete(ebb_request *request)
{
//printf("request complete \n");
ebb_connection *connection = request->data;
struct hello_connection *connection_data = connection->data;
if(ebb_request_should_keep_alive(request))
connection_data->responses_to_write++;
else
connection_data->responses_to_write = 1;
ebb_connection_write(connection, MSG, sizeof MSG, continue_responding);
free(request);
}
static ebb_request* new_request(ebb_connection *connection)
{
//printf("request %d\n", ++c);
ebb_request *request = malloc(sizeof(ebb_request));
ebb_request_init(request);
request->data = connection;
request->on_complete = request_complete;
return request;
}
ebb_connection* new_connection(ebb_server *server, struct sockaddr_in *addr)
{
struct hello_connection *connection_data = malloc(sizeof(struct hello_connection));
if(connection_data == NULL)
return NULL;
connection_data->responses_to_write = 0;
ebb_connection *connection = malloc(sizeof(ebb_connection));
if(connection == NULL) {
free(connection_data);
return NULL;
}
ebb_connection_init(connection);
connection->data = connection_data;
connection->new_request = new_request;
connection->on_close = on_close;
printf("connection: %d\n", c++);
return connection;
}
int main(int argc, char **_)
{
struct ev_loop *loop = ev_default_loop(0);
ebb_server server;
ebb_server_init(&server, loop);
if(argc > 1) {
printf("using SSL\n");
ebb_server_set_secure(&server, "ca-cert.pem", "ca-key.pem");
}
server.new_connection = new_connection;
printf("hello_world listening on port 5000\n");
ebb_server_listen_on_port(&server, 5000);
ev_loop(loop, 0);
return 0;
}

412
deps/libebb/rbtree.c

@ -1,412 +0,0 @@
/* Copyright (c) 2008 Derrick Coetzee
* http://en.literateprograms.org/Red-black_tree_(C)?oldid=7982
*
* 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 "rbtree.h"
#include <assert.h>
#ifndef NULL
# define NULL ((void*)0)
#endif
typedef rbtree_node node;
typedef enum rbtree_node_color color;
static node grandparent(node n);
static node sibling(node n);
static node uncle(node n);
static void verify_properties(rbtree t);
static void verify_property_1(node root);
/* static void verify_property_2(node root); */
static color node_color(node n);
static void verify_property_4(node root);
/* static void verify_property_5(node root); */
static void verify_property_5_helper(node n, int black_count, int* black_count_path);
static node lookup_node(rbtree t, void* key);
static void rotate_left(rbtree t, node n);
static void rotate_right(rbtree t, node n);
static void replace_node(rbtree t, node oldn, node newn);
static void insert_case1(rbtree t, node n);
static void insert_case2(rbtree t, node n);
static void insert_case3(rbtree t, node n);
static void insert_case4(rbtree t, node n);
static void insert_case5(rbtree t, node n);
static node maximum_node(node root);
static void delete_case1(rbtree t, node n);
static void delete_case2(rbtree t, node n);
static void delete_case3(rbtree t, node n);
static void delete_case4(rbtree t, node n);
static void delete_case5(rbtree t, node n);
static void delete_case6(rbtree t, node n);
node grandparent(node n) {
assert (n != NULL);
assert (n->parent != NULL); /* Not the root node */
assert (n->parent->parent != NULL); /* Not child of root */
return n->parent->parent;
}
node sibling(node n) {
assert (n != NULL);
assert (n->parent != NULL); /* Root node has no sibling */
if (n == n->parent->left)
return n->parent->right;
else
return n->parent->left;
}
node uncle(node n) {
assert (n != NULL);
assert (n->parent != NULL); /* Root node has no uncle */
assert (n->parent->parent != NULL); /* Children of root have no uncle */
return sibling(n->parent);
}
void verify_properties(rbtree t) {
#ifdef VERIFY_RBTREE
verify_property_1(t->root);
verify_property_2(t->root);
/* Property 3 is implicit */
verify_property_4(t->root);
verify_property_5(t->root);
#endif
}
void verify_property_1(node n) {
assert(node_color(n) == RED || node_color(n) == BLACK);
if (n == NULL) return;
verify_property_1(n->left);
verify_property_1(n->right);
}
/*
void verify_property_2(node root) {
assert(node_color(root) == BLACK);
}
*/
color node_color(node n) {
return n == NULL ? BLACK : n->color;
}
void verify_property_4(node n) {
if (node_color(n) == RED) {
assert (node_color(n->left) == BLACK);
assert (node_color(n->right) == BLACK);
assert (node_color(n->parent) == BLACK);
}
if (n == NULL) return;
verify_property_4(n->left);
verify_property_4(n->right);
}
/*
void verify_property_5(node root) {
int black_count_path = -1;
verify_property_5_helper(root, 0, &black_count_path);
}
*/
void verify_property_5_helper(node n, int black_count, int* path_black_count) {
if (node_color(n) == BLACK) {
black_count++;
}
if (n == NULL) {
if (*path_black_count == -1) {
*path_black_count = black_count;
} else {
assert (black_count == *path_black_count);
}
return;
}
verify_property_5_helper(n->left, black_count, path_black_count);
verify_property_5_helper(n->right, black_count, path_black_count);
}
void rbtree_init(rbtree t, rbtree_compare_func compare) {
t->root = NULL;
t->compare = compare;
verify_properties(t);
}
node lookup_node(rbtree t, void* key) {
node n = t->root;
while (n != NULL) {
int comp_result = t->compare(key, n->key);
if (comp_result == 0) {
return n;
} else if (comp_result < 0) {
n = n->left;
} else {
assert(comp_result > 0);
n = n->right;
}
}
return n;
}
void* rbtree_lookup(rbtree t, void* key) {
node n = lookup_node(t, key);
return n == NULL ? NULL : n->value;
}
void rotate_left(rbtree t, node n) {
node r = n->right;
replace_node(t, n, r);
n->right = r->left;
if (r->left != NULL) {
r->left->parent = n;
}
r->left = n;
n->parent = r;
}
void rotate_right(rbtree t, node n) {
node L = n->left;
replace_node(t, n, L);
n->left = L->right;
if (L->right != NULL) {
L->right->parent = n;
}
L->right = n;
n->parent = L;
}
void replace_node(rbtree t, node oldn, node newn) {
if (oldn->parent == NULL) {
t->root = newn;
} else {
if (oldn == oldn->parent->left)
oldn->parent->left = newn;
else
oldn->parent->right = newn;
}
if (newn != NULL) {
newn->parent = oldn->parent;
}
}
void rbtree_insert(rbtree t, rbtree_node inserted_node) {
inserted_node->color = RED;
inserted_node->left = NULL;
inserted_node->right = NULL;
inserted_node->parent = NULL;
if (t->root == NULL) {
t->root = inserted_node;
} else {
node n = t->root;
while (1) {
int comp_result = t->compare(inserted_node->key, n->key);
if (comp_result == 0) {
n->value = inserted_node->value;
return;
} else if (comp_result < 0) {
if (n->left == NULL) {
n->left = inserted_node;
break;
} else {
n = n->left;
}
} else {
assert (comp_result > 0);
if (n->right == NULL) {
n->right = inserted_node;
break;
} else {
n = n->right;
}
}
}
inserted_node->parent = n;
}
insert_case1(t, inserted_node);
verify_properties(t);
}
void insert_case1(rbtree t, node n) {
if (n->parent == NULL)
n->color = BLACK;
else
insert_case2(t, n);
}
void insert_case2(rbtree t, node n) {
if (node_color(n->parent) == BLACK)
return; /* Tree is still valid */
else
insert_case3(t, n);
}
void insert_case3(rbtree t, node n) {
if (node_color(uncle(n)) == RED) {
n->parent->color = BLACK;
uncle(n)->color = BLACK;
grandparent(n)->color = RED;
insert_case1(t, grandparent(n));
} else {
insert_case4(t, n);
}
}
void insert_case4(rbtree t, node n) {
if (n == n->parent->right && n->parent == grandparent(n)->left) {
rotate_left(t, n->parent);
n = n->left;
} else if (n == n->parent->left && n->parent == grandparent(n)->right) {
rotate_right(t, n->parent);
n = n->right;
}
insert_case5(t, n);
}
void insert_case5(rbtree t, node n) {
n->parent->color = BLACK;
grandparent(n)->color = RED;
if (n == n->parent->left && n->parent == grandparent(n)->left) {
rotate_right(t, grandparent(n));
} else {
assert (n == n->parent->right && n->parent == grandparent(n)->right);
rotate_left(t, grandparent(n));
}
}
rbtree_node rbtree_delete(rbtree t, void* key) {
node child;
node n = lookup_node(t, key);
if (n == NULL) return NULL; /* Key not found, do nothing */
if (n->left != NULL && n->right != NULL) {
/* Copy key/value from predecessor and then delete it instead */
node pred = maximum_node(n->left);
n->key = pred->key;
n->value = pred->value;
n = pred;
}
assert(n->left == NULL || n->right == NULL);
child = n->right == NULL ? n->left : n->right;
if (node_color(n) == BLACK) {
n->color = node_color(child);
delete_case1(t, n);
}
replace_node(t, n, child);
verify_properties(t);
return n;
}
static node maximum_node(node n) {
assert (n != NULL);
while (n->right != NULL) {
n = n->right;
}
return n;
}
void delete_case1(rbtree t, node n) {
if (n->parent == NULL)
return;
else
delete_case2(t, n);
}
void delete_case2(rbtree t, node n) {
if (node_color(sibling(n)) == RED) {
n->parent->color = RED;
sibling(n)->color = BLACK;
if (n == n->parent->left)
rotate_left(t, n->parent);
else
rotate_right(t, n->parent);
}
delete_case3(t, n);
}
void delete_case3(rbtree t, node n) {
if (node_color(n->parent) == BLACK &&
node_color(sibling(n)) == BLACK &&
node_color(sibling(n)->left) == BLACK &&
node_color(sibling(n)->right) == BLACK)
{
sibling(n)->color = RED;
delete_case1(t, n->parent);
}
else
delete_case4(t, n);
}
void delete_case4(rbtree t, node n) {
if (node_color(n->parent) == RED &&
node_color(sibling(n)) == BLACK &&
node_color(sibling(n)->left) == BLACK &&
node_color(sibling(n)->right) == BLACK)
{
sibling(n)->color = RED;
n->parent->color = BLACK;
}
else
delete_case5(t, n);
}
void delete_case5(rbtree t, node n) {
if (n == n->parent->left &&
node_color(sibling(n)) == BLACK &&
node_color(sibling(n)->left) == RED &&
node_color(sibling(n)->right) == BLACK)
{
sibling(n)->color = RED;
sibling(n)->left->color = BLACK;
rotate_right(t, sibling(n));
}
else if (n == n->parent->right &&
node_color(sibling(n)) == BLACK &&
node_color(sibling(n)->right) == RED &&
node_color(sibling(n)->left) == BLACK)
{
sibling(n)->color = RED;
sibling(n)->right->color = BLACK;
rotate_left(t, sibling(n));
}
delete_case6(t, n);
}
void delete_case6(rbtree t, node n) {
sibling(n)->color = node_color(n->parent);
n->parent->color = BLACK;
if (n == n->parent->left) {
assert (node_color(sibling(n)->right) == RED);
sibling(n)->right->color = BLACK;
rotate_left(t, n->parent);
}
else
{
assert (node_color(sibling(n)->left) == RED);
sibling(n)->left->color = BLACK;
rotate_right(t, n->parent);
}
}

54
deps/libebb/rbtree.h

@ -1,54 +0,0 @@
/* Copyright (c) 2008 Derrick Coetzee
* http://en.literateprograms.org/Red-black_tree_(C)?oldid=7982
* Small changes by Ryah Dahl
*
* 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.
*/
#ifndef _RBTREE_H_
#define _RBTREE_H_
enum rbtree_node_color { RED, BLACK };
typedef int (*rbtree_compare_func)(void* left_key, void* right_key);
typedef struct rbtree_node_t {
struct rbtree_node_t* left; /* private */
struct rbtree_node_t* right; /* private */
struct rbtree_node_t* parent; /* private */
enum rbtree_node_color color; /* private */
void* key; /* public */
void* value; /* public */
} *rbtree_node;
typedef struct rbtree_t {
rbtree_node root; /* private */
rbtree_compare_func compare; /* private */
} *rbtree;
void rbtree_init(rbtree t, rbtree_compare_func);
void* rbtree_lookup(rbtree t, void* key);
void rbtree_insert(rbtree t, rbtree_node);
/* you must free the returned node */
rbtree_node rbtree_delete(rbtree t, void* key);
#endif

60
deps/libebb/test_examples.rb

@ -1,60 +0,0 @@
require 'test/unit'
require 'socket'
REQ = "GET /hello/%d HTTP/1.1\r\n\r\n"
HOST = '0.0.0.0'
PORT = 5000
class TCPSocket
def full_send(string)
written = 0
while(written < string.length)
sent = write(string)
string = string.slice(sent, 10000)
end
written
end
def full_read
response = ""
while chunk = read(10000)
response += chunk
end
response
end
end
class EbbTest < Test::Unit::TestCase
def setup
@socket = TCPSocket.new(HOST, PORT)
end
def test_bad_req
@socket.full_send("hello")
assert_equal "", @socket.full_read
#assert @socket.closed?
end
def test_single
written = 0
req = REQ % 1
@socket.full_send(req)
response = @socket.full_read()
count = 0
response.scan("hello world") { count += 1 }
assert_equal 1, count
end
def test_pipeline
written = 0
req = (REQ % 1) + (REQ % 2) + (REQ % 3) + (REQ % 4)
@socket.full_send(req)
response = @socket.full_read()
count = 0
response.scan("hello world") { count += 1 }
assert_equal 4, count
end
end

108
deps/libebb/test_rbtree.c

@ -1,108 +0,0 @@
/* Copyright (c) 2008 Derrick Coetzee
* http://en.literateprograms.org/Red-black_tree_(C)?oldid=7982
*
* 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 "rbtree.h"
#include <stdio.h>
#include <assert.h>
#include <stdlib.h> /* rand(), malloc(), free() */
static int compare_int(void* left, void* right);
static void print_tree(rbtree t);
static void print_tree_helper(rbtree_node n, int indent);
int compare_int(void* leftp, void* rightp) {
int left = (int)leftp;
int right = (int)rightp;
if (left < right)
return -1;
else if (left > right)
return 1;
else {
assert (left == right);
return 0;
}
}
#define INDENT_STEP 4
void print_tree_helper(rbtree_node n, int indent);
void print_tree(rbtree t) {
print_tree_helper(t->root, 0);
puts("");
}
void print_tree_helper(rbtree_node n, int indent) {
int i;
if (n == NULL) {
fputs("<empty tree>", stdout);
return;
}
if (n->right != NULL) {
print_tree_helper(n->right, indent + INDENT_STEP);
}
for(i=0; i<indent; i++)
fputs(" ", stdout);
if (n->color == BLACK)
printf("%d\n", (int)n->key);
else
printf("<%d>\n", (int)n->key);
if (n->left != NULL) {
print_tree_helper(n->left, indent + INDENT_STEP);
}
}
int main() {
int i;
struct rbtree_t t;
rbtree_init(&t, compare_int);
print_tree(&t);
for(i=0; i<5000; i++) {
int x = rand() % 10000;
int y = rand() % 10000;
#ifdef TRACE
print_tree(&t);
printf("Inserting %d -> %d\n\n", x, y);
#endif
rbtree_node node = (rbtree_node) malloc(sizeof(struct rbtree_node_t));
node->key = (void*)x;
node->value = (void*)y;
rbtree_insert(&t, node);
assert(rbtree_lookup(&t, (void*)x) == (void*)y);
}
for(i=0; i<60000; i++) {
int x = rand() % 10000;
#ifdef TRACE
print_tree(&t);
printf("Deleting key %d\n\n", x);
#endif
rbtree_node n = rbtree_delete(&t, (void*)x);
if(n != NULL) {
free(n);
}
}
printf("Okay\n");
return 0;
}

746
deps/libebb/test_request_parser.c

@ -1,746 +0,0 @@
/* unit tests for request parser
* Copyright 2008 ryah dahl, ry@ndahl.us
*
* This software may be distributed under the "MIT" license included in the
* README
*/
#include "ebb_request_parser.h"
#include <stdlib.h>
#include <assert.h>
#include <stdio.h>
#include <string.h>
#define TRUE 1
#define FALSE 0
#define MAX_HEADERS 500
#define MAX_ELEMENT_SIZE 500
static ebb_request_parser parser;
struct request_data {
const char *raw;
int request_method;
char request_path[MAX_ELEMENT_SIZE];
char request_uri[MAX_ELEMENT_SIZE];
char fragment[MAX_ELEMENT_SIZE];
char query_string[MAX_ELEMENT_SIZE];
char body[MAX_ELEMENT_SIZE];
int num_headers;
char header_fields[MAX_HEADERS][MAX_ELEMENT_SIZE];
char header_values[MAX_HEADERS][MAX_ELEMENT_SIZE];
int should_keep_alive;
ebb_request request;
};
static struct request_data requests[5];
static int num_requests;
const struct request_data curl_get =
{ raw: "GET /test HTTP/1.1\r\n"
"User-Agent: curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1\r\n"
"Host: 0.0.0.0:5000\r\n"
"Accept: */*\r\n"
"\r\n"
, should_keep_alive: TRUE
, request_method: EBB_GET
, query_string: ""
, fragment: ""
, request_path: "/test"
, request_uri: "/test"
, num_headers: 3
, header_fields: { "User-Agent", "Host", "Accept" }
, header_values: { "curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1", "0.0.0.0:5000", "*/*" }
, body: ""
};
const struct request_data firefox_get =
{ raw: "GET /favicon.ico HTTP/1.1\r\n"
"Host: 0.0.0.0:5000\r\n"
"User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0\r\n"
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
"Accept-Language: en-us,en;q=0.5\r\n"
"Accept-Encoding: gzip,deflate\r\n"
"Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n"
"Keep-Alive: 300\r\n"
"Connection: keep-alive\r\n"
"\r\n"
, should_keep_alive: TRUE
, request_method: EBB_GET
, query_string: ""
, fragment: ""
, request_path: "/favicon.ico"
, request_uri: "/favicon.ico"
, num_headers: 8
, header_fields:
{ "Host"
, "User-Agent"
, "Accept"
, "Accept-Language"
, "Accept-Encoding"
, "Accept-Charset"
, "Keep-Alive"
, "Connection"
}
, header_values:
{ "0.0.0.0:5000"
, "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0"
, "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
, "en-us,en;q=0.5"
, "gzip,deflate"
, "ISO-8859-1,utf-8;q=0.7,*;q=0.7"
, "300"
, "keep-alive"
}
, body: ""
};
const struct request_data dumbfuck =
{ raw: "GET /dumbfuck HTTP/1.1\r\n"
"aaaaaaaaaaaaa:++++++++++\r\n"
"\r\n"
, should_keep_alive: TRUE
, request_method: EBB_GET
, query_string: ""
, fragment: ""
, request_path: "/dumbfuck"
, request_uri: "/dumbfuck"
, num_headers: 1
, header_fields: { "aaaaaaaaaaaaa" }
, header_values: { "++++++++++" }
, body: ""
};
const struct request_data fragment_in_uri =
{ raw: "GET /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n"
"\r\n"
, should_keep_alive: TRUE
, request_method: EBB_GET
, query_string: "page=1"
, fragment: "posts-17408"
, request_path: "/forums/1/topics/2375"
/* XXX request uri does not include fragment? */
, request_uri: "/forums/1/topics/2375?page=1"
, num_headers: 0
, body: ""
};
// get - no headers - no body
const struct request_data get_no_headers_no_body =
{ raw: "GET /get_no_headers_no_body/world HTTP/1.1\r\n"
"\r\n"
, should_keep_alive: TRUE
, request_method: EBB_GET
, query_string: ""
, fragment: ""
, request_path: "/get_no_headers_no_body/world"
, request_uri: "/get_no_headers_no_body/world"
, num_headers: 0
, body: ""
};
// get - one header - no body
const struct request_data get_one_header_no_body =
{ raw: "GET /get_one_header_no_body HTTP/1.1\r\n"
"Accept: */*\r\n"
"\r\n"
, should_keep_alive: TRUE
, request_method: EBB_GET
, query_string: ""
, fragment: ""
, request_path: "/get_one_header_no_body"
, request_uri: "/get_one_header_no_body"
, num_headers: 1
, header_fields: { "Accept" }
, header_values: { "*/*" }
, body: ""
};
// get - no headers - body "HELLO"
const struct request_data get_funky_content_length_body_hello =
{ raw: "GET /get_funky_content_length_body_hello HTTP/1.0\r\n"
"conTENT-Length: 5\r\n"
"\r\n"
"HELLO"
, should_keep_alive: FALSE
, request_method: EBB_GET
, query_string: ""
, fragment: ""
, request_path: "/get_funky_content_length_body_hello"
, request_uri: "/get_funky_content_length_body_hello"
, num_headers: 1
, header_fields: { "conTENT-Length" }
, header_values: { "5" }
, body: "HELLO"
};
// post - one header - body "World"
const struct request_data post_identity_body_world =
{ raw: "POST /post_identity_body_world?q=search#hey HTTP/1.1\r\n"
"Accept: */*\r\n"
"Transfer-Encoding: identity\r\n"
"Content-Length: 5\r\n"
"\r\n"
"World"
, should_keep_alive: TRUE
, request_method: EBB_POST
, query_string: "q=search"
, fragment: "hey"
, request_path: "/post_identity_body_world"
, request_uri: "/post_identity_body_world?q=search"
, num_headers: 3
, header_fields: { "Accept", "Transfer-Encoding", "Content-Length" }
, header_values: { "*/*", "identity", "5" }
, body: "World"
};
// post - no headers - chunked body "all your base are belong to us"
const struct request_data post_chunked_all_your_base =
{ raw: "POST /post_chunked_all_your_base HTTP/1.1\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n"
"1e\r\nall your base are belong to us\r\n"
"0\r\n"
"\r\n"
, should_keep_alive: TRUE
, request_method: EBB_POST
, query_string: ""
, fragment: ""
, request_path: "/post_chunked_all_your_base"
, request_uri: "/post_chunked_all_your_base"
, num_headers: 1
, header_fields: { "Transfer-Encoding" }
, header_values: { "chunked" }
, body: "all your base are belong to us"
};
// two chunks ; triple zero ending
const struct request_data two_chunks_mult_zero_end =
{ raw: "POST /two_chunks_mult_zero_end HTTP/1.1\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n"
"5\r\nhello\r\n"
"6\r\n world\r\n"
"000\r\n"
"\r\n"
, should_keep_alive: TRUE
, request_method: EBB_POST
, query_string: ""
, fragment: ""
, request_path: "/two_chunks_mult_zero_end"
, request_uri: "/two_chunks_mult_zero_end"
, num_headers: 1
, header_fields: { "Transfer-Encoding" }
, header_values: { "chunked" }
, body: "hello world"
};
// chunked with trailing headers. blech.
const struct request_data chunked_w_trailing_headers =
{ raw: "POST /chunked_w_trailing_headers HTTP/1.1\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n"
"5\r\nhello\r\n"
"6\r\n world\r\n"
"0\r\n"
"Vary: *\r\n"
"Content-Type: text/plain\r\n"
"\r\n"
, should_keep_alive: TRUE
, request_method: EBB_POST
, query_string: ""
, fragment: ""
, request_path: "/chunked_w_trailing_headers"
, request_uri: "/chunked_w_trailing_headers"
, num_headers: 1
, header_fields: { "Transfer-Encoding" }
, header_values: { "chunked" }
, body: "hello world"
};
// with bullshit after the length
const struct request_data chunked_w_bullshit_after_length =
{ raw: "POST /chunked_w_bullshit_after_length HTTP/1.1\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n"
"5; ihatew3;whatthefuck=aretheseparametersfor\r\nhello\r\n"
"6; blahblah; blah\r\n world\r\n"
"0\r\n"
"\r\n"
, should_keep_alive: TRUE
, request_method: EBB_POST
, query_string: ""
, fragment: ""
, request_path: "/chunked_w_bullshit_after_length"
, request_uri: "/chunked_w_bullshit_after_length"
, num_headers: 1
, header_fields: { "Transfer-Encoding" }
, header_values: { "chunked" }
, body: "hello world"
};
const struct request_data *fixtures[] =
{ &curl_get
, &firefox_get
, &dumbfuck
, &fragment_in_uri
, &get_no_headers_no_body
, &get_one_header_no_body
, &get_funky_content_length_body_hello
, &post_identity_body_world
, &post_chunked_all_your_base
, &two_chunks_mult_zero_end
, &chunked_w_trailing_headers
, &chunked_w_bullshit_after_length
, NULL
};
int request_data_eq
( struct request_data *r1
, const struct request_data *r2
)
{
if(ebb_request_should_keep_alive(&r1->request) != r2->should_keep_alive) {
printf("requests disagree on keep-alive");
return FALSE;
}
if(0 != strcmp(r1->body, r2->body)) {
printf("body '%s' != '%s'\n", r1->body, r2->body);
return FALSE;
}
if(0 != strcmp(r1->fragment, r2->fragment)) {
printf("fragment '%s' != '%s'\n", r1->fragment, r2->fragment);
return FALSE;
}
if(0 != strcmp(r1->query_string, r2->query_string)) {
printf("query_string '%s' != '%s'\n", r1->query_string, r2->query_string);
return FALSE;
}
if(r1->request.method != r2->request_method) {
printf("request_method '%d' != '%d'\n", r1->request.method, r2->request_method);
return FALSE;
}
if(0 != strcmp(r1->request_path, r2->request_path)) {
printf("request_path '%s' != '%s'\n", r1->request_path, r2->request_path);
return FALSE;
}
if(0 != strcmp(r1->request_uri, r2->request_uri)) {
printf("request_uri '%s' != '%s'\n", r1->request_uri, r2->request_uri);
return FALSE;
}
if(r1->num_headers != r2->num_headers) {
printf("num_headers '%d' != '%d'\n", r1->num_headers, r2->num_headers);
return FALSE;
}
int i;
for(i = 0; i < r1->num_headers; i++) {
if(0 != strcmp(r1->header_fields[i], r2->header_fields[i])) {
printf("header field '%s' != '%s'\n", r1->header_fields[i], r2->header_fields[i]);
return FALSE;
}
if(0 != strcmp(r1->header_values[i], r2->header_values[i])) {
printf("header field '%s' != '%s'\n", r1->header_values[i], r2->header_values[i]);
return FALSE;
}
}
return TRUE;
}
int request_eq
( int index
, const struct request_data *expected
)
{
return request_data_eq(&requests[index], expected);
}
void request_complete(ebb_request *info)
{
num_requests++;
}
void request_path_cb(ebb_request *request, const char *p, size_t len)
{
strncat(requests[num_requests].request_path, p, len);
}
void request_uri_cb(ebb_request *request, const char *p, size_t len)
{
strncat(requests[num_requests].request_uri, p, len);
}
void query_string_cb(ebb_request *request, const char *p, size_t len)
{
strncat(requests[num_requests].query_string, p, len);
}
void fragment_cb(ebb_request *request, const char *p, size_t len)
{
strncat(requests[num_requests].fragment, p, len);
}
void header_field_cb(ebb_request *request, const char *p, size_t len, int header_index)
{
strncat(requests[num_requests].header_fields[header_index], p, len);
}
void header_value_cb(ebb_request *request, const char *p, size_t len, int header_index)
{
strncat(requests[num_requests].header_values[header_index], p, len);
requests[num_requests].num_headers = header_index + 1;
}
void body_handler(ebb_request *request, const char *p, size_t len)
{
strncat(requests[num_requests].body, p, len);
// printf("body_handler: '%s'\n", requests[num_requests].body);
}
ebb_request* new_request ()
{
requests[num_requests].num_headers = 0;
requests[num_requests].request_method = -1;
requests[num_requests].request_path[0] = 0;
requests[num_requests].request_uri[0] = 0;
requests[num_requests].fragment[0] = 0;
requests[num_requests].query_string[0] = 0;
requests[num_requests].body[0] = 0;
int i;
for(i = 0; i < MAX_HEADERS; i++) {
requests[num_requests].header_fields[i][0] = 0;
requests[num_requests].header_values[i][0] = 0;
}
ebb_request *r = &requests[num_requests].request;
ebb_request_init(r);
r->on_complete = request_complete;
r->on_header_field = header_field_cb;
r->on_header_value = header_value_cb;
r->on_path = request_path_cb;
r->on_uri = request_uri_cb;
r->on_fragment = fragment_cb;
r->on_query_string = query_string_cb;
r->on_body = body_handler;
r->on_headers_complete = NULL;
r->data = &requests[num_requests];
// printf("new request %d\n", num_requests);
return r;
}
void parser_init()
{
num_requests = 0;
ebb_request_parser_init(&parser);
parser.new_request = new_request;
}
int test_request
( const struct request_data *request_data
)
{
size_t traversed = 0;
parser_init();
traversed = ebb_request_parser_execute( &parser
, request_data->raw
, strlen(request_data->raw)
);
if( ebb_request_parser_has_error(&parser) )
return FALSE;
if(! ebb_request_parser_is_finished(&parser) )
return FALSE;
if(num_requests != 1)
return FALSE;
return request_eq(0, request_data);
}
int test_error
( const char *buf
)
{
size_t traversed = 0;
parser_init();
traversed = ebb_request_parser_execute(&parser, buf, strlen(buf));
return ebb_request_parser_has_error(&parser);
}
int test_multiple3
( const struct request_data *r1
, const struct request_data *r2
, const struct request_data *r3
)
{
char total[80*1024] = "\0";
strcat(total, r1->raw);
strcat(total, r2->raw);
strcat(total, r3->raw);
size_t traversed = 0;
parser_init();
traversed = ebb_request_parser_execute(&parser, total, strlen(total));
if( ebb_request_parser_has_error(&parser) )
return FALSE;
if(! ebb_request_parser_is_finished(&parser) )
return FALSE;
if(num_requests != 3)
return FALSE;
if(!request_eq(0, r1)){
printf("request 1 error.\n");
return FALSE;
}
if(!request_eq(1, r2)){
printf("request 2 error.\n");
return FALSE;
}
if(!request_eq(2, r3)){
printf("request 3 error.\n");
return FALSE;
}
return TRUE;
}
/**
* SCAN through every possible breaking to make sure the
* parser can handle getting the content in any chunks that
* might come from the socket
*/
int test_scan2
( const struct request_data *r1
, const struct request_data *r2
, const struct request_data *r3
)
{
char total[80*1024] = "\0";
char buf1[80*1024] = "\0";
char buf2[80*1024] = "\0";
strcat(total, r1->raw);
strcat(total, r2->raw);
strcat(total, r3->raw);
int total_len = strlen(total);
//printf("total_len = %d\n", total_len);
int i;
for(i = 1; i < total_len - 1; i ++ ) {
parser_init();
int buf1_len = i;
strncpy(buf1, total, buf1_len);
buf1[buf1_len] = 0;
int buf2_len = total_len - i;
strncpy(buf2, total+i, buf2_len);
buf2[buf2_len] = 0;
ebb_request_parser_execute(&parser, buf1, buf1_len);
if( ebb_request_parser_has_error(&parser) ) {
return FALSE;
}
/*
if(ebb_request_parser_is_finished(&parser))
return FALSE;
*/
ebb_request_parser_execute(&parser, buf2, buf2_len);
if( ebb_request_parser_has_error(&parser))
return FALSE;
if(!ebb_request_parser_is_finished(&parser))
return FALSE;
if(3 != num_requests) {
printf("scan error: got %d requests in iteration %d\n", num_requests, i);
return FALSE;
}
if(!request_eq(0, r1)) {
printf("not maching r1\n");
return FALSE;
}
if(!request_eq(1, r2)) {
printf("not maching r2\n");
return FALSE;
}
if(!request_eq(2, r3)) {
printf("not maching r3\n");
return FALSE;
}
}
return TRUE;
}
int test_scan3
( const struct request_data *r1
, const struct request_data *r2
, const struct request_data *r3
)
{
char total[80*1024] = "\0";
char buf1[80*1024] = "\0";
char buf2[80*1024] = "\0";
char buf3[80*1024] = "\0";
strcat(total, r1->raw);
strcat(total, r2->raw);
strcat(total, r3->raw);
int total_len = strlen(total);
//printf("total_len = %d\n", total_len);
int i,j;
for(j = 2; j < total_len - 1; j ++ ) {
for(i = 1; i < j; i ++ ) {
parser_init();
int buf1_len = i;
strncpy(buf1, total, buf1_len);
buf1[buf1_len] = 0;
int buf2_len = j - i;
strncpy(buf2, total+i, buf2_len);
buf2[buf2_len] = 0;
int buf3_len = total_len - j;
strncpy(buf3, total+j, buf3_len);
buf3[buf3_len] = 0;
/*
printf("buf1: %s - %d\n", buf1, buf1_len);
printf("buf2: %s - %d \n", buf2, buf2_len );
printf("buf3: %s - %d\n\n", buf3, buf3_len);
*/
ebb_request_parser_execute(&parser, buf1, buf1_len);
if( ebb_request_parser_has_error(&parser) ) {
return FALSE;
}
ebb_request_parser_execute(&parser, buf2, buf2_len);
if( ebb_request_parser_has_error(&parser) ) {
return FALSE;
}
ebb_request_parser_execute(&parser, buf3, buf3_len);
if( ebb_request_parser_has_error(&parser))
return FALSE;
if(!ebb_request_parser_is_finished(&parser))
return FALSE;
if(3 != num_requests) {
printf("scan error: only got %d requests in iteration %d\n", num_requests, i);
return FALSE;
}
if(!request_eq(0, r1)) {
printf("not maching r1\n");
return FALSE;
}
if(!request_eq(1, r2)) {
printf("not maching r2\n");
return FALSE;
}
if(!request_eq(2, r3)) {
printf("not maching r3\n");
return FALSE;
}
}
}
return TRUE;
}
int main()
{
assert(test_error("hello world"));
assert(test_error("GET / HTP/1.1\r\n\r\n"));
assert(test_request(&curl_get));
assert(test_request(&firefox_get));
// Zed's header tests
assert(test_request(&dumbfuck));
const char *dumbfuck2 = "GET / HTTP/1.1\r\nX-SSL-Bullshit: -----BEGIN CERTIFICATE-----\r\n\tMIIFbTCCBFWgAwIBAgICH4cwDQYJKoZIhvcNAQEFBQAwcDELMAkGA1UEBhMCVUsx\r\n\tETAPBgNVBAoTCGVTY2llbmNlMRIwEAYDVQQLEwlBdXRob3JpdHkxCzAJBgNVBAMT\r\n\tAkNBMS0wKwYJKoZIhvcNAQkBFh5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMu\r\n\tdWswHhcNMDYwNzI3MTQxMzI4WhcNMDcwNzI3MTQxMzI4WjBbMQswCQYDVQQGEwJV\r\n\tSzERMA8GA1UEChMIZVNjaWVuY2UxEzARBgNVBAsTCk1hbmNoZXN0ZXIxCzAJBgNV\r\n\tBAcTmrsogriqMWLAk1DMRcwFQYDVQQDEw5taWNoYWVsIHBhcmQYJKoZIhvcNAQEB\r\n\tBQADggEPADCCAQoCggEBANPEQBgl1IaKdSS1TbhF3hEXSl72G9J+WC/1R64fAcEF\r\n\tW51rEyFYiIeZGx/BVzwXbeBoNUK41OK65sxGuflMo5gLflbwJtHBRIEKAfVVp3YR\r\n\tgW7cMA/s/XKgL1GEC7rQw8lIZT8RApukCGqOVHSi/F1SiFlPDxuDfmdiNzL31+sL\r\n\t0iwHDdNkGjy5pyBSB8Y79dsSJtCW/iaLB0/n8Sj7HgvvZJ7x0fr+RQjYOUUfrePP\r\n\tu2MSpFyf+9BbC/aXgaZuiCvSR+8Snv3xApQY+fULK/xY8h8Ua51iXoQ5jrgu2SqR\r\n\twgA7BUi3G8LFzMBl8FRCDYGUDy7M6QaHXx1ZWIPWNKsCAwEAAaOCAiQwggIgMAwG\r\n\tA1UdEwEB/wQCMAAwEQYJYIZIAYb4QgEBBAQDAgWgMA4GA1UdDwEB/wQEAwID6DAs\r\n\tBglghkgBhvhCAQ0EHxYdVUsgZS1TY2llbmNlIFVzZXIgQ2VydGlmaWNhdGUwHQYD\r\n\tVR0OBBYEFDTt/sf9PeMaZDHkUIldrDYMNTBZMIGaBgNVHSMEgZIwgY+AFAI4qxGj\r\n\tloCLDdMVKwiljjDastqooXSkcjBwMQswCQYDVQQGEwJVSzERMA8GA1UEChMIZVNj\r\n\taWVuY2UxEjAQBgNVBAsTCUF1dGhvcml0eTELMAkGA1UEAxMCQ0ExLTArBgkqhkiG\r\n\t9w0BCQEWHmNhLW9wZXJhdG9yQGdyaWQtc3VwcG9ydC5hYy51a4IBADApBgNVHRIE\r\n\tIjAggR5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMudWswGQYDVR0gBBIwEDAO\r\n\tBgwrBgEEAdkvAQEBAQYwPQYJYIZIAYb4QgEEBDAWLmh0dHA6Ly9jYS5ncmlkLXN1\r\n\tcHBvcnQuYWMudmT4sopwqlBWsvcHViL2NybC9jYWNybC5jcmwwPQYJYIZIAYb4QgEDBDAWLmh0\r\n\tdHA6Ly9jYS5ncmlkLXN1cHBvcnQuYWMudWsvcHViL2NybC9jYWNybC5jcmwwPwYD\r\n\tVR0fBDgwNjA0oDKgMIYuaHR0cDovL2NhLmdyaWQt5hYy51ay9wdWIv\r\n\tY3JsL2NhY3JsLmNybDANBgkqhkiG9w0BAQUFAAOCAQEAS/U4iiooBENGW/Hwmmd3\r\n\tXCy6Zrt08YjKCzGNjorT98g8uGsqYjSxv/hmi0qlnlHs+k/3Iobc3LjS5AMYr5L8\r\n\tUO7OSkgFFlLHQyC9JzPfmLCAugvzEbyv4Olnsr8hbxF1MbKZoQxUZtMVu29wjfXk\r\n\thTeApBv7eaKCWpSp7MCbvgzm74izKhu3vlDk9w6qVrxePfGgpKPqfHiOoGhFnbTK\r\n\twTC6o2xq5y0qZ03JonF7OJspEd3I5zKY3E+ov7/ZhW6DqT8UFvsAdjvQbXyhV8Eu\r\n\tYhixw1aKEPzNjNowuIseVogKOLXxWI5vAi5HgXdS0/ES5gDGsABo4fqovUKlgop3\r\n\tRA==\r\n\t-----END CERTIFICATE-----\r\n\r\n";
assert(test_error(dumbfuck2));
assert(test_request(&fragment_in_uri));
/* TODO sending junk and large headers gets rejected */
/* check to make sure our predefined requests are okay */
assert(test_request(&get_no_headers_no_body));
assert(test_request(&get_one_header_no_body));
assert(test_request(&get_no_headers_no_body));
// no content-length
const char *bad_get_no_headers_no_body = "GET /bad_get_no_headers_no_body/world HTTP/1.1\r\nAccept: */*\r\nHELLO\r\n";
assert(test_error(bad_get_no_headers_no_body)); // error if there is a body without content length
assert(test_request(&get_funky_content_length_body_hello));
assert(test_request(&post_identity_body_world));
assert(test_request(&post_chunked_all_your_base));
assert(test_request(&two_chunks_mult_zero_end));
assert(test_request(&chunked_w_trailing_headers));
assert(test_request(&chunked_w_bullshit_after_length));
assert(1 == requests[0].request.version_major);
assert(1 == requests[0].request.version_minor);
// three requests - no bodies
assert( test_multiple3( &get_no_headers_no_body
, &get_one_header_no_body
, &get_no_headers_no_body
));
// three requests - one body
assert( test_multiple3(&get_no_headers_no_body, &get_funky_content_length_body_hello, &get_no_headers_no_body));
// three requests with bodies -- last is chunked
assert( test_multiple3(&get_funky_content_length_body_hello, &post_identity_body_world, &post_chunked_all_your_base));
// three chunked requests
assert( test_multiple3(&two_chunks_mult_zero_end, &post_chunked_all_your_base, &chunked_w_trailing_headers));
assert(test_scan2(&get_no_headers_no_body, &get_one_header_no_body, &get_no_headers_no_body));
assert(test_scan2(&get_funky_content_length_body_hello, &post_identity_body_world, &post_chunked_all_your_base));
assert(test_scan2(&two_chunks_mult_zero_end, &chunked_w_trailing_headers, &chunked_w_bullshit_after_length));
assert(test_scan3(&get_no_headers_no_body, &get_one_header_no_body, &get_no_headers_no_body));
assert(test_scan3(&get_funky_content_length_body_hello, &post_identity_body_world, &post_chunked_all_your_base));
assert(test_scan3(&two_chunks_mult_zero_end, &chunked_w_trailing_headers, &chunked_w_bullshit_after_length));
printf("okay\n");
return 0;
}

35
ragel.py

@ -1,35 +0,0 @@
#! /usr/bin/env python
# encoding: utf-8
"Ragel: '.rl' files are converted into .c files using 'ragel': {.rl -> .c -> .o}"
import TaskGen, Task, Runner
def rageltaskfun(task):
env = task.env
ragelbin = env.get_flat('RAGEL')
if ragelbin:
if task.inputs[0].srcpath(env) == '../src/config_parser.rl':
cmd = '%s -o %s -C -T0 %s' % (ragelbin, task.outputs[0].bldpath(env), task.inputs[0].srcpath(env))
else:
cmd = '%s -o %s -C -T1 %s' % (ragelbin, task.outputs[0].bldpath(env), task.inputs[0].srcpath(env))
else:
src = task.inputs[0].srcpath(env)
src = src[:src.rfind('.')] + '.c'
cmd = 'cp %s %s' % (src, task.outputs[0].bldpath(env))
return task.generator.bld.exec_command(cmd)
rageltask = Task.task_type_from_func('ragel', rageltaskfun, vars = ['RAGEL'], color = 'BLUE', ext_in = '.rl', ext_out = '.c', before = 'c')
@TaskGen.extension('.rl')
@TaskGen.before('apply_core')
def ragel(self, node):
out = node.change_ext('.c')
self.allnodes.append(out)
tsk = self.create_task('ragel')
tsk.set_inputs(node)
tsk.set_outputs(out)
def detect(conf):
dang = conf.find_program('ragel', var='RAGEL')

686
src/http.cc

@ -1,691 +1,61 @@
#include "node.h" #include "node.h"
#include "http.h" #include "http.h"
#include <http_parser.h>
#include <oi_socket.h>
#include <ebb_request_parser.h>
#include <string>
#include <list>
#include <assert.h> #include <assert.h>
#include <stdio.h>
using namespace v8; using namespace v8;
using namespace node;
using namespace std; using namespace std;
static Persistent<ObjectTemplate> request_template;
// globals
static Persistent<String> path_str;
static Persistent<String> uri_str;
static Persistent<String> query_string_str;
static Persistent<String> fragment_str;
static Persistent<String> method_str;
static Persistent<String> http_version_str;
static Persistent<String> headers_str;
static Persistent<String> on_request_str;
static Persistent<String> on_body_str;
static Persistent<String> respond_str;
static Persistent<String> copy_str;
static Persistent<String> delete_str;
static Persistent<String> get_str;
static Persistent<String> head_str;
static Persistent<String> lock_str;
static Persistent<String> mkcol_str;
static Persistent<String> move_str;
static Persistent<String> options_str;
static Persistent<String> post_str;
static Persistent<String> propfind_str;
static Persistent<String> proppatch_str;
static Persistent<String> put_str;
static Persistent<String> trace_str;
static Persistent<String> unlock_str;
#define INVALID_STATE_ERR 1
class HttpServer {
public:
HttpServer (Handle<Object> _js_server);
~HttpServer ();
int Start(struct addrinfo *servinfo);
void Stop();
Handle<Value> Callback()
{
HandleScope scope;
Handle<Value> value = js_server->Get(on_request_str);
return scope.Close(value);
}
private:
oi_server server;
Persistent<Object> js_server;
};
class HttpRequest;
class Connection {
public:
Connection();
~Connection();
void Parse(const void *buf, size_t count);
void Write();
void Close();
void AddRequest (HttpRequest *request);
oi_socket socket;
Persistent<Function> js_onrequest;
private:
ebb_request_parser parser;
list<HttpRequest*> requests;
list<HttpRequest*> finished_requests;
friend class HttpServer;
};
class HttpRequest {
public:
HttpRequest (Connection &c);
/* Deleted from C++ as soon as possible.
* Javascript object might linger. This is okay
*/
~HttpRequest();
void MakeBodyCallback (const char *base, size_t length);
Local<Object> CreateJSObject ();
void Respond (Handle<Value> data);
string path;
string query_string;
string fragment;
string uri;
list<string> header_fields;
list<string> header_values;
Connection &connection;
ebb_request parser_info;
list<oi_buf*> output;
bool done;
Persistent<Object> js_object;
};
static Handle<Value>
GetMethodString (int method)
{
switch(method) {
case EBB_COPY: return copy_str;
case EBB_DELETE: return delete_str;
case EBB_GET: return get_str;
case EBB_HEAD: return head_str;
case EBB_LOCK: return lock_str;
case EBB_MKCOL: return mkcol_str;
case EBB_MOVE: return move_str;
case EBB_OPTIONS: return options_str;
case EBB_POST: return post_str;
case EBB_PROPFIND: return propfind_str;
case EBB_PROPPATCH: return proppatch_str;
case EBB_PUT: return put_str;
case EBB_TRACE: return trace_str;
case EBB_UNLOCK: return unlock_str;
}
return Null();
}
static Handle<Value>
RespondCallback (const Arguments& args)
{
HandleScope scope;
Handle<Value> v = args.Holder()->GetInternalField(0);
if(v->IsUndefined()) {
// check that args.Holder()->GetInternalField(0)
// is not NULL if so raise INVALID_STATE_ERR
printf("null request external\n");
ThrowException(Integer::New(INVALID_STATE_ERR));
return Undefined();
}
Handle<External> field = Handle<External>::Cast(v);
HttpRequest* request = static_cast<HttpRequest*>(field->Value());
request->Respond(args[0]);
return Undefined();
}
void void
HttpRequest::Respond (Handle<Value> data) HTTPClient::Initialize (Handle<Object> target)
{
if(data == Null()) {
done = true;
} else {
Handle<String> s = data->ToString();
size_t l1 = s->Utf8Length(), l2;
oi_buf *buf = oi_buf_new2(l1);
l2 = s->WriteUtf8(buf->base, l1);
assert(l1 == l2);
output.push_back(buf);
}
connection.Write();
}
static void
on_path (ebb_request *req, const char *buf, size_t len)
{ {
HttpRequest *request = static_cast<HttpRequest*> (req->data);
request->path.append(buf, len);
}
static void
on_uri (ebb_request *req, const char *buf, size_t len)
{
HttpRequest *request = static_cast<HttpRequest*> (req->data);
request->uri.append(buf, len);
}
static void
on_query_string (ebb_request *req, const char *buf, size_t len)
{
HttpRequest *request = static_cast<HttpRequest*> (req->data);
request->query_string.append(buf, len);
}
static void
on_fragment (ebb_request *req, const char *buf, size_t len)
{
HttpRequest *request = static_cast<HttpRequest*> (req->data);
request->fragment.append(buf, len);
}
static const char upcase[] =
"\0______________________________"
"_________________0123456789_____"
"__ABCDEFGHIJKLMNOPQRSTUVWXYZ____"
"__ABCDEFGHIJKLMNOPQRSTUVWXYZ____"
"________________________________"
"________________________________"
"________________________________"
"________________________________";
static void
on_header_field (ebb_request *req, const char *buf, size_t len, int header_index)
{
HttpRequest *request = static_cast<HttpRequest*> (req->data);
char upbuf[len];
for(int i = 0; i < len; i++)
upbuf[i] = upcase[buf[i]];
if( request->header_fields.size() == header_index - 1) {
request->header_fields.back().append(upbuf, len);
} else {
request->header_fields.push_back( string(upbuf, len) );
}
}
static void
on_header_value (ebb_request *req, const char *buf, size_t len, int header_index)
{
HttpRequest *request = static_cast<HttpRequest*> (req->data);
if( request->header_values.size() == header_index - 1) {
request->header_values.back().append(buf, len);
} else {
request->header_values.push_back( string(buf, len) );
}
}
static void
on_headers_complete (ebb_request *req)
{
HttpRequest *request = static_cast<HttpRequest*> (req->data);
HandleScope scope; HandleScope scope;
Handle<Object> js_request = request->CreateJSObject(); Local<FunctionTemplate> t = FunctionTemplate::New(HTTPClient::v8New);
t->InstanceTemplate()->SetInternalFieldCount(1);
// Set up an exception handler before calling the Process function target->Set(String::NewSymbol("HTTPClient"), t->GetFunction());
TryCatch try_catch;
// Invoke the process function, giving the global object as 'this' NODE_SET_METHOD(t->InstanceTemplate(), "connect", Connection::v8Connect);
// and one argument, the request. NODE_SET_METHOD(t->InstanceTemplate(), "close", Connection::v8Close);
const int argc = 1; NODE_SET_METHOD(t->InstanceTemplate(), "send", Connection::v8Send);
Handle<Value> argv[argc] = { js_request }; NODE_SET_METHOD(t->InstanceTemplate(), "sendEOF", Connection::v8SendEOF);
Handle<Value> r = request->connection.js_onrequest->Call(Context::GetCurrent()->Global(), argc, argv);
if(try_catch.HasCaught())
node::fatal_exception(try_catch);
} }
static void Handle<Value>
on_request_complete (ebb_request *req) HTTPClient::v8New (const Arguments& args)
{
HttpRequest *request = static_cast<HttpRequest*> (req->data);
request->MakeBodyCallback(NULL, 0); // EOF
}
static void
on_body (ebb_request *req, const char *base, size_t length)
{
HttpRequest *request = static_cast<HttpRequest*> (req->data);
if(length)
request->MakeBodyCallback(base, length);
}
static ebb_request * on_request
( void *data
)
{
Connection *connection = static_cast<Connection*> (data);
HttpRequest *request = new HttpRequest(*connection);
connection->AddRequest(request);
return &request->parser_info;
}
static void on_read
( oi_socket *socket
, const void *buf
, size_t count
)
{
Connection *connection = static_cast<Connection*> (socket->data);
if(count == 0) {
connection->Close();
} else {
//write(1, buf, count);
connection->Parse(buf, count);
}
}
static void on_close
( oi_socket *socket
)
{
Connection *connection = static_cast<Connection*> (socket->data);
delete connection;
}
HttpRequest::~HttpRequest ()
{
HandleScope scope; // needed?
// delete a reference c++ HttpRequest
js_object->SetInternalField(0, Undefined());
js_object->Delete(respond_str);
// dispose of Persistent handle so that
// it can be GC'd normally.
js_object.Dispose();
}
HttpRequest::HttpRequest (Connection &c) : connection(c)
{
ebb_request_init(&parser_info);
parser_info.on_path = on_path;
parser_info.on_query_string = on_query_string;
parser_info.on_uri = on_uri;
parser_info.on_fragment = on_fragment;
parser_info.on_header_field = on_header_field;
parser_info.on_header_value = on_header_value;
parser_info.on_headers_complete = on_headers_complete;
parser_info.on_body = on_body;
parser_info.on_complete = on_request_complete;
parser_info.data = this;
done = false;
}
void
HttpRequest::MakeBodyCallback (const char *base, size_t length)
{
HandleScope handle_scope;
Handle<Value> onbody_val = js_object->Get(on_body_str);
if (!onbody_val->IsFunction()) return;
Handle<Function> onbody = Handle<Function>::Cast(onbody_val);
TryCatch try_catch;
const int argc = 1;
Handle<Value> argv[argc];
if(length) {
// TODO ByteArray?
//
uint16_t expanded_base[length];
for(int i = 0; i < length; i++) {
expanded_base[i] = base[i];
}
Handle<String> chunk = String::New(expanded_base, length);
argv[0] = chunk;
} else {
argv[0] = Null();
}
Handle<Value> result = onbody->Call(js_object, argc, argv);
if(try_catch.HasCaught())
node::fatal_exception(try_catch);
}
Local<Object>
HttpRequest::CreateJSObject ()
{ {
HandleScope scope; HandleScope scope;
if (request_template.IsEmpty()) { if (args[0]->IsFunction() == false)
Handle<ObjectTemplate> raw_template = ObjectTemplate::New(); return ThrowException(String::New("Must pass a class as the first argument."));
raw_template->SetInternalFieldCount(1);
raw_template->Set(respond_str, FunctionTemplate::New(RespondCallback));
request_template = Persistent<ObjectTemplate>::New(raw_template);
}
// Create an empty http request wrapper.
Handle<Object> result = request_template->NewInstance();
// Wrap the raw C++ pointer in an External so it can be referenced
// from within JavaScript.
Handle<External> request_ptr = External::New(this);
// Store the request pointer in the JavaScript wrapper.
result->SetInternalField(0, request_ptr);
result->Set ( path_str
, String::New(path.c_str(), path.length())
);
result->Set ( uri_str
, String::New(uri.c_str(), uri.length())
);
result->Set ( query_string_str
, String::New(query_string.c_str(), query_string.length())
);
result->Set ( fragment_str
, String::New(fragment.c_str(), fragment.length())
);
result->Set ( method_str
, GetMethodString(parser_info.method)
);
char version[10];
snprintf ( version
, 10 // big enough? :)
, "%d.%d"
, parser_info.version_major
, parser_info.version_minor
);
result->Set ( http_version_str
, String::New(version)
);
Handle<Object> headers = Object::New();
list<string>::iterator field_iterator = header_fields.begin();
list<string>::iterator value_iterator = header_values.begin();
while( value_iterator != header_values.end() ) {
string &f = *field_iterator;
string &v = *value_iterator;
headers->Set( String::NewSymbol(f.c_str(), f.length())
, String::New(v.c_str(), v.length() )
);
field_iterator++;
value_iterator++;
}
result->Set(headers_str, headers);
js_object = Persistent<Object>::New(result);
// XXX does the request's js_object need a MakeWeak callback?
// i dont think so because at some point the connection closes
// and we're going to delete the request.
return scope.Close(result);
}
static oi_socket*
on_connection (oi_server *_server, struct sockaddr *addr, socklen_t len)
{
HandleScope scope;
HttpServer *server = static_cast<HttpServer*> (_server->data);
Handle<Value> callback_v = server->Callback();
if(callback_v == Undefined())
return NULL;
Connection *connection = new Connection();
Handle<Function> f = Handle<Function>::Cast(callback_v);
connection->js_onrequest = Persistent<Function>::New(f);
return &connection->socket;
}
Connection::Connection ()
{
oi_socket_init (&socket, 30.0); // TODO make timeout adjustable
socket.on_read = on_read;
socket.on_error = NULL;
socket.on_close = on_close;
socket.on_timeout = NULL;
socket.on_drain = NULL;
socket.data = this;
ebb_request_parser_init (&parser);
parser.new_request = on_request;
parser.data = this;
}
Connection::~Connection ()
{
list<HttpRequest*>::iterator it = requests.begin();
// delete all the requests
for(it = requests.begin(); it != requests.end(); it++)
delete *it;
for(it = finished_requests.begin(); it != finished_requests.end(); it++)
delete *it;
}
void
Connection::Parse(const void *buf, size_t count)
{
// FIXME change ebb_request_parser to have void* arg
ebb_request_parser_execute ( &parser
, static_cast<const char*> (buf)
, count
);
if(ebb_request_parser_has_error(&parser)) {
fprintf(stderr, "parse error closing connection\n");
oi_socket_close(&socket);
}
}
void
Connection::AddRequest(HttpRequest *request)
{
requests.push_back(request);
}
void
Connection::Write ( )
{
if(requests.size() == 0)
return;
HttpRequest *request = requests.front();
while(request->output.size() > 0) {
oi_buf *buf = request->output.front();
oi_socket_write(&socket, buf);
request->output.pop_front();
}
if(request->done) {
if(!ebb_request_should_keep_alive(&request->parser_info)) {
socket.on_drain = oi_socket_close;
}
requests.pop_front(); Handle<Function> protocol = Handle<Function>::Cast(args[0]);
finished_requests.push_back(request);
Write(); int argc = args.Length();
} Handle<Value> argv[argc];
}
void argv[0] = args.This();
Connection::Close ( ) for (int i = 1; i < args.Length(); i++) {
{ argv[i] = args[i];
oi_socket_close(&socket);
} }
static void Local<Object> protocol_instance = protocol->NewInstance(argc, argv);
server_destroy (Persistent<Value> _, void *data)
{
HttpServer *server = static_cast<HttpServer *> (data);
delete server;
}
HttpServer::HttpServer (Handle<Object> _js_server) new HTTPClient(args.This(), protocol_instance);
{
oi_server_init(&server, 1024);
server.on_connection = on_connection;
server.data = this;
HandleScope scope;
js_server = Persistent<Object>::New (_js_server);
// are we ever going to need this external?
js_server->SetInternalField (0, External::New(this));
js_server.MakeWeak (this, server_destroy);
}
HttpServer::~HttpServer () return args.This();
{
Stop();
js_server.Dispose();
js_server.Clear(); // necessary?
}
int
HttpServer::Start(struct addrinfo *servinfo)
{
int r = oi_server_listen(&server, servinfo);
if(r == 0)
oi_server_attach(EV_DEFAULT_UC_ &server);
return r;
} }
void void
HttpServer::Stop() HTTPClient::OnReceive (const void *buf, size_t len)
{ {
oi_server_close (&server); printf("http client got data!\n");
oi_server_detach (&server);
} }
/* This constructor takes 2 arguments: host, port. */ HTTPClient::HTTPClient (Handle<Object> handle, Handle<Object> protocol)
static Handle<Value> : Connection(handle, protocol)
newHTTPHttpServer (const Arguments& args)
{ {
if (args.Length() < 3)
return Undefined();
HandleScope scope;
char *host = NULL;
String::AsciiValue host_v(args[0]->ToString());
if(args[0]->IsString()) {
host = *host_v;
}
String::AsciiValue port(args[1]->ToString());
Handle<Function> onrequest = Handle<Function>::Cast(args[2]);
args.This()->Set(on_request_str, onrequest);
// get addrinfo for localhost, PORT
struct addrinfo *servinfo;
struct addrinfo hints;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
// FIXME BLOCKING
int r = getaddrinfo(host, *port, &hints, &servinfo);
if (r != 0)
return Undefined(); // XXX raise error?
//
//
//
// TODO host is ignored for now assumed localhost
//
//
//
//
HttpServer *server = new HttpServer(args.This());
if(server == NULL)
return Undefined(); // XXX raise error?
r = server->Start(servinfo);
if (r != 0)
return Undefined(); // XXX raise error?
return args.This();
}
void
node::Init_http (Handle<Object> target)
{
HandleScope scope;
Local<FunctionTemplate> server_t = FunctionTemplate::New(newHTTPHttpServer);
server_t->InstanceTemplate()->SetInternalFieldCount(1);
server_t->Set("INVALID_STATE_ERR", Integer::New(INVALID_STATE_ERR));
target->Set(String::New("HTTPServer"), server_t->GetFunction());
path_str = Persistent<String>::New( String::NewSymbol("path") );
uri_str = Persistent<String>::New( String::NewSymbol("uri") );
query_string_str = Persistent<String>::New( String::NewSymbol("query_string") );
fragment_str = Persistent<String>::New( String::NewSymbol("fragment") );
method_str = Persistent<String>::New( String::NewSymbol("method") );
http_version_str = Persistent<String>::New( String::NewSymbol("http_version") );
headers_str = Persistent<String>::New( String::NewSymbol("headers") );
on_request_str = Persistent<String>::New( String::NewSymbol("onrequest") );
on_body_str = Persistent<String>::New( String::NewSymbol("onbody") );
respond_str = Persistent<String>::New( String::NewSymbol("respond") );
copy_str = Persistent<String>::New( String::New("COPY") );
delete_str = Persistent<String>::New( String::New("DELETE") );
get_str = Persistent<String>::New( String::New("GET") );
head_str = Persistent<String>::New( String::New("HEAD") );
lock_str = Persistent<String>::New( String::New("LOCK") );
mkcol_str = Persistent<String>::New( String::New("MKCOL") );
move_str = Persistent<String>::New( String::New("MOVE") );
options_str = Persistent<String>::New( String::New("OPTIONS") );
post_str = Persistent<String>::New( String::New("POST") );
propfind_str = Persistent<String>::New( String::New("PROPFIND") );
proppatch_str = Persistent<String>::New( String::New("PROPPATCH") );
put_str = Persistent<String>::New( String::New("PUT") );
trace_str = Persistent<String>::New( String::New("TRACE") );
unlock_str = Persistent<String>::New( String::New("UNLOCK") );
} }

13
src/http.h

@ -2,10 +2,21 @@
#define node_http_h #define node_http_h
#include <v8.h> #include <v8.h>
#include "net.h"
namespace node { namespace node {
void Init_http (v8::Handle<v8::Object> target); class HTTPClient : public node::Connection {
public:
static void Initialize (v8::Handle<v8::Object> target);
HTTPClient (v8::Handle<v8::Object> handle, v8::Handle<v8::Object> protocol);
static v8::Handle<v8::Value> v8New (const v8::Arguments& args);
protected:
void OnReceive (const void *buf, size_t len);
};
} // namespace node } // namespace node
#endif #endif

23
src/net.cc

@ -59,21 +59,12 @@ Connection::Initialize (v8::Handle<v8::Object> target)
NODE_SET_METHOD(t->InstanceTemplate(), "sendEOF", Connection::v8SendEOF); NODE_SET_METHOD(t->InstanceTemplate(), "sendEOF", Connection::v8SendEOF);
} }
Local<Object> Connection::Connection (Handle<Object> handle, Handle<Object> protocol)
Connection::NewServerSideInstance (Local<Function> protocol, Handle<Object> server)
{
HandleScope scope;
Handle<Value> argv[] = { protocol, server };
Local<Object> instance = tcp_connection_constructor->NewInstance(2, argv);
return scope.Close(instance);
}
Connection::Connection (Handle<Object> handle)
: ObjectWrap(handle) : ObjectWrap(handle)
{ {
HandleScope scope; HandleScope scope;
Local<Object> protocol = GetProtocol(); handle_->Set(PROTOCOL_SYMBOL, protocol);
encoding_ = RAW; encoding_ = RAW;
Local<Value> encoding_v = protocol->Get(ENCODING_SYMBOL); Local<Value> encoding_v = protocol->Get(ENCODING_SYMBOL);
@ -136,9 +127,8 @@ Connection::v8New (const Arguments& args)
} }
Local<Object> protocol_instance = protocol->NewInstance(argc, argv); Local<Object> protocol_instance = protocol->NewInstance(argc, argv);
args.This()->Set(PROTOCOL_SYMBOL, protocol_instance);
new Connection(args.This()); new Connection(args.This(), protocol_instance);
return args.This(); return args.This();
} }
@ -387,10 +377,11 @@ Acceptor::OnConnection (struct sockaddr *addr, socklen_t len)
} }
Local<Function> protocol = Local<Function>::Cast(protocol_v); Local<Function> protocol = Local<Function>::Cast(protocol_v);
Local<Object> connection_handle = Handle<Value> argv[] = { protocol, handle_ };
Connection::NewServerSideInstance(protocol, handle_); Local<Object> connection_handle = tcp_connection_constructor->NewInstance(2, argv);
Connection *connection = NODE_UNWRAP(Connection, connection_handle);
Connection *connection = new Connection(connection_handle);
return connection; return connection;
} }

25
src/net.h

@ -11,10 +11,8 @@ class Connection : public ObjectWrap {
public: public:
static void Initialize (v8::Handle<v8::Object> target); static void Initialize (v8::Handle<v8::Object> target);
Connection (v8::Handle<v8::Object> handle); Connection (v8::Handle<v8::Object> handle, v8::Handle<v8::Object> protocol);
~Connection () { virtual ~Connection () { Close(); }
Close();
}
int Connect (struct addrinfo *address) { int Connect (struct addrinfo *address) {
return oi_socket_connect (&socket_, address); return oi_socket_connect (&socket_, address);
@ -39,20 +37,17 @@ protected:
static v8::Handle<v8::Value> v8SendEOF (const v8::Arguments& args); static v8::Handle<v8::Value> v8SendEOF (const v8::Arguments& args);
static v8::Handle<v8::Value> v8Close (const v8::Arguments& args); static v8::Handle<v8::Value> v8Close (const v8::Arguments& args);
void OnConnect (void); virtual void OnConnect (void);
void OnReceive (const void *buf, size_t len); virtual void OnReceive (const void *buf, size_t len);
void OnDrain (void); virtual void OnDrain (void);
void OnEOF (void); virtual void OnEOF (void);
void OnDisconnect (void); virtual void OnDisconnect (void);
void OnError (oi_error e); virtual void OnError (oi_error e);
void OnTimeout (void); virtual void OnTimeout (void);
v8::Local<v8::Object> GetProtocol (void); v8::Local<v8::Object> GetProtocol (void);
static v8::Local<v8::Object> NewServerSideInstance (
v8::Local<v8::Function> protocol,
v8::Handle<v8::Object> server);
private: //private:
/* liboi callbacks */ /* liboi callbacks */
static void on_connect (oi_socket *s) { static void on_connect (oi_socket *s) {
Connection *connection = static_cast<Connection*> (s->data); Connection *connection = static_cast<Connection*> (s->data);

2
src/node.cc

@ -252,7 +252,7 @@ main (int argc, char *argv[])
Connection::Initialize(g); Connection::Initialize(g);
node::Init_timer(g); node::Init_timer(g);
node::Init_file(g); node::Init_file(g);
node::Init_http(g); HTTPClient::Initialize(g);
// NATIVE JAVASCRIPT MODULES // NATIVE JAVASCRIPT MODULES
TryCatch try_catch; TryCatch try_catch;

2
src/node.h

@ -20,7 +20,7 @@ void eio_warmup (void); // call this before creating a new eio event.
class ObjectWrap { class ObjectWrap {
public: public:
ObjectWrap (v8::Handle<v8::Object> handle); ObjectWrap (v8::Handle<v8::Object> handle);
~ObjectWrap ( ); virtual ~ObjectWrap ( );
protected: protected:
static void* Unwrap (v8::Handle<v8::Object> handle); static void* Unwrap (v8::Handle<v8::Object> handle);

25
wscript

@ -18,7 +18,6 @@ def set_options(opt):
# the gcc module provides a --debug-level option # the gcc module provides a --debug-level option
opt.tool_options('compiler_cxx') opt.tool_options('compiler_cxx')
opt.tool_options('compiler_cc') opt.tool_options('compiler_cc')
opt.tool_options('ragel', tdir=".")
opt.add_option( '--debug' opt.add_option( '--debug'
, action='store_true' , action='store_true'
, default=False , default=False
@ -29,10 +28,6 @@ def set_options(opt):
def configure(conf): def configure(conf):
conf.check_tool('compiler_cxx') conf.check_tool('compiler_cxx')
conf.check_tool('compiler_cc') conf.check_tool('compiler_cc')
conf.check_tool('ragel', tooldir=".")
if not conf.env['RAGEL']:
fatal('ragel not found')
exit(1)
conf.env["USE_DEBUG"] = Options.options.debug conf.env["USE_DEBUG"] = Options.options.debug
@ -124,15 +119,15 @@ def build(bld):
if bld.env["USE_DEBUG"]: if bld.env["USE_DEBUG"]:
oi.clone("debug") oi.clone("debug")
### ebb ### http_parser
ebb = bld.new_task_gen("cc", "staticlib") http_parser = bld.new_task_gen("cc", "staticlib")
ebb.source = "deps/libebb/ebb_request_parser.rl" http_parser.source = "deps/http_parser/http_parser.c"
ebb.includes = "deps/libebb/" http_parser.includes = "deps/http_parser/"
ebb.name = "ebb" http_parser.name = "http_parser"
ebb.target = "ebb" http_parser.target = "http_parser"
ebb.install_path = None http_parser.install_path = None
if bld.env["USE_DEBUG"]: if bld.env["USE_DEBUG"]:
ebb.clone("debug") http_parser.clone("debug")
### src/native.cc ### src/native.cc
def javascript_in_c(task): def javascript_in_c(task):
@ -167,9 +162,9 @@ def build(bld):
deps/libev deps/libev
deps/libeio deps/libeio
deps/liboi deps/liboi
deps/libebb deps/http_parser
""" """
node.uselib_local = "oi ev eio ebb" node.uselib_local = "oi ev eio http_parser"
node.uselib = "V8 RT" node.uselib = "V8 RT"
node.install_path = '${PREFIX}/bin' node.install_path = '${PREFIX}/bin'
node.chmod = 0755 node.chmod = 0755

Loading…
Cancel
Save