#include #include "ev_dispatch.h" #include "ev_http.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' * * # create a new dispatch loop * d = Evdispatch::Loop.new * * # start the event loop thread * * # send a dispatch http request and store a handle to the request * google_id = d.request("http://www.google.com/") * * # do some processing and later on check for the response * * res = d.response( google_id ) * * puts res[:body] * * # sometime later you can stop the event loop * d.stop * * * You only get 1 background event loop, calling start multiple times will have no effect. * You typically don't need or want to stop the event loop after it's active. It will sit in the * background and happily wait for new requests using a minimal amount of cpu while waiting. * Everything in the background thread is written in C++ and has absolutely no hooks back into ruby. * The results of the work being processed in the background can be retrieved by ruby but that is it. * * the request method can be used passing it options will set specific libcurl options * */ static VALUE rb_Evdispatch; static VALUE rb_Loop; /** * call-seq: * loop.start -> loop * * Starts up the background event loop */ static VALUE Loop_start( VALUE self ) { EVD::Dispatch *d; Data_Get_Struct( self, EVD::Dispatch, d ); d->start(); return self; } /** * call-seq: * loop.request_http(url) -> request_id * * Notifies the background thread of a new request. Call this method to start a basic HTTP GET request. */ static VALUE Loop_request_http( VALUE self, VALUE url ) { EVD::Dispatch *d; Data_Get_Struct( self, EVD::Dispatch, d ); EVD::request_t id = d->request( new EVD::HttpRequest( *d, RSTRING_PTR(url) ) ); return rb_int_new(id); } #define SET_LONG_VAL(name)\ {\ VALUE obj = rb_hash_aref( options, ID2SYM(rb_intern(name)) ); \ if( !NIL_P(obj) ) {\ snprintf( VALUE_BUFFER, VALUE_BUFFER_SIZE, "%ld", FIX2LONG(obj) );\ req->set_opt(name, VALUE_BUFFER);\ }\ } #define SET_STR_VAL(name)\ {\ VALUE obj = rb_hash_aref( options, ID2SYM(rb_intern(name)) ); \ if( !NIL_P(obj) ) {\ req->set_opt(name, RSTRING_PTR(obj));\ }\ } /** * 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. * 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. * 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 * */ static VALUE Loop_request( int argc, VALUE *argv, VALUE self ) { EVD::Dispatch *d; const int VALUE_BUFFER_SIZE = 1024; char VALUE_BUFFER[VALUE_BUFFER_SIZE]; Data_Get_Struct( self, EVD::Dispatch, d ); VALUE url, options; // 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 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"); 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::POP_STATE rstate; EVD::request_t rid = FIX2LONG(id); Data_Get_Struct( self, EVD::Dispatch, d ); long seconds = FIX2LONG(timeout_seconds); long nanoseconds = FIX2LONG(timeout_mseconds)*1000*1000; //printf( "wait seconds %ld, nanoseconds %ld\n", seconds, nanoseconds ); rstate = d->wait_for_response_by_id( rid, EVD::Timer(seconds, nanoseconds) ); return rb_int_new(rstate); } static VALUE Loop_response_for( VALUE self, VALUE id ) { EVD::Dispatch *d; Data_Get_Struct( self, EVD::Dispatch, d ); EVD::HttpResponse *res = NULL; EVD::request_t rid = FIX2LONG(id); res = (EVD::HttpResponse*)d->response_for( rid ); if( res ) { VALUE result = rb_hash_new(); rb_hash_aset( result, ID2SYM(rb_intern("name")), rb_str_new( res->name.c_str(), res->name.length() ) ); rb_hash_aset( result, ID2SYM(rb_intern("body")), rb_str_new( res->body.c_str(), res->body.length() ) ); rb_hash_aset( result, ID2SYM(rb_intern("id")), rb_int_new( res->id ) ); rb_hash_aset( result, ID2SYM(rb_intern("response_time")), rb_float_new( res->response_time ) ); rb_hash_aset( result, ID2SYM(rb_intern("header")), rb_str_new( res->m_header.c_str(), res->m_header.length() ) ); delete res; return result; } return Qnil; } static VALUE Loop_blocking_response_for( int argc, VALUE *argv, VALUE self ) { VALUE req_id, options; EVD::Dispatch *dispatcher; Data_Get_Struct( self, EVD::Dispatch, dispatcher ); // 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 ) ); } EVD::request_t id = FIX2LONG(req_id); VALUE timeout_value = rb_hash_aref( options, ID2SYM(rb_intern("timeout")) ); double timeout = RFLOAT( timeout_value )->value; struct timeval start; EVD::Timer::current_time(&start); long int secs = (int)timeout; long int msecs = (secs == 0) ? 500 : 0; // XXX: timer hack for small timeout //printf( "waiting on id: %d, %d:%d\n", id, secs, msecs ); while( dispatcher->wait_for_response_by_id( id, EVD::Timer(secs,msecs*1000*1000) ) ) { if( EVD::Timer::elapsed_time( &start ) > timeout ){ printf("exceeded max elasped time...\n"); break; } } EVD::HttpResponse *res = (EVD::HttpResponse *)dispatcher->response_for( id ); if( res ) { VALUE result = rb_hash_new(); rb_hash_aset( result, ID2SYM(rb_intern("name")), rb_str_new( res->name.c_str(), res->name.length() ) ); rb_hash_aset( result, ID2SYM(rb_intern("body")), rb_str_new( res->body.c_str(), res->body.length() ) ); rb_hash_aset( result, ID2SYM(rb_intern("id")), rb_int_new( res->id ) ); rb_hash_aset( result, ID2SYM(rb_intern("response_time")), rb_float_new( res->response_time ) ); rb_hash_aset( result, ID2SYM(rb_intern("header")), rb_str_new( res->m_header.c_str(), res->m_header.length() ) ); delete res; return result; } return Qnil; } static VALUE Loop_flush( VALUE self ) { EVD::Dispatch *d; Data_Get_Struct( self, EVD::Dispatch, d ); d->flush(); return Qnil; } static VALUE Loop_stop( VALUE self ) { EVD::Dispatch *d; Data_Get_Struct( self, EVD::Dispatch, d ); d->stop(); return Qnil; } static void Loop_free( EVD::Dispatch *d ) { fprintf(stderr,"Freeing loop\n"); delete d; } static VALUE Loop_alloc(VALUE klass) { VALUE object; EVD::Dispatch *d = new EVD::Dispatch(); object = Data_Wrap_Struct( klass, NULL, Loop_free, d ); return object; } 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 ); }