codders-curb 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/LICENSE +51 -0
  2. data/README +194 -0
  3. data/Rakefile +320 -0
  4. data/doc.rb +42 -0
  5. data/ext/curb.c +977 -0
  6. data/ext/curb.h +52 -0
  7. data/ext/curb_easy.c +3404 -0
  8. data/ext/curb_easy.h +90 -0
  9. data/ext/curb_errors.c +647 -0
  10. data/ext/curb_errors.h +129 -0
  11. data/ext/curb_macros.h +159 -0
  12. data/ext/curb_multi.c +633 -0
  13. data/ext/curb_multi.h +26 -0
  14. data/ext/curb_postfield.c +523 -0
  15. data/ext/curb_postfield.h +40 -0
  16. data/ext/curb_upload.c +80 -0
  17. data/ext/curb_upload.h +30 -0
  18. data/ext/extconf.rb +399 -0
  19. data/lib/curb.rb +4 -0
  20. data/lib/curl.rb +57 -0
  21. data/lib/curl/easy.rb +468 -0
  22. data/lib/curl/multi.rb +248 -0
  23. data/tests/alltests.rb +3 -0
  24. data/tests/bug_crash_on_debug.rb +39 -0
  25. data/tests/bug_crash_on_progress.rb +33 -0
  26. data/tests/bug_curb_easy_blocks_ruby_threads.rb +52 -0
  27. data/tests/bug_curb_easy_post_with_string_no_content_length_header.rb +83 -0
  28. data/tests/bug_instance_post_differs_from_class_post.rb +53 -0
  29. data/tests/bug_multi_segfault.rb +14 -0
  30. data/tests/bug_postfields_crash.rb +26 -0
  31. data/tests/bug_postfields_crash2.rb +57 -0
  32. data/tests/bug_require_last_or_segfault.rb +40 -0
  33. data/tests/bugtests.rb +9 -0
  34. data/tests/helper.rb +199 -0
  35. data/tests/mem_check.rb +65 -0
  36. data/tests/require_last_or_segfault_script.rb +36 -0
  37. data/tests/tc_curl_download.rb +75 -0
  38. data/tests/tc_curl_easy.rb +1011 -0
  39. data/tests/tc_curl_easy_setopt.rb +31 -0
  40. data/tests/tc_curl_multi.rb +485 -0
  41. data/tests/tc_curl_postfield.rb +143 -0
  42. data/tests/timeout.rb +100 -0
  43. data/tests/timeout_server.rb +33 -0
  44. data/tests/unittests.rb +2 -0
  45. metadata +133 -0
