Upgrade http-parser, fixes issue 77
authorRyan Dahl <ry@tinyclouds.org>
Thu, 29 Apr 2010 06:28:52 +0000 (23:28 -0700)
committerRyan Dahl <ry@tinyclouds.org>
Thu, 29 Apr 2010 06:28:52 +0000 (23:28 -0700)
http://github.com/ry/node/issues#issue/77

deps/http_parser/.gitignore
deps/http_parser/Makefile
deps/http_parser/http_parser.c
deps/http_parser/test.c

index 42f643c..72e9d02 100644 (file)
@@ -2,17 +2,23 @@ OPT_DEBUG=-O0 -g -Wall -Wextra -Werror -I.
 OPT_FAST=-O3 -DHTTP_PARSER_STRICT=0 -I.
 
 
-test: test_debug
-       ./test_debug
+test: test_g
+       ./test_g
 
-test_debug: http_parser_debug.o test.c
-       gcc $(OPT_DEBUG) http_parser.o test.c -o $@
+test_g: http_parser_g.o test_g.o
+       gcc $(OPT_DEBUG) http_parser_g.o test_g.o -o $@
 
-http_parser_debug.o: http_parser.c http_parser.h Makefile
-       gcc $(OPT_DEBUG) -c http_parser.c
+test_g.o: test.c Makefile
+       gcc $(OPT_DEBUG) -c test.c -o $@
 
-test-valgrind: test_debug
-       valgrind ./test_debug
+test.o: test.c Makefile
+       gcc $(OPT_FAST) -c test.c -o $@
+
+http_parser_g.o: http_parser.c http_parser.h Makefile
+       gcc $(OPT_DEBUG) -c http_parser.c -o $@
+
+test-valgrind: test_g
+       valgrind ./test_g
 
 http_parser.o: http_parser.c http_parser.h Makefile
        gcc $(OPT_FAST) -c http_parser.c
@@ -28,6 +34,6 @@ tags: http_parser.c http_parser.h test.c
        ctags $^
 
 clean:
-       rm -f *.o test test_fast test_debug http_parser.tar tags
+       rm -f *.o test test_fast test_g http_parser.tar tags
 
 .PHONY: clean package test-run test-run-timed test-valgrind
index e342560..7823dd0 100644 (file)
@@ -195,7 +195,8 @@ enum state
   , s_body_identity_eof
   };
 
-#define PARSING_HEADER(state) (state <= s_headers_almost_done)
+
+#define PARSING_HEADER(state) (state <= s_headers_almost_done && 0 == (parser->flags & F_TRAILING))
 
 
 enum header_states
index 8646184..9accabd 100644 (file)
@@ -34,6 +34,8 @@
 #define MAX_HEADERS 10
 #define MAX_ELEMENT_SIZE 500
 
