gus-curb 0.8.7

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