taf2-curb 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/ext/curb_errors.h ADDED
@@ -0,0 +1,117 @@
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
+
101
+ /* multi errors */
102
+ extern VALUE mCurlErrCallMultiPerform;
103
+ extern VALUE mCurlErrBadHandle;
104
+ extern VALUE mCurlErrBadEasyHandle;
105
+ extern VALUE mCurlErrOutOfMemory;
106
+ extern VALUE mCurlErrInternalError;
107
+ extern VALUE mCurlErrBadSocket;
108
+ extern VALUE mCurlErrUnknownOption;
109
+
110
+ /* binding errors */
111
+ extern VALUE eCurlErrInvalidPostField;
112
+
113
+ void init_curb_errors();
114
+ void raise_curl_easy_error_exception(CURLcode code);
115
+ void raise_curl_multi_error_exception(CURLMcode code);
116
+
117
+ #endif
data/ext/curb_macros.h ADDED
@@ -0,0 +1,114 @@
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
+ /* getter/setter macros for various things */
12
+ /* setter for anything that stores a ruby VALUE in the struct */
13
+ #define CURB_OBJECT_SETTER(type, attr) \
14
+ type *ptr; \
15
+ \
16
+ Data_Get_Struct(self, type, ptr); \
17
+ ptr->attr = attr; \
18
+ \
19
+ return attr;
20
+
21
+ /* getter for anything that stores a ruby VALUE */
22
+ #define CURB_OBJECT_GETTER(type, attr) \
23
+ type *ptr; \
24
+ \
25
+ Data_Get_Struct(self, type, ptr); \
26
+ return ptr->attr;
27
+
28
+ /* setter for bool flags */
29
+ #define CURB_BOOLEAN_SETTER(type, attr) \
30
+ type *ptr; \
31
+ Data_Get_Struct(self, type, ptr); \
32
+ \
33
+ if (attr == Qnil || attr == Qfalse) { \
34
+ ptr->attr = 0; \
35
+ } else { \
36
+ ptr->attr = 1; \
37
+ } \
38
+ \
39
+ return attr;
40
+
41
+ /* getter for bool flags */
42
+ #define CURB_BOOLEAN_GETTER(type, attr) \
43
+ type *ptr; \
44
+ Data_Get_Struct(self, type, ptr); \
45
+ \
46
+ return((ptr->attr) ? Qtrue : Qfalse);
47
+
48
+ /* special setter for on_event handlers that take a block */
49
+ #define CURB_HANDLER_PROC_SETTER(type, handler) \
50
+ type *ptr; \
51
+ VALUE oldproc; \
52
+ \
53
+ Data_Get_Struct(self, type, ptr); \
54
+ \
55
+ oldproc = ptr->handler; \
56
+ rb_scan_args(argc, argv, "0&", &ptr->handler); \
57
+ \
58
+ return oldproc; \
59
+
60
+ /* setter for numerics that are kept in c ints */
61
+ #define CURB_IMMED_SETTER(type, attr, nilval) \
62
+ type *ptr; \
63
+ \
64
+ Data_Get_Struct(self, type, ptr); \
65
+ if (attr == Qnil) { \
66
+ ptr->attr = nilval; \
67
+ } else { \
68
+ ptr->attr = NUM2INT(attr); \
69
+ } \
70
+ \
71
+ return attr; \
72
+
73
+ /* setter for numerics that are kept in c ints */
74
+ #define CURB_IMMED_GETTER(type, attr, nilval) \
75
+ type *ptr; \
76
+ \
77
+ Data_Get_Struct(self, type, ptr); \
78
+ if (ptr->attr == nilval) { \
79
+ return Qnil; \
80
+ } else { \
81
+ return INT2NUM(ptr->attr); \
82
+ }
83
+
84
+ /* special setter for port / port ranges */
85
+ #define CURB_IMMED_PORT_SETTER(type, attr, msg) \
86
+ type *ptr; \
87
+ \
88
+ Data_Get_Struct(self, type, ptr); \
89
+ if (attr == Qnil) { \
90
+ ptr->attr = 0; \
91
+ } else { \
92
+ int port = FIX2INT(attr); \
93
+ \
94
+ if ((port) && ((port & 0xFFFF) == port)) { \
95
+ ptr->attr = port; \
96
+ } else { \
97
+ rb_raise(rb_eArgError, "Invalid " msg " %d (expected between 1 and 65535)", port); \
98
+ } \
99
+ } \
100
+ \
101
+ return attr; \
102
+
103
+ /* special getter for port / port ranges */
104
+ #define CURB_IMMED_PORT_GETTER(type, attr) \
105
+ type *ptr; \
106
+ \
107
+ Data_Get_Struct(self, type, ptr); \
108
+ if (ptr->attr == 0) { \
109
+ return Qnil; \
110
+ } else { \
111
+ return INT2FIX(ptr->attr); \
112
+ }
113
+
114
+ #endif
data/ext/curb_multi.c ADDED
@@ -0,0 +1,323 @@
1
+ /* curb_easy.c - Curl easy mode
2
+ * Copyright (c)2008 Todd A. Fisher.
3
+ * Licensed under the Ruby License. See LICENSE for details.
4
+ *
5
+ * $Id$
6
+ */
7
+ #include <st.h>
8
+ #include "curb_easy.h"
9
+ #include "curb_errors.h"
10
+ #include "curb_postfield.h"
11
+ #include "curb_multi.h"
12
+
13
+ #include <errno.h>
14
+
15
+ extern VALUE mCurl;
16
+ static VALUE idCall;
17
+
18
+ #ifdef RDOC_NEVER_DEFINED
19
+ mCurl = rb_define_module("Curl");
20
+ #endif
21
+
22
+ VALUE cCurlMulti;
23
+
24
+ static VALUE ruby_curl_multi_remove(VALUE , VALUE );
25
+ static void rb_curl_multi_remove(ruby_curl_multi *rbcm, VALUE easy);
26
+ static void rb_curl_multi_read_info(VALUE self, CURLM *mptr);
27
+
28
+ static void rb_curl_multi_mark_all_easy(VALUE key, VALUE rbeasy, ruby_curl_multi *rbcm) {
29
+ //printf( "mark easy: 0x%X\n", (long)rbeasy );
30
+ rb_gc_mark(rbeasy);
31
+ }
32
+
33
+ static void curl_multi_mark(ruby_curl_multi *rbcm) {
34
+ rb_gc_mark(rbcm->requests);
35
+ rb_hash_foreach( rbcm->requests, (int (*)())rb_curl_multi_mark_all_easy, (VALUE)rbcm );
36
+ }
37
+
38
+ static void curl_multi_flush_easy(VALUE key, VALUE easy, ruby_curl_multi *rbcm) {
39
+ rb_curl_multi_remove(rbcm, easy);
40
+ }
41
+
42
+ static void curl_multi_free(ruby_curl_multi *rbcm) {
43
+ //printf("hash entries: %d\n", RHASH(rbcm->requests)->tbl->num_entries );
44
+ rb_hash_foreach( rbcm->requests, (int (*)())curl_multi_flush_easy, (VALUE)rbcm );
45
+
46
+ curl_multi_cleanup(rbcm->handle);
47
+ }
48
+
49
+ /*
50
+ * call-seq:
51
+ * Curl::Multi.new => #&lt;Curl::Easy...&gt;
52
+ *
53
+ * Create a new Curl::Multi instance
54
+ */
55
+ static VALUE ruby_curl_multi_new(VALUE self) {
56
+ VALUE new_curlm;
57
+
58
+ ruby_curl_multi *rbcm = ALLOC(ruby_curl_multi);
59
+
60
+ rbcm->handle = curl_multi_init();
61
+
62
+ rbcm->requests = rb_hash_new();
63
+
64
+ rbcm->active = 0;
65
+ rbcm->running = 0;
66
+
67
+ new_curlm = Data_Wrap_Struct(cCurlMulti, curl_multi_mark, curl_multi_free, rbcm);
68
+
69
+ return new_curlm;
70
+ }
71
+
72
+ /*
73
+ * call-seq:
74
+ * multi = Curl::Multi.new
75
+ * easy = Curl::Easy.new('url')
76
+ *
77
+ * multi.add(easy)
78
+ *
79
+ * Add an easy handle to the multi stack
80
+ */
81
+ static VALUE ruby_curl_multi_add(VALUE self, VALUE easy) {
82
+ CURLMcode mcode;
83
+ ruby_curl_easy *rbce;
84
+ ruby_curl_multi *rbcm;
85
+
86
+ Data_Get_Struct(self, ruby_curl_multi, rbcm);
87
+ Data_Get_Struct(easy, ruby_curl_easy, rbce);
88
+
89
+ mcode = curl_multi_add_handle(rbcm->handle, rbce->curl);
90
+ if (mcode != CURLM_CALL_MULTI_PERFORM && mcode != CURLM_OK) {
91
+ raise_curl_multi_error_exception(mcode);
92
+ }
93
+
94
+ /* save a pointer to self */
95
+ rbce->self = easy;
96
+
97
+ /* setup the easy handle */
98
+ ruby_curl_easy_setup( rbce, &(rbce->bodybuf), &(rbce->headerbuf), &(rbce->curl_headers) );
99
+
100
+ rbcm->active++;
101
+ if (mcode == CURLM_CALL_MULTI_PERFORM) {
102
+ curl_multi_perform(rbcm->handle, &(rbcm->running));
103
+ }
104
+
105
+ rb_hash_aset( rbcm->requests, rb_int_new((long)rbce->curl), easy );
106
+ // active should equal INT2FIX(RHASH(rbcm->requests)->tbl->num_entries)
107
+
108
+ if (rbcm->active > rbcm->running) {
109
+ rb_curl_multi_read_info(self, rbcm->handle);
110
+ }
111
+
112
+ return self;
113
+ }
114
+
115
+ /*
116
+ * call-seq:
117
+ * multi = Curl::Multi.new
118
+ * easy = Curl::Easy.new('url')
119
+ *
120
+ * multi.add(easy)
121
+ *
122
+ * # sometime later
123
+ * multi.remove(easy)
124
+ *
125
+ * Remove an easy handle from a multi stack
126
+ *
127
+ * Will raise an exception if the easy handle is not found
128
+ */
129
+ static VALUE ruby_curl_multi_remove(VALUE self, VALUE easy) {
130
+ ruby_curl_multi *rbcm;
131
+
132
+ Data_Get_Struct(self, ruby_curl_multi, rbcm);
133
+
134
+ rb_curl_multi_remove(rbcm,easy);
135
+ // active should equal INT2FIX(RHASH(rbcm->requests)->tbl->num_entries)
136
+
137
+ return self;
138
+ }
139
+ static void rb_curl_multi_remove(ruby_curl_multi *rbcm, VALUE easy) {
140
+ CURLMcode result;
141
+ ruby_curl_easy *rbce;
142
+ Data_Get_Struct(easy, ruby_curl_easy, rbce);
143
+
144
+ rbcm->active--;
145
+
146
+ //printf( "calling rb_curl_multi_remove: 0x%X, active: %d\n", (long)easy, rbcm->active );
147
+
148
+ result = curl_multi_remove_handle(rbcm->handle, rbce->curl);
149
+ if (result != 0) {
150
+ raise_curl_multi_error_exception(result);
151
+ }
152
+
153
+ ruby_curl_easy_cleanup( easy, rbce, rbce->bodybuf, rbce->headerbuf, rbce->curl_headers );
154
+ rbce->headerbuf = Qnil;
155
+ rbce->bodybuf = Qnil;
156
+ rb_hash_delete( rbcm->requests, rb_int_new((long)rbce->curl) );
157
+ }
158
+
159
+ static void rb_curl_multi_read_info(VALUE self, CURLM *multi_handle) {
160
+ int msgs_left, result;
161
+ CURLMsg *msg;
162
+ CURLcode ecode;
163
+ CURL *easy_handle;
164
+ ruby_curl_easy *rbce = NULL;
165
+ // VALUE finished = rb_ary_new();
166
+
167
+ /* check for finished easy handles and remove from the multi handle */
168
+ while ((msg = curl_multi_info_read(multi_handle, &msgs_left))) {
169
+
170
+ if (msg->msg != CURLMSG_DONE) {
171
+ continue;
172
+ }
173
+
174
+ easy_handle = msg->easy_handle;
175
+ result = msg->data.result;
176
+ if (easy_handle) {
177
+ ecode = curl_easy_getinfo(easy_handle, CURLINFO_PRIVATE, &rbce);
178
+ if (ecode != 0) {
179
+ raise_curl_easy_error_exception(ecode);
180
+ }
181
+ //printf( "finished: 0x%X\n", (long)rbce->self );
182
+ //rb_ary_push(finished, rbce->self);
183
+ ruby_curl_multi_remove( self, rbce->self );
184
+
185
+ if (rbce->complete_proc != Qnil) {
186
+ rb_funcall( rbce->complete_proc, idCall, 1, self );
187
+ }
188
+
189
+ long response_code = -1;
190
+ curl_easy_getinfo(rbce->curl, CURLINFO_RESPONSE_CODE, &response_code);
191
+
192
+ if (result != 0) {
193
+ if (rbce->failure_proc != Qnil) {
194
+ rb_funcall( rbce->failure_proc, idCall, 1, rbce->self );
195
+ }
196
+ }
197
+ else if (rbce->success_proc != Qnil &&
198
+ ((response_code >= 200 && response_code < 300) || response_code == 0)) {
199
+ /* NOTE: we allow response_code == 0, in the case the file is being read from disk */
200
+ rb_funcall( rbce->success_proc, idCall, 1, rbce->self );
201
+ }
202
+ else if (rbce->failure_proc != Qnil &&
203
+ (response_code >= 300 && response_code < 600)) {
204
+ rb_funcall( rbce->failure_proc, idCall, 1, rbce->self );
205
+ }
206
+ }
207
+ else {
208
+ //printf( "missing easy handle\n" );
209
+ }
210
+ }
211
+
212
+ /*
213
+ while (RARRAY(finished)->len > 0) {
214
+ //printf( "finished handle\n" );
215
+ ruby_curl_multi_remove( self, rb_ary_pop(finished) );
216
+ }
217
+ */
218
+ }
219
+
220
+ /* called within ruby_curl_multi_perform */
221
+ static void rb_curl_multi_run(VALUE self, CURLM *multi_handle, int *still_running) {
222
+ CURLMcode mcode;
223
+
224
+ do {
225
+ mcode = curl_multi_perform(multi_handle, still_running);
226
+ } while (mcode == CURLM_CALL_MULTI_PERFORM);
227
+
228
+ if (mcode != CURLM_OK) {
229
+ raise_curl_multi_error_exception(mcode);
230
+ }
231
+
232
+ rb_curl_multi_read_info( self, multi_handle );
233
+ }
234
+
235
+ /*
236
+ * call-seq:
237
+ * multi = Curl::Multi.new
238
+ * easy1 = Curl::Easy.new('url')
239
+ * easy2 = Curl::Easy.new('url')
240
+ *
241
+ * multi.add(easy1)
242
+ * multi.add(easy2)
243
+ *
244
+ * multi.perform do
245
+ * # while idle other code my execute here
246
+ * end
247
+ *
248
+ * Run multi handles, looping selecting when data can be transfered
249
+ */
250
+ static VALUE ruby_curl_multi_perform(VALUE self) {
251
+ CURLMcode mcode;
252
+ ruby_curl_multi *rbcm;
253
+ int maxfd, rc;
254
+ fd_set fdread, fdwrite, fdexcep;
255
+
256
+ long timeout;
257
+ struct timeval tv = {0, 0};
258
+
259
+ Data_Get_Struct(self, ruby_curl_multi, rbcm);
260
+ //rb_gc_mark(self);
261
+
262
+ rb_curl_multi_run( self, rbcm->handle, &(rbcm->running) );
263
+
264
+ while(rbcm->running) {
265
+ FD_ZERO(&fdread);
266
+ FD_ZERO(&fdwrite);
267
+ FD_ZERO(&fdexcep);
268
+
269
+ /* load the fd sets from the multi handle */
270
+ mcode = curl_multi_fdset(rbcm->handle, &fdread, &fdwrite, &fdexcep, &maxfd);
271
+ if (mcode != CURLM_OK) {
272
+ raise_curl_multi_error_exception(mcode);
273
+ }
274
+
275
+ /* get the curl suggested time out */
276
+ mcode = curl_multi_timeout(rbcm->handle, &timeout);
277
+ if (mcode != CURLM_OK) {
278
+ raise_curl_multi_error_exception(mcode);
279
+ }
280
+
281
+ if (timeout == 0) { /* no delay */
282
+ rb_curl_multi_run( self, rbcm->handle, &(rbcm->running) );
283
+ continue;
284
+ }
285
+ else if (timeout == -1) {
286
+ timeout = 1; /* You must not wait too long
287
+ (more than a few seconds perhaps) before
288
+ you call curl_multi_perform() again */
289
+ }
290
+
291
+ if (rb_block_given_p()) {
292
+ rb_yield(self);
293
+ }
294
+
295
+ tv.tv_sec = timeout / 1000;
296
+ tv.tv_usec = (timeout * 1000) % 1000000;
297
+
298
+ rc = rb_thread_select(maxfd+1, &fdread, &fdwrite, &fdexcep, &tv);
299
+ if (rc < 0) {
300
+ rb_raise(rb_eRuntimeError, "select(): %s", strerror(errno));
301
+ }
302
+
303
+ rb_curl_multi_run( self, rbcm->handle, &(rbcm->running) );
304
+
305
+ }
306
+
307
+ return Qnil;
308
+ }
309
+
310
+ /* =================== INIT LIB =====================*/
311
+ void init_curb_multi() {
312
+ idCall = rb_intern("call");
313
+
314
+ cCurlMulti = rb_define_class_under(mCurl, "Multi", rb_cObject);
315
+
316
+ /* Class methods */
317
+ rb_define_singleton_method(cCurlMulti, "new", ruby_curl_multi_new, -1);
318
+
319
+ /* Instnace methods */
320
+ rb_define_method(cCurlMulti, "add", ruby_curl_multi_add, 1);
321
+ rb_define_method(cCurlMulti, "remove", ruby_curl_multi_remove, 1);
322
+ rb_define_method(cCurlMulti, "perform", ruby_curl_multi_perform, 0);
323
+ }
data/ext/curb_multi.h ADDED
@@ -0,0 +1,25 @@
1
+ /* curb_multi.h - Curl easy mode
2
+ * Copyright (c)2008 Todd A. Fisher.
3
+ * Licensed under the Ruby License. See LICENSE for details.
4
+ *
5
+ * $Id$
6
+ */
7
+ #ifndef __CURB_MULTI_H
8
+ #define __CURB_MULTI_H
9
+
10
+ #include "curb.h"
11
+ #include "curb_easy.h"
12
+ #include <curl/multi.h>
13
+
14
+ typedef struct {
15
+ int active;
16
+ int running;
17
+ VALUE requests; /* hash of handles currently added */
18
+ CURLM *handle;
19
+ } ruby_curl_multi;
20
+
21
+ extern VALUE cCurlMulti;
22
+ void init_curb_multi();
23
+
24
+
25
+ #endif