about summary refs log tree commit homepage
path: root/ext/unicorn_http/unicorn_http.rl
diff options
context:
space:
mode:
Diffstat (limited to 'ext/unicorn_http/unicorn_http.rl')
-rw-r--r--ext/unicorn_http/unicorn_http.rl461
1 files changed, 336 insertions, 125 deletions
diff --git a/ext/unicorn_http/unicorn_http.rl b/ext/unicorn_http/unicorn_http.rl
index 1ad2a5d..971cdc1 100644
--- a/ext/unicorn_http/unicorn_http.rl
+++ b/ext/unicorn_http/unicorn_http.rl
@@ -12,23 +12,81 @@
 #include "global_variables.h"
 #include "c_util.h"
 
+void init_unicorn_httpdate(void);
+
 #define UH_FL_CHUNKED  0x1
 #define UH_FL_HASBODY  0x2
 #define UH_FL_INBODY   0x4
 #define UH_FL_HASTRAILER 0x8
 #define UH_FL_INTRAILER 0x10
 #define UH_FL_INCHUNK  0x20
-#define UH_FL_KAMETHOD 0x40
+#define UH_FL_REQEOF 0x40
 #define UH_FL_KAVERSION 0x80
 #define UH_FL_HASHEADER 0x100
+#define UH_FL_TO_CLEAR 0x200
+
+/* all of these flags need to be set for keepalive to be supported */
+#define UH_FL_KEEPALIVE (UH_FL_KAVERSION | UH_FL_REQEOF | UH_FL_HASHEADER)
+
+/*
+ * whether or not to trust X-Forwarded-Proto and X-Forwarded-SSL when
+ * setting rack.url_scheme
+ */
+static VALUE trust_x_forward = Qtrue;
+
+static unsigned long keepalive_requests = 100; /* same as nginx */
+
+/*
+ * Returns the maximum number of keepalive requests a client may make
+ * before the parser refuses to continue.
+ */
+static VALUE ka_req(VALUE self)
+{
+  return ULONG2NUM(keepalive_requests);
+}
 
