taf2-curb 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
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