ghazel-curb 0.5.9.0

Sign up to get free protection for your applications and to get access to all the features.
data/ext/curb_multi.c ADDED
@@ -0,0 +1,487 @@
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
+
7
+ #include "curb_config.h"
8
+ #ifdef HAVE_RUBY19_ST_H
9
+ #include <ruby.h>
10
+ #include <ruby/st.h>
11
+ #else
12
+ #include <ruby.h>
13
+ #include <st.h>
14
+ #endif
15
+ #include "curb_easy.h"
16
+ #include "curb_errors.h"
17
+ #include "curb_postfield.h"
18
+ #include "curb_multi.h"
19
+
20
+ #include <errno.h>
21
+
22
+ extern VALUE mCurl;
23
+ static VALUE idCall;
24
+
25
+ #ifdef RDOC_NEVER_DEFINED
26
+ mCurl = rb_define_module("Curl");
27
+ #endif
28
+
29
+ VALUE cCurlMulti;
30
+
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
+ CURLMcode result;
47
+ ruby_curl_easy *rbce;
48
+ VALUE r;
49
+ Data_Get_Struct(easy, ruby_curl_easy, rbce);
50
+ result = curl_multi_remove_handle(rbcm->handle, rbce->curl);
51
+ if (result != 0) {
52
+ raise_curl_multi_error_exception(result);
53
+ }
54
+ // XXX: easy handle may not be finished yet... so don't clean it GC pass will get it next time
55
+ r = rb_hash_delete( rbcm->requests, easy );
56
+ if( r != easy || r == Qnil ) {
57
+ rb_raise(rb_eRuntimeError, "Critical:: Unable to remove easy from requests");
58
+ }
59
+ }
60
+
61
+ static int
62
+ rb_hash_clear_i(VALUE key, VALUE value, VALUE dummy) {
63
+ return ST_DELETE;
64
+ }
65
+
66
+ static void curl_multi_free(ruby_curl_multi *rbcm) {
67
+
68
+ //printf("hash entries: %d\n", RHASH(rbcm->requests)->tbl->num_entries );
69
+ if (rbcm && !rbcm->requests == Qnil && rb_type(rbcm->requests) == T_HASH && RHASH_LEN(rbcm->requests) > 0) {
70
+
71
+ rb_hash_foreach( rbcm->requests, (int (*)())curl_multi_flush_easy, (VALUE)rbcm );
72
+
73
+ rb_hash_foreach(rbcm->requests, rb_hash_clear_i, 0); //rb_hash_clear(rbcm->requests);
74
+ rbcm->requests = Qnil;
75
+ }
76
+ curl_multi_cleanup(rbcm->handle);
77
+ free(rbcm);
78
+ }
79
+
80
+ /*
81
+ * call-seq:
82
+ * Curl::Multi.new => #&lt;Curl::Easy...&gt;
83
+ *
84
+ * Create a new Curl::Multi instance
85
+ */
86
+ VALUE ruby_curl_multi_new(VALUE klass) {
87
+ VALUE new_curlm;
88
+
89
+ ruby_curl_multi *rbcm = ALLOC(ruby_curl_multi);
90
+
91
+ rbcm->handle = curl_multi_init();
92
+
93
+ rbcm->requests = rb_hash_new();
94
+
95
+ rbcm->active = 0;
96
+ rbcm->running = 0;
97
+
98
+ new_curlm = Data_Wrap_Struct(klass, curl_multi_mark, curl_multi_free, rbcm);
99
+
100
+ return new_curlm;
101
+ }
102
+
103
+ // Hash#foreach callback for ruby_curl_multi_requests
104
+ static int ruby_curl_multi_requests_callback(VALUE key, VALUE value, VALUE result_array) {
105
+ rb_ary_push(result_array, value);
106
+
107
+ return ST_CONTINUE;
108
+ }
109
+
110
+ /*
111
+ * call-seq:
112
+ * multi.requests => [#&lt;Curl::Easy...&gt;, ...]
113
+ *
114
+ * Returns an array containing all the active requests on this Curl::Multi object.
115
+ */
116
+ static VALUE ruby_curl_multi_requests(VALUE self) {
117
+ ruby_curl_multi *rbcm;
118
+ VALUE result_array;
119
+
120
+ Data_Get_Struct(self, ruby_curl_multi, rbcm);
121
+
122
+ result_array = rb_ary_new();
123
+
124
+ // iterate over the requests hash, and stuff references into the array.
125
+ rb_hash_foreach( rbcm->requests, ruby_curl_multi_requests_callback, result_array );
126
+
127
+ return result_array;
128
+ }
129
+
130
+ /*
131
+ * call-seq:
132
+ * multi.idle? => true or false
133
+ *
134
+ * Returns whether or not this Curl::Multi handle is processing any requests. E.g. this returns
135
+ * true when multi.requests.length == 0.
136
+ */
137
+ static VALUE ruby_curl_multi_idle(VALUE self) {
138
+ ruby_curl_multi *rbcm;
139
+
140
+ Data_Get_Struct(self, ruby_curl_multi, rbcm);
141
+
142
+ if ( FIX2INT( rb_funcall(rbcm->requests, rb_intern("length"), 0) ) == 0 ) {
143
+ return Qtrue;
144
+ } else {
145
+ return Qfalse;
146
+ }
147
+ }
148
+
149
+ /*
150
+ * call-seq:
151
+ * multi = Curl::Multi.new
152
+ * multi.max_connects = 800
153
+ *
154
+ * Set the max connections in the cache for a multi handle
155
+ */
156
+ static VALUE ruby_curl_multi_max_connects(VALUE self, VALUE count) {
157
+ #ifdef HAVE_CURLMOPT_MAXCONNECTS
158
+ ruby_curl_multi *rbcm;
159
+
160
+ Data_Get_Struct(self, ruby_curl_multi, rbcm);
161
+ curl_multi_setopt(rbcm->handle, CURLMOPT_MAXCONNECTS, NUM2INT(count));
162
+ #endif
163
+
164
+ return count;
165
+ }
166
+
167
+ /*
168
+ * call-seq:
169
+ * multi = Curl::Multi.new
170
+ * multi.pipeline = true
171
+ *
172
+ * Pass a long set to 1 to enable or 0 to disable. Enabling pipelining on a multi handle will make it
173
+ * attempt to perform HTTP Pipelining as far as possible for transfers using this handle. This means
174
+ * that if you add a second request that can use an already existing connection, the second request will
175
+ * be "piped" on the same connection rather than being executed in parallel. (Added in 7.16.0)
176
+ *
177
+ */
178
+ static VALUE ruby_curl_multi_pipeline(VALUE self, VALUE onoff) {
179
+ #ifdef HAVE_CURLMOPT_PIPELINING
180
+ ruby_curl_multi *rbcm;
181
+
182
+ Data_Get_Struct(self, ruby_curl_multi, rbcm);
183
+ curl_multi_setopt(rbcm->handle, CURLMOPT_PIPELINING, onoff == Qtrue ? 1 : 0);
184
+ #endif
185
+ return onoff;
186
+ }
187
+
188
+ /*
189
+ * call-seq:
190
+ * multi = Curl::Multi.new
191
+ * easy = Curl::Easy.new('url')
192
+ *
193
+ * multi.add(easy)
194
+ *
195
+ * Add an easy handle to the multi stack
196
+ */
197
+ VALUE ruby_curl_multi_add(VALUE self, VALUE easy) {
198
+ CURLMcode mcode;
199
+ ruby_curl_easy *rbce;
200
+ ruby_curl_multi *rbcm;
201
+
202
+ Data_Get_Struct(self, ruby_curl_multi, rbcm);
203
+ Data_Get_Struct(easy, ruby_curl_easy, rbce);
204
+
205
+ mcode = curl_multi_add_handle(rbcm->handle, rbce->curl);
206
+ if (mcode != CURLM_CALL_MULTI_PERFORM && mcode != CURLM_OK) {
207
+ raise_curl_multi_error_exception(mcode);
208
+ }
209
+
210
+ /* save a pointer to self */
211
+ rbce->self = easy;
212
+
213
+ /* setup the easy handle */
214
+ ruby_curl_easy_setup( rbce, &(rbce->bodybuf), &(rbce->headerbuf), &(rbce->curl_headers) );
215
+
216
+ rbcm->active++;
217
+
218
+ /* Increase the running count, so that the perform loop keeps running.
219
+ * If this number is not correct, the next call to curl_multi_perform will correct it. */
220
+ rbcm->running++;
221
+
222
+ rb_hash_aset( rbcm->requests, easy, easy );
223
+
224
+ return self;
225
+ }
226
+
227
+ /*
228
+ * call-seq:
229
+ * multi = Curl::Multi.new
230
+ * easy = Curl::Easy.new('url')
231
+ *
232
+ * multi.add(easy)
233
+ *
234
+ * # sometime later
235
+ * multi.remove(easy)
236
+ *
237
+ * Remove an easy handle from a multi stack.
238
+ *
239
+ * Will raise an exception if the easy handle is not found
240
+ */
241
+ VALUE ruby_curl_multi_remove(VALUE self, VALUE easy) {
242
+ ruby_curl_multi *rbcm;
243
+ ruby_curl_easy *rbce;
244
+
245
+ Data_Get_Struct(self, ruby_curl_multi, rbcm);
246
+
247
+ Data_Get_Struct(easy, ruby_curl_easy, rbce);
248
+
249
+ rb_curl_multi_remove(rbcm,easy);
250
+
251
+ return self;
252
+ }
253
+ static void rb_curl_multi_remove(ruby_curl_multi *rbcm, VALUE easy) {
254
+ CURLMcode result;
255
+ ruby_curl_easy *rbce;
256
+ VALUE r;
257
+
258
+ Data_Get_Struct(easy, ruby_curl_easy, rbce);
259
+
260
+ result = curl_multi_remove_handle(rbcm->handle, rbce->curl);
261
+ if (result != 0) {
262
+ raise_curl_multi_error_exception(result);
263
+ }
264
+
265
+ rbcm->active--;
266
+
267
+ ruby_curl_easy_cleanup( easy, rbce, rbce->bodybuf, rbce->headerbuf, rbce->curl_headers );
268
+ rbce->headerbuf = Qnil;
269
+ rbce->bodybuf = Qnil;
270
+
271
+ // active should equal INT2FIX(RHASH(rbcm->requests)->tbl->num_entries)
272
+ r = rb_hash_delete( rbcm->requests, easy );
273
+ if( r != easy || r == Qnil ) {
274
+ rb_raise(rb_eRuntimeError, "Critical:: Unable to remove easy from requests");
275
+ }
276
+ }
277
+
278
+ // Hash#foreach callback for ruby_curl_multi_cancel
279
+ static int ruby_curl_multi_cancel_callback(VALUE key, VALUE value, ruby_curl_multi *rbcm) {
280
+ rb_curl_multi_remove(rbcm, value);
281
+
282
+ return ST_CONTINUE;
283
+ }
284
+
285
+ /*
286
+ * call-seq:
287
+ * multi.cancel!
288
+ *
289
+ * Cancels all requests currently being made on this Curl::Multi handle.
290
+ */
291
+ static VALUE ruby_curl_multi_cancel(VALUE self) {
292
+ ruby_curl_multi *rbcm;
293
+
294
+ Data_Get_Struct(self, ruby_curl_multi, rbcm);
295
+
296
+ rb_hash_foreach( rbcm->requests, ruby_curl_multi_cancel_callback, (VALUE)rbcm );
297
+
298
+ // for chaining
299
+ return self;
300
+ }
301
+
302
+ static void rb_curl_mutli_handle_complete(VALUE self, CURL *easy_handle, int result) {
303
+
304
+ long response_code = -1;
305
+ ruby_curl_easy *rbce = NULL;
306
+ VALUE ref;
307
+ CURLcode ecode = curl_easy_getinfo(easy_handle, CURLINFO_PRIVATE, (char**)&rbce);
308
+
309
+ if (ecode != 0) {
310
+ raise_curl_easy_error_exception(ecode);
311
+ }
312
+
313
+ rbce->last_result = result; /* save the last easy result code */
314
+
315
+ ruby_curl_multi_remove( self, rbce->self );
316
+
317
+ if (rbce->complete_proc != Qnil) {
318
+ rb_funcall( rbce->complete_proc, idCall, 1, rbce->self );
319
+ }
320
+
321
+ curl_easy_getinfo(rbce->curl, CURLINFO_RESPONSE_CODE, &response_code);
322
+
323
+ ref = rbce->self;
324
+ /* break reference */
325
+ rbce->self = Qnil;
326
+
327
+ if (result != 0) {
328
+ if (rbce->failure_proc != Qnil) {
329
+ rb_funcall( rbce->failure_proc, idCall, 2, ref, rb_curl_easy_error(result) );
330
+ }
331
+ }
332
+ else if (rbce->success_proc != Qnil &&
333
+ ((response_code >= 200 && response_code < 300) || response_code == 0)) {
334
+ /* NOTE: we allow response_code == 0, in the case of non http requests e.g. reading from disk */
335
+ rb_funcall( rbce->success_proc, idCall, 1, ref );
336
+ }
337
+ else if (rbce->failure_proc != Qnil &&
338
+ (response_code >= 300 && response_code <= 999)) {
339
+ rb_funcall( rbce->failure_proc, idCall, 2, ref, rb_curl_easy_error(result) );
340
+ }
341
+ }
342
+
343
+ static void rb_curl_multi_read_info(VALUE self, CURLM *multi_handle) {
344
+ int msgs_left, result;
345
+ CURLMsg *msg;
346
+ CURL *easy_handle;
347
+
348
+ /* check for finished easy handles and remove from the multi handle */
349
+ while ((msg = curl_multi_info_read(multi_handle, &msgs_left))) {
350
+ if (msg->msg == CURLMSG_DONE) {
351
+ easy_handle = msg->easy_handle;
352
+ result = msg->data.result;
353
+ if (easy_handle) {
354
+ rb_curl_mutli_handle_complete(self, easy_handle, result);
355
+ }
356
+ }
357
+ }
358
+ }
359
+
360
+ /* called within ruby_curl_multi_perform */
361
+ static void rb_curl_multi_run(VALUE self, CURLM *multi_handle, int *still_running) {
362
+ CURLMcode mcode;
363
+
364
+ do {
365
+ mcode = curl_multi_perform(multi_handle, still_running);
366
+ } while (mcode == CURLM_CALL_MULTI_PERFORM);
367
+
368
+ if (mcode != CURLM_OK) {
369
+ raise_curl_multi_error_exception(mcode);
370
+ }
371
+
372
+ rb_curl_multi_read_info( self, multi_handle );
373
+ }
374
+
375
+ /*
376
+ * call-seq:
377
+ * multi = Curl::Multi.new
378
+ * easy1 = Curl::Easy.new('url')
379
+ * easy2 = Curl::Easy.new('url')
380
+ *
381
+ * multi.add(easy1)
382
+ * multi.add(easy2)
383
+ *
384
+ * multi.perform do
385
+ * # while idle other code my execute here
386
+ * end
387
+ *
388
+ * Run multi handles, looping selecting when data can be transfered
389
+ */
390
+ VALUE ruby_curl_multi_perform(int argc, VALUE *argv, VALUE self) {
391
+ CURLMcode mcode;
392
+ ruby_curl_multi *rbcm;
393
+ int maxfd, rc;
394
+ fd_set fdread, fdwrite, fdexcep;
395
+
396
+ long timeout_milliseconds;
397
+ struct timeval tv = {0, 0};
398
+ VALUE block = Qnil;
399
+
400
+ rb_scan_args(argc, argv, "0&", &block);
401
+
402
+ Data_Get_Struct(self, ruby_curl_multi, rbcm);
403
+
404
+ rb_curl_multi_run( self, rbcm->handle, &(rbcm->running) );
405
+
406
+ while(rbcm->running) {
407
+ FD_ZERO(&fdread);
408
+ FD_ZERO(&fdwrite);
409
+ FD_ZERO(&fdexcep);
410
+
411
+ /* load the fd sets from the multi handle */
412
+ mcode = curl_multi_fdset(rbcm->handle, &fdread, &fdwrite, &fdexcep, &maxfd);
413
+ if (mcode != CURLM_OK) {
414
+ raise_curl_multi_error_exception(mcode);
415
+ }
416
+
417
+ #ifdef HAVE_CURL_MULTI_TIMEOUT
418
+ /* get the curl suggested time out */
419
+ mcode = curl_multi_timeout(rbcm->handle, &timeout_milliseconds);
420
+ if (mcode != CURLM_OK) {
421
+ raise_curl_multi_error_exception(mcode);
422
+ }
423
+ #else
424
+ /* libcurl doesn't have a timeout method defined... make a wild guess */
425
+ timeout_milliseconds = -1;
426
+ #endif
427
+ //printf("libcurl says wait: %ld ms or %ld s\n", timeout_milliseconds, timeout_milliseconds/1000);
428
+
429
+ if (timeout_milliseconds == 0) { /* no delay */
430
+ rb_curl_multi_run( self, rbcm->handle, &(rbcm->running) );
431
+ continue;
432
+ }
433
+ else if(timeout_milliseconds < 0) {
434
+ timeout_milliseconds = 500; /* wait half a second, libcurl doesn't know how long to wait */
435
+ }
436
+ #ifdef __APPLE_CC__
437
+ if(timeout_milliseconds > 1000) {
438
+ timeout_milliseconds = 1000; /* apple libcurl sometimes reports huge timeouts... let's cap it */
439
+ }
440
+ #endif
441
+
442
+ tv.tv_sec = timeout_milliseconds / 1000; // convert milliseconds to seconds
443
+ tv.tv_usec = (timeout_milliseconds % 1000) * 1000; // get the remainder of milliseconds and convert to micro seconds
444
+
445
+ rc = rb_thread_select(maxfd+1, &fdread, &fdwrite, &fdexcep, &tv);
446
+ switch(rc) {
447
+ case -1:
448
+ rb_raise(rb_eRuntimeError, "select(): %s", strerror(errno));
449
+ break;
450
+ case 0:
451
+ if (block != Qnil) {
452
+ rb_funcall(block, rb_intern("call"), 1, self);
453
+ }
454
+ // if (rb_block_given_p()) {
455
+ // rb_yield(self);
456
+ // }
457
+ default:
458
+ rb_curl_multi_run( self, rbcm->handle, &(rbcm->running) );
459
+ break;
460
+ }
461
+
462
+ }
463
+
464
+ return Qtrue;
465
+ }
466
+
467
+ /* =================== INIT LIB =====================*/
468
+ void init_curb_multi() {
469
+ idCall = rb_intern("call");
470
+
471
+ cCurlMulti = rb_define_class_under(mCurl, "Multi", rb_cObject);
472
+
473
+ /* Class methods */
474
+ rb_define_singleton_method(cCurlMulti, "new", ruby_curl_multi_new, 0);
475
+
476
+ /* "Attributes" */
477
+ rb_define_method(cCurlMulti, "requests", ruby_curl_multi_requests, 0);
478
+ rb_define_method(cCurlMulti, "idle?", ruby_curl_multi_idle, 0);
479
+
480
+ /* Instnace methods */
481
+ rb_define_method(cCurlMulti, "max_connects=", ruby_curl_multi_max_connects, 1);
482
+ rb_define_method(cCurlMulti, "pipeline=", ruby_curl_multi_pipeline, 1);
483
+ rb_define_method(cCurlMulti, "add", ruby_curl_multi_add, 1);
484
+ rb_define_method(cCurlMulti, "remove", ruby_curl_multi_remove, 1);
485
+ rb_define_method(cCurlMulti, "cancel!", ruby_curl_multi_cancel, 0);
486
+ rb_define_method(cCurlMulti, "perform", ruby_curl_multi_perform, -1);
487
+ }
data/ext/curb_multi.h ADDED
@@ -0,0 +1,26 @@
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
+ VALUE ruby_curl_multi_new(VALUE klass);
24
+
25
+
26
+ #endif