rhack 1.1.8 → 1.2.0

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