-/* both of these flags need to be set for keepalive to be supported */
-#define UH_FL_KEEPALIVE (UH_FL_KAMETHOD | UH_FL_KAVERSION)
+/*
+ * Sets the maximum number of keepalive requests a client may make.
+ * A special value of +nil+ causes this to be the maximum value
+ * possible (this is architecture-dependent).
+ */
+static VALUE set_ka_req(VALUE self, VALUE val)
+{
+  keepalive_requests = NIL_P(val) ? ULONG_MAX : NUM2ULONG(val);
+
+  return ka_req(self);
+}
+
+/*
+ * Sets whether or not the parser will trust X-Forwarded-Proto and
+ * X-Forwarded-SSL headers and set "rack.url_scheme" to "https" accordingly.
+ * Rainbows!/Zbatery installations facing untrusted clients directly
+ * should set this to +false+
+ */
+static VALUE set_xftrust(VALUE self, VALUE val)
+{
+  if (Qtrue == val || Qfalse == val)
+    trust_x_forward = val;
+  else
+    rb_raise(rb_eTypeError, "must be true or false");
+
+  return val;
+}
+
+/*
+ * returns whether or not the parser will trust X-Forwarded-Proto and
+ * X-Forwarded-SSL headers and set "rack.url_scheme" to "https" accordingly
+ */
+static VALUE xftrust(VALUE self)
+{
+  return trust_x_forward;
+}
 
 /* keep this small for Rainbows! since every client has one */
 struct http_parser {
   int cs; /* Ragel internal state */
   unsigned int flags;
+  unsigned long nr_requests;
   size_t mark;
   size_t offset;
   union { /* these 2 fields don't nest */
@@ -39,6 +97,8 @@ struct http_parser {
     size_t field_len; /* only used during header processing */
     size_t dest_offset; /* only used during body processing */
   } s;
+  VALUE buf;
+  VALUE env;
   VALUE cont; /* Qfalse: unset, Qnil: ignored header, T_STRING: append */
   union {
     off_t content;
@@ -46,7 +106,18 @@ struct http_parser {
   } len;
 };
 
-static void finalize_header(struct http_parser *hp, VALUE req);
+static ID id_clear;
+
+static void finalize_header(struct http_parser *hp);
+
+static void parser_error(const char *msg)
+{
+  VALUE exc = rb_exc_new2(eHttpParserError, msg);
+  VALUE bt = rb_ary_new();
+
+  rb_funcall(exc, rb_intern("set_backtrace"), 1, bt);
+  rb_exc_raise(exc);
+}
 
 #define REMAINING (unsigned long)(pe - p)
 #define LEN(AT, FPC) (FPC - buffer - hp->AT)
@@ -66,51 +137,34 @@ static void finalize_header(struct http_parser *hp, VALUE req);
  */
 static void hp_keepalive_connection(struct http_parser *hp, VALUE val)
 {
-  /* REQUEST_METHOD is always set before any headers */
-  if (HP_FL_TEST(hp, KAMETHOD)) {
-    if (STR_CSTR_CASE_EQ(val, "keep-alive")) {
-      /* basically have HTTP/1.0 masquerade as HTTP/1.1+ */
-      HP_FL_SET(hp, KAVERSION);
-    } else if (STR_CSTR_CASE_EQ(val, "close")) {
-      /*
-       * it doesn't matter what HTTP version or request method we have,
-       * if a client says "Connection: close", we disable keepalive
-       */
-      HP_FL_UNSET(hp, KEEPALIVE);
-    } else {
-      /*
-       * client could've sent anything, ignore it for now.  Maybe
-       * "HP_FL_UNSET(hp, KEEPALIVE);" just in case?
-       * Raising an exception might be too mean...
-       */
-    }
+  if (STR_CSTR_CASE_EQ(val, "keep-alive")) {
+    /* basically have HTTP/1.0 masquerade as HTTP/1.1+ */
+    HP_FL_SET(hp, KAVERSION);
+  } else if (STR_CSTR_CASE_EQ(val, "close")) {
+    /*
+     * it doesn't matter what HTTP version or request method we have,
+     * if a client says "Connection: close", we disable keepalive
+     */
+    HP_FL_UNSET(hp, KAVERSION);
+  } else {
+    /*
+     * client could've sent anything, ignore it for now.  Maybe
+     * "HP_FL_UNSET(hp, KAVERSION);" just in case?
+     * Raising an exception might be too mean...
+     */
   }
 }
 
 static void
-request_method(struct http_parser *hp, VALUE req, const char *ptr, size_t len)
+request_method(struct http_parser *hp, const char *ptr, size_t len)
 {
-  VALUE v;
+  VALUE v = rb_str_new(ptr, len);
 
-  /*
-   * we only support keepalive for GET and HEAD requests for now other
-   * methods are too rarely seen to be worth optimizing.  POST is unsafe
-   * since some clients send extra bytes after POST bodies.
-   */
-  if (CONST_MEM_EQ("GET", ptr, len)) {
-    HP_FL_SET(hp, KAMETHOD);
-    v = g_GET;
-  } else if (CONST_MEM_EQ("HEAD", ptr, len)) {
-    HP_FL_SET(hp, KAMETHOD);
-    v = g_HEAD;
-  } else {
-    v = rb_str_new(ptr, len);
-  }
-  rb_hash_aset(req, g_request_method, v);
+  rb_hash_aset(hp->env, g_request_method, v);
 }
 
 static void
-http_version(struct http_parser *hp, VALUE req, const char *ptr, size_t len)
+http_version(struct http_parser *hp, const char *ptr, size_t len)
 {
   VALUE v;
 
@@ -125,14 +179,14 @@ http_version(struct http_parser *hp, VALUE req, const char *ptr, size_t len)
   } else {
     v = rb_str_new(ptr, len);
   }
-  rb_hash_aset(req, g_server_protocol, v);
-  rb_hash_aset(req, g_http_version, v);
+  rb_hash_aset(hp->env, g_server_protocol, v);
+  rb_hash_aset(hp->env, g_http_version, v);
 }
 
 static inline void hp_invalid_if_trailer(struct http_parser *hp)
 {
   if (HP_FL_TEST(hp, INTRAILER))
-    rb_raise(eHttpParserError, "invalid Trailer");
+    parser_error("invalid Trailer");
 }
 
 static void write_cont_value(struct http_parser *hp,
@@ -141,7 +195,7 @@ static void write_cont_value(struct http_parser *hp,
   char *vptr;
 
   if (hp->cont == Qfalse)
-     rb_raise(eHttpParserError, "invalid continuation line");
+     parser_error("invalid continuation line");
   if (NIL_P(hp->cont))
      return; /* we're ignoring this header (probably Host:) */
 
@@ -163,7 +217,7 @@ static void write_cont_value(struct http_parser *hp,
   rb_str_buf_cat(hp->cont, vptr, LEN(mark, p));
 }
 
-static void write_value(VALUE req, struct http_parser *hp,
+static void write_value(struct http_parser *hp,
                         const char *buffer, const char *p)
 {
   VALUE f = find_common_field(PTR_TO(start.field), hp->s.field_len);
@@ -192,8 +246,9 @@ static void write_value(VALUE req, struct http_parser *hp,
   } else if (f == g_content_length) {
     hp->len.content = parse_length(RSTRING_PTR(v), RSTRING_LEN(v));
     if (hp->len.content < 0)
-      rb_raise(eHttpParserError, "invalid Content-Length");
-    HP_FL_SET(hp, HASBODY);
+      parser_error("invalid Content-Length");
+    if (hp->len.content != 0)
+      HP_FL_SET(hp, HASBODY);
     hp_invalid_if_trailer(hp);
   } else if (f == g_http_transfer_encoding) {
     if (STR_CSTR_CASE_EQ(v, "chunked")) {
@@ -209,9 +264,9 @@ static void write_value(VALUE req, struct http_parser *hp,
     assert_frozen(f);
   }
 
-  e = rb_hash_aref(req, f);
+  e = rb_hash_aref(hp->env, f);
   if (NIL_P(e)) {
-    hp->cont = rb_hash_aset(req, f, v);
+    hp->cont = rb_hash_aset(hp->env, f, v);
   } else if (f == g_http_host) {
     /*
      * ignored, absolute URLs in REQUEST_URI take precedence over
@@ -236,59 +291,55 @@ static void write_value(VALUE req, struct http_parser *hp,
   action downcase_char { downcase_char(deconst(fpc)); }
   action write_field { hp->s.field_len = LEN(start.field, fpc); }
   action start_value { MARK(mark, fpc); }
-  action write_value { write_value(req, hp, buffer, fpc); }
+  action write_value { write_value(hp, buffer, fpc); }
   action write_cont_value { write_cont_value(hp, buffer, fpc); }
-  action request_method {
-    request_method(hp, req, PTR_TO(mark), LEN(mark, fpc));
-  }
+  action request_method { request_method(hp, PTR_TO(mark), LEN(mark, fpc)); }
   action scheme {
-    rb_hash_aset(req, g_rack_url_scheme, STR_NEW(mark, fpc));
-  }
-  action host {
-    rb_hash_aset(req, g_http_host, STR_NEW(mark, fpc));
+    rb_hash_aset(hp->env, g_rack_url_scheme, STR_NEW(mark, fpc));
   }
+  action host { rb_hash_aset(hp->env, g_http_host, STR_NEW(mark, fpc)); }
   action request_uri {
     VALUE str;
 
     VALIDATE_MAX_LENGTH(LEN(mark, fpc), REQUEST_URI);
-    str = rb_hash_aset(req, g_request_uri, STR_NEW(mark, fpc));
+    str = rb_hash_aset(hp->env, g_request_uri, STR_NEW(mark, fpc));
     /*
      * "OPTIONS * HTTP/1.1\r\n" is a valid request, but we can't have '*'
      * in REQUEST_PATH or PATH_INFO or else Rack::Lint will complain
      */
     if (STR_CSTR_EQ(str, "*")) {
       str = rb_str_new(NULL, 0);
-      rb_hash_aset(req, g_path_info, str);
-      rb_hash_aset(req, g_request_path, str);
+      rb_hash_aset(hp->env, g_path_info, str);
+      rb_hash_aset(hp->env, g_request_path, str);
     }
   }
   action fragment {
     VALIDATE_MAX_LENGTH(LEN(mark, fpc), FRAGMENT);
-    rb_hash_aset(req, g_fragment, STR_NEW(mark, fpc));
+    rb_hash_aset(hp->env, g_fragment, STR_NEW(mark, fpc));
   }
   action start_query {MARK(start.query, fpc); }
   action query_string {
     VALIDATE_MAX_LENGTH(LEN(start.query, fpc), QUERY_STRING);
-    rb_hash_aset(req, g_query_string, STR_NEW(start.query, fpc));
+    rb_hash_aset(hp->env, g_query_string, STR_NEW(start.query, fpc));
   }
-  action http_version { http_version(hp, req, PTR_TO(mark), LEN(mark, fpc)); }
+  action http_version { http_version(hp, PTR_TO(mark), LEN(mark, fpc)); }
   action request_path {
     VALUE val;
 
     VALIDATE_MAX_LENGTH(LEN(mark, fpc), REQUEST_PATH);
-    val = rb_hash_aset(req, g_request_path, STR_NEW(mark, fpc));
+    val = rb_hash_aset(hp->env, g_request_path, STR_NEW(mark, fpc));
 
     /* rack says PATH_INFO must start with "/" or be empty */
     if (!STR_CSTR_EQ(val, "*"))
-      rb_hash_aset(req, g_path_info, val);
+      rb_hash_aset(hp->env, g_path_info, val);
   }
   action add_to_chunk_size {
     hp->len.chunk = step_incr(hp->len.chunk, fc, 16);
     if (hp->len.chunk < 0)
-      rb_raise(eHttpParserError, "invalid chunk size");
+      parser_error("invalid chunk size");
   }
   action header_done {
-    finalize_header(hp, req);
+    finalize_header(hp);
 
     cs = http_parser_first_final;
     if (HP_FL_TEST(hp, HASBODY)) {
@@ -296,6 +347,7 @@ static void write_value(VALUE req, struct http_parser *hp,
       if (HP_FL_TEST(hp, CHUNKED))
         cs = http_parser_en_ChunkedBody;
     } else {
+      HP_FL_SET(hp, REQEOF);
       assert(!HP_FL_TEST(hp, CHUNKED) && "chunked encoding without body!");
     }
     /*
@@ -321,7 +373,7 @@ static void write_value(VALUE req, struct http_parser *hp,
   action skip_chunk_data {
   skip_chunk_data_hack: {
     size_t nr = MIN((size_t)hp->len.chunk, REMAINING);
-    memcpy(RSTRING_PTR(req) + hp->s.dest_offset, fpc, nr);
+    memcpy(RSTRING_PTR(hp->cont) + hp->s.dest_offset, fpc, nr);
     hp->s.dest_offset += nr;
     hp->len.chunk -= nr;
     p += nr;
@@ -344,15 +396,20 @@ static void write_value(VALUE req, struct http_parser *hp,
 static void http_parser_init(struct http_parser *hp)
 {
   int cs = 0;
-  memset(hp, 0, sizeof(struct http_parser));
+  hp->flags = 0;
+  hp->mark = 0;
+  hp->offset = 0;
+  hp->start.field = 0;
+  hp->s.field_len = 0;
+  hp->len.content = 0;
   hp->cont = Qfalse; /* zero on MRI, should be optimized away by above */
   %% write init;
   hp->cs = cs;
 }
 
 /** exec **/
-static void http_parser_execute(struct http_parser *hp,
-  VALUE req, char *buffer, size_t len)
+static void
+http_parser_execute(struct http_parser *hp, char *buffer, size_t len)
 {
   const char *p, *pe;
   int cs = hp->cs;
@@ -392,54 +449,110 @@ static struct http_parser *data_get(VALUE self)
   return hp;
 }
 
-static void finalize_header(struct http_parser *hp, VALUE req)
+/*
+ * set rack.url_scheme to "https" or "http", no others are allowed by Rack
+ * this resembles the Rack::Request#scheme method as of rack commit
+ * 35bb5ba6746b5d346de9202c004cc926039650c7
+ */
+static void set_url_scheme(VALUE env, VALUE *server_port)
 {
-  VALUE temp = rb_hash_aref(req, g_rack_url_scheme);
-  VALUE server_name = g_localhost;
-  VALUE server_port = g_port_80;
+  VALUE scheme = rb_hash_aref(env, g_rack_url_scheme);
 
-  /* set rack.url_scheme to "https" or "http", no others are allowed by Rack */
-  if (NIL_P(temp)) {
-    temp = rb_hash_aref(req, g_http_x_forwarded_proto);
-    if (!NIL_P(temp) && STR_CSTR_EQ(temp, "https"))
-      server_port = g_port_443;
-    else
-      temp = g_http;
-    rb_hash_aset(req, g_rack_url_scheme, temp);
-  } else if (STR_CSTR_EQ(temp, "https")) {
-    server_port = g_port_443;
+  if (NIL_P(scheme)) {
+    if (trust_x_forward == Qfalse) {
+      scheme = g_http;
+    } else {
+      scheme = rb_hash_aref(env, g_http_x_forwarded_ssl);
+      if (!NIL_P(scheme) && STR_CSTR_EQ(scheme, "on")) {
+        *server_port = g_port_443;
+        scheme = g_https;
+      } else {
+        scheme = rb_hash_aref(env, g_http_x_forwarded_proto);
+        if (NIL_P(scheme)) {
+          scheme = g_http;
+        } else {
+          long len = RSTRING_LEN(scheme);
+          if (len >= 5 && !memcmp(RSTRING_PTR(scheme), "https", 5)) {
+            if (len != 5)
+              scheme = g_https;
+            *server_port = g_port_443;
+          } else {
+            scheme = g_http;
+          }
+        }
+      }
+    }
+    rb_hash_aset(env, g_rack_url_scheme, scheme);
+  } else if (STR_CSTR_EQ(scheme, "https")) {
+    *server_port = g_port_443;
   } else {
-    assert(server_port == g_port_80 && "server_port not set");
+    assert(*server_port == g_port_80 && "server_port not set");
   }
+}
+
+/*
+ * Parse and set the SERVER_NAME and SERVER_PORT variables
+ * Not supporting X-Forwarded-Host/X-Forwarded-Port in here since
+ * anybody who needs them is using an unsupported configuration and/or
+ * incompetent.  Rack::Request will handle X-Forwarded-{Port,Host} just
+ * fine.
+ */
+static void set_server_vars(VALUE env, VALUE *server_port)
+{
+  VALUE server_name = g_localhost;
+  VALUE host = rb_hash_aref(env, g_http_host);
+
+  if (!NIL_P(host)) {
+    char *host_ptr = RSTRING_PTR(host);
+    long host_len = RSTRING_LEN(host);
+    char *colon;
+
+    if (*host_ptr == '[') { /* ipv6 address format */
+      char *rbracket = memchr(host_ptr + 1, ']', host_len - 1);
+
+      if (rbracket)
+        colon = (rbracket[1] == ':') ? rbracket + 1 : NULL;
+      else
+        colon = memchr(host_ptr + 1, ':', host_len - 1);
+    } else {
+      colon = memchr(host_ptr, ':', host_len);
+    }
 
-  /* parse and set the SERVER_NAME and SERVER_PORT variables */
-  temp = rb_hash_aref(req, g_http_host);
-  if (!NIL_P(temp)) {
-    char *colon = memchr(RSTRING_PTR(temp), ':', RSTRING_LEN(temp));
     if (colon) {
-      long port_start = colon - RSTRING_PTR(temp) + 1;
+      long port_start = colon - host_ptr + 1;
 
-      server_name = rb_str_substr(temp, 0, colon - RSTRING_PTR(temp));
-      if ((RSTRING_LEN(temp) - port_start) > 0)
-        server_port = rb_str_substr(temp, port_start, RSTRING_LEN(temp));
+      server_name = rb_str_substr(host, 0, colon - host_ptr);
+      if ((host_len - port_start) > 0)
+        *server_port = rb_str_substr(host, port_start, host_len);
     } else {
-      server_name = temp;
+      server_name = host;
     }
   }
-  rb_hash_aset(req, g_server_name, server_name);
-  rb_hash_aset(req, g_server_port, server_port);
+  rb_hash_aset(env, g_server_name, server_name);
+  rb_hash_aset(env, g_server_port, *server_port);
+}
+
+static void finalize_header(struct http_parser *hp)
+{
+  VALUE server_port = g_port_80;
+
+  set_url_scheme(hp->env, &server_port);
+  set_server_vars(hp->env, &server_port);
+
   if (!HP_FL_TEST(hp, HASHEADER))
-    rb_hash_aset(req, g_server_protocol, g_http_09);
+    rb_hash_aset(hp->env, g_server_protocol, g_http_09);
 
   /* rack requires QUERY_STRING */
-  if (NIL_P(rb_hash_aref(req, g_query_string)))
-    rb_hash_aset(req, g_query_string, rb_str_new(NULL, 0));
+  if (NIL_P(rb_hash_aref(hp->env, g_query_string)))
+    rb_hash_aset(hp->env, g_query_string, rb_str_new(NULL, 0));
 }
 
 static void hp_mark(void *ptr)
 {
   struct http_parser *hp = ptr;
 
+  rb_gc_mark(hp->buf);
+  rb_gc_mark(hp->env);
   rb_gc_mark(hp->cont);
 }
 
@@ -458,7 +571,29 @@ static VALUE HttpParser_alloc(VALUE klass)
  */
 static VALUE HttpParser_init(VALUE self)
 {
-  http_parser_init(data_get(self));
+  struct http_parser *hp = data_get(self);
+
+  http_parser_init(hp);
+  hp->buf = rb_str_new(NULL, 0);
+  hp->env = rb_hash_new();
+  hp->nr_requests = keepalive_requests;
+
+  return self;
+}
+
+/**
+ * call-seq:
+ *    parser.clear => parser
+ *
+ * Resets the parser to it's initial state so that you can reuse it
+ * rather than making new ones.
+ */
+static VALUE HttpParser_clear(VALUE self)
+{
+  struct http_parser *hp = data_get(self);
+
+  http_parser_init(hp);
+  rb_funcall(hp->env, id_clear, 0);
 
   return self;
 }
@@ -469,11 +604,18 @@ static VALUE HttpParser_init(VALUE self)
  *
  * Resets the parser to it's initial state so that you can reuse it
  * rather than making new ones.
+ *
+ * This method is deprecated and to be removed in Unicorn 4.x
  */
 static VALUE HttpParser_reset(VALUE self)
 {
-  http_parser_init(data_get(self));
+  static int warned;
 
+  if (!warned) {
+    rb_warn("Unicorn::HttpParser#reset is deprecated; "
+            "use Unicorn::HttpParser#clear instead");
+  }
+  HttpParser_clear(self);
   return Qnil;
 }
 
@@ -513,48 +655,67 @@ static VALUE HttpParser_content_length(VALUE self)
 }
 
 /**
- * Document-method: trailers
+ * Document-method: parse
  * call-seq:
- *    parser.trailers(req, data) => req or nil
- *
- * This is an alias for HttpParser#headers
- */
-
-/**
- * Document-method: headers
- * call-seq:
- *    parser.headers(req, data) => req or nil
+ *    parser.parse => env or nil
  *
  * Takes a Hash and a String of data, parses the String of data filling
  * in the Hash returning the Hash if parsing is finished, nil otherwise
- * When returning the req Hash, it may modify data to point to where
+ * When returning the env Hash, it may modify data to point to where
  * body processing should begin.
  *
  * Raises HttpParserError if there are parsing errors.
  */
-static VALUE HttpParser_headers(VALUE self, VALUE req, VALUE data)
+static VALUE HttpParser_parse(VALUE self)
 {
   struct http_parser *hp = data_get(self);
+  VALUE data = hp->buf;
 
-  rb_str_update(data);
+  if (HP_FL_TEST(hp, TO_CLEAR)) {
+    http_parser_init(hp);
+    rb_funcall(hp->env, id_clear, 0);
+  }
 
-  http_parser_execute(hp, req, RSTRING_PTR(data), RSTRING_LEN(data));
+  http_parser_execute(hp, RSTRING_PTR(data), RSTRING_LEN(data));
   VALIDATE_MAX_LENGTH(hp->offset, HEADER);
 
   if (hp->cs == http_parser_first_final ||
       hp->cs == http_parser_en_ChunkedBody) {
     advance_str(data, hp->offset + 1);
     hp->offset = 0;
+    if (HP_FL_TEST(hp, INTRAILER))
+      HP_FL_SET(hp, REQEOF);
 
-    return req;
+    return hp->env;
   }
 
   if (hp->cs == http_parser_error)
-    rb_raise(eHttpParserError, "Invalid HTTP format, parsing fails.");
+    parser_error("Invalid HTTP format, parsing fails.");
 
   return Qnil;
 }
 
+/**
+ * Document-method: trailers
+ * call-seq:
+ *    parser.trailers(req, data) => req or nil
+ *
+ * This is an alias for HttpParser#headers
+ */
+
+/**
+ * Document-method: headers
+ */
+static VALUE HttpParser_headers(VALUE self, VALUE env, VALUE buf)
+{
+  struct http_parser *hp = data_get(self);
+
+  hp->env = env;
+  hp->buf = buf;
+
+  return HttpParser_parse(self);
+}
+
 static int chunked_eof(struct http_parser *hp)
 {
   return ((hp->cs == http_parser_first_final) || HP_FL_TEST(hp, INTRAILER));
@@ -597,6 +758,26 @@ static VALUE HttpParser_keepalive(VALUE self)
 
 /**
  * call-seq:
+ *    parser.next? => true or false
+ *
+ * Exactly like HttpParser#keepalive?, except it will reset the internal
+ * parser state on next parse if it returns true.  It will also respect
+ * the maximum *keepalive_requests* value and return false if that is
+ * reached.
+ */
+static VALUE HttpParser_next(VALUE self)
+{
+  struct http_parser *hp = data_get(self);
+
+  if ((HP_FL_ALL(hp, KEEPALIVE)) && (hp->nr_requests-- != 0)) {
+    HP_FL_SET(hp, TO_CLEAR);
+    return Qtrue;
+  }
+  return Qfalse;
+}
+
+/**
+ * call-seq:
  *    parser.headers? => true or false
  *
  * This should be used to detect if a request has headers (and if
@@ -610,6 +791,16 @@ static VALUE HttpParser_has_headers(VALUE self)
   return HP_FL_TEST(hp, HASHEADER) ? Qtrue : Qfalse;
 }
 
+static VALUE HttpParser_buf(VALUE self)
+{
+  return data_get(self)->buf;
+}
+
+static VALUE HttpParser_env(VALUE self)
+{
+  return data_get(self)->env;
+}
+
 /**
  * call-seq:
  *    parser.filter_body(buf, data) => nil/data
@@ -630,7 +821,6 @@ static VALUE HttpParser_filter_body(VALUE self, VALUE buf, VALUE data)
   char *dptr;
   long dlen;
 
-  rb_str_update(data);
   dptr = RSTRING_PTR(data);
   dlen = RSTRING_LEN(data);
 
@@ -641,9 +831,11 @@ static VALUE HttpParser_filter_body(VALUE self, VALUE buf, VALUE data)
   if (HP_FL_TEST(hp, CHUNKED)) {
     if (!chunked_eof(hp)) {
       hp->s.dest_offset = 0;
-      http_parser_execute(hp, buf, dptr, dlen);
+      hp->cont = buf;
+      hp->buf = data;
+      http_parser_execute(hp, dptr, dlen);
       if (hp->cs == http_parser_error)
-        rb_raise(eHttpParserError, "Invalid HTTP format, parsing fails.");
+        parser_error("Invalid HTTP format, parsing fails.");
 
       assert(hp->s.dest_offset <= hp->offset &&
              "destination buffer overflow");
@@ -662,10 +854,13 @@ static VALUE HttpParser_filter_body(VALUE self, VALUE buf, VALUE data)
     if (hp->len.content > 0) {
       long nr = MIN(dlen, hp->len.content);
 
+      hp->buf = data;
       memcpy(RSTRING_PTR(buf), dptr, nr);
       hp->len.content -= nr;
-      if (hp->len.content == 0)
+      if (hp->len.content == 0) {
+        HP_FL_SET(hp, REQEOF);
         hp->cs = http_parser_first_final;
+      }
       advance_str(data, nr);
       rb_str_set_len(buf, nr);
       data = Qnil;
@@ -691,15 +886,20 @@ void Init_unicorn_http(void)
 
   init_globals();
   rb_define_alloc_func(cHttpParser, HttpParser_alloc);
-  rb_define_method(cHttpParser, "initialize", HttpParser_init,0);
-  rb_define_method(cHttpParser, "reset", HttpParser_reset,0);
+  rb_define_method(cHttpParser, "initialize", HttpParser_init, 0);
+  rb_define_method(cHttpParser, "clear", HttpParser_clear, 0);
+  rb_define_method(cHttpParser, "reset", HttpParser_reset, 0);
+  rb_define_method(cHttpParser, "parse", HttpParser_parse, 0);
   rb_define_method(cHttpParser, "headers", HttpParser_headers, 2);
-  rb_define_method(cHttpParser, "filter_body", HttpParser_filter_body, 2);
   rb_define_method(cHttpParser, "trailers", HttpParser_headers, 2);
+  rb_define_method(cHttpParser, "filter_body", HttpParser_filter_body, 2);
   rb_define_method(cHttpParser, "content_length", HttpParser_content_length, 0);
   rb_define_method(cHttpParser, "body_eof?", HttpParser_body_eof, 0);
   rb_define_method(cHttpParser, "keepalive?", HttpParser_keepalive, 0);
   rb_define_method(cHttpParser, "headers?", HttpParser_has_headers, 0);
+  rb_define_method(cHttpParser, "next?", HttpParser_next, 0);
+  rb_define_method(cHttpParser, "buf", HttpParser_buf, 0);
+  rb_define_method(cHttpParser, "env", HttpParser_env, 0);
 
   /*
    * The maximum size a single chunk when using chunked transfer encoding.
@@ -716,11 +916,22 @@ void Init_unicorn_http(void)
    */
   rb_define_const(cHttpParser, "LENGTH_MAX", OFFT2NUM(UH_OFF_T_MAX));
 
+  /* default value for keepalive_requests */
+  rb_define_const(cHttpParser, "KEEPALIVE_REQUESTS_DEFAULT",
+                  ULONG2NUM(keepalive_requests));
+
+  rb_define_singleton_method(cHttpParser, "keepalive_requests", ka_req, 0);
+  rb_define_singleton_method(cHttpParser, "keepalive_requests=", set_ka_req, 1);
+  rb_define_singleton_method(cHttpParser, "trust_x_forwarded=", set_xftrust, 1);
+  rb_define_singleton_method(cHttpParser, "trust_x_forwarded?", xftrust, 0);
+
   init_common_fields();
   SET_GLOBAL(g_http_host, "HOST");
   SET_GLOBAL(g_http_trailer, "TRAILER");
   SET_GLOBAL(g_http_transfer_encoding, "TRANSFER_ENCODING");
   SET_GLOBAL(g_content_length, "CONTENT_LENGTH");
   SET_GLOBAL(g_http_connection, "CONNECTION");
+  id_clear = rb_intern("clear");
+  init_unicorn_httpdate();
 }
 #undef SET_GLOBAL