text: /* request parsing and request object */
  
  #include "nyara.h"
  #include "request.h"
  #include <sys/time.h>
  
  static VALUE str_html;
  static VALUE request_class;
  static VALUE sym_reading;
  static VALUE sym_writing;
  static VALUE str_transfer_encoding;
  
  #define P \
    Request* p;\
    Data_Get_Struct(self, Request, p);
  
  static void request_mark(void* pp) {
    Request* p = pp;
    if (p) {
      rb_gc_mark_maybe(p->header);
      rb_gc_mark_maybe(p->accept);
      rb_gc_mark_maybe(p->format);
      rb_gc_mark_maybe(p->fiber);
      rb_gc_mark_maybe(p->scope);
      rb_gc_mark_maybe(p->path_with_query);
      rb_gc_mark_maybe(p->path);
      rb_gc_mark_maybe(p->query);
      rb_gc_mark_maybe(p->last_field);
      rb_gc_mark_maybe(p->last_value);
      rb_gc_mark_maybe(p->last_part);
      rb_gc_mark_maybe(p->body);
  
      rb_gc_mark_maybe(p->cookie);
      rb_gc_mark_maybe(p->session);
      rb_gc_mark_maybe(p->flash);
  
      rb_gc_mark_maybe(p->response_content_type);
      rb_gc_mark_maybe(p->response_header);
      rb_gc_mark_maybe(p->response_header_extra_lines);
      rb_gc_mark_maybe(p->watched_fds);
      rb_gc_mark_maybe(p->instance);
    }
  }
  
  static void request_free(void* pp) {
    Request* p = pp;
    if (p) {
      if (p->fd) {
        nyara_detach_rid(p->rid);
      }
      if (p->mparser) {
        multipart_parser_free(p->mparser);
        p->mparser = NULL;
      }
      xfree(p);
    }
  }
  
  static Request* _request_alloc() {
    static int curr_rid = 1;
  
    Request* p = ALLOC(Request);
    http_parser_init(&(p->hparser), HTTP_REQUEST);
    p->mparser = NULL;
  
    p->method = HTTP_GET;
    p->fd = 0;
    p->parse_state = 0;
    p->status = 200;
  
    volatile VALUE header = rb_class_new_instance(0, NULL, nyara_header_hash_class);
    volatile VALUE path = rb_enc_str_new("", 0, u8_encoding);
    volatile VALUE query = rb_class_new_instance(0, NULL, nyara_param_hash_class);
    p->header = header;
    p->accept = Qnil;
    p->format = Qnil;
    p->fiber = Qnil;
    p->scope = Qnil;
    p->path_with_query = Qnil;
    p->path = path;
    p->query = query;
    p->last_field = Qnil;
    p->last_value = Qnil;
    p->last_part = Qnil;
    p->body = Qnil;
  
    p->cookie = Qnil;
    p->session = Qnil;
    p->flash = Qnil;
  
    p->response_content_type = Qnil;
    p->response_header = Qnil;
    p->response_header_extra_lines = Qnil;
  
    volatile VALUE watched_fds = rb_ary_new();
    p->watched_fds = watched_fds;
    p->instance = Qnil;
  
    p->sleeping = false;
    nyara_request_touch(p);
  
    p->self = Data_Wrap_Struct(request_class, request_mark, request_free, p);
    p->rid = INT2FIX(curr_rid++); // todo synchronize?
  
    return p;
  }
  
  Request* nyara_request_new(int fd) {
    Request* p = _request_alloc();
    p->fd = fd;
    return p;
  }
  
  void nyara_request_term_close(VALUE self) {
    P;
    VALUE transfer_enc = rb_hash_aref(p->response_header, str_transfer_encoding);
    if (TYPE(transfer_enc) == T_STRING) {
      if (RSTRING_LEN(transfer_enc) == 7) {
        if (strncmp(RSTRING_PTR(transfer_enc), "chunked", 7) == 0) {
          // usually this succeeds, while not, it doesn't matter cause we are closing it
          if (write(p->fd, "0\r\n\r\n", 5)) {
          }
        }
      }
    }
    if (p->fd) {
      nyara_detach_rid(p->rid);
    }
  }
  
  void nyara_request_touch(Request* p) {
    struct timeval tv;
    gettimeofday(&tv, NULL);
    p->updated_at = tv.tv_sec;
  }
  
  static VALUE request_http_method(VALUE self) {
    P;
    // todo reduce allocation
    const char* str = http_method_str(p->method);
    return rb_enc_str_new(str, strlen(str), u8_encoding);
  }
  
  static VALUE request_header(VALUE self) {
    P;
    return p->header;
  }
  
  static VALUE request_scope(VALUE self) {
    P;
    return p->scope;
  }
  
  static VALUE request_path(VALUE self) {
    P;
    return p->path;
  }
  
  static VALUE request_query(VALUE self) {
    P;
    return p->query;
  }
  
  static VALUE request_path_with_query(VALUE self) {
    P;
    return p->path_with_query;
  }
  
  static VALUE request_accept(VALUE self) {
    P;
    return p->accept;
  }
  
  static VALUE request_format(VALUE self) {
    P;
    return p->format == Qnil ? str_html : p->format;
  }
  
  static VALUE request_cookie(VALUE self) {
    P;
    return p->cookie;
  }
  
  static VALUE request_session(VALUE self) {
    P;
    return p->session;
  }
  
  static VALUE request_session_eq(VALUE self, VALUE session) {
    P;
    if (p->session != Qnil) {
      rb_raise(rb_eRuntimeError, "request.session already initialized");
    }
    return p->session = session;
  }
  
  static VALUE request_flash(VALUE self) {
    P;
    return p->flash;
  }
  
  static VALUE request_flash_eq(VALUE self, VALUE flash) {
    P;
    if (p->flash != Qnil) {
      rb_raise(rb_eRuntimeError, "request.flash already initialized");
    }
    return p->flash = flash;
  }
  
  static VALUE request_body(VALUE self) {
    P;
    while (p->parse_state != PS_MESSAGE_COMPLETE) {
      rb_fiber_yield(1, &sym_reading);
    }
    return p->body;
  }
  
  static VALUE request_message_complete_p(VALUE self) {
    P;
    return (p->parse_state == PS_MESSAGE_COMPLETE) ? Qtrue : Qfalse;
  }
  
  static VALUE request_status(VALUE self) {
    P;
    return INT2FIX(p->status);
  }
  
  static VALUE request_response_content_type(VALUE self) {
    P;
    return p->response_content_type;
  }
  
  static VALUE request_response_content_type_eq(VALUE self, VALUE ct) {
    P;
    p->response_content_type = ct;
    return self;
  }
  
  static VALUE request_response_header(VALUE self) {
    P;
    return p->response_header;
  }
  
  static VALUE request_response_header_extra_lines(VALUE self) {
    P;
    return p->response_header_extra_lines;
  }
  
  static VALUE ext_request_set_status(VALUE _, VALUE self, VALUE n) {
    P;
    p->status = NUM2INT(n);
    return n;
  }
  
  // return true if success
  bool nyara_send_data(int fd, const char* buf, long len) {
    while(len) {
      long written = write(fd, buf, len);
      if (written <= 0) {
        if (errno == EAGAIN || errno == EWOULDBLOCK) {
          rb_fiber_yield(1, &sym_writing);
        } else {
          return false;
        }
      } else {
        buf += written;
        len -= written;
        if (len) {
          rb_fiber_yield(1, &sym_writing);
        }
      }
    }
    return true;
  }
  
  static VALUE ext_request_send_data(VALUE _, VALUE self, VALUE data) {
    P;
    char* buf = RSTRING_PTR(data);
    long len = RSTRING_LEN(data);
    nyara_send_data(p->fd, buf, len);
    return Qnil;
  }
  
  static VALUE ext_request_send_chunk(VALUE _, VALUE self, VALUE str) {
    long len = RSTRING_LEN(str);
    if (!len) {
      return Qnil;
    }
    P;
  
    char pre_buf[20]; // enough space to hold a long + 2 chars
    long pre_len = sprintf(pre_buf, "%lx\r\n", len);
    if (pre_len <= 0) {
      rb_raise(rb_eRuntimeError, "fail to format chunk length for len: %ld", len);
    }
    bool success = \
      nyara_send_data(p->fd, pre_buf, pre_len) &&
      nyara_send_data(p->fd, RSTRING_PTR(str), len) &&
      nyara_send_data(p->fd, "\r\n", 2);
  
    if (!success) {
      rb_sys_fail("write(2)");
    }
  
    return Qnil;
  }
  
  // for test: find or create a request with a fd
  static VALUE ext_request_new(VALUE _) {
    return _request_alloc()->self;
  }
  
  // NOTE: must pair with request_unset_fd
  static VALUE ext_request_set_fd(VALUE _, VALUE self, VALUE vfd) {
    P;
    int fd = NUM2INT(vfd);
    if (fd) {
      nyara_set_nonblock(fd);
      p->fd = fd;
    }
    return Qnil;
  }
  
  // unset fd so the free won't cause segfault
  static VALUE ext_request_unset_fd(VALUE _, VALUE self) {
    P;
    p->fd = 0;
    return Qnil;
  }
  
  // set internal attrs in the request object<br>
  // method_num is required, others are optional
  static VALUE ext_request_set_attrs(VALUE _, VALUE self, VALUE attrs) {
  # define ATTR(key) rb_hash_delete(attrs, ID2SYM(rb_intern(key)))
  # define HEADER_HASH_NEW rb_class_new_instance(0, NULL, nyara_header_hash_class)
    P;
  
    VALUE method_num = ATTR("method_num");
    if (method_num == Qnil) {
      rb_raise(rb_eArgError, "bad method_num");
    }
    p->method                      = NUM2INT(method_num);
    p->path                        = ATTR("path");
    p->query                       = ATTR("query");
    p->fiber                       = ATTR("fiber");
    p->scope                       = ATTR("scope");
    p->header                      = ATTR("header");
    p->format                      = ATTR("format");
    p->body                        = ATTR("body");
    p->cookie                      = ATTR("cookie");
    p->session                     = ATTR("session");
    p->flash                       = ATTR("flash");
    p->response_header             = ATTR("response_header");
    p->response_header_extra_lines = ATTR("response_header_extra_lines");
    p->parse_state                 = PS_MESSAGE_COMPLETE;
  
    if (!RTEST(p->header)) p->header = HEADER_HASH_NEW;
    if (!RTEST(p->response_header)) p->response_header = HEADER_HASH_NEW;
    if (!RTEST(p->response_header_extra_lines)) p->response_header_extra_lines = rb_ary_new();
  
    if (!RTEST(rb_funcall(attrs, rb_intern("empty?"), 0))) {
      VALUE attrs_inspect = rb_funcall(attrs, rb_intern("inspect"), 0);
      rb_raise(rb_eArgError, "unkown attrs: %.*s", (int)RSTRING_LEN(attrs_inspect), RSTRING_PTR(attrs_inspect));
    }
    return self;
  # undef HEADER_HASH_NEW
  # undef ATTR
  }
  
  void Init_request(VALUE nyara, VALUE ext) {
    str_html = rb_enc_str_new("html", strlen("html"), u8_encoding);
    OBJ_FREEZE(str_html);
    rb_gc_register_mark_object(str_html);
    sym_reading = ID2SYM(rb_intern("reading"));
    sym_writing = ID2SYM(rb_intern("writing"));
    str_transfer_encoding = rb_enc_str_new("Transfer-Encoding", strlen("Transfer-Encoding"), u8_encoding);
    rb_gc_register_mark_object(str_transfer_encoding);
  
    // request
    request_class = rb_define_class_under(nyara, "Request", rb_cObject);
    rb_define_method(request_class, "http_method", request_http_method, 0);
    rb_define_method(request_class, "header", request_header, 0);
    rb_define_method(request_class, "scope", request_scope, 0);
    rb_define_method(request_class, "path", request_path, 0);
    rb_define_method(request_class, "query", request_query, 0);
    rb_define_method(request_class, "path_with_query", request_path_with_query, 0);
    rb_define_method(request_class, "accept", request_accept, 0);
    rb_define_method(request_class, "format", request_format, 0);
    rb_define_method(request_class, "cookie", request_cookie, 0);
    rb_define_method(request_class, "session", request_session, 0);
    rb_define_method(request_class, "session=", request_session_eq, 1);
    rb_define_method(request_class, "flash", request_flash, 0);
    rb_define_method(request_class, "flash=", request_flash_eq, 1);
    rb_define_method(request_class, "body", request_body, 0);
    rb_define_method(request_class, "message_complete?", request_message_complete_p, 0);
  
    rb_define_method(request_class, "status", request_status, 0);
    rb_define_method(request_class, "response_content_type", request_response_content_type, 0);
    rb_define_method(request_class, "response_content_type=", request_response_content_type_eq, 1);
    rb_define_method(request_class, "response_header", request_response_header, 0);
    rb_define_method(request_class, "response_header_extra_lines", request_response_header_extra_lines, 0);
  
    // hide internal methods in ext
    rb_define_singleton_method(ext, "request_set_status", ext_request_set_status, 2);
    rb_define_singleton_method(ext, "request_send_data", ext_request_send_data, 2);
    rb_define_singleton_method(ext, "request_send_chunk", ext_request_send_chunk, 2);
    // for test
    rb_define_singleton_method(ext, "request_new", ext_request_new, 0);
    rb_define_singleton_method(ext, "request_set_fd", ext_request_set_fd, 2);
    rb_define_singleton_method(ext, "request_unset_fd", ext_request_unset_fd, 1);
    rb_define_singleton_method(ext, "request_set_attrs", ext_request_set_attrs, 2);
  }
  