+#define MIN(a,b) ((a) < (b) ? (a) : (b))
+
 static http_parser *parser;
 
 struct message {
@@ -47,6 +49,7 @@ struct message {
   char fragment[MAX_ELEMENT_SIZE];
   char query_string[MAX_ELEMENT_SIZE];
   char body[MAX_ELEMENT_SIZE];
+  size_t body_size;
   int num_headers;
   enum { NONE=0, FIELD, VALUE } last_header_element;
   char headers [MAX_HEADERS][2][MAX_ELEMENT_SIZE];
@@ -597,6 +600,7 @@ const struct message responses[] =
   ,.status_code= 404
   ,.num_headers= 0
   ,.headers= {}
+  ,.body_size= 0
   ,.body= ""
   }
 
@@ -639,6 +643,7 @@ const struct message responses[] =
     { {"Content-Type", "text/plain" }
     , {"Transfer-Encoding", "chunked" }
     }
+  ,.body_size = 37+28
   ,.body =
          "This is the data in the first chunk\r\n"
          "and this is the second one\r\n"
@@ -785,11 +790,21 @@ body_cb (http_parser *p, const char *buf, size_t len)
 {
   assert(p == parser);
   strncat(messages[num_messages].body, buf, len);
+  messages[num_messages].body_size += len;
  // printf("body_cb: '%s'\n", requests[num_messages].body);
   return 0;
 }
 
 int
+count_body_cb (http_parser *p, const char *buf, size_t len)
+{
+  assert(p == parser);
+  assert(buf);
+  messages[num_messages].body_size += len;
+  return 0;
+}
+
+int
 message_begin_cb (http_parser *p)
 {
   assert(p == parser);
@@ -830,7 +845,7 @@ message_complete_cb (http_parser *p)
   return 0;
 }
 
-static http_parser_settings settings = 
+static http_parser_settings settings =
   {.on_message_begin = message_begin_cb
   ,.on_header_field = header_field_cb
   ,.on_header_value = header_value_cb
@@ -843,6 +858,19 @@ static http_parser_settings settings =
   ,.on_message_complete = message_complete_cb
   };
 
+static http_parser_settings settings_count_body =
+  {.on_message_begin = message_begin_cb
+  ,.on_header_field = header_field_cb
+  ,.on_header_value = header_value_cb
+  ,.on_path = request_path_cb
+  ,.on_url = request_url_cb
+  ,.on_fragment = fragment_cb
+  ,.on_query_string = query_string_cb
+  ,.on_body = count_body_cb
+  ,.on_headers_complete = headers_complete_cb
+  ,.on_message_complete = message_complete_cb
+  };
+
 void
 parser_init (enum http_parser_type type)
 {
@@ -874,6 +902,14 @@ inline size_t parse (const char *buf, size_t len)
   return nparsed;
 }
 
+inline size_t parse_count_body (const char *buf, size_t len)
+{
+  size_t nparsed;
+  currently_parsing_eof = (len == 0);
+  nparsed = http_parser_execute(parser, settings_count_body, buf, len);
+  return nparsed;
+}
+
 static inline int
 check_str_eq (const struct message *m,
               const char *prop,
@@ -936,7 +972,11 @@ message_eq (int index, const struct message *expected)
   MESSAGE_CHECK_STR_EQ(expected, m, query_string);
   MESSAGE_CHECK_STR_EQ(expected, m, fragment);
   MESSAGE_CHECK_STR_EQ(expected, m, request_url);
-  MESSAGE_CHECK_STR_EQ(expected, m, body);
+  if (expected->body_size) {
+    MESSAGE_CHECK_NUM_EQ(expected, m, body_size);
+  } else {
+    MESSAGE_CHECK_STR_EQ(expected, m, body);
+  }
 
   MESSAGE_CHECK_NUM_EQ(expected, m, num_headers);
 
@@ -1031,6 +1071,42 @@ test:
 }
 
 void
+test_message_count_body (const struct message *message)
+{
+  parser_init(message->type);
+
+  size_t read;
+  size_t l = strlen(message->raw);
+  size_t i, toread;
+  size_t chunk = 4024;
+
+  for (i = 0; i < l; i+= chunk) {
+    toread = MIN(l-i, chunk);
+    read = parse_count_body(message->raw + i, toread);
+    if (read != toread) {
+      print_error(message->raw, read);
+      exit(1);
+    }
+  }
+
+
+  read = parse_count_body(NULL, 0);
+  if (read != 0) {
+    print_error(message->raw, read);
+    exit(1);
+  }
+
+  if (num_messages != 1) {
+    printf("\n*** num_messages != 1 after testing '%s' ***\n\n", message->name);
+    exit(1);
+  }
+
+  if(!message_eq(0, message)) exit(1);
+
+  parser_free();
+}
+
+void
 test_error (const char *buf)
 {
   parser_init(HTTP_REQUEST);
@@ -1214,6 +1290,50 @@ error:
   exit(1);
 }
 
+// user required to free the result
+// string terminated by \0
+char *
+create_large_chunked_message (int body_size_in_kb, const char* headers)
+{
+  int i;
+  size_t needed, wrote = 0;
+  size_t headers_len = strlen(headers);
+  size_t bufsize = headers_len + 10;
+  char * buf = malloc(bufsize);
+
+  strncpy(buf, headers, headers_len);
+  wrote += headers_len;
+
+  for (i = 0; i < body_size_in_kb; i++) {
+    // write 1kb chunk into the body.
+    needed = 5 + 1024 + 2; // "400\r\nCCCC...CCCC\r\n"
+    if (bufsize - wrote < needed) {
+      buf = realloc(buf, bufsize + needed);
+      bufsize += needed;
+    }
+
+    strcpy(buf + wrote, "400\r\n");
+    wrote += 5;
+    memset(buf + wrote, 'C', 1024);
+    wrote += 1024;
+    strcpy(buf + wrote, "\r\n");
+    wrote += 2;
+  }
+
+  needed = 5; // "0\r\n\r\n"
+  if (bufsize - wrote < needed) {
+    buf = realloc(buf, bufsize + needed);
+    bufsize += needed;
+  }
+  strcpy(buf + wrote, "0\r\n\r\n");
+  wrote += 5;
+
+  assert(buf[wrote] == 0);
+
+  return buf;
+}
+
+
 int
 main (void)
 {
@@ -1243,6 +1363,38 @@ main (void)
     }
   }
 
+  test_message_count_body(&responses[NO_HEADERS_NO_BODY_404]);
+  test_message_count_body(&responses[TRAILING_SPACE_ON_CHUNKED_BODY]);
+
+  // test very large chunked response
+  {
+    char * msg = create_large_chunked_message(31337,
+      "HTTP/1.0 200 OK\r\n"
+      "Transfer-Encoding: chunked\r\n"
+      "Content-Type: text/plain\r\n"
+      "\r\n");
+    struct message large_chunked =
+      {.name= "large chunked"
+      ,.type= HTTP_RESPONSE
+      ,.raw= msg
+      ,.should_keep_alive= FALSE
+      ,.message_complete_on_eof= FALSE
+      ,.http_major= 1
+      ,.http_minor= 0
+      ,.status_code= 200
+      ,.num_headers= 2
+      ,.headers=
+        { { "Transfer-Encoding", "chunked" }
+        , { "Content-Type", "text/plain" }
+        }
+      ,.body_size= 31337*1024
+      };
+    test_message_count_body(&large_chunked);
+    free(msg);
+  }
+
+
+
   printf("response scan 1/1      ");
   test_scan( &responses[TRAILING_SPACE_ON_CHUNKED_BODY]
            , &responses[NO_HEADERS_NO_BODY_404]