curb 0.1.4 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of curb might be problematic. Click here for more details.

@@ -24,8 +24,8 @@ typedef struct {
24
24
 
25
25
  VALUE body_proc;
26
26
  VALUE header_proc;
27
- VALUE body_data; /* These have the result from the last curl_easy_perform */
28
- VALUE header_data; /* unless a block is supplied (when they'll be nil) */
27
+ VALUE body_data; /* Holds the response body from the last call to curl_easy_perform */
28
+ VALUE header_data; /* unless a block is supplied (they'll be nil) */
29
29
  VALUE progress_proc;
30
30
  VALUE debug_proc;
31
31
  VALUE interface;
@@ -33,7 +33,13 @@ typedef struct {
33
33
  VALUE proxypwd;
34
34
  VALUE headers; /* ruby array of strings with headers to set */
35
35
  VALUE cookiejar; /* filename */
36
-
36
+ VALUE cert;
37
+ VALUE encoding;
38
+
39
+ VALUE success_proc;
40
+ VALUE failure_proc;
41
+ VALUE complete_proc;
42
+
37
43
  /* Other opts */
38
44
  unsigned short local_port; // 0 is no port
39
45
  unsigned short local_port_range; // " " " "
@@ -65,10 +71,24 @@ typedef struct {
65
71
  * and in case it's asked for before the next call.
66
72
  */
67
73
  VALUE postdata_buffer;
74
+
75
+ /* when added to a multi handle these buffers are needed
76
+ * when the easy handle isn't supplied the body proc
77
+ * or a custom http header is passed.
78
+ */
79
+ VALUE bodybuf;
80
+ VALUE headerbuf;
81
+ struct curl_slist *curl_headers;
82
+
83
+ VALUE self; /* pointer to self, used by multi interface */
84
+
68
85
  } ruby_curl_easy;
69
86
 
70
87
  extern VALUE cCurlEasy;
71
88
 
89
+ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce, VALUE *bodybuf, VALUE *headerbuf, struct curl_slist **headers);
90
+ VALUE ruby_curl_easy_cleanup(VALUE self, ruby_curl_easy *rbce, VALUE bodybuf, VALUE headerbuf, struct curl_slist *headers);
91
+
72
92
  void init_curb_easy();
73
93
 
74
94
  #endif
@@ -98,9 +98,19 @@ VALUE eCurlErrTFTPUnknownID;
98
98
  VALUE eCurlErrTFTPFileExists;
99
99
  VALUE eCurlErrTFTPNoSuchUser;
100
100
 
101
+ /* multi errors */
102
+ VALUE mCurlErrCallMultiPerform;
103
+ VALUE mCurlErrBadHandle;
104
+ VALUE mCurlErrBadEasyHandle;
105
+ VALUE mCurlErrOutOfMemory;
106
+ VALUE mCurlErrInternalError;
107
+ VALUE mCurlErrBadSocket;
108
+ VALUE mCurlErrUnknownOption;
109
+
101
110
  /* binding errors */
102
111
  VALUE eCurlErrInvalidPostField;
103
112
 
113
+
104
114
  /* rb_raise an approriate exception for the supplied CURLcode */
105
115
  void raise_curl_easy_error_exception(CURLcode code) {
106
116
  VALUE exclz;
@@ -299,48 +309,54 @@ void raise_curl_easy_error_exception(CURLcode code) {
299
309
  case CURLE_FTP_SSL_FAILED: /* 64 - Requested FTP SSL level failed */
300
310
  exclz = eCurlErrFTPSSLFailed;
301
311
  break;
312
+ #ifdef HAVE_CURLE_SEND_FAIL_REWIND
302
313
  case CURLE_SEND_FAIL_REWIND: /* 65 - Sending the data requires a rewind that failed */
303
314
  exclz = eCurlErrSendFailedRewind;
304
- break;
315
+ break;
316
+ #endif
317
+ #ifdef HAVE_CURLE_SSL_ENGINE_INITFAILED
305
318
  case CURLE_SSL_ENGINE_INITFAILED: /* 66 - failed to initialise ENGINE */
306
319
  exclz = eCurlErrSSLEngineInitFailed;
307
320
  break;
321
+ #endif
322
+ #ifdef HAVE_CURLE_LOGIN_DENIED
308
323
  case CURLE_LOGIN_DENIED: /* 67 - user, password or similar was not accepted and we failed to login */
309
324
  exclz = eCurlErrLoginDenied;
310
325
  break;
326
+ #endif
311
327
 
312
328
  // recent additions, may not be present in all supported versions
313
- #ifdef CURLE_TFTP_NOTFOUND
329
+ #ifdef HAVE_CURLE_TFTP_NOTFOUND
314
330
  case CURLE_TFTP_NOTFOUND: /* 68 - file not found on server */
315
331
  exclz = eCurlErrTFTPNotFound;
316
332
  break;
317
333
  #endif
318
- #ifdef CURLE_TFTP_PERM
334
+ #ifdef HAVE_CURLE_TFTP_PERM
319
335
  case CURLE_TFTP_PERM: /* 69 - permission problem on server */
320
336
  exclz = eCurlErrTFTPPermission;
321
337
  break;
322
338
  #endif
323
- #ifdef CURLE_TFTP_DISKFULL
339
+ #ifdef HAVE_CURLE_TFTP_DISKFULL
324
340
  case CURLE_TFTP_DISKFULL: /* 70 - out of disk space on server */
325
341
  exclz = eCurlErrTFTPDiskFull;
326
342
  break;
327
343
  #endif
328
- #ifdef CURLE_TFTP_ILLEGAL
344
+ #ifdef HAVE_CURLE_TFTP_ILLEGAL
329
345
  case CURLE_TFTP_ILLEGAL: /* 71 - Illegal TFTP operation */
330
346
  exclz = eCurlErrTFTPIllegalOperation;
331
347
  break;
332
348
  #endif
333
- #ifdef CURLE_TFTP_UNKNOWNID
349
+ #ifdef HAVE_CURLE_TFTP_UNKNOWNID
334
350
  case CURLE_TFTP_UNKNOWNID: /* 72 - Unknown transfer ID */
335
351
  exclz = eCurlErrTFTPUnknownID;
336
352
  break;
337
353
  #endif
338
- #ifdef CURLE_TFTP_EXISTS
354
+ #ifdef HAVE_CURLE_TFTP_EXISTS
339
355
  case CURLE_TFTP_EXISTS: /* 73 - File already exists */
340
356
  exclz = eCurlErrTFTPFileExists;
341
357
  break;
342
358
  #endif
343
- #ifdef CURLE_TFTP_NOSUCHUSER
359
+ #ifdef HAVE_CURLE_TFTP_NOSUCHUSER
344
360
  case CURLE_TFTP_NOSUCHUSER: /* 74 - No such user */
345
361
  exclz = eCurlErrTFTPNotFound;
346
362
  break;
@@ -356,6 +372,43 @@ void raise_curl_easy_error_exception(CURLcode code) {
356
372
 
357
373
  rb_raise(exclz, exmsg);
358
374
  }
375
+ void raise_curl_multi_error_exception(CURLMcode code) {
376
+ VALUE exclz;
377
+ const char *exmsg = NULL;
378
+
379
+ switch(code) {
380
+ case CURLM_CALL_MULTI_PERFORM: /* -1 */
381
+ exclz = mCurlErrCallMultiPerform;
382
+ break;
383
+ case CURLM_BAD_HANDLE: /* 1 */
384
+ exclz = mCurlErrBadHandle;
385
+ break;
386
+ case CURLM_BAD_EASY_HANDLE: /* 2 */
387
+ exclz = mCurlErrBadEasyHandle;
388
+ break;
389
+ case CURLM_OUT_OF_MEMORY: /* 3 */
390
+ exclz = mCurlErrOutOfMemory;
391
+ break;
392
+ case CURLM_INTERNAL_ERROR: /* 4 */
393
+ exclz = mCurlErrInternalError;
394
+ break;
395
+ case CURLM_BAD_SOCKET: /* 5 */
396
+ exclz = mCurlErrBadSocket;
397
+ break;
398
+ case CURLM_UNKNOWN_OPTION: /* 6 */
399
+ exclz = mCurlErrUnknownOption;
400
+ break;
401
+ default:
402
+ exclz = eCurlErrError;
403
+ exmsg = "Unknown error result from libcurl";
404
+ }
405
+
406
+ if (!exmsg) {
407
+ exmsg = curl_multi_strerror(code);
408
+ }
409
+
410
+ rb_raise(exclz, exmsg);
411
+ }
359
412
 
360
413
  void init_curb_errors() {
361
414
  mCurlErr = rb_define_module_under(mCurl, "Err");
@@ -12,6 +12,7 @@
12
12
  /* base errors */
13
13
  extern VALUE cCurlErr;
14
14
 
15
+ /* easy errors */
15
16
  extern VALUE mCurlErr;
16
17
  extern VALUE eCurlErrError;
17
18
  extern VALUE eCurlErrFTPError;
@@ -97,10 +98,20 @@ extern VALUE eCurlErrTFTPUnknownID;
97
98
  extern VALUE eCurlErrTFTPFileExists;
98
99
  extern VALUE eCurlErrTFTPNoSuchUser;
99
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
+
100
110
  /* binding errors */
101
111
  extern VALUE eCurlErrInvalidPostField;
102
112
 
103
113
  void init_curb_errors();
104
114
  void raise_curl_easy_error_exception(CURLcode code);
115
+ void raise_curl_multi_error_exception(CURLMcode code);
105
116
 
106
117
  #endif
@@ -0,0 +1,353 @@
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
+
8
+ #include "curb_config.h"
9
+ #ifdef HAVE_RUBY19_ST_H
10
+ #include <ruby/st.h>
11
+ #else
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
+ extern VALUE mCurl;
22
+ static VALUE idCall;
23
+
24
+ #ifdef RDOC_NEVER_DEFINED
25
+ mCurl = rb_define_module("Curl");
26
+ #endif
27
+
28
+ VALUE cCurlMulti;
29
+
30
+ static VALUE ruby_curl_multi_remove(VALUE , VALUE );
31
+ static void rb_curl_multi_remove(ruby_curl_multi *rbcm, VALUE easy);
32
+ static void rb_curl_multi_read_info(VALUE self, CURLM *mptr);
33
+
34
+ static void rb_curl_multi_mark_all_easy(VALUE key, VALUE rbeasy, ruby_curl_multi *rbcm) {
35
+ //printf( "mark easy: 0x%X\n", (long)rbeasy );
36
+ rb_gc_mark(rbeasy);
37
+ }
38
+
39
+ static void curl_multi_mark(ruby_curl_multi *rbcm) {
40
+ rb_gc_mark(rbcm->requests);
41
+ rb_hash_foreach( rbcm->requests, (int (*)())rb_curl_multi_mark_all_easy, (VALUE)rbcm );
42
+ }
43
+
44
+ static void curl_multi_flush_easy(VALUE key, VALUE easy, ruby_curl_multi *rbcm) {
45
+ rb_curl_multi_remove(rbcm, easy);
46
+ }
47
+
48
+ static void curl_multi_free(ruby_curl_multi *rbcm) {
49
+ //printf("hash entries: %d\n", RHASH(rbcm->requests)->tbl->num_entries );
50
+ if (rbcm && RHASH_LEN(rbcm->requests) > 0) {
51
+ rb_hash_foreach( rbcm->requests, (int (*)())curl_multi_flush_easy, (VALUE)rbcm );
52
+
53
+ curl_multi_cleanup(rbcm->handle);
54
+ //rb_hash_clear(rbcm->requests)
55
+ rbcm->requests = Qnil;
56
+ }
57
+ }
58
+
59
+ /*
60
+ * call-seq:
61
+ * Curl::Multi.new => #&lt;Curl::Easy...&gt;
62
+ *
63
+ * Create a new Curl::Multi instance
64
+ */
65
+ static VALUE ruby_curl_multi_new(VALUE self) {
66
+ VALUE new_curlm;
67
+
68
+ ruby_curl_multi *rbcm = ALLOC(ruby_curl_multi);
69
+
70
+ rbcm->handle = curl_multi_init();
71
+
72
+ rbcm->requests = rb_hash_new();
73
+
74
+ rbcm->active = 0;
75
+ rbcm->running = 0;
76
+
77
+ new_curlm = Data_Wrap_Struct(cCurlMulti, curl_multi_mark, curl_multi_free, rbcm);
78
+
79
+ return new_curlm;
80
+ }
81
+
82
+ /*
83
+ * call-seq:
84
+ * multi = Curl::Multi.new
85
+ * multi.max_connects = 800
86
+ *
87
+ * Set the max connections in the cache for a multi handle
88
+ */
89
+ static VALUE ruby_curl_multi_max_connects(VALUE self, VALUE count) {
90
+ ruby_curl_multi *rbcm;
91
+
92
+ Data_Get_Struct(self, ruby_curl_multi, rbcm);
93
+ curl_multi_setopt(rbcm->handle, CURLMOPT_MAXCONNECTS, NUM2INT(count));
94
+
95
+ return self;
96
+ }
97
+
98
+ /*
99
+ * call-seq:
100
+ * multi = Curl::Multi.new
101
+ * easy = Curl::Easy.new('url')
102
+ *
103
+ * multi.add(easy)
104
+ *
105
+ * Add an easy handle to the multi stack
106
+ */
107
+ static VALUE ruby_curl_multi_add(VALUE self, VALUE easy) {
108
+ CURLMcode mcode;
109
+ ruby_curl_easy *rbce;
110
+ ruby_curl_multi *rbcm;
111
+
112
+ Data_Get_Struct(self, ruby_curl_multi, rbcm);
113
+ Data_Get_Struct(easy, ruby_curl_easy, rbce);
114
+
115
+ mcode = curl_multi_add_handle(rbcm->handle, rbce->curl);
116
+ if (mcode != CURLM_CALL_MULTI_PERFORM && mcode != CURLM_OK) {
117
+ raise_curl_multi_error_exception(mcode);
118
+ }
119
+
120
+ /* save a pointer to self */
121
+ rbce->self = easy;
122
+
123
+ /* setup the easy handle */
124
+ ruby_curl_easy_setup( rbce, &(rbce->bodybuf), &(rbce->headerbuf), &(rbce->curl_headers) );
125
+
126
+ rbcm->active++;
127
+ if (mcode == CURLM_CALL_MULTI_PERFORM) {
128
+ curl_multi_perform(rbcm->handle, &(rbcm->running));
129
+ }
130
+
131
+ rb_hash_aset( rbcm->requests, rb_int_new((long)rbce->curl), easy );
132
+ // active should equal INT2FIX(RHASH(rbcm->requests)->tbl->num_entries)
133
+
134
+ if (rbcm->active > rbcm->running) {
135
+ rb_curl_multi_read_info(self, rbcm->handle);
136
+ }
137
+
138
+ return self;
139
+ }
140
+
141
+ /*
142
+ * call-seq:
143
+ * multi = Curl::Multi.new
144
+ * easy = Curl::Easy.new('url')
145
+ *
146
+ * multi.add(easy)
147
+ *
148
+ * # sometime later
149
+ * multi.remove(easy)
150
+ *
151
+ * Remove an easy handle from a multi stack
152
+ *
153
+ * Will raise an exception if the easy handle is not found
154
+ */
155
+ static VALUE ruby_curl_multi_remove(VALUE self, VALUE easy) {
156
+ ruby_curl_multi *rbcm;
157
+
158
+ Data_Get_Struct(self, ruby_curl_multi, rbcm);
159
+
160
+ ruby_curl_easy *rbce;
161
+ Data_Get_Struct(easy, ruby_curl_easy, rbce);
162
+
163
+ rb_curl_multi_remove(rbcm,easy);
164
+ // active should equal INT2FIX(RHASH(rbcm->requests)->tbl->num_entries)
165
+ rb_hash_delete( rbcm->requests, rb_int_new((long)rbce->curl) );
166
+
167
+ return self;
168
+ }
169
+ static void rb_curl_multi_remove(ruby_curl_multi *rbcm, VALUE easy) {
170
+ CURLMcode result;
171
+ ruby_curl_easy *rbce;
172
+ Data_Get_Struct(easy, ruby_curl_easy, rbce);
173
+
174
+ rbcm->active--;
175
+
176
+ //printf( "calling rb_curl_multi_remove: 0x%X, active: %d\n", (long)easy, rbcm->active );
177
+
178
+ result = curl_multi_remove_handle(rbcm->handle, rbce->curl);
179
+ if (result != 0) {
180
+ raise_curl_multi_error_exception(result);
181
+ }
182
+
183
+ ruby_curl_easy_cleanup( easy, rbce, rbce->bodybuf, rbce->headerbuf, rbce->curl_headers );
184
+ rbce->headerbuf = Qnil;
185
+ rbce->bodybuf = Qnil;
186
+ }
187
+
188
+ static void rb_curl_multi_read_info(VALUE self, CURLM *multi_handle) {
189
+ int msgs_left, result;
190
+ CURLMsg *msg;
191
+ CURLcode ecode;
192
+ CURL *easy_handle;
193
+ ruby_curl_easy *rbce = NULL;
194
+ // VALUE finished = rb_ary_new();
195
+
196
+ /* check for finished easy handles and remove from the multi handle */
197
+ while ((msg = curl_multi_info_read(multi_handle, &msgs_left))) {
198
+
199
+ if (msg->msg != CURLMSG_DONE) {
200
+ continue;
201
+ }
202
+
203
+ easy_handle = msg->easy_handle;
204
+ result = msg->data.result;
205
+ if (easy_handle) {
206
+ ecode = curl_easy_getinfo(easy_handle, CURLINFO_PRIVATE, (char**)&rbce);
207
+ if (ecode != 0) {
208
+ raise_curl_easy_error_exception(ecode);
209
+ }
210
+ //printf( "finished: 0x%X\n", (long)rbce->self );
211
+ //rb_ary_push(finished, rbce->self);
212
+ ruby_curl_multi_remove( self, rbce->self );
213
+
214
+ if (rbce->complete_proc != Qnil) {
215
+ rb_funcall( rbce->complete_proc, idCall, 1, self );
216
+ }
217
+
218
+ long response_code = -1;
219
+ curl_easy_getinfo(rbce->curl, CURLINFO_RESPONSE_CODE, &response_code);
220
+
221
+ if (result != 0) {
222
+ if (rbce->failure_proc != Qnil) {
223
+ rb_funcall( rbce->failure_proc, idCall, 1, rbce->self );
224
+ }
225
+ }
226
+ else if (rbce->success_proc != Qnil &&
227
+ ((response_code >= 200 && response_code < 300) || response_code == 0)) {
228
+ /* NOTE: we allow response_code == 0, in the case the file is being read from disk */
229
+ rb_funcall( rbce->success_proc, idCall, 1, rbce->self );
230
+ }
231
+ else if (rbce->failure_proc != Qnil &&
232
+ (response_code >= 300 && response_code <= 999)) {
233
+ rb_funcall( rbce->failure_proc, idCall, 1, rbce->self );
234
+ }
235
+ }
236
+ else {
237
+ //printf( "missing easy handle\n" );
238
+ }
239
+ }
240
+
241
+ /*
242
+ while (RARRAY(finished)->len > 0) {
243
+ //printf( "finished handle\n" );
244
+ ruby_curl_multi_remove( self, rb_ary_pop(finished) );
245
+ }
246
+ */
247
+ }
248
+
249
+ /* called within ruby_curl_multi_perform */
250
+ static void rb_curl_multi_run(VALUE self, CURLM *multi_handle, int *still_running) {
251
+ CURLMcode mcode;
252
+
253
+ do {
254
+ mcode = curl_multi_perform(multi_handle, still_running);
255
+ } while (mcode == CURLM_CALL_MULTI_PERFORM);
256
+
257
+ if (mcode != CURLM_OK) {
258
+ raise_curl_multi_error_exception(mcode);
259
+ }
260
+
261
+ rb_curl_multi_read_info( self, multi_handle );
262
+ }
263
+
264
+ /*
265
+ * call-seq:
266
+ * multi = Curl::Multi.new
267
+ * easy1 = Curl::Easy.new('url')
268
+ * easy2 = Curl::Easy.new('url')
269
+ *
270
+ * multi.add(easy1)
271
+ * multi.add(easy2)
272
+ *
273
+ * multi.perform do
274
+ * # while idle other code my execute here
275
+ * end
276
+ *
277
+ * Run multi handles, looping selecting when data can be transfered
278
+ */
279
+ static VALUE ruby_curl_multi_perform(VALUE self) {
280
+ CURLMcode mcode;
281
+ ruby_curl_multi *rbcm;
282
+ int maxfd, rc;
283
+ fd_set fdread, fdwrite, fdexcep;
284
+
285
+ long timeout;
286
+ struct timeval tv = {0, 0};
287
+
288
+ Data_Get_Struct(self, ruby_curl_multi, rbcm);
289
+ //rb_gc_mark(self);
290
+
291
+ rb_curl_multi_run( self, rbcm->handle, &(rbcm->running) );
292
+
293
+ while(rbcm->running) {
294
+ FD_ZERO(&fdread);
295
+ FD_ZERO(&fdwrite);
296
+ FD_ZERO(&fdexcep);
297
+
298
+ /* load the fd sets from the multi handle */
299
+ mcode = curl_multi_fdset(rbcm->handle, &fdread, &fdwrite, &fdexcep, &maxfd);
300
+ if (mcode != CURLM_OK) {
301
+ raise_curl_multi_error_exception(mcode);
302
+ }
303
+
304
+ /* get the curl suggested time out */
305
+ mcode = curl_multi_timeout(rbcm->handle, &timeout);
306
+ if (mcode != CURLM_OK) {
307
+ raise_curl_multi_error_exception(mcode);
308
+ }
309
+
310
+ if (timeout == 0) { /* no delay */
311
+ rb_curl_multi_run( self, rbcm->handle, &(rbcm->running) );
312
+ continue;
313
+ }
314
+ else if (timeout == -1) {
315
+ timeout = 1; /* You must not wait too long
316
+ (more than a few seconds perhaps) before
317
+ you call curl_multi_perform() again */
318
+ }
319
+
320
+ if (rb_block_given_p()) {
321
+ rb_yield(self);
322
+ }
323
+
324
+ tv.tv_sec = timeout / 1000;
325
+ tv.tv_usec = (timeout * 1000) % 1000000;
326
+
327
+ rc = rb_thread_select(maxfd+1, &fdread, &fdwrite, &fdexcep, &tv);
328
+ if (rc < 0) {
329
+ rb_raise(rb_eRuntimeError, "select(): %s", strerror(errno));
330
+ }
331
+
332
+ rb_curl_multi_run( self, rbcm->handle, &(rbcm->running) );
333
+
334
+ }
335
+
336
+ return Qnil;
337
+ }
338
+
339
+ /* =================== INIT LIB =====================*/
340
+ void init_curb_multi() {
341
+ idCall = rb_intern("call");
342
+
343
+ cCurlMulti = rb_define_class_under(mCurl, "Multi", rb_cObject);
344
+
345
+ /* Class methods */
346
+ rb_define_singleton_method(cCurlMulti, "new", ruby_curl_multi_new, -1);
347
+
348
+ /* Instnace methods */
349
+ rb_define_method(cCurlMulti, "max_connects=", ruby_curl_multi_max_connects, 1);
350
+ rb_define_method(cCurlMulti, "add", ruby_curl_multi_add, 1);
351
+ rb_define_method(cCurlMulti, "remove", ruby_curl_multi_remove, 1);
352
+ rb_define_method(cCurlMulti, "perform", ruby_curl_multi_perform, 0);
353
+ }