rhack 1.2.1 → 1.2.7

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