data/ext/curb_errors.h ADDED
@@ -0,0 +1,129 @@
1
+ /* curb_errors.h - Ruby exception types for curl errors
2
+ * Copyright (c)2006 Ross Bamford.
3
+ * Licensed under the Ruby License. See LICENSE for details.
4
+ *
5
+ * $Id: curb_errors.h 4 2006-11-17 18:35:31Z roscopeco $
6
+ */
7
+ #ifndef __CURB_ERRORS_H
8
+ #define __CURB_ERRORS_H
9
+
10
+ #include "curb.h"
11
+
12
+ /* base errors */
13
+ extern VALUE cCurlErr;
14
+
15
+ /* easy errors */
16
+ extern VALUE mCurlErr;
17
+ extern VALUE eCurlErrError;
18
+ extern VALUE eCurlErrFTPError;
19
+ extern VALUE eCurlErrHTTPError;
20
+ extern VALUE eCurlErrFileError;
21
+ extern VALUE eCurlErrLDAPError;
22
+ extern VALUE eCurlErrTelnetError;
23
+ extern VALUE eCurlErrTFTPError;
24
+
25
+ /* libcurl errors */
26
+ extern VALUE eCurlErrUnsupportedProtocol;
27
+ extern VALUE eCurlErrFailedInit;
28
+ extern VALUE eCurlErrMalformedURL;
29
+ extern VALUE eCurlErrMalformedURLUser;
30
+ extern VALUE eCurlErrProxyResolution;
31
+ extern VALUE eCurlErrHostResolution;
32
+ extern VALUE eCurlErrConnectFailed;
33
+ extern VALUE eCurlErrFTPWierdReply;
34
+ extern VALUE eCurlErrFTPAccessDenied;
35
+ extern VALUE eCurlErrFTPBadPassword;
36
+ extern VALUE eCurlErrFTPWierdPassReply;
37
+ extern VALUE eCurlErrFTPWierdUserReply;
38
+ extern VALUE eCurlErrFTPWierdPasvReply;
39
+ extern VALUE eCurlErrFTPWierd227Format;
40
+ extern VALUE eCurlErrFTPCantGetHost;
41
+ extern VALUE eCurlErrFTPCantReconnect;
42
+ extern VALUE eCurlErrFTPCouldntSetBinary;
43
+ extern VALUE eCurlErrPartialFile;
44
+ extern VALUE eCurlErrFTPCouldntRetrFile;
45
+ extern VALUE eCurlErrFTPWrite;
46
+ extern VALUE eCurlErrFTPQuote;
47
+ extern VALUE eCurlErrHTTPFailed;
48
+ extern VALUE eCurlErrWriteError;
49
+ extern VALUE eCurlErrMalformedUser;
50
+ extern VALUE eCurlErrFTPCouldntStorFile;
51
+ extern VALUE eCurlErrReadError;
52
+ extern VALUE eCurlErrOutOfMemory;
53
+ extern VALUE eCurlErrTimeout;
54
+ extern VALUE eCurlErrFTPCouldntSetASCII;
55
+ extern VALUE eCurlErrFTPPortFailed;
56
+ extern VALUE eCurlErrFTPCouldntUseRest;
57
+ extern VALUE eCurlErrFTPCouldntGetSize;
58
+ extern VALUE eCurlErrHTTPRange;
59
+ extern VALUE eCurlErrHTTPPost;
60
+ extern VALUE eCurlErrSSLConnectError;
61
+ extern VALUE eCurlErrBadResume;
62
+ extern VALUE eCurlErrFileCouldntRead;
63
+ extern VALUE eCurlErrLDAPCouldntBind;
64
+ extern VALUE eCurlErrLDAPSearchFailed;
65
+ extern VALUE eCurlErrLibraryNotFound;
66
+ extern VALUE eCurlErrFunctionNotFound;
67
+ extern VALUE eCurlErrAbortedByCallback;
68
+ extern VALUE eCurlErrBadFunctionArgument;
69
+ extern VALUE eCurlErrBadCallingOrder;
70
+ extern VALUE eCurlErrInterfaceFailed;
71
+ extern VALUE eCurlErrBadPasswordEntered;
72
+ extern VALUE eCurlErrTooManyRedirects;
73
+ extern VALUE eCurlErrTelnetUnknownOption;
74
+ extern VALUE eCurlErrTelnetBadOptionSyntax;
75
+ extern VALUE eCurlErrObsolete;
76
+ extern VALUE eCurlErrSSLPeerCertificate;
77
+ extern VALUE eCurlErrGotNothing;
78
+ extern VALUE eCurlErrSSLEngineNotFound;
79
+ extern VALUE eCurlErrSSLEngineSetFailed;
80
+ extern VALUE eCurlErrSendError;
81
+ extern VALUE eCurlErrRecvError;
82
+ extern VALUE eCurlErrShareInUse;
83
+ extern VALUE eCurlErrSSLCertificate;
84
+ extern VALUE eCurlErrSSLCipher;
85
+ extern VALUE eCurlErrSSLCACertificate;
86
+ extern VALUE eCurlErrBadContentEncoding;
87
+ extern VALUE eCurlErrLDAPInvalidURL;
88
+ extern VALUE eCurlErrFileSizeExceeded;
89
+ extern VALUE eCurlErrFTPSSLFailed;
90
+ extern VALUE eCurlErrSendFailedRewind;
91
+ extern VALUE eCurlErrSSLEngineInitFailed;
92
+ extern VALUE eCurlErrLoginDenied;
93
+ extern VALUE eCurlErrTFTPNotFound;
94
+ extern VALUE eCurlErrTFTPPermission;
95
+ extern VALUE eCurlErrTFTPDiskFull;
96
+ extern VALUE eCurlErrTFTPIllegalOperation;
97
+ extern VALUE eCurlErrTFTPUnknownID;
98
+ extern VALUE eCurlErrTFTPFileExists;
99
+ extern VALUE eCurlErrTFTPNoSuchUser;
100
+ extern VALUE eCurlErrConvFailed;
101
+ extern VALUE eCurlErrConvReqd;
102
+ extern VALUE eCurlErrSSLCacertBadfile;
103
+ extern VALUE eCurlErrRemoteFileNotFound;
104
+ extern VALUE eCurlErrSSH;
105
+ extern VALUE eCurlErrSSLShutdownFailed;
106
+ extern VALUE eCurlErrAgain;
107
+ extern VALUE eCurlErrSSLCRLBadfile;
108
+ extern VALUE eCurlErrSSLIssuerError;
109
+
110
+ /* multi errors */
111
+ extern VALUE mCurlErrFailedInit;
112
+ extern VALUE mCurlErrCallMultiPerform;
113
+ extern VALUE mCurlErrBadHandle;
114
+ extern VALUE mCurlErrBadEasyHandle;
115
+ extern VALUE mCurlErrOutOfMemory;
116
+ extern VALUE mCurlErrInternalError;
117
+ extern VALUE mCurlErrBadSocket;
118
+ extern VALUE mCurlErrUnknownOption;
119
+
120
+ /* binding errors */
121
+ extern VALUE eCurlErrInvalidPostField;
122
+
123
+ void init_curb_errors();
124
+ void raise_curl_easy_error_exception(CURLcode code);
125
+ void raise_curl_multi_error_exception(CURLMcode code);
126
+ VALUE rb_curl_easy_error(CURLcode code);
127
+ VALUE rb_curl_multi_error(CURLMcode code);
128
+
129
+ #endif
data/ext/curb_macros.h ADDED
@@ -0,0 +1,159 @@
1
+ /* Curb - helper macros for ruby integration
2
+ * Copyright (c)2006 Ross Bamford.
3
+ * Licensed under the Ruby License. See LICENSE for details.
4
+ *
5
+ * $Id: curb_macros.h 13 2006-11-23 23:54:25Z roscopeco $
6
+ */
7
+
8
+ #ifndef __CURB_MACROS_H
9
+ #define __CURB_MACROS_H
10
+
11
+ #define rb_easy_sym(sym) ID2SYM(rb_intern(sym))
12
+ #define rb_easy_hkey(key) ID2SYM(rb_intern(key))
13
+ #define rb_easy_set(key,val) rb_hash_aset(rbce->opts, rb_easy_hkey(key) , val)
14
+ #define rb_easy_get(key) rb_hash_aref(rbce->opts, rb_easy_hkey(key))
15
+ #define rb_easy_del(key) rb_hash_delete(rbce->opts, rb_easy_hkey(key))
16
+ #define rb_easy_nil(key) (rb_hash_aref(rbce->opts, rb_easy_hkey(key)) == Qnil)
17
+ #define rb_easy_type_check(key,type) (rb_type(rb_hash_aref(rbce->opts, rb_easy_hkey(key))) == type)
18
+
19
+ // TODO: rb_sym_to_s may not be defined?
20
+ #define rb_easy_get_str(key) \
21
+ RSTRING_PTR((rb_easy_type_check(key,T_STRING) ? rb_easy_get(key) : rb_str_to_str(rb_easy_get(key))))
22
+
23
+ /* getter/setter macros for various things */
24
+ /* setter for anything that stores a ruby VALUE in the struct */
25
+ #define CURB_OBJECT_SETTER(type, attr) \
26
+ type *ptr; \
27
+ \
28
+ Data_Get_Struct(self, type, ptr); \
29
+ ptr->attr = attr; \
30
+ \
31
+ return attr;
32
+
33
+ /* getter for anything that stores a ruby VALUE */
34
+ #define CURB_OBJECT_GETTER(type, attr) \
35
+ type *ptr; \
36
+ \
37
+ Data_Get_Struct(self, type, ptr); \
38
+ return ptr->attr;
39
+
40
+ /* setter for anything that stores a ruby VALUE in the struct opts hash */
41
+ #define CURB_OBJECT_HSETTER(type, attr) \
42
+ type *ptr; \
43
+ \
44
+ Data_Get_Struct(self, type, ptr); \
45
+ rb_hash_aset(ptr->opts, rb_easy_hkey(#attr), attr); \
46
+ \
47
+ return attr;
48
+
49
+ /* getter for anything that stores a ruby VALUE in the struct opts hash */
50
+ #define CURB_OBJECT_HGETTER(type, attr) \
51
+ type *ptr; \
52
+ \
53
+ Data_Get_Struct(self, type, ptr); \
54
+ return rb_hash_aref(ptr->opts, rb_easy_hkey(#attr));
55
+
56
+ /* setter for bool flags */
57
+ #define CURB_BOOLEAN_SETTER(type, attr) \
58
+ type *ptr; \
59
+ Data_Get_Struct(self, type, ptr); \
60
+ \
61
+ if (attr == Qnil || attr == Qfalse) { \
62
+ ptr->attr = 0; \
63
+ } else { \
64
+ ptr->attr = 1; \
65
+ } \
66
+ \
67
+ return attr;
68
+
69
+ /* getter for bool flags */
70
+ #define CURB_BOOLEAN_GETTER(type, attr) \
71
+ type *ptr; \
72
+ Data_Get_Struct(self, type, ptr); \
73
+ \
74
+ return((ptr->attr) ? Qtrue : Qfalse);
75
+
76
+ /* special setter for on_event handlers that take a block */
77
+ #define CURB_HANDLER_PROC_SETTER(type, handler) \
78
+ type *ptr; \
79
+ VALUE oldproc; \
80
+ \
81
+ Data_Get_Struct(self, type, ptr); \
82
+ \
83
+ oldproc = ptr->handler; \
84
+ rb_scan_args(argc, argv, "0&", &ptr->handler); \
85
+ \
86
+ return oldproc; \
87
+
88
+ /* special setter for on_event handlers that take a block, same as above but stores int he opts hash */
89
+ #define CURB_HANDLER_PROC_HSETTER(type, handler) \
90
+ type *ptr; \
91
+ VALUE oldproc, newproc; \
92
+ \
93
+ Data_Get_Struct(self, type, ptr); \
94
+ \
95
+ oldproc = rb_hash_aref(ptr->opts, rb_easy_hkey(#handler)); \
96
+ rb_scan_args(argc, argv, "0&", &newproc); \
97
+ \
98
+ rb_hash_aset(ptr->opts, rb_easy_hkey(#handler), newproc); \
99
+ \
100
+ return oldproc;
101
+
102
+ /* setter for numerics that are kept in c ints */
103
+ #define CURB_IMMED_SETTER(type, attr, nilval) \
104
+ type *ptr; \
105
+ \
106
+ Data_Get_Struct(self, type, ptr); \
107
+ if (attr == Qnil) { \
108
+ ptr->attr = nilval; \
109
+ } else { \
110
+ ptr->attr = NUM2INT(attr); \
111
+ } \
112
+ \
113
+ return attr; \
114
+
115
+ /* setter for numerics that are kept in c ints */
116
+ #define CURB_IMMED_GETTER(type, attr, nilval) \
117
+ type *ptr; \
118
+ \
119
+ Data_Get_Struct(self, type, ptr); \
120
+ if (ptr->attr == nilval) { \
121
+ return Qnil; \
122
+ } else { \
123
+ return INT2NUM(ptr->attr); \
124
+ }
125
+
126
+ /* special setter for port / port ranges */
127
+ #define CURB_IMMED_PORT_SETTER(type, attr, msg) \
128
+ type *ptr; \
129
+ \
130
+ Data_Get_Struct(self, type, ptr); \
131
+ if (attr == Qnil) { \
132
+ ptr->attr = 0; \
133
+ } else { \
134
+ int port = FIX2INT(attr); \
135
+ \
136
+ if ((port) && ((port & 0xFFFF) == port)) { \
137
+ ptr->attr = port; \
138
+ } else { \
139
+ rb_raise(rb_eArgError, "Invalid " msg " %d (expected between 1 and 65535)", port); \
140
+ } \
141
+ } \
142
+ \
143
+ return attr; \
144
+
145
+ /* special getter for port / port ranges */
146
+ #define CURB_IMMED_PORT_GETTER(type, attr) \
147
+ type *ptr; \
148
+ \
149
+ Data_Get_Struct(self, type, ptr); \
150
+ if (ptr->attr == 0) { \
151
+ return Qnil; \
152
+ } else { \
153
+ return INT2FIX(ptr->attr); \
154
+ }
155
+
156
+ #define CURB_DEFINE(name) \
157
+ rb_define_const(mCurl, #name, INT2FIX(name))
158
+
159
+ #endif
data/ext/curb_multi.c ADDED
@@ -0,0 +1,633 @@
1
+ /* curb_multi.c - Curl multi mode
2
+ * Copyright (c)2008 Todd A. Fisher.
3
+ * Licensed under the Ruby License. See LICENSE for details.
4
+ *
5
+ */
6
+ #include "curb_config.h"
7
+ #ifdef HAVE_RUBY19_ST_H
8
+ #include <ruby.h>
9
+ #include <ruby/st.h>
10
+ #else
11
+ #include <ruby.h>
12
+ #include <st.h>
13
+ #endif
14
+ #include "curb_easy.h"
15
+ #include "curb_errors.h"
16
+ #include "curb_postfield.h"
17
+ #include "curb_multi.h"
18
+
19
+ #include <errno.h>
20
+
21
+ #ifdef _WIN32
22
+ // for O_RDWR and O_BINARY
23
+ #include <fcntl.h>
24
+ #endif
25
+
26
+ extern VALUE mCurl;
27
+ static VALUE idCall;
28
+
29
+ #ifdef RDOC_NEVER_DEFINED
30
+ mCurl = rb_define_module("Curl");
31
+ #endif
32
+
33
+ VALUE cCurlMulti;
34
+
35
+ static long cCurlMutiDefaulttimeout = 100; /* milliseconds */
36
+
37
+ static void rb_curl_multi_remove(ruby_curl_multi *rbcm, VALUE easy);
38
+ static void rb_curl_multi_read_info(VALUE self, CURLM *mptr);
39
+ static void rb_curl_multi_run(VALUE self, CURLM *multi_handle, int *still_running);
40
+
41
+ static VALUE callback_exception(VALUE unused) {
42
+ return Qfalse;
43
+ }
44
+
45
+ static void curl_multi_mark(ruby_curl_multi *rbcm) {
46
+ rb_gc_mark(rbcm->requests);
47
+ }
48
+
49
+ static void curl_multi_flush_easy(VALUE key, VALUE easy, ruby_curl_multi *rbcm) {
50
+ CURLMcode result;
51
+ ruby_curl_easy *rbce;
52
+
53
+ Data_Get_Struct(easy, ruby_curl_easy, rbce);
54
+ result = curl_multi_remove_handle(rbcm->handle, rbce->curl);
55
+ if (result != 0) {
56
+ raise_curl_multi_error_exception(result);
57
+ }
58
+ }
59
+
60
+ static int
61
+ rb_hash_clear_i(VALUE key, VALUE value, VALUE dummy) {
62
+ return ST_DELETE;
63
+ }
64
+
65
+ static void curl_multi_free(ruby_curl_multi *rbcm) {
66
+
67
+ if (rbcm && !rbcm->requests == Qnil && rb_type(rbcm->requests) == T_HASH && RHASH_LEN(rbcm->requests) > 0) {
68
+
69
+ rb_hash_foreach( rbcm->requests, (int (*)())curl_multi_flush_easy, (VALUE)rbcm );
70
+
71
+ rb_hash_foreach(rbcm->requests, rb_hash_clear_i, 0); //rb_hash_clear(rbcm->requests);
72
+ rbcm->requests = Qnil;
73
+ }
74
+ curl_multi_cleanup(rbcm->handle);
75
+ free(rbcm);
76
+ }
77
+
78
+ /*
79
+ * call-seq:
80
+ * Curl::Multi.new => #&lt;Curl::Easy...&gt;
81
+ *
82
+ * Create a new Curl::Multi instance
83
+ */
84
+ VALUE ruby_curl_multi_new(VALUE klass) {
85
+ VALUE new_curlm;
86
+
87
+ ruby_curl_multi *rbcm = ALLOC(ruby_curl_multi);
88
+
89
+ rbcm->handle = curl_multi_init();
90
+ if (!rbcm->handle) {
91
+ rb_raise(mCurlErrFailedInit, "Failed to initialize multi handle");
92
+ }
93
+
94
+ rbcm->requests = rb_hash_new();
95
+
96
+ rbcm->active = 0;
97
+ rbcm->running = 0;
98
+
99
+ new_curlm = Data_Wrap_Struct(klass, curl_multi_mark, curl_multi_free, rbcm);
100
+
101
+ return new_curlm;
102
+ }
103
+
104
+ /*
105
+ * call-seq:
106
+ * Curl::Multi.default_timeout = 4 => 4
107
+ *
108
+ * Set the global default time out for all Curl::Multi Handles. This value is used
109
+ * when libcurl cannot determine a timeout value when calling curl_multi_timeout.
110
+ *
111
+ */
112
+ VALUE ruby_curl_multi_set_default_timeout(VALUE klass, VALUE timeout) {
113
+ cCurlMutiDefaulttimeout = FIX2LONG(timeout);
114
+ return timeout;
115
+ }
116
+
117
+ /*
118
+ * call-seq:
119
+ * Curl::Multi.default_timeout = 4 => 4
120
+ *
121
+ * Get the global default time out for all Curl::Multi Handles.
122
+ *
123
+ */
124
+ VALUE ruby_curl_multi_get_default_timeout(VALUE klass) {
125
+ return INT2FIX(cCurlMutiDefaulttimeout);
126
+ }
127
+
128
+ /* Hash#foreach callback for ruby_curl_multi_requests */
129
+ static int ruby_curl_multi_requests_callback(VALUE key, VALUE value, VALUE result_array) {
130
+ rb_ary_push(result_array, value);
131
+
132
+ return ST_CONTINUE;
133
+ }
134
+
135
+ /*
136
+ * call-seq:
137
+ * multi.requests => [#&lt;Curl::Easy...&gt;, ...]
138
+ *
139
+ * Returns an array containing all the active requests on this Curl::Multi object.
140
+ */
141
+ static VALUE ruby_curl_multi_requests(VALUE self) {
142
+ ruby_curl_multi *rbcm;
143
+ VALUE result_array;
144
+
145
+ Data_Get_Struct(self, ruby_curl_multi, rbcm);
146
+
147
+ result_array = rb_ary_new();
148
+
149
+ /* iterate over the requests hash, and stuff references into the array. */
150
+ rb_hash_foreach(rbcm->requests, ruby_curl_multi_requests_callback, result_array);
151
+
152
+ return result_array;
153
+ }
154
+
155
+ /*
156
+ * call-seq:
157
+ * multi.idle? => true or false
158
+ *
159
+ * Returns whether or not this Curl::Multi handle is processing any requests. E.g. this returns
160
+ * true when multi.requests.length == 0.
161
+ */
162
+ static VALUE ruby_curl_multi_idle(VALUE self) {
163
+ ruby_curl_multi *rbcm;
164
+
165
+ Data_Get_Struct(self, ruby_curl_multi, rbcm);
166
+
167
+ if ( FIX2INT( rb_funcall(rbcm->requests, rb_intern("length"), 0) ) == 0 ) {
168
+ return Qtrue;
169
+ } else {
170
+ return Qfalse;
171
+ }
172
+ }
173
+
174
+ /*
175
+ * call-seq:
176
+ * multi = Curl::Multi.new
177
+ * multi.max_connects = 800
178
+ *
179
+ * Set the max connections in the cache for a multi handle
180
+ */
181
+ static VALUE ruby_curl_multi_max_connects(VALUE self, VALUE count) {
182
+ #ifdef HAVE_CURLMOPT_MAXCONNECTS
183
+ ruby_curl_multi *rbcm;
184
+
185
+ Data_Get_Struct(self, ruby_curl_multi, rbcm);
186
+ curl_multi_setopt(rbcm->handle, CURLMOPT_MAXCONNECTS, NUM2INT(count));
187
+ #endif
188
+
189
+ return count;
190
+ }
191
+
192
+ /*
193
+ * call-seq:
194
+ * multi = Curl::Multi.new
195
+ * multi.pipeline = true
196
+ *
197
+ * Pass a long set to 1 to enable or 0 to disable. Enabling pipelining on a multi handle will make it
198
+ * attempt to perform HTTP Pipelining as far as possible for transfers using this handle. This means
199
+ * that if you add a second request that can use an already existing connection, the second request will
200
+ * be "piped" on the same connection rather than being executed in parallel. (Added in 7.16.0)
201
+ *
202
+ */
203
+ static VALUE ruby_curl_multi_pipeline(VALUE self, VALUE onoff) {
204
+ #ifdef HAVE_CURLMOPT_PIPELINING
205
+ ruby_curl_multi *rbcm;
206
+
207
+ Data_Get_Struct(self, ruby_curl_multi, rbcm);
208
+ curl_multi_setopt(rbcm->handle, CURLMOPT_PIPELINING, onoff == Qtrue ? 1 : 0);
209
+ #endif
210
+ return onoff;
211
+ }
212
+
213
+ /*
214
+ * call-seq:
215
+ * multi = Curl::Multi.new
216
+ * easy = Curl::Easy.new('url')
217
+ *
218
+ * multi.add(easy)
219
+ *
220
+ * Add an easy handle to the multi stack
221
+ */
222
+ VALUE ruby_curl_multi_add(VALUE self, VALUE easy) {
223
+ CURLMcode mcode;
224
+ ruby_curl_easy *rbce;
225
+ ruby_curl_multi *rbcm;
226
+
227
+ Data_Get_Struct(self, ruby_curl_multi, rbcm);
228
+ Data_Get_Struct(easy, ruby_curl_easy, rbce);
229
+
230
+ /* setup the easy handle */
231
+ ruby_curl_easy_setup( rbce );
232
+
233
+ mcode = curl_multi_add_handle(rbcm->handle, rbce->curl);
234
+ if (mcode != CURLM_CALL_MULTI_PERFORM && mcode != CURLM_OK) {
235
+ raise_curl_multi_error_exception(mcode);
236
+ }
237
+
238
+ rbcm->active++;
239
+
240
+ /* Increase the running count, so that the perform loop keeps running.
241
+ * If this number is not correct, the next call to curl_multi_perform will correct it. */
242
+ rbcm->running++;
243
+
244
+ rb_hash_aset( rbcm->requests, easy, easy );
245
+
246
+ return self;
247
+ }
248
+
249
+ /*
250
+ * call-seq:
251
+ * multi = Curl::Multi.new
252
+ * easy = Curl::Easy.new('url')
253
+ *
254
+ * multi.add(easy)
255
+ *
256
+ * # sometime later
257
+ * multi.remove(easy)
258
+ *
259
+ * Remove an easy handle from a multi stack.
260
+ *
261
+ * Will raise an exception if the easy handle is not found
262
+ */
263
+ VALUE ruby_curl_multi_remove(VALUE self, VALUE easy) {
264
+ ruby_curl_multi *rbcm;
265
+ ruby_curl_easy *rbce;
266
+
267
+ Data_Get_Struct(self, ruby_curl_multi, rbcm);
268
+
269
+ Data_Get_Struct(easy, ruby_curl_easy, rbce);
270
+
271
+ rb_curl_multi_remove(rbcm,easy);
272
+
273
+ return self;
274
+ }
275
+ static void rb_curl_multi_remove(ruby_curl_multi *rbcm, VALUE easy) {
276
+ CURLMcode result;
277
+ ruby_curl_easy *rbce;
278
+ VALUE r;
279
+
280
+ Data_Get_Struct(easy, ruby_curl_easy, rbce);
281
+
282
+ result = curl_multi_remove_handle(rbcm->handle, rbce->curl);
283
+ if (result != 0) {
284
+ raise_curl_multi_error_exception(result);
285
+ }
286
+
287
+ rbcm->active--;
288
+
289
+ ruby_curl_easy_cleanup( easy, rbce );
290
+
291
+ // active should equal INT2FIX(RHASH(rbcm->requests)->tbl->num_entries)
292
+ r = rb_hash_delete( rbcm->requests, easy );
293
+ if( r != easy || r == Qnil ) {
294
+ rb_warn("Possibly lost track of Curl::Easy VALUE, it may not be reclaimed by GC");
295
+ }
296
+ }
297
+
298
+ /* Hash#foreach callback for ruby_curl_multi_cancel */
299
+ static int ruby_curl_multi_cancel_callback(VALUE key, VALUE value, ruby_curl_multi *rbcm) {
300
+ rb_curl_multi_remove(rbcm, value);
301
+
302
+ return ST_CONTINUE;
303
+ }
304
+
305
+ /*
306
+ * call-seq:
307
+ * multi.cancel!
308
+ *
309
+ * Cancels all requests currently being made on this Curl::Multi handle.
310
+ */
311
+ static VALUE ruby_curl_multi_cancel(VALUE self) {
312
+ ruby_curl_multi *rbcm;
313
+
314
+ Data_Get_Struct(self, ruby_curl_multi, rbcm);
315
+
316
+ rb_hash_foreach( rbcm->requests, ruby_curl_multi_cancel_callback, (VALUE)rbcm );
317
+
318
+ /* for chaining */
319
+ return self;
320
+ }
321
+
322
+ // on_success, on_failure, on_complete
323
+ static VALUE call_status_handler1(VALUE ary) {
324
+ return rb_funcall(rb_ary_entry(ary, 0), idCall, 1, rb_ary_entry(ary, 1));
325
+ }
326
+ static VALUE call_status_handler2(VALUE ary) {
327
+ return rb_funcall(rb_ary_entry(ary, 0), idCall, 2, rb_ary_entry(ary, 1), rb_ary_entry(ary, 2));
328
+ }
329
+
330
+ static void rb_curl_mutli_handle_complete(VALUE self, CURL *easy_handle, int result) {
331
+
332
+ long response_code = -1;
333
+ VALUE easy;
334
+ ruby_curl_easy *rbce = NULL;
335
+ VALUE callargs, val = Qtrue;
336
+
337
+ CURLcode ecode = curl_easy_getinfo(easy_handle, CURLINFO_PRIVATE, (char**)&easy);
338
+
339
+ Data_Get_Struct(easy, ruby_curl_easy, rbce);
340
+
341
+ rbce->last_result = result; /* save the last easy result code */
342
+
343
+ ruby_curl_multi_remove( self, easy );
344
+
345
+ /* after running a request cleanup the headers, these are set before each request */
346
+ if (rbce->curl_headers) {
347
+ curl_slist_free_all(rbce->curl_headers);
348
+ rbce->curl_headers = NULL;
349
+ }
350
+
351
+ if (ecode != 0) {
352
+ raise_curl_easy_error_exception(ecode);
353
+ }
354
+
355
+ if (!rb_easy_nil("complete_proc")) {
356
+ callargs = rb_ary_new3(2, rb_easy_get("complete_proc"), easy);
357
+ rbce->callback_active = 1;
358
+ val = rb_rescue(call_status_handler1, callargs, callback_exception, Qnil);
359
+ rbce->callback_active = 0;
360
+ //rb_funcall( rb_easy_get("complete_proc"), idCall, 1, easy );
361
+ }
362
+
363
+ curl_easy_getinfo(rbce->curl, CURLINFO_RESPONSE_CODE, &response_code);
364
+
365
+ if (result != 0) {
366
+ if (!rb_easy_nil("failure_proc")) {
367
+ callargs = rb_ary_new3(3, rb_easy_get("failure_proc"), easy, rb_curl_easy_error(result));
368
+ rbce->callback_active = 1;
369
+ val = rb_rescue(call_status_handler2, callargs, callback_exception, Qnil);
370
+ rbce->callback_active = 0;
371
+ //rb_funcall( rb_easy_get("failure_proc"), idCall, 2, easy, rb_curl_easy_error(result) );
372
+ }
373
+ }
374
+ else if (!rb_easy_nil("success_proc") &&
375
+ ((response_code >= 200 && response_code < 300) || response_code == 0)) {
376
+ /* NOTE: we allow response_code == 0, in the case of non http requests e.g. reading from disk */
377
+ callargs = rb_ary_new3(2, rb_easy_get("success_proc"), easy);
378
+ rbce->callback_active = 1;
379
+ val = rb_rescue(call_status_handler1, callargs, callback_exception, Qnil);
380
+ rbce->callback_active = 0;
381
+ //rb_funcall( rb_easy_get("success_proc"), idCall, 1, easy );
382
+ }
383
+ else if (!rb_easy_nil("redirect_proc") &&
384
+ (response_code >= 300 && response_code < 400)) {
385
+ rbce->callback_active = 1;
386
+ callargs = rb_ary_new3(3, rb_easy_get("redirect_proc"), easy, rb_curl_easy_error(result));
387
+ rbce->callback_active = 0;
388
+ val = rb_rescue(call_status_handler2, callargs, callback_exception, Qnil);
389
+ }
390
+ else if (!rb_easy_nil("missing_proc") &&
391
+ (response_code >= 400 && response_code < 500)) {
392
+ rbce->callback_active = 1;
393
+ callargs = rb_ary_new3(3, rb_easy_get("missing_proc"), easy, rb_curl_easy_error(result));
394
+ rbce->callback_active = 0;
395
+ val = rb_rescue(call_status_handler2, callargs, callback_exception, Qnil);
396
+ }
397
+ else if (!rb_easy_nil("failure_proc") &&
398
+ (response_code >= 500 && response_code <= 999)) {
399
+ callargs = rb_ary_new3(3, rb_easy_get("failure_proc"), easy, rb_curl_easy_error(result));
400
+ rbce->callback_active = 1;
401
+ val = rb_rescue(call_status_handler2, callargs, callback_exception, Qnil);
402
+ rbce->callback_active = 0;
403
+ //rb_funcall( rb_easy_get("failure_proc"), idCall, 2, easy, rb_curl_easy_error(result) );
404
+ }
405
+
406
+ if (val == Qfalse) {
407
+ rb_warn("uncaught exception from callback");
408
+ // exception was raised?
409
+ //fprintf(stderr, "exception raised from callback\n");
410
+ }
411
+
412
+ }
413
+
414
+ static void rb_curl_multi_read_info(VALUE self, CURLM *multi_handle) {
415
+ int msgs_left, result;
416
+ CURLMsg *msg;
417
+ CURL *easy_handle;
418
+
419
+ /* check for finished easy handles and remove from the multi handle */
420
+ while ((msg = curl_multi_info_read(multi_handle, &msgs_left))) {
421
+ if (msg->msg == CURLMSG_DONE) {
422
+ easy_handle = msg->easy_handle;
423
+ result = msg->data.result;
424
+ if (easy_handle) {
425
+ rb_curl_mutli_handle_complete(self, easy_handle, result);
426
+ }
427
+ }
428
+ }
429
+ }
430
+
431
+ /* called within ruby_curl_multi_perform */
432
+ static void rb_curl_multi_run(VALUE self, CURLM *multi_handle, int *still_running) {
433
+ CURLMcode mcode;
434
+
435
+ do {
436
+ mcode = curl_multi_perform(multi_handle, still_running);
437
+ } while (mcode == CURLM_CALL_MULTI_PERFORM);
438
+
439
+ if (mcode != CURLM_OK) {
440
+ raise_curl_multi_error_exception(mcode);
441
+ }
442
+
443
+ }
444
+
445
+ #ifdef _WIN32
446
+ void create_crt_fd(fd_set *os_set, fd_set *crt_set)
447
+ {
448
+ int i;
449
+ crt_set->fd_count = os_set->fd_count;
450
+ for (i = 0; i < os_set->fd_count; i++) {
451
+ WSAPROTOCOL_INFO wsa_pi;
452
+ // dupicate the SOCKET
453
+ int r = WSADuplicateSocket(os_set->fd_array[i], GetCurrentProcessId(), &wsa_pi);
454
+ SOCKET s = WSASocket(wsa_pi.iAddressFamily, wsa_pi.iSocketType, wsa_pi.iProtocol, &wsa_pi, 0, 0);
455
+ // create the CRT fd so ruby can get back to the SOCKET
456
+ int fd = _open_osfhandle(s, O_RDWR|O_BINARY);
457
+ os_set->fd_array[i] = s;
458
+ crt_set->fd_array[i] = fd;
459
+ }
460
+ }
461
+
462
+ void cleanup_crt_fd(fd_set *os_set, fd_set *crt_set)
463
+ {
464
+ int i;
465
+ for (i = 0; i < os_set->fd_count; i++) {
466
+ // cleanup the CRT fd
467
+ _close(crt_set->fd_array[i]);
468
+ // cleanup the duplicated SOCKET
469
+ closesocket(os_set->fd_array[i]);
470
+ }
471
+ }
472
+ #endif
473
+
474
+ #ifdef HAVE_RB_THREAD_BLOCKING_REGION
475
+ struct _select_set {
476
+ int maxfd;
477
+ fd_set *fdread, *fdwrite, *fdexcep;
478
+ struct timeval *tv;
479
+ };
480
+
481
+ static VALUE curb_select(void *args) {
482
+ struct _select_set* set = args;
483
+ int rc = select(set->maxfd, set->fdread, set->fdwrite, set->fdexcep, set->tv);
484
+ return INT2FIX(rc);
485
+ }
486
+ #endif
487
+
488
+ /*
489
+ * call-seq:
490
+ * multi = Curl::Multi.new
491
+ * easy1 = Curl::Easy.new('url')
492
+ * easy2 = Curl::Easy.new('url')
493
+ *
494
+ * multi.add(easy1)
495
+ * multi.add(easy2)
496
+ *
497
+ * multi.perform do
498
+ * # while idle other code my execute here
499
+ * end
500
+ *
501
+ * Run multi handles, looping selecting when data can be transfered
502
+ */
503
+ VALUE ruby_curl_multi_perform(int argc, VALUE *argv, VALUE self) {
504
+ CURLMcode mcode;
505
+ ruby_curl_multi *rbcm;
506
+ int maxfd, rc;
507
+ fd_set fdread, fdwrite, fdexcep;
508
+ #ifdef _WIN32
509
+ fd_set crt_fdread, crt_fdwrite, crt_fdexcep;
510
+ #endif
511
+ long timeout_milliseconds;
512
+ struct timeval tv = {0, 0};
513
+ VALUE block = Qnil;
514
+ #ifdef HAVE_RB_THREAD_BLOCKING_REGION
515
+ struct _select_set fdset_args;
516
+ #endif
517
+
518
+ rb_scan_args(argc, argv, "0&", &block);
519
+
520
+ Data_Get_Struct(self, ruby_curl_multi, rbcm);
521
+
522
+ timeout_milliseconds = cCurlMutiDefaulttimeout;
523
+
524
+ rb_curl_multi_run( self, rbcm->handle, &(rbcm->running) );
525
+ rb_curl_multi_read_info( self, rbcm->handle );
526
+ if (block != Qnil) { rb_funcall(block, rb_intern("call"), 1, self); }
527
+
528
+ do {
529
+ while (rbcm->running) {
530
+
531
+ #ifdef HAVE_CURL_MULTI_TIMEOUT
532
+ /* get the curl suggested time out */
533
+ mcode = curl_multi_timeout(rbcm->handle, &timeout_milliseconds);
534
+ if (mcode != CURLM_OK) {
535
+ raise_curl_multi_error_exception(mcode);
536
+ }
537
+ #else
538
+ /* libcurl doesn't have a timeout method defined, initialize to -1 we'll pick up the default later */
539
+ timeout_milliseconds = -1;
540
+ #endif
541
+
542
+ if (timeout_milliseconds == 0) { /* no delay */
543
+ rb_curl_multi_run( self, rbcm->handle, &(rbcm->running) );
544
+ rb_curl_multi_read_info( self, rbcm->handle );
545
+ if (block != Qnil) { rb_funcall(block, rb_intern("call"), 1, self); }
546
+ continue;
547
+ }
548
+
549
+ if (timeout_milliseconds < 0 || timeout_milliseconds > cCurlMutiDefaulttimeout) {
550
+ timeout_milliseconds = cCurlMutiDefaulttimeout; /* libcurl doesn't know how long to wait, use a default timeout */
551
+ /* or buggy versions libcurl sometimes reports huge timeouts... let's cap it */
552
+ }
553
+
554
+ tv.tv_sec = 0; /* never wait longer than 1 second */
555
+ tv.tv_usec = (int)(timeout_milliseconds * 1000); /* XXX: int is the right type for OSX, what about linux? */
556
+
557
+ FD_ZERO(&fdread);
558
+ FD_ZERO(&fdwrite);
559
+ FD_ZERO(&fdexcep);
560
+
561
+ /* load the fd sets from the multi handle */
562
+ mcode = curl_multi_fdset(rbcm->handle, &fdread, &fdwrite, &fdexcep, &maxfd);
563
+ if (mcode != CURLM_OK) {
564
+ raise_curl_multi_error_exception(mcode);
565
+ }
566
+
567
+ #ifdef _WIN32
568
+ create_crt_fd(&fdread, &crt_fdread);
569
+ create_crt_fd(&fdwrite, &crt_fdwrite);
570
+ create_crt_fd(&fdexcep, &crt_fdexcep);
571
+ #endif
572
+
573
+ #ifdef HAVE_RB_THREAD_BLOCKING_REGION
574
+ fdset_args.maxfd = maxfd+1;
575
+ fdset_args.fdread = &fdread;
576
+ fdset_args.fdwrite = &fdwrite;
577
+ fdset_args.fdexcep = &fdexcep;
578
+ fdset_args.tv = &tv;
579
+ rc = rb_thread_blocking_region(curb_select, &fdset_args, RUBY_UBF_IO, 0);
580
+ #else
581
+ rc = rb_thread_select(maxfd+1, &fdread, &fdwrite, &fdexcep, &tv);
582
+ #endif
583
+
584
+ #ifdef _WIN32
585
+ cleanup_crt_fd(&fdread, &crt_fdread);
586
+ cleanup_crt_fd(&fdwrite, &crt_fdwrite);
587
+ cleanup_crt_fd(&fdexcep, &crt_fdexcep);
588
+ #endif
589
+
590
+ switch(rc) {
591
+ case -1:
592
+ rb_raise(rb_eRuntimeError, "select(): %s", strerror(errno));
593
+ break;
594
+ case 0: /* timeout */
595
+ default: /* action */
596
+ rb_curl_multi_run( self, rbcm->handle, &(rbcm->running) );
597
+ rb_curl_multi_read_info( self, rbcm->handle );
598
+ if (block != Qnil) { rb_funcall(block, rb_intern("call"), 1, self); }
599
+ break;
600
+ }
601
+ }
602
+
603
+ } while( rbcm->running );
604
+
605
+ rb_curl_multi_read_info( self, rbcm->handle );
606
+ if (block != Qnil) { rb_funcall(block, rb_intern("call"), 1, self); }
607
+
608
+ return Qtrue;
609
+ }
610
+
611
+ /* =================== INIT LIB =====================*/
612
+ void init_curb_multi() {
613
+ idCall = rb_intern("call");
614
+
615
+ cCurlMulti = rb_define_class_under(mCurl, "Multi", rb_cObject);
616
+
617
+ /* Class methods */
618
+ rb_define_singleton_method(cCurlMulti, "new", ruby_curl_multi_new, 0);
619
+ rb_define_singleton_method(cCurlMulti, "default_timeout=", ruby_curl_multi_set_default_timeout, 1);
620
+ rb_define_singleton_method(cCurlMulti, "default_timeout", ruby_curl_multi_get_default_timeout, 0);
621
+
622
+ /* "Attributes" */
623
+ rb_define_method(cCurlMulti, "requests", ruby_curl_multi_requests, 0);
624
+ rb_define_method(cCurlMulti, "idle?", ruby_curl_multi_idle, 0);
625
+
626
+ /* Instnace methods */
627
+ rb_define_method(cCurlMulti, "max_connects=", ruby_curl_multi_max_connects, 1);
628
+ rb_define_method(cCurlMulti, "pipeline=", ruby_curl_multi_pipeline, 1);
629
+ rb_define_method(cCurlMulti, "add", ruby_curl_multi_add, 1);
630
+ rb_define_method(cCurlMulti, "remove", ruby_curl_multi_remove, 1);
631
+ rb_define_method(cCurlMulti, "cancel!", ruby_curl_multi_cancel, 0);
632
+ rb_define_method(cCurlMulti, "perform", ruby_curl_multi_perform, -1);
633
+ }