ext/revdispatch/revdispatch.cc in evdispatch-0.2.6 vs ext/revdispatch/revdispatch.cc in evdispatch-0.3.0

- old
+ new

@@ -1,19 +1,9 @@ -#include <ruby.h> -#include "ev_dispatch.h" -#include "ev_http.h" +#include "util.h" +#include "rhttp.h" -/* ruby 1.9 compat */ -#ifndef RSTRING_PTR -#define RSTRING_PTR(str) RSTRING(str)->ptr -#endif -#ifndef RSTRING_LEN -#define RSTRING_LEN(str) RSTRING(str)->len -#endif - - /* * * Using ruby dispatch interface: * * require 'rev_dispatch' @@ -45,11 +35,14 @@ * the request method can be used passing it options will set specific libcurl options * */ static VALUE rb_Evdispatch; static VALUE rb_Loop; +static VALUE rb_Response; +static void Response_free( rResponse *res ); + /** * call-seq: * loop.start -> loop * * Starts up the background event loop @@ -75,10 +68,12 @@ VALUE Loop_request_http( VALUE self, VALUE url ) { EVD::Dispatch *d; Data_Get_Struct( self, EVD::Dispatch, d ); + Check_Type( url, T_STRING ); + EVD::request_t id = d->request( new EVD::HttpRequest( *d, RSTRING_PTR(url) ) ); return rb_int_new(id); } @@ -96,33 +91,44 @@ VALUE obj = rb_hash_aref( options, ID2SYM(rb_intern(name)) ); \ if( !NIL_P(obj) ) {\ req->set_opt(name, RSTRING_PTR(obj));\ }\ } +// set options +#define SET_REQ_OPTS \ + SET_LONG_VAL("port"); \ + SET_STR_VAL("autoreferer"); \ + SET_LONG_VAL("followlocation"); \ + SET_LONG_VAL("maxredirs"); \ + SET_STR_VAL("referer"); \ + SET_STR_VAL("useragent"); \ + SET_STR_VAL("cookie"); \ + SET_STR_VAL("post"); /** * call-seq: * loop.request( url, options ) -> request_id * * Notify the background of a new request. Can send a few options to influence the request: * * options ( from libcurl ): * - * :port: port to connect to host with - * :autoreferer: Pass a non-zero parameter to enable this. When enabled, libcurl will automatically set the Referer: field in requests where it follows a Location: redirect. - * :followlocation: A non-zero parameter tells the library to follow any Location: header that the server sends as part of an HTTP header. + * :port: (Fixnum) port to connect to host with + * :autoreferer: (Fixnum) Pass a non-zero parameter to enable this. When enabled, libcurl will automatically set the Referer: field in requests where it follows a Location: redirect. + * :followlocation: (Fixnum) A non-zero parameter tells the library to follow any Location: header that the server sends as part of an HTTP header. * This means that the library will re-send the same request on the new location and follow new Location: headers all the way until no more such headers are returned. 'maxredirs' can be used to limit the number of redirects libcurl will follow. - * :maxredirs: Pass a long. The set number will be the redirection limit. If that many redirections have been followed, the next redirect will cause an error (CURLE_TOO_MANY_REDIRECTS). This option only makes sense if the CURLOPT_FOLLOWLOCATION is used at the same time. Added in 7.15.1: Setting the limit to 0 will make libcurl refuse any redirect. Set it to -1 for an infinite number of redirects (which is the default) - * :referer: Pass a pointer to a zero terminated string as parameter. It will be used to set the Referer: header in the http request sent to the remote server. This can be used to fool servers or scripts. You can also set any custom header with CURLOPT_HTTPHEADER. - * :useragent: Pass a pointer to a zero terminated string as parameter. It will be used to set the User-Agent: header in the http request sent to the remote server. This can be used to fool servers or scripts. You can also set any custom header with CURLOPT_HTTPHEADER. - * :cookie: Pass a pointer to a zero terminated string as parameter. It will be used to set a cookie in the http request. The format of the string should be NAME=CONTENTS, where NAME is the cookie name and CONTENTS is what the cookie should contain. + * :maxredirs: (Fixnum) Pass a long. The set number will be the redirection limit. If that many redirections have been followed, the next redirect will cause an error (CURLE_TOO_MANY_REDIRECTS). This option only makes sense if the CURLOPT_FOLLOWLOCATION is used at the same time. Added in 7.15.1: Setting the limit to 0 will make libcurl refuse any redirect. Set it to -1 for an infinite number of redirects (which is the default) + * :referer: (String) Pass a pointer to a zero terminated string as parameter. It will be used to set the Referer: header in the http request sent to the remote server. This can be used to fool servers or scripts. You can also set any custom header with CURLOPT_HTTPHEADER. + * :useragent: (String) Pass a pointer to a zero terminated string as parameter. It will be used to set the User-Agent: header in the http request sent to the remote server. This can be used to fool servers or scripts. You can also set any custom header with CURLOPT_HTTPHEADER. + * :cookie: (String) Pass a pointer to a zero terminated string as parameter. It will be used to set a cookie in the http request. The format of the string should be NAME=CONTENTS, where NAME is the cookie name and CONTENTS is what the cookie should contain. * If you need to set multiple cookies, you need to set them all using a single option and thus you need to concatenate them all in one single string. Set multiple cookies in one string like this: "name1=content1; name2=content2;" etc. * Note that this option sets the cookie header explictly in the outgoing request(s). If multiple requests are done due to authentication, followed redirections or similar, they will all get this cookie passed on. * Using this option multiple times will only make the latest string override the previous ones. * - * :post: Pass a string and sets CURLOPT_POST, CURLOPT_POSTFIELDSIZE, and CURLOPT_COPYPOSTFIELDS + * :post: (String) Pass a string and sets CURLOPT_POST, CURLOPT_POSTFIELDSIZE, and CURLOPT_COPYPOSTFIELDS * + * :stream: (Evdispatch::Response) Send the response to the IO object. Ignore the response id returned it'd invalid */ static VALUE Loop_request( int argc, VALUE *argv, VALUE self ) { EVD::Dispatch *d; @@ -135,33 +141,52 @@ // required 1 argument the 'url' and 1 optional the hash of options if( rb_scan_args( argc, argv, "11", &url, &options ) == 1 ) { options = rb_hash_new(); } - EVD::HttpRequest *req = new EVD::HttpRequest( *d, RSTRING_PTR(url) ); + Check_Type(url, T_STRING ); + Check_Type(options, T_HASH ); - // check for some specific options - SET_LONG_VAL("port"); - SET_STR_VAL("autoreferer"); - SET_LONG_VAL("followlocation"); - SET_LONG_VAL("maxredirs"); - SET_STR_VAL("referer"); - SET_STR_VAL("useragent"); - SET_STR_VAL("cookie"); - SET_STR_VAL("post"); + VALUE response = rb_hash_aref( options, ID2SYM(rb_intern("stream")) ); + if( !NIL_P(response) && TYPE(response) == T_TRUE ) { + + rResponse *res = new rResponse; + VALUE response_object = Data_Wrap_Struct( rb_Response, NULL, Response_free, res ); - return rb_int_new( d->request(req) ); + if( !res->init() ) { + rb_raise(rb_eIOError, "Failed to create new pipe"); + } + + EVD::HttpRequest *req = new EVD::HttpRequest( *d, RSTRING_PTR(url), res->m_pipe[1] ); + + SET_REQ_OPTS + + res->m_response = req->m_response; + + d->request(req); + return response_object; + } + else { + EVD::HttpRequest *req = new EVD::HttpRequest( *d, RSTRING_PTR(url) ); + SET_REQ_OPTS + + return rb_int_new( d->request(req) ); + } } static VALUE Loop_wait_for_response( VALUE self, VALUE id, VALUE timeout_seconds, VALUE timeout_mseconds ) { EVD::Dispatch *d; EVD::Queue<EVD::Response>::POP_STATE rstate; EVD::request_t rid = FIX2LONG(id); Data_Get_Struct( self, EVD::Dispatch, d ); + + Check_Type( id, T_FIXNUM ); + Check_Type( timeout_seconds, T_FIXNUM ); + Check_Type( timeout_mseconds, T_FIXNUM ); long seconds = FIX2LONG(timeout_seconds); long nanoseconds = FIX2LONG(timeout_mseconds)*1000*1000; //printf( "wait seconds %ld, nanoseconds %ld\n", seconds, nanoseconds ); @@ -173,10 +198,11 @@ static VALUE Loop_response_for( VALUE self, VALUE id ) { EVD::Dispatch *d; Data_Get_Struct( self, EVD::Dispatch, d ); + Check_Type( id, T_FIXNUM ); EVD::HttpResponse *res = NULL; EVD::request_t rid = FIX2LONG(id); res = (EVD::HttpResponse*)d->response_for( rid ); @@ -207,10 +233,12 @@ // required 1 argument the 'url' and 1 optional the hash of options if( rb_scan_args( argc, argv, "11", &req_id, &options ) == 1 ) { options = rb_hash_new(); rb_hash_aset( options, ID2SYM(rb_intern("timeout")), rb_float_new( 2.0 ) ); } + Check_Type( req_id, T_FIXNUM ); + Check_Type( options, T_HASH ); EVD::request_t id = FIX2LONG(req_id); VALUE timeout_value = rb_hash_aref( options, ID2SYM(rb_intern("timeout")) ); double timeout = RFLOAT( timeout_value )->value; @@ -267,11 +295,10 @@ } static void Loop_free( EVD::Dispatch *d ) { - fprintf(stderr,"Freeing loop\n"); delete d; } static VALUE Loop_alloc(VALUE klass) @@ -282,22 +309,91 @@ object = Data_Wrap_Struct( klass, NULL, Loop_free, d ); return object; } +static +void Response_free( rResponse *res ) +{ + delete res; +} + +static +VALUE Response_alloc(VALUE klass) +{ + VALUE object; + + rResponse *res = new rResponse; + object = Data_Wrap_Struct( klass, NULL, Response_free, res ); + + return object; +} + + /* + This code might be interesting later if we decide to wire in direct writing to a Ruby socket or file + const char *klass_name = rb_class2name(CLASS_OF(stream)); + if( !strcmp("IO", klass_name) || !strcmp("Socket", klass_name) || !strcmp("File", klass_name) ) { + RFile *fstream = RFILE(stream); + struct OpenFile *fptr = fstream->fptr; + FILE *file = fptr->f; + int fd = fileno(file); + write(fd,"hi",2); + }*/ + +static +VALUE Response_read( VALUE self ) +{ + rResponse *res; + Data_Get_Struct( self, rResponse, res ); + + int status = res->read_body_partial(); + + if( status == -1 ) { + rb_raise(rb_eIOError, "Failed to read pipe"); + } + + return rb_int_new(status); +} + +static +VALUE Response_body( VALUE self ) +{ + rResponse *res; + Data_Get_Struct( self, rResponse, res ); + return rb_str_new(res->m_buffer.c_str(), res->m_buffer.length()); +} + +static +VALUE Response_headers( VALUE self ) +{ + rResponse *res; + Data_Get_Struct( self, rResponse, res ); + return rb_str_new(res->m_response->m_header.c_str(), res->m_response->m_header.length() ); +} + extern "C" void Init_revdispatch() { rb_Evdispatch = rb_define_module( "Evdispatch" ); rb_Loop = rb_define_class_under( rb_Evdispatch, "Loop", rb_cObject ); // setup the Loop object rb_define_alloc_func( rb_Loop, Loop_alloc ); - rb_define_method( rb_Loop, "start", (VALUE (*)(...))Loop_start, 0 ); - rb_define_method( rb_Loop, "request_http", (VALUE (*)(...))Loop_request_http, 1 ); - rb_define_method( rb_Loop, "request", (VALUE (*)(...))Loop_request, -1 ); - rb_define_method( rb_Loop, "flush", (VALUE (*)(...))Loop_flush, 0 ); - rb_define_method( rb_Loop, "blocking_response_for", (VALUE (*)(...))Loop_blocking_response_for, -1 ); - rb_define_method( rb_Loop, "response_for", (VALUE (*)(...))Loop_response_for, 1 ); - rb_define_method( rb_Loop, "wait_for_response", (VALUE (*)(...))Loop_wait_for_response, 3 ); - rb_define_method( rb_Loop, "stop", (VALUE (*)(...))Loop_stop, 0 ); + rb_define_method( rb_Loop, "start", RB_METHOD(Loop_start), 0 ); + rb_define_method( rb_Loop, "request_http", RB_METHOD(Loop_request_http), 1 ); + rb_define_method( rb_Loop, "request", RB_METHOD(Loop_request), -1 ); + rb_define_method( rb_Loop, "flush", RB_METHOD(Loop_flush), 0 ); + rb_define_method( rb_Loop, "blocking_response_for", RB_METHOD(Loop_blocking_response_for), -1 ); + rb_define_method( rb_Loop, "response_for", RB_METHOD(Loop_response_for), 1 ); + rb_define_method( rb_Loop, "wait_for_response", RB_METHOD(Loop_wait_for_response), 3 ); + rb_define_method( rb_Loop, "stop", RB_METHOD(Loop_stop), 0 ); + + // define the response object + rb_Response = rb_define_class_under( rb_Evdispatch, "Response", rb_cObject ); + + // setup the Response object + rb_define_alloc_func( rb_Response, Response_alloc ); + + rb_define_method( rb_Response, "read", RB_METHOD(Response_read), 0 ); + rb_define_method( rb_Response, "body", RB_METHOD(Response_body), 0 ); + rb_define_method( rb_Response, "headers", RB_METHOD(Response_headers), 0 ); }