Index: ext/curb.h =================================================================== --- ext/curb.h (revision 56) +++ ext/curb.h (working copy) @@ -14,6 +14,7 @@ #include "curb_easy.h" #include "curb_errors.h" #include "curb_postfield.h" +#include "curb_multi.h" #include "curb_macros.h" Index: ext/curb_easy.h =================================================================== --- ext/curb_easy.h (revision 56) +++ ext/curb_easy.h (working copy) @@ -24,8 +24,8 @@ VALUE body_proc; VALUE header_proc; - VALUE body_data; /* These have the result from the last curl_easy_perform */ - VALUE header_data; /* unless a block is supplied (when they'll be nil) */ + VALUE body_data; /* Holds the response body from the last call to curl_easy_perform */ + VALUE header_data; /* unless a block is supplied (they'll be nil) */ VALUE progress_proc; VALUE debug_proc; VALUE interface; @@ -33,7 +33,7 @@ VALUE proxypwd; VALUE headers; /* ruby array of strings with headers to set */ VALUE cookiejar; /* filename */ - + /* Other opts */ unsigned short local_port; // 0 is no port unsigned short local_port_range; // " " " " @@ -65,10 +65,22 @@ * and in case it's asked for before the next call. */ VALUE postdata_buffer; + + /* when added to a multi handle these buffers are needed + * when the easy handle isn't supplied the body proc + * or a custom http header is passed. + */ + VALUE bodybuf; + VALUE headerbuf; + struct curl_slist *curl_headers; + } ruby_curl_easy; extern VALUE cCurlEasy; +VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce, VALUE *bodybuf, VALUE *headerbuf, struct curl_slist **headers); +VALUE ruby_curl_easy_cleanup(ruby_curl_easy *rbce, VALUE bodybuf, VALUE headerbuf, struct curl_slist *headers); + void init_curb_easy(); #endif Index: ext/curb_multi.c =================================================================== --- ext/curb_multi.c (revision 0) +++ ext/curb_multi.c (revision 0) @@ -0,0 +1,199 @@ +#include +#include "curb_easy.h" +#include "curb_errors.h" +#include "curb_postfield.h" +#include "curb_multi.h" + +extern VALUE mCurl; +VALUE cCurlMulti; + +static VALUE ruby_curl_multi_alloc(VALUE klass) { + return Data_Wrap_Struct(klass, NULL, curl_multi_cleanup, curl_multi_init()); +} + +/* + * call-seq: + * multi = Curl::Multi.new + * easy = Curl::Easy.new('url') + * + * multi.add(easy) + * + * Add an easy handle to the multi handle + */ +static VALUE ruby_curl_multi_add(VALUE self, VALUE easy) { + CURLMcode result; + CURLM *mptr; + ruby_curl_easy *rbce; + + Data_Get_Struct(self, CURLM, mptr); + Data_Get_Struct(easy, ruby_curl_easy, rbce); + + result = curl_multi_add_handle(mptr, rbce->curl); + if (result != 0) { + raise_curl_multi_error_exception(result); + } + + /* setup the easy handle */ + ruby_curl_easy_setup( rbce, &(rbce->bodybuf), &(rbce->headerbuf), &(rbce->curl_headers) ); + + return self; +} + +/* + * call-seq: + * multi = Curl::Multi.new + * easy = Curl::Easy.new('url') + * + * multi.add(easy) + * + * # sometime later + * multi.remove(easy) + * + * Remove an easy handle from a multi handle + * + * Will raise an exception if the easy handle is not found + */ +static VALUE ruby_curl_multi_remove(VALUE self, VALUE easy) { + CURLMcode result; + CURLM *mptr; + ruby_curl_easy *rbce; + + Data_Get_Struct(self, CURLM, mptr); + Data_Get_Struct(easy, ruby_curl_easy, rbce); + + result = curl_multi_remove_handle(mptr, rbce->curl); + if (result != 0) { + raise_curl_multi_error_exception(result); + } + + return self; +} + +/* called within ruby_curl_multi_perform */ +static void rb_curl_multi_run(CURLM *multi_handle, int *still_running) { + int msgs_left; + CURLMsg *msg; + CURLcode ecode; + CURLMcode mcode; + CURL *easy_handle; + ruby_curl_easy *rbce; + + do { + mcode = curl_multi_perform(multi_handle, still_running); + } while (mcode == CURLM_CALL_MULTI_PERFORM); + + if (mcode != CURLM_OK) { + raise_curl_multi_error_exception(mcode); + } + + /* check for finished easy handles and remove from the multi handle */ + while ((msg = curl_multi_info_read(multi_handle, &msgs_left))) { + + if (msg->msg != CURLMSG_DONE) { + continue; + } + + easy_handle = msg->easy_handle; + if (easy_handle) { + ecode = curl_easy_getinfo(easy_handle, CURLINFO_PRIVATE, &rbce); + if (ecode != 0) { + raise_curl_easy_error_exception(ecode); + } + mcode = curl_multi_remove_handle(multi_handle, easy_handle); + if (mcode != CURLM_OK) { + raise_curl_multi_error_exception(mcode); + } + ruby_curl_easy_cleanup( rbce, rbce->bodybuf, rbce->headerbuf, rbce->curl_headers ); + } + } +} + +/* + * call-seq: + * multi = Curl::Multi.new + * easy1 = Curl::Easy.new('url') + * easy2 = Curl::Easy.new('url') + * + * multi.add(easy1) + * multi.add(easy2) + * + * multi.perform do + * # while idle other code my execute here + * end + * + * Run multi handles, looping selecting when data can be transfered + */ +static VALUE ruby_curl_multi_perform(VALUE self) { + CURLMcode mcode; + CURLM *multi_handle; + int still_running, maxfd, rc; + fd_set fdread, fdwrite, fdexcep; + + long timeout; + struct timeval tv = {0, 0}; + + Data_Get_Struct(self, CURLM, multi_handle); + + do { + mcode = curl_multi_perform(multi_handle, &still_running); + } while (mcode == CURLM_CALL_MULTI_PERFORM); + + if (mcode != CURLM_OK) { + raise_curl_multi_error_exception(mcode); + } + + while(still_running) { + FD_ZERO(&fdread); + FD_ZERO(&fdwrite); + FD_ZERO(&fdexcep); + + /* load the fd sets from the multi handle */ + mcode = curl_multi_fdset(multi_handle, &fdread, &fdwrite, &fdexcep, &maxfd); + if (mcode != CURLM_OK) { + raise_curl_multi_error_exception(mcode); + } + + /* get the curl suggested time out */ + mcode = curl_multi_timeout(multi_handle, &timeout); + if (mcode != CURLM_OK) { + raise_curl_multi_error_exception(mcode); + } + + if (timeout == 0) { /* no delay */ + rb_curl_multi_run( multi_handle, &still_running ); + continue; + } + else if (timeout == -1) { + timeout = 1; /* You must not wait too long + (more than a few seconds perhaps) before + you call curl_multi_perform() again */ + } + + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout * 1000) % 1000000; + + rc = select(maxfd+1, &fdread, &fdwrite, &fdexcep, &tv); + if (rc < 0) { + rb_raise(rb_eRuntimeError, "select(): %s", strerror(errno)); + } + + rb_curl_multi_run( multi_handle, &still_running ); + + } + + return Qnil; +} + + +/* =================== INIT LIB =====================*/ +void init_curb_multi() { + cCurlMulti = rb_define_class_under(mCurl, "Multi", rb_cObject); + + /* Class methods */ + rb_define_alloc_func( cCurlMulti, ruby_curl_multi_alloc ); + + /* Instnace methods */ + rb_define_method(cCurlMulti, "add", ruby_curl_multi_add, 1); + rb_define_method(cCurlMulti, "remove", ruby_curl_multi_remove, 1); + rb_define_method(cCurlMulti, "perform", ruby_curl_multi_perform, 0); +} Index: ext/curb_errors.c =================================================================== --- ext/curb_errors.c (revision 56) +++ ext/curb_errors.c (working copy) @@ -98,9 +98,19 @@ VALUE eCurlErrTFTPFileExists; VALUE eCurlErrTFTPNoSuchUser; +/* multi errors */ +VALUE mCurlErrCallMultiPerform; +VALUE mCurlErrBadHandle; +VALUE mCurlErrBadEasyHandle; +VALUE mCurlErrOutOfMemory; +VALUE mCurlErrInternalError; +VALUE mCurlErrBadSocket; +VALUE mCurlErrUnknownOption; + /* binding errors */ VALUE eCurlErrInvalidPostField; + /* rb_raise an approriate exception for the supplied CURLcode */ void raise_curl_easy_error_exception(CURLcode code) { VALUE exclz; @@ -356,7 +366,44 @@ rb_raise(exclz, exmsg); } +void raise_curl_multi_error_exception(CURLMcode code) { + VALUE exclz; + const char *exmsg = NULL; + switch(code) { + case CURLM_CALL_MULTI_PERFORM: /* -1 */ + exclz = mCurlErrCallMultiPerform; + break; + case CURLM_BAD_HANDLE: /* 1 */ + exclz = mCurlErrBadHandle; + break; + case CURLM_BAD_EASY_HANDLE: /* 2 */ + exclz = mCurlErrBadEasyHandle; + break; + case CURLM_OUT_OF_MEMORY: /* 3 */ + exclz = mCurlErrOutOfMemory; + break; + case CURLM_INTERNAL_ERROR: /* 4 */ + exclz = mCurlErrInternalError; + break; + case CURLM_BAD_SOCKET: /* 5 */ + exclz = mCurlErrBadSocket; + break; + case CURLM_UNKNOWN_OPTION: /* 6 */ + exclz = mCurlErrUnknownOption; + break; + default: + exclz = eCurlErrError; + exmsg = "Unknown error result from libcurl"; + } + + if (!exmsg) { + exmsg = curl_multi_strerror(code); + } + + rb_raise(exclz, exmsg); +} + void init_curb_errors() { mCurlErr = rb_define_module_under(mCurl, "Err"); eCurlErrError = rb_define_class_under(mCurlErr, "CurlError", rb_eRuntimeError); Index: ext/curb_multi.h =================================================================== --- ext/curb_multi.h (revision 0) +++ ext/curb_multi.h (revision 0) @@ -0,0 +1,17 @@ +/* curb_multi.h - Curl easy mode + * Copyright (c)2008 Todd A. Fisher. + * Licensed under the Ruby License. See LICENSE for details. + * + * $Id$ + */ +#ifndef __CURB_MULTI_H +#define __CURB_MULTI_H + +#include "curb.h" +#include "curb_easy.h" +#include + +void init_curb_multi(); + + +#endif Index: ext/curb.c =================================================================== --- ext/curb.c (revision 56) +++ ext/curb.c (working copy) @@ -333,4 +333,5 @@ init_curb_errors(); init_curb_easy(); init_curb_postfield(); + init_curb_multi(); } Index: ext/curb_easy.c =================================================================== --- ext/curb_easy.c (revision 56) +++ ext/curb_easy.c (working copy) @@ -94,9 +94,14 @@ rb_gc_mark(rbce->cookiejar); rb_gc_mark(rbce->postdata_buffer); + rb_gc_mark(rbce->bodybuf); + rb_gc_mark(rbce->headerbuf); } void curl_easy_free(ruby_curl_easy *rbce) { + if (rbce->curl_headers) { + curl_slist_free_all(rbce->curl_headers); + } curl_easy_cleanup(rbce->curl); free(rbce); } @@ -168,6 +173,9 @@ /* buffers */ rbce->postdata_buffer = Qnil; + rbce->bodybuf = Qnil; + rbce->headerbuf = Qnil; + rbce->curl_headers = NULL; new_curl = Data_Wrap_Struct(cCurlEasy, curl_easy_mark, curl_easy_free, rbce); @@ -194,6 +202,7 @@ newrbce = ALLOC(ruby_curl_easy); memcpy(newrbce, rbce, sizeof(ruby_curl_easy)); newrbce->curl = curl_easy_duphandle(rbce->curl); + newrbce->curl_headers = NULL; return Data_Wrap_Struct(cCurlEasy, curl_easy_mark, curl_easy_free, newrbce); } @@ -1044,234 +1053,259 @@ } /*********************************************** - * - * This is the main worker for the perform methods (get, post, head, put). - * It's not surfaced as a Ruby method - instead, the individual request - * methods are responsible for setting up stuff specific to that type, - * then calling this to handle common stuff and do the perform. - * + * + * Setup a connection + * * Always returns Qtrue, rb_raise on error. - * */ -static VALUE handle_perform(ruby_curl_easy *rbce) { +VALUE ruby_curl_easy_setup( ruby_curl_easy *rbce, VALUE *body_buffer, VALUE *header_buffer, struct curl_slist **hdrs ) { // TODO this could do with a bit of refactoring... CURL *curl; - CURLcode result = -1; - struct curl_slist *headers = NULL; curl = rbce->curl; - if (rbce->url != Qnil) { - VALUE url = rb_check_string_type(rbce->url); - VALUE bodybuf, headerbuf; + if (rbce->url == Qnil) { + rb_raise(eCurlErrError, "No URL supplied"); + } + + VALUE url = rb_check_string_type(rbce->url); + + // Need to configure the handler as per settings in rbce + curl_easy_setopt(curl, CURLOPT_URL, StringValuePtr(url)); - // Need to configure the handler as per settings in rbce - curl_easy_setopt(curl, CURLOPT_URL, StringValuePtr(url)); - - // network stuff and auth - if (rbce->interface != Qnil) { - curl_easy_setopt(curl, CURLOPT_INTERFACE, StringValuePtr(rbce->interface)); - } else { - curl_easy_setopt(curl, CURLOPT_INTERFACE, NULL); - } - - if (rbce->userpwd != Qnil) { - curl_easy_setopt(curl, CURLOPT_USERPWD, StringValuePtr(rbce->userpwd)); - } else { - curl_easy_setopt(curl, CURLOPT_USERPWD, NULL); - } - - if (rbce->proxy_url != Qnil) { - curl_easy_setopt(curl, CURLOPT_PROXY, StringValuePtr(rbce->proxy_url)); - } else { - curl_easy_setopt(curl, CURLOPT_PROXY, NULL); - } - - if (rbce->proxypwd != Qnil) { - curl_easy_setopt(curl, CURLOPT_PROXYUSERPWD, StringValuePtr(rbce->proxypwd)); - } else { - curl_easy_setopt(curl, CURLOPT_PROXYUSERPWD, NULL); - } - - // body/header procs - if (rbce->body_proc != Qnil) { - bodybuf = Qnil; - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &proc_data_handler); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, rbce->body_proc); - } else { - bodybuf = rb_str_buf_new(32768); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &default_data_handler); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, bodybuf); - } - - if (rbce->header_proc != Qnil) { - headerbuf = Qnil; - curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, &proc_data_handler); - curl_easy_setopt(curl, CURLOPT_HEADERDATA, rbce->header_proc); - } else { - headerbuf = rb_str_buf_new(32768); - curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, &default_data_handler); - curl_easy_setopt(curl, CURLOPT_HEADERDATA, headerbuf); - } + // network stuff and auth + if (rbce->interface != Qnil) { + curl_easy_setopt(curl, CURLOPT_INTERFACE, StringValuePtr(rbce->interface)); + } else { + curl_easy_setopt(curl, CURLOPT_INTERFACE, NULL); + } + + if (rbce->userpwd != Qnil) { + curl_easy_setopt(curl, CURLOPT_USERPWD, StringValuePtr(rbce->userpwd)); + } else { + curl_easy_setopt(curl, CURLOPT_USERPWD, NULL); + } + + if (rbce->proxy_url != Qnil) { + curl_easy_setopt(curl, CURLOPT_PROXY, StringValuePtr(rbce->proxy_url)); + } else { + curl_easy_setopt(curl, CURLOPT_PROXY, NULL); + } + + if (rbce->proxypwd != Qnil) { + curl_easy_setopt(curl, CURLOPT_PROXYUSERPWD, StringValuePtr(rbce->proxypwd)); + } else { + curl_easy_setopt(curl, CURLOPT_PROXYUSERPWD, NULL); + } + + // body/header procs + if (rbce->body_proc != Qnil) { + *body_buffer = Qnil; + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &proc_data_handler); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, rbce->body_proc); + } else { + *body_buffer = rb_str_buf_new(32768); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &default_data_handler); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, *body_buffer); + } + + if (rbce->header_proc != Qnil) { + *header_buffer = Qnil; + curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, &proc_data_handler); + curl_easy_setopt(curl, CURLOPT_HEADERDATA, rbce->header_proc); + } else { + *header_buffer = rb_str_buf_new(32768); + curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, &default_data_handler); + curl_easy_setopt(curl, CURLOPT_HEADERDATA, *header_buffer); + } - // progress and debug procs - if (rbce->progress_proc != Qnil) { - curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, &proc_progress_handler); - curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, rbce->progress_proc); - curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0); - } else { - curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1); - } - - if (rbce->debug_proc != Qnil) { - curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, &proc_debug_handler); - curl_easy_setopt(curl, CURLOPT_DEBUGDATA, rbce->debug_proc); - curl_easy_setopt(curl, CURLOPT_VERBOSE, 1); - } else { - // have to remove handler to re-enable standard verbosity - curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, NULL); - curl_easy_setopt(curl, CURLOPT_DEBUGDATA, NULL); - curl_easy_setopt(curl, CURLOPT_VERBOSE, rbce->verbose); - } - - /* general opts */ - - curl_easy_setopt(curl, CURLOPT_HEADER, rbce->header_in_body); + // progress and debug procs + if (rbce->progress_proc != Qnil) { + curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, &proc_progress_handler); + curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, rbce->progress_proc); + curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0); + } else { + curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1); + } + + if (rbce->debug_proc != Qnil) { + curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, &proc_debug_handler); + curl_easy_setopt(curl, CURLOPT_DEBUGDATA, rbce->debug_proc); + curl_easy_setopt(curl, CURLOPT_VERBOSE, 1); + } else { + // have to remove handler to re-enable standard verbosity + curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, NULL); + curl_easy_setopt(curl, CURLOPT_DEBUGDATA, NULL); + curl_easy_setopt(curl, CURLOPT_VERBOSE, rbce->verbose); + } + + /* general opts */ + + curl_easy_setopt(curl, CURLOPT_HEADER, rbce->header_in_body); - curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, rbce->follow_location); - curl_easy_setopt(curl, CURLOPT_MAXREDIRS, rbce->max_redirs); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, rbce->follow_location); + curl_easy_setopt(curl, CURLOPT_MAXREDIRS, rbce->max_redirs); - curl_easy_setopt(curl, CURLOPT_HTTPPROXYTUNNEL, rbce->proxy_tunnel); - curl_easy_setopt(curl, CURLOPT_FILETIME, rbce->fetch_file_time); - curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, rbce->ssl_verify_peer); - curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, rbce->ssl_verify_peer); - - if ((rbce->use_netrc != Qnil) && (rbce->use_netrc != Qfalse)) { - curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, CURL_NETRC_OPTIONAL); - } else { - curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, CURL_NETRC_IGNORED); - } + curl_easy_setopt(curl, CURLOPT_HTTPPROXYTUNNEL, rbce->proxy_tunnel); + curl_easy_setopt(curl, CURLOPT_FILETIME, rbce->fetch_file_time); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, rbce->ssl_verify_peer); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, rbce->ssl_verify_peer); + + if ((rbce->use_netrc != Qnil) && (rbce->use_netrc != Qfalse)) { + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, CURL_NETRC_OPTIONAL); + } else { + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, CURL_NETRC_IGNORED); + } - curl_easy_setopt(curl, CURLOPT_UNRESTRICTED_AUTH, rbce->unrestricted_auth); - - curl_easy_setopt(curl, CURLOPT_TIMEOUT, rbce->timeout); - curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, rbce->connect_timeout); - curl_easy_setopt(curl, CURLOPT_DNS_CACHE_TIMEOUT, rbce->dns_cache_timeout); + curl_easy_setopt(curl, CURLOPT_UNRESTRICTED_AUTH, rbce->unrestricted_auth); + + curl_easy_setopt(curl, CURLOPT_TIMEOUT, rbce->timeout); + curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, rbce->connect_timeout); + curl_easy_setopt(curl, CURLOPT_DNS_CACHE_TIMEOUT, rbce->dns_cache_timeout); #if LIBCURL_VERSION_NUM >= 0x070a08 - curl_easy_setopt(curl, CURLOPT_FTP_RESPONSE_TIMEOUT, rbce->ftp_response_timeout); + curl_easy_setopt(curl, CURLOPT_FTP_RESPONSE_TIMEOUT, rbce->ftp_response_timeout); #else - if (rbce->ftp_response_timeout > 0) { - rb_warn("Installed libcurl is too old to support ftp_response_timeout"); - } + if (rbce->ftp_response_timeout > 0) { + rb_warn("Installed libcurl is too old to support ftp_response_timeout"); + } #endif + + // Set up localport / proxy port + // FIXME these won't get returned to default if they're unset Ruby + if (rbce->proxy_port > 0) { + curl_easy_setopt(curl, CURLOPT_PROXYPORT, rbce->proxy_port); + } + + if (rbce->local_port > 0) { +#if LIBCURL_VERSION_NUM >= 0x070f02 + curl_easy_setopt(curl, CURLOPT_LOCALPORT, rbce->local_port); - // Set up localport / proxy port - // FIXME these won't get returned to default if they're unset Ruby - if (rbce->proxy_port > 0) { - curl_easy_setopt(curl, CURLOPT_PROXYPORT, rbce->proxy_port); + if (rbce->local_port_range > 0) { + curl_easy_setopt(curl, CURLOPT_LOCALPORTRANGE, rbce->local_port_range); } - - if (rbce->local_port > 0) { -#if LIBCURL_VERSION_NUM >= 0x070f02 - curl_easy_setopt(curl, CURLOPT_LOCALPORT, rbce->local_port); - - if (rbce->local_port_range > 0) { - curl_easy_setopt(curl, CURLOPT_LOCALPORTRANGE, rbce->local_port_range); - } #else - rb_warn("Installed libcurl is too old to support local_port"); + rb_warn("Installed libcurl is too old to support local_port"); #endif - } - - if (rbce->proxy_type != -1) { + } + + if (rbce->proxy_type != -1) { #if LIBCURL_VERSION_NUM >= 0x070a00 - if (rbce->proxy_type == -2) { - rb_warn("Installed libcurl is too old to support the selected proxy type"); - } else { - curl_easy_setopt(curl, CURLOPT_PROXYTYPE, rbce->proxy_type); - } + if (rbce->proxy_type == -2) { + rb_warn("Installed libcurl is too old to support the selected proxy type"); } else { - curl_easy_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_HTTP); + curl_easy_setopt(curl, CURLOPT_PROXYTYPE, rbce->proxy_type); + } + } else { + curl_easy_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_HTTP); #else - rb_warn("Installed libcurl is too old to support proxy_type"); + rb_warn("Installed libcurl is too old to support proxy_type"); #endif - } + } - if (rbce->http_auth_types > 0) { + if (rbce->http_auth_types > 0) { #if LIBCURL_VERSION_NUM >= 0x070a06 - curl_easy_setopt(curl, CURLOPT_HTTPAUTH, rbce->http_auth_types); - } else { - curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY); + curl_easy_setopt(curl, CURLOPT_HTTPAUTH, rbce->http_auth_types); + } else { + curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY); #else - rb_warn("Installed libcurl is too old to support http_auth_types"); + rb_warn("Installed libcurl is too old to support http_auth_types"); #endif - } + } - if (rbce->proxy_auth_types > 0) { + if (rbce->proxy_auth_types > 0) { #if LIBCURL_VERSION_NUM >= 0x070a07 - curl_easy_setopt(curl, CURLOPT_PROXYAUTH, rbce->proxy_auth_types); - } else { - curl_easy_setopt(curl, CURLOPT_PROXYAUTH, CURLAUTH_ANY); + curl_easy_setopt(curl, CURLOPT_PROXYAUTH, rbce->proxy_auth_types); + } else { + curl_easy_setopt(curl, CURLOPT_PROXYAUTH, CURLAUTH_ANY); #else - rb_warn("Installed libcurl is too old to support proxy_auth_types"); + rb_warn("Installed libcurl is too old to support proxy_auth_types"); #endif + } + + // Set up HTTP cookie handling if necessary + // FIXME this may not get disabled if it's enabled, the disabled again from ruby. + if (rbce->enable_cookies) { + if (rbce->cookiejar != Qnil) { + curl_easy_setopt(curl, CURLOPT_COOKIEJAR, StringValuePtr(rbce->cookiejar)); + } else { + curl_easy_setopt(curl, CURLOPT_COOKIEFILE, ""); /* "" = magic to just enable */ } - - // Set up HTTP cookie handling if necessary - // FIXME this may not get disabled if it's enabled, the disabled again from ruby. - if (rbce->enable_cookies) { - if (rbce->cookiejar != Qnil) { - curl_easy_setopt(curl, CURLOPT_COOKIEJAR, StringValuePtr(rbce->cookiejar)); - } else { - curl_easy_setopt(curl, CURLOPT_COOKIEFILE, ""); /* "" = magic to just enable */ - } + } + + // Setup HTTP headers if necessary + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, NULL); // clear + + if (rbce->headers != Qnil) { + if ((rb_type(rbce->headers) == T_ARRAY) || (rb_type(rbce->headers) == T_HASH)) { + rb_iterate(rb_each, rbce->headers, cb_each_http_header, (VALUE)hdrs); + } else { + VALUE headers_str = rb_obj_as_string(rbce->headers); + *hdrs = curl_slist_append(*hdrs, StringValuePtr(headers_str)); } - // Setup HTTP headers if necessary - curl_easy_setopt(curl, CURLOPT_HTTPHEADER, NULL); // clear - - if (rbce->headers != Qnil) { - if ((rb_type(rbce->headers) == T_ARRAY) || (rb_type(rbce->headers) == T_HASH)) { - rb_iterate(rb_each, rbce->headers, cb_each_http_header, (VALUE)&headers); - } else { - VALUE headers_str = rb_obj_as_string(rbce->headers); - headers = curl_slist_append(headers, StringValuePtr(headers_str)); - } - - if (headers) { - curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); - } + if (*hdrs) { + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, *hdrs); } - - // Okay, do it. - result = curl_easy_perform(curl); - - // Free everything up - if (headers) { - curl_slist_free_all(headers); - } - - // Sort out the built-in body/header data. - if (bodybuf != Qnil) { - rbce->body_data = rb_str_to_str(bodybuf); - } else { - rbce->body_data = Qnil; - } + } - if (headerbuf != Qnil) { - rbce->header_data = rb_str_to_str(headerbuf); - } else { - rbce->header_data = Qnil; - } + return Qnil; +} +/*********************************************** + * + * Clean up a connection + * + * Always returns Qtrue. + */ +VALUE ruby_curl_easy_cleanup( ruby_curl_easy *rbce, VALUE bodybuf, VALUE headerbuf, struct curl_slist *headers ) { - if (result != 0) { - raise_curl_easy_error_exception(result); - } + // Free everything up + if (headers) { + curl_slist_free_all(headers); + } + + // Sort out the built-in body/header data. + if (bodybuf != Qnil) { + rbce->body_data = rb_str_to_str(bodybuf); } else { - rb_raise(eCurlErrError, "No URL supplied"); + rbce->body_data = Qnil; } + + if (headerbuf != Qnil) { + rbce->header_data = rb_str_to_str(headerbuf); + } else { + rbce->header_data = Qnil; + } + + return Qnil; +} + +/*********************************************** + * + * This is the main worker for the perform methods (get, post, head, put). + * It's not surfaced as a Ruby method - instead, the individual request + * methods are responsible for setting up stuff specific to that type, + * then calling this to handle common stuff and do the perform. + * + * Always returns Qtrue, rb_raise on error. + * + */ +static VALUE handle_perform(ruby_curl_easy *rbce) { + + CURLcode result = -1; + struct curl_slist *headers = NULL; + VALUE bodybuf = Qnil, headerbuf = Qnil; + + ruby_curl_easy_setup(rbce, &bodybuf, &headerbuf, &headers); + + result = curl_easy_perform(rbce->curl); + + ruby_curl_easy_cleanup(rbce, bodybuf, headerbuf, headers); + if (result != 0) { + raise_curl_easy_error_exception(result); + } + return Qtrue; } Index: ext/curb_errors.h =================================================================== --- ext/curb_errors.h (revision 56) +++ ext/curb_errors.h (working copy) @@ -12,6 +12,7 @@ /* base errors */ extern VALUE cCurlErr; +/* easy errors */ extern VALUE mCurlErr; extern VALUE eCurlErrError; extern VALUE eCurlErrFTPError; @@ -97,10 +98,20 @@ extern VALUE eCurlErrTFTPFileExists; extern VALUE eCurlErrTFTPNoSuchUser; +/* multi errors */ +extern VALUE mCurlErrCallMultiPerform; +extern VALUE mCurlErrBadHandle; +extern VALUE mCurlErrBadEasyHandle; +extern VALUE mCurlErrOutOfMemory; +extern VALUE mCurlErrInternalError; +extern VALUE mCurlErrBadSocket; +extern VALUE mCurlErrUnknownOption; + /* binding errors */ extern VALUE eCurlErrInvalidPostField; void init_curb_errors(); void raise_curl_easy_error_exception(CURLcode code); +void raise_curl_multi_error_exception(CURLMcode code); #endif Index: tests/tc_curl_multi.rb =================================================================== --- tests/tc_curl_multi.rb (revision 0) +++ tests/tc_curl_multi.rb (revision 0) @@ -0,0 +1,35 @@ +require File.join(File.dirname(__FILE__), 'helper') + +class TestCurbCurlMulti < Test::Unit::TestCase + def test_new_multi_01 + d1 = "" + c1 = Curl::Easy.new($TEST_URL) do |curl| + curl.headers["User-Agent"] = "myapp-0.0" + curl.verbose = true + curl.on_body {|d| d1 << d; d.length } + end + + d2 = "" + c2 = Curl::Easy.new($TEST_URL) do |curl| + curl.headers["User-Agent"] = "myapp-0.0" + curl.on_body {|d| d2 << d; d.length } + end + + #c1.perform + #c2.perform + + #assert_match(/^# DO NOT REMOVE THIS COMMENT/, d1) + #assert_match(/^# DO NOT REMOVE THIS COMMENT/, d2) + # + m = Curl::Multi.new + + m.add( c1 ) + m.add( c2 ) + + m.perform + + assert_match(/^# DO NOT REMOVE THIS COMMENT/, d1) + assert_match(/^# DO NOT REMOVE THIS COMMENT/, d2) + + end +end Index: tests/tc_curl_easy.rb =================================================================== --- tests/tc_curl_easy.rb (revision 56) +++ tests/tc_curl_easy.rb (working copy) @@ -439,4 +439,4 @@ assert_equal "some.file", c.cookiejar end -end \ No newline at end of file +end