/* request parsing and request object */ #include "nyara.h" #include #include "request.h" static VALUE str_html; static rb_encoding* u8_encoding; static VALUE request_class; static VALUE sym_writing; #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->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); } } static void request_free(void* pp) { Request* p = pp; if (p) { if (p->fd) { nyara_detach_fd(p->fd); } xfree(p); } } static Request* _request_alloc() { 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->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->sleeping = false; p->self = Data_Wrap_Struct(request_class, request_mark, request_free, p); return p; } VALUE nyara_request_new(int fd) { Request* p = _request_alloc(); p->fd = fd; return p->self; } void nyara_request_term_close(VALUE self, bool write_last_chunk) { P; if (write_last_chunk || p->status == 200) { // usually this succeeds, while not, it doesn't matter cause we are closing it if (write(p->fd, "0\r\n\r\n", 5)) { } } nyara_detach_fd(p->fd); p->fd = 0; } static VALUE request_http_method(VALUE self) { P; return rb_str_new2(http_method_str(p->method)); } 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_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 static bool _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); _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 = \ _send_data(p->fd, pre_buf, pre_len) && _send_data(p->fd, RSTRING_PTR(str), len) && _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; } static VALUE ext_request_set_fd(VALUE _, VALUE self, VALUE vfd) { P; p->fd = NUM2INT(vfd); return Qnil; } // set internal attrs in the request object 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->response_header = ATTR("response_header"); p->response_header_extra_lines = ATTR("response_header_extra_lines"); 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_str_new2("html"); OBJ_FREEZE(str_html); rb_gc_register_mark_object(str_html); u8_encoding = rb_utf8_encoding(); sym_writing = ID2SYM(rb_intern("writing")); // 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, "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_set_attrs", ext_request_set_attrs, 2); }