ovirt-engine-sdk 4.0.1 → 4.4.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,1218 @@
1
+ /*
2
+ Copyright (c) 2016-2017 Red Hat, Inc.
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ #include <ruby.h>
18
+ #include <ruby/thread.h>
19
+
20
+ #include <ctype.h>
21
+ #include <curl/curl.h>
22
+ #include <stdarg.h>
23
+ #include <stdbool.h>
24
+ #include <stdlib.h>
25
+ #include <string.h>
26
+ #include <sys/time.h>
27
+ #include <sys/select.h>
28
+
29
+ #include "ov_module.h"
30
+ #include "ov_error.h"
31
+ #include "ov_string.h"
32
+ #include "ov_http_client.h"
33
+ #include "ov_http_request.h"
34
+ #include "ov_http_response.h"
35
+ #include "ov_http_transfer.h"
36
+
37
+ /* Class: */
38
+ VALUE ov_http_client_class;
39
+
40
+ /* Symbols: */
41
+ static VALUE CA_FILE_SYMBOL;
42
+ static VALUE COMPRESS_SYMBOL;
43
+ static VALUE CONNECTIONS_SYMBOL;
44
+ static VALUE DEBUG_SYMBOL;
45
+ static VALUE INSECURE_SYMBOL;
46
+ static VALUE LOG_SYMBOL;
47
+ static VALUE PASSWORD_SYMBOL;
48
+ static VALUE PIPELINE_SYMBOL;
49
+ static VALUE PROXY_PASSWORD_SYMBOL;
50
+ static VALUE PROXY_URL_SYMBOL;
51
+ static VALUE PROXY_USERNAME_SYMBOL;
52
+ static VALUE TIMEOUT_SYMBOL;
53
+ static VALUE CONNECT_TIMEOUT_SYMBOL;
54
+ static VALUE COOKIES_SYMBOL;
55
+
56
+ /* Method identifiers: */
57
+ static ID COMPARE_BY_IDENTITY_ID;
58
+ static ID DEBUG_ID;
59
+ static ID DOWNCASE_ID;
60
+ static ID ENCODE_WWW_FORM_ID;
61
+ static ID INFO_ID;
62
+ static ID INFO_Q_ID;
63
+ static ID READ_ID;
64
+ static ID STRING_ID;
65
+ static ID STRING_IO_ID;
66
+ static ID URI_ID;
67
+ static ID WARN_ID;
68
+ static ID WARN_Q_ID;
69
+ static ID WRITE_ID;
70
+
71
+ /* References to classes: */
72
+ static VALUE STRING_IO_CLASS;
73
+ static VALUE URI_CLASS;
74
+
75
+ /* Constants: */
76
+ const char CR = '\x0D';
77
+ const char LF = '\x0A';
78
+
79
+ /* Version of libcurl: */
80
+ static curl_version_info_data* libcurl_version;
81
+
82
+ /* Before version 7.38.0 of libcurl the NEGOTIATE authentication method was named GSSNEGOTIATE: */
83
+ #ifndef CURLAUTH_NEGOTIATE
84
+ #define CURLAUTH_NEGOTIATE CURLAUTH_GSSNEGOTIATE
85
+ #endif
86
+
87
+ /* Before version 7.43.0 of libcurl the CURLPIPE_HTTP1 constant didn't exist, the value 1 was used
88
+ directly instead: */
89
+ #ifndef CURLPIPE_HTTP1
90
+ #define CURLPIPE_HTTP1 1
91
+ #endif
92
+
93
+ /* Define options that may not be available in some versions of libcurl: */
94
+ #if LIBCURL_VERSION_NUM < 0x071e00 /* 7.30.0 */
95
+ #define CURLMOPT_MAX_HOST_CONNECTIONS 7
96
+ #define CURLMOPT_MAX_PIPELINE_LENGTH 8
97
+ #define CURLMOPT_MAX_TOTAL_CONNECTIONS 13
98
+ #endif
99
+
100
+ #if LIBCURL_VERSION_NUM < 0x070f03 /* 7.16.3 */
101
+ #define CURLMOPT_MAXCONNECTS 6
102
+ #endif
103
+
104
+ #if LIBCURL_VERSION_NUM < 0x070f00 /* 7.16.0 */
105
+ #define CURLMOPT_PIPELINING 3
106
+ #endif
107
+
108
+ typedef struct {
109
+ VALUE io; /* IO */
110
+ char* ptr;
111
+ size_t size;
112
+ size_t nmemb;
113
+ size_t result;
114
+ } ov_http_client_io_context;
115
+
116
+ typedef struct {
117
+ VALUE response; /* HttpResponse */
118
+ char* buffer;
119
+ size_t size;
120
+ size_t nitems;
121
+ size_t result;
122
+ } ov_http_client_header_context;
123
+
124
+ typedef struct {
125
+ VALUE client; /* HttpClient */
126
+ curl_infotype type;
127
+ char* data;
128
+ size_t size;
129
+ } ov_http_client_debug_context;
130
+
131
+ typedef struct {
132
+ CURLM* handle;
133
+ CURLcode code;
134
+ bool cancel;
135
+ } ov_http_client_wait_context;
136
+
137
+
138
+ static void ov_http_client_log_info(VALUE log, const char* format, ...) {
139
+ VALUE enabled;
140
+ VALUE message;
141
+ va_list args;
142
+
143
+ if (!NIL_P(log)) {
144
+ enabled = rb_funcall(log, INFO_Q_ID, 0);
145
+ if (RTEST(enabled)) {
146
+ va_start(args, format);
147
+ message = rb_vsprintf(format, args);
148
+ rb_funcall(log, INFO_ID, 1, message);
149
+ va_end(args);
150
+ }
151
+ }
152
+ }
153
+
154
+ static void ov_http_client_log_warn(VALUE log, const char* format, ...) {
155
+ VALUE enabled;
156
+ VALUE message;
157
+ va_list args;
158
+
159
+ if (!NIL_P(log)) {
160
+ enabled = rb_funcall(log, WARN_Q_ID, 0);
161
+ if (RTEST(enabled)) {
162
+ va_start(args, format);
163
+ message = rb_vsprintf(format, args);
164
+ rb_funcall(log, WARN_ID, 1, message);
165
+ va_end(args);
166
+ }
167
+ }
168
+ }
169
+
170
+ static void ov_http_client_check_closed(ov_http_client_object* object) {
171
+ if (object->handle == NULL) {
172
+ rb_raise(ov_error_class, "The client is already closed");
173
+ }
174
+ }
175
+
176
+ static void ov_http_client_mark(void* vptr) {
177
+ ov_http_client_object* ptr;
178
+
179
+ ptr = vptr;
180
+ rb_gc_mark(ptr->log);
181
+ rb_gc_mark(ptr->queue);
182
+ rb_gc_mark(ptr->pending);
183
+ rb_gc_mark(ptr->completed);
184
+ }
185
+
186
+ static void ov_http_client_free(void* vptr) {
187
+ ov_http_client_object* ptr;
188
+
189
+ /* Get the pointer to the object: */
190
+ ptr = vptr;
191
+
192
+ /* Release the resources used by libcurl: */
193
+ if (ptr->handle != NULL) {
194
+ curl_multi_cleanup(ptr->handle);
195
+ curl_share_cleanup(ptr->share);
196
+ ptr->handle = NULL;
197
+ }
198
+
199
+ /* Free the strings: */
200
+ ov_string_free(ptr->ca_file);
201
+ ov_string_free(ptr->proxy_url);
202
+ ov_string_free(ptr->proxy_username);
203
+ ov_string_free(ptr->proxy_password);
204
+ ov_string_free(ptr->cookies);
205
+
206
+ /* Free this object: */
207
+ xfree(ptr);
208
+ }
209
+
210
+ rb_data_type_t ov_http_client_type = {
211
+ .wrap_struct_name = "OVHTTPCLIENT",
212
+ .function = {
213
+ .dmark = ov_http_client_mark,
214
+ .dfree = ov_http_client_free,
215
+ .dsize = NULL,
216
+ .reserved = { NULL, NULL }
217
+ },
218
+ #ifdef RUBY_TYPED_FREE_IMMEDIATELY
219
+ .parent = NULL,
220
+ .data = NULL,
221
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY,
222
+ #endif
223
+ };
224
+
225
+ static VALUE ov_http_client_alloc(VALUE klass) {
226
+ ov_http_client_object* ptr;
227
+
228
+ ptr = ALLOC(ov_http_client_object);
229
+ ptr->handle = NULL;
230
+ ptr->share = NULL;
231
+ ptr->log = Qnil;
232
+ ptr->limit = 0;
233
+ ptr->queue = Qnil;
234
+ ptr->pending = Qnil;
235
+ ptr->completed = Qnil;
236
+ ptr->compress = false;
237
+ ptr->debug = false;
238
+ ptr->insecure = false;
239
+ ptr->ca_file = NULL;
240
+ ptr->proxy_url = NULL;
241
+ ptr->proxy_username = NULL;
242
+ ptr->proxy_password = NULL;
243
+ ptr->timeout = 0;
244
+ ptr->connect_timeout = 0;
245
+ ptr->cookies = NULL;
246
+ return TypedData_Wrap_Struct(klass, &ov_http_client_type, ptr);
247
+ }
248
+
249
+ static VALUE ov_http_client_close(VALUE self) {
250
+ ov_http_client_object* ptr;
251
+
252
+ /* Get the pointer to the native object and check that it isn't closed: */
253
+ ov_http_client_ptr(self, ptr);
254
+ ov_http_client_check_closed(ptr);
255
+
256
+ /* Release the resources used by libcurl: */
257
+ curl_multi_cleanup(ptr->handle);
258
+ ptr->handle = NULL;
259
+
260
+ return Qnil;
261
+ }
262
+
263
+ static void* ov_http_client_read_task(void* data) {
264
+ VALUE bytes;
265
+ VALUE count;
266
+ ov_http_client_io_context* context_ptr;
267
+
268
+ /* The passed data is a pointer to the IO context: */
269
+ context_ptr = (ov_http_client_io_context*) data;
270
+
271
+ /* Read the data using the "read" method and write the raw bytes to the buffer provided by libcurl: */
272
+ count = INT2NUM(context_ptr->size * context_ptr->nmemb);
273
+ bytes = rb_funcall(context_ptr->io, READ_ID, 1, count);
274
+ if (NIL_P(bytes)) {
275
+ context_ptr->result = 0;
276
+ }
277
+ else {
278
+ context_ptr->result = RSTRING_LEN(bytes);
279
+ memcpy(context_ptr->ptr, StringValuePtr(bytes), context_ptr->result);
280
+ }
281
+
282
+ return NULL;
283
+ }
284
+
285
+ static size_t ov_http_client_read_function(char *ptr, size_t size, size_t nmemb, void *userdata) {
286
+ VALUE transfer;
287
+ ov_http_client_io_context context;
288
+ ov_http_transfer_object* transfer_ptr;
289
+
290
+ /* The passed user data is the transfer: */
291
+ transfer = (VALUE) userdata;
292
+
293
+ /* Get the pointer to the transfer: */
294
+ ov_http_transfer_ptr(transfer, transfer_ptr);
295
+
296
+ /* Check if the operation has been cancelled, and return immediately, this will cause the perform method to
297
+ return an error to the caller: */
298
+ if (transfer_ptr->cancel) {
299
+ return CURL_READFUNC_ABORT;
300
+ }
301
+
302
+ /* Execute the read with the global interpreter lock acquired, as it needs to call Ruby methods: */
303
+ context.ptr = ptr;
304
+ context.size = size;
305
+ context.nmemb = nmemb;
306
+ context.io = transfer_ptr->in;
307
+ rb_thread_call_with_gvl(ov_http_client_read_task, &context);
308
+ return context.result;
309
+ }
310
+
311
+ static void* ov_http_client_write_task(void* data) {
312
+ VALUE bytes;
313
+ VALUE count;
314
+ ov_http_client_io_context* context_ptr;
315
+
316
+ /* The passed data is a pointer to the IO context: */
317
+ context_ptr = (ov_http_client_io_context*) data;
318
+
319
+ /* Convert the buffer to a Ruby string and write it to the IO object, using the "write" method: */
320
+ bytes = rb_str_new(context_ptr->ptr, context_ptr->size * context_ptr->nmemb);
321
+ count = rb_funcall(context_ptr->io, WRITE_ID, 1, bytes);
322
+ context_ptr->result = NUM2INT(count);
323
+
324
+ return NULL;
325
+ }
326
+
327
+ static size_t ov_http_client_write_function(char *ptr, size_t size, size_t nmemb, void *userdata) {
328
+ VALUE transfer;
329
+ ov_http_client_io_context context;
330
+ ov_http_transfer_object* transfer_ptr;
331
+
332
+ /* The passed user data is the transfer: */
333
+ transfer = (VALUE) userdata;
334
+
335
+ /* Get the pointer to the transfer: */
336
+ ov_http_transfer_ptr(transfer, transfer_ptr);
337
+
338
+ /* Check if the operation has been cancelled, and return immediately, this will cause the perform method to
339
+ return an error to the caller: */
340
+ if (transfer_ptr->cancel) {
341
+ return 0;
342
+ }
343
+
344
+ /* Execute the write with the global interpreter lock acquired, as it needs to call Ruby methods: */
345
+ context.ptr = ptr;
346
+ context.size = size;
347
+ context.nmemb = nmemb;
348
+ context.io = transfer_ptr->out;
349
+ rb_thread_call_with_gvl(ov_http_client_write_task, &context);
350
+ return context.result;
351
+ }
352
+
353
+ static void* ov_http_client_header_task(void* data) {
354
+ VALUE name;
355
+ VALUE value;
356
+ char* buffer;
357
+ char* pointer;
358
+ ov_http_client_header_context* context_ptr;
359
+ ov_http_response_object* response_ptr;
360
+ size_t length;
361
+
362
+ /* The passed data is the pointer to the context: */
363
+ context_ptr = (ov_http_client_header_context*) data;
364
+
365
+ /* Get the pointer to the response: */
366
+ ov_http_response_ptr(context_ptr->response, response_ptr);
367
+
368
+ /* We should always tell the library that we processed all the data: */
369
+ context_ptr->result = context_ptr->size * context_ptr->nitems;
370
+
371
+ /* The library provides the headers for all the responses it receives, including the responses for intermediate
372
+ requests used for authentication negotation. We are interested only in the headers of the last response, so
373
+ if the given data is the begin of a new response, we clear the headers hash. */
374
+ length = context_ptr->result;
375
+ buffer = context_ptr->buffer;
376
+ if (length >= 5 && strncmp("HTTP/", buffer, 5) == 0) {
377
+ rb_hash_clear(response_ptr->headers);
378
+ return NULL;
379
+ }
380
+
381
+ /* Remove trailing white space: */
382
+ while (length > 0 && isspace(buffer[length - 1])) {
383
+ length--;
384
+ }
385
+
386
+ /* Parse the header and add it to the response object: */
387
+ pointer = memchr(buffer, ':', length);
388
+ if (pointer != NULL) {
389
+ name = rb_str_new(buffer, pointer - buffer);
390
+ name = rb_funcall(name, DOWNCASE_ID, 0);
391
+ pointer++;
392
+ while (pointer - buffer < length && isspace(*pointer)) {
393
+ pointer++;
394
+ }
395
+ value = rb_str_new(pointer, length - (pointer - buffer));
396
+ rb_hash_aset(response_ptr->headers, name, value);
397
+ }
398
+
399
+ return NULL;
400
+ }
401
+
402
+ static size_t ov_http_client_header_function(char *buffer, size_t size, size_t nitems, void *userdata) {
403
+ VALUE transfer;
404
+ ov_http_client_header_context context;
405
+ ov_http_transfer_object* transfer_ptr;
406
+
407
+ /* The passed user data is a pointer to the transfer: */
408
+ transfer = (VALUE) userdata;
409
+
410
+ /* Get the pointer to the transfer: */
411
+ ov_http_transfer_ptr(transfer, transfer_ptr);
412
+
413
+ /* Check if the operation has been cancelled, and return immediately, this will cause the perform method to
414
+ return an error to the caller: */
415
+ if (transfer_ptr->cancel) {
416
+ return 0;
417
+ }
418
+
419
+ /* Parse the header with the global interpreter lock acquired, as it needs to call Ruby methods: */
420
+ context.response = transfer_ptr->response;
421
+ context.buffer = buffer;
422
+ context.size = size;
423
+ context.nitems = nitems;
424
+ rb_thread_call_with_gvl(ov_http_client_header_task, &context);
425
+ return context.result;
426
+ }
427
+
428
+ static void* ov_http_client_debug_task(void* data) {
429
+ VALUE line;
430
+ VALUE log;
431
+ char* text;
432
+ int c;
433
+ int s;
434
+ ov_http_client_debug_context* context_ptr;
435
+ ov_http_client_object* client_ptr;
436
+ size_t i;
437
+ size_t j;
438
+ size_t size;
439
+
440
+ /* The passed data is a pointer to the context: */
441
+ context_ptr = (ov_http_client_debug_context*) data;
442
+
443
+ /* Get the pointer to the client: */
444
+ ov_http_client_ptr(context_ptr->client, client_ptr);
445
+
446
+ /* Do nothing if there is no log: */
447
+ log = client_ptr->log;
448
+ if (NIL_P(log)) {
449
+ return NULL;
450
+ }
451
+
452
+ /* Split the text into lines, and send a debug message for each line: */
453
+ text = context_ptr->data;
454
+ size = context_ptr->size;
455
+ i = 0;
456
+ s = 0;
457
+ for (j = 0; j <= size; j++) {
458
+ c = j < size? text[j]: -1;
459
+ switch (s) {
460
+ case 0:
461
+ if (c == CR || c == LF || c == -1) {
462
+ line = rb_str_new(text + i, j - i);
463
+ rb_funcall(log, DEBUG_ID, 1, line);
464
+ i = j + 1;
465
+ s = 1;
466
+ }
467
+ break;
468
+ case 1:
469
+ if (c == CR || c == LF || c == -1) {
470
+ i++;
471
+ }
472
+ else {
473
+ s = 0;
474
+ }
475
+ break;
476
+ }
477
+ }
478
+
479
+ return NULL;
480
+ }
481
+
482
+ static int ov_http_client_debug_function(CURL* handle, curl_infotype type, char* data, size_t size, void* userptr) {
483
+ VALUE transfer;
484
+ ov_http_client_debug_context context;
485
+ ov_http_transfer_object* transfer_ptr;
486
+
487
+ /* The passed user pointer is the transfer: */
488
+ transfer = (VALUE) userptr;
489
+
490
+ /* Get the pointer to the transfer: */
491
+ ov_http_transfer_ptr(transfer, transfer_ptr);
492
+
493
+ /* Execute the debug code with the global interpreter lock acquired, as it needs to call Ruby methods: */
494
+ context.client = transfer_ptr->client;
495
+ context.type = type;
496
+ context.data = data;
497
+ context.size = size;
498
+ rb_thread_call_with_gvl(ov_http_client_debug_task, &context);
499
+ return 0;
500
+ }
501
+
502
+ static VALUE ov_http_client_initialize(int argc, VALUE* argv, VALUE self) {
503
+ VALUE opt;
504
+ VALUE opts;
505
+ long connections;
506
+ long pipeline;
507
+ ov_http_client_object* ptr;
508
+
509
+ /* Get the pointer to the native object: */
510
+ ov_http_client_ptr(self, ptr);
511
+
512
+ /* Check the number of arguments: */
513
+ if (argc > 1) {
514
+ rb_raise(ov_error_class, "Expected at most one argument, 'opts', but received %d", argc);
515
+ }
516
+ opts = argc > 0? argv[0]: Qnil;
517
+ if (NIL_P(opts)) {
518
+ opts = rb_hash_new();
519
+ }
520
+ else {
521
+ Check_Type(opts, T_HASH);
522
+ }
523
+
524
+ /* Get the value of the 'ca_file' parameter: */
525
+ opt = rb_hash_aref(opts, CA_FILE_SYMBOL);
526
+ if (NIL_P(opt)) {
527
+ ptr->ca_file = NULL;
528
+ }
529
+ else {
530
+ Check_Type(opt, T_STRING);
531
+ ptr->ca_file = ov_string_dup(opt);
532
+ }
533
+
534
+ /* Get the value of the 'insecure' parameter: */
535
+ opt = rb_hash_aref(opts, INSECURE_SYMBOL);
536
+ ptr->insecure = NIL_P(opt)? false: RTEST(opt);
537
+
538
+ /* Get the value of the 'debug' parameter: */
539
+ opt = rb_hash_aref(opts, DEBUG_SYMBOL);
540
+ ptr->debug = NIL_P(opt)? false: RTEST(opt);
541
+
542
+ /* Get the value of the 'compress' parameter: */
543
+ opt = rb_hash_aref(opts, COMPRESS_SYMBOL);
544
+ ptr->compress = NIL_P(opt)? true: RTEST(opt);
545
+
546
+ /* Get the value of the 'timeout' parameter: */
547
+ opt = rb_hash_aref(opts, TIMEOUT_SYMBOL);
548
+ if (NIL_P(opt)) {
549
+ ptr->timeout = 0;
550
+ }
551
+ else {
552
+ Check_Type(opt, T_FIXNUM);
553
+ ptr->timeout = NUM2INT(opt);
554
+ }
555
+
556
+ /* Get the value of the 'connect_timeout' parameter: */
557
+ opt = rb_hash_aref(opts, CONNECT_TIMEOUT_SYMBOL);
558
+ if (NIL_P(opt)) {
559
+ ptr->connect_timeout = 0;
560
+ }
561
+ else {
562
+ Check_Type(opt, T_FIXNUM);
563
+ ptr->connect_timeout = NUM2INT(opt);
564
+ }
565
+
566
+ /* Get the value of the 'proxy_url' parameter: */
567
+ opt = rb_hash_aref(opts, PROXY_URL_SYMBOL);
568
+ if (NIL_P(opt)) {
569
+ ptr->proxy_url = NULL;
570
+ }
571
+ else {
572
+ Check_Type(opt, T_STRING);
573
+ ptr->proxy_url = ov_string_dup(opt);
574
+ }
575
+
576
+ /* Get the value of the 'proxy_username' parameter: */
577
+ opt = rb_hash_aref(opts, PROXY_USERNAME_SYMBOL);
578
+ if (NIL_P(opt)) {
579
+ ptr->proxy_username = NULL;
580
+ }
581
+ else {
582
+ Check_Type(opt, T_STRING);
583
+ ptr->proxy_username = ov_string_dup(opt);
584
+ }
585
+
586
+ /* Get the value of the 'proxy_password' parameter: */
587
+ opt = rb_hash_aref(opts, PROXY_PASSWORD_SYMBOL);
588
+ if (NIL_P(opt)) {
589
+ ptr->proxy_password = NULL;
590
+ }
591
+ else {
592
+ Check_Type(opt, T_STRING);
593
+ ptr->proxy_password = ov_string_dup(opt);
594
+ }
595
+
596
+ /* Get the value of the 'log' parameter: */
597
+ opt = rb_hash_aref(opts, LOG_SYMBOL);
598
+ ptr->log = opt;
599
+
600
+ /* Get the value of the 'pipeline' parameter: */
601
+ opt = rb_hash_aref(opts, PIPELINE_SYMBOL);
602
+ if (NIL_P(opt)) {
603
+ pipeline = 0;
604
+ }
605
+ else {
606
+ Check_Type(opt, T_FIXNUM);
607
+ pipeline = NUM2LONG(opt);
608
+ }
609
+ if (pipeline < 0) {
610
+ rb_raise(rb_eArgError, "The maximum pipeline length can't be %ld, minimum is 0.", pipeline);
611
+ }
612
+
613
+ /* Get the value of the 'connections' parameter: */
614
+ opt = rb_hash_aref(opts, CONNECTIONS_SYMBOL);
615
+ if (NIL_P(opt)) {
616
+ connections = 1;
617
+ }
618
+ else {
619
+ Check_Type(opt, T_FIXNUM);
620
+ connections = NUM2LONG(opt);
621
+ }
622
+ if (connections < 1) {
623
+ rb_raise(rb_eArgError, "The maximum number of connections can't be %ld, minimum is 1.", connections);
624
+ }
625
+
626
+ /* Get the value of the 'cookies' parameter. If it is a string it will be used as the path of the file where the
627
+ cookies will be stored. If it is any other thing it will be treated as a boolean flag indicating if cookies
628
+ should be enabled but not loaded/saved from/to any file. */
629
+ opt = rb_hash_aref(opts, COOKIES_SYMBOL);
630
+ if (TYPE(opt) == T_STRING) {
631
+ ptr->cookies = ov_string_dup(opt);
632
+ }
633
+ else if (RTEST(opt)) {
634
+ ptr->cookies = ov_string_dup(rb_str_new2(""));
635
+ }
636
+ else {
637
+ ptr->cookies = NULL;
638
+ }
639
+
640
+ /* Create the queue that contains requests that haven't been sent to libcurl yet: */
641
+ ptr->queue = rb_ary_new();
642
+
643
+ /* Create the hash that contains the transfers are pending an completed. Both use the identity of the request
644
+ as key. */
645
+ ptr->completed = rb_funcall(rb_hash_new(), COMPARE_BY_IDENTITY_ID, 0);
646
+ ptr->pending = rb_funcall(rb_hash_new(), COMPARE_BY_IDENTITY_ID, 0);
647
+
648
+ /* Calculate the max number of requests that can be handled by libcurl simultaneously. For versions of libcurl
649
+ newer than 7.30.0 the limit can be increased when using pipelining. For older versions it can't be increased
650
+ because libcurl would create additional connections for the requests that can't be pipelined. */
651
+ ptr->limit = connections;
652
+ if (pipeline > 0 && libcurl_version->version_num >= 0x071e00 /* 7.30.0 */) {
653
+ ptr->limit *= pipeline;
654
+ }
655
+
656
+ /* Create the libcurl multi handle: */
657
+ ptr->handle = curl_multi_init();
658
+ if (ptr->handle == NULL) {
659
+ rb_raise(ov_error_class, "Can't create libcurl multi object");
660
+ }
661
+
662
+ /* Create the libcurl share handle in order to share cookie data: */
663
+ ptr->share = curl_share_init();
664
+ if (ptr->share == NULL) {
665
+ rb_raise(ov_error_class, "Can't create libcurl share object");
666
+ }
667
+ if (ptr->cookies != NULL) {
668
+ curl_share_setopt(ptr->share, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE);
669
+ }
670
+
671
+ /* Enable pipelining: */
672
+ if (pipeline > 0) {
673
+ curl_multi_setopt(ptr->handle, CURLMOPT_PIPELINING, CURLPIPE_HTTP1);
674
+ if (libcurl_version->version_num >= 0x071e00 /* 7.30.0 */) {
675
+ curl_multi_setopt(ptr->handle, CURLMOPT_MAX_PIPELINE_LENGTH, pipeline);
676
+ }
677
+ else {
678
+ ov_http_client_log_warn(
679
+ ptr->log,
680
+ "Can't set maximum pipeline length to %d, it isn't supported by libcurl %s. Upgrade to 7.30.0 or "
681
+ "newer to avoid this issue.",
682
+ pipeline,
683
+ libcurl_version->version
684
+ );
685
+ }
686
+ }
687
+
688
+ /* Set the max number of connections: */
689
+ if (connections > 0) {
690
+ if (libcurl_version->version_num >= 0x071e00 /* 7.30.0 */) {
691
+ curl_multi_setopt(ptr->handle, CURLMOPT_MAX_HOST_CONNECTIONS, connections);
692
+ curl_multi_setopt(ptr->handle, CURLMOPT_MAX_TOTAL_CONNECTIONS, connections);
693
+ }
694
+ else {
695
+ ov_http_client_log_warn(
696
+ ptr->log,
697
+ "Can't set maximum number of connections to %d, it isn't supported by libcurl %s. Upgrade to 7.30.0 "
698
+ "or newer to avoid this issue.",
699
+ connections,
700
+ libcurl_version->version
701
+ );
702
+ }
703
+ if (libcurl_version->version_num >= 0x070f03 /* 7.16.3 */) {
704
+ curl_multi_setopt(ptr->handle, CURLMOPT_MAXCONNECTS, connections);
705
+ }
706
+ else {
707
+ ov_http_client_log_warn(
708
+ ptr->log,
709
+ "Can't set total maximum connection cache size to %d, it isn't supported by libcurl %s. Upgrade to "
710
+ "7.16.3 or newer to avoid this issue.",
711
+ connections,
712
+ libcurl_version->version
713
+ );
714
+ }
715
+ }
716
+
717
+ return self;
718
+ }
719
+
720
+ static VALUE ov_http_client_build_url( VALUE url, VALUE query) {
721
+ /* Copy the URL: */
722
+ if (NIL_P(url)) {
723
+ rb_raise(ov_error_class, "The 'url' parameter can't be nil");
724
+ }
725
+ Check_Type(url, T_STRING);
726
+
727
+ /* Append the query: */
728
+ if (!NIL_P(query)) {
729
+ Check_Type(query, T_HASH);
730
+ if (RHASH_SIZE(query) > 0) {
731
+ url = rb_sprintf("%"PRIsVALUE"?%"PRIsVALUE"", url, rb_funcall(URI_CLASS, ENCODE_WWW_FORM_ID, 1, query));
732
+ }
733
+ }
734
+
735
+ return url;
736
+ }
737
+
738
+ static int ov_http_client_add_header(VALUE name, VALUE value, struct curl_slist** headers) {
739
+ VALUE header = Qnil;
740
+
741
+ header = rb_sprintf("%"PRIsVALUE": %"PRIsVALUE"", name, value);
742
+ *headers = curl_slist_append(*headers, StringValueCStr(header));
743
+
744
+ return ST_CONTINUE;
745
+ }
746
+
747
+ static void* ov_http_client_complete_task(void* data) {
748
+ CURLM* handle;
749
+ CURLMsg* message;
750
+ VALUE error_class;
751
+ VALUE error_instance;
752
+ VALUE transfer;
753
+ long code;
754
+ ov_http_client_object* client_ptr;
755
+ ov_http_request_object* request_ptr;
756
+ ov_http_response_object* response_ptr;
757
+ ov_http_transfer_object* transfer_ptr;
758
+
759
+ /* The passed pointer is the libcurl message describing the completed transfer: */
760
+ message = (CURLMsg*) data;
761
+ handle = message->easy_handle;
762
+
763
+ /* The transfer is stored as the private data of the libcurl easy handle: */
764
+ curl_easy_getinfo(handle, CURLINFO_PRIVATE, &transfer);
765
+
766
+ /* Get the pointers to the transfer, client and response: */
767
+ ov_http_transfer_ptr(transfer, transfer_ptr);
768
+ ov_http_client_ptr(transfer_ptr->client, client_ptr);
769
+ ov_http_request_ptr(transfer_ptr->request, request_ptr);
770
+ ov_http_response_ptr(transfer_ptr->response, response_ptr);
771
+
772
+ /* Remove the transfer from the pending hash: */
773
+ rb_hash_delete(client_ptr->pending, transfer_ptr->request);
774
+
775
+ if (message->data.result == CURLE_OK) {
776
+ /* Copy the response code and the response body to the response object: */
777
+ curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &code);
778
+ response_ptr->code = LONG2NUM(code);
779
+ response_ptr->body = rb_funcall(transfer_ptr->out, STRING_ID, 0);
780
+
781
+ /* Put the request and the response in the completed transfers hash: */
782
+ rb_hash_aset(client_ptr->completed, transfer_ptr->request, transfer_ptr->response);
783
+
784
+ /* Send a summary of the response to the log: */
785
+ ov_http_client_log_info(
786
+ client_ptr->log,
787
+ "Received response code %"PRIsVALUE" for %"PRIsVALUE" request to URL '%"PRIsVALUE"'.",
788
+ response_ptr->code,
789
+ request_ptr->method,
790
+ request_ptr->url
791
+ );
792
+ }
793
+ else {
794
+ /* Select the error class according to the kind of error returned by libcurl: */
795
+ switch (message->data.result) {
796
+ case CURLE_COULDNT_CONNECT:
797
+ case CURLE_COULDNT_RESOLVE_HOST:
798
+ case CURLE_COULDNT_RESOLVE_PROXY:
799
+ error_class = ov_connection_error_class;
800
+ break;
801
+ case CURLE_OPERATION_TIMEDOUT:
802
+ error_class = ov_timeout_error_class;
803
+ break;
804
+ default:
805
+ error_class = ov_error_class;
806
+ }
807
+
808
+ /* Put the request and error in the completed transfers hash: */
809
+ error_instance = rb_sprintf("Can't send request: %s", curl_easy_strerror(message->data.result));
810
+ error_instance = rb_class_new_instance(1, &error_instance, error_class);
811
+ rb_hash_aset(client_ptr->completed, transfer_ptr->request, error_instance);
812
+ }
813
+
814
+ /* Now that the libcurl easy handle is released, we can release the headers as well: */
815
+ curl_slist_free_all(transfer_ptr->headers);
816
+
817
+ return NULL;
818
+ }
819
+
820
+ static void* ov_http_client_wait_task(void* data) {
821
+ CURLMsg* message;
822
+ int count;
823
+ int pending;
824
+ long timeout;
825
+ ov_http_client_wait_context* context_ptr;
826
+ #if LIBCURL_VERSION_NUM < 0x071c00
827
+ fd_set fd_read;
828
+ fd_set fd_write;
829
+ fd_set fd_error;
830
+ int fd_count;
831
+ struct timeval tv;
832
+ #endif
833
+
834
+ /* The passed data is the wait context: */
835
+ context_ptr = data;
836
+
837
+ /* Get the timeout preferred by libcurl, or one 100 ms by default: */
838
+ curl_multi_timeout(context_ptr->handle, &timeout);
839
+ if (timeout < 0) {
840
+ timeout = 100;
841
+ }
842
+
843
+ #if LIBCURL_VERSION_NUM >= 0x071c00
844
+ /* Wait till there is activity: */
845
+ context_ptr->code = curl_multi_wait(context_ptr->handle, NULL, 0, timeout, NULL);
846
+ if (context_ptr->code != CURLE_OK) {
847
+ return NULL;
848
+ }
849
+ #else
850
+ /* Versions of libcurl older than 7.28.0 don't provide the 'curl_multi_wait' function, so we need to get the file
851
+ descriptors used by libcurl, and explicitly use the 'select' system call: */
852
+ FD_ZERO(&fd_read);
853
+ FD_ZERO(&fd_write);
854
+ FD_ZERO(&fd_error);
855
+ context_ptr->code = curl_multi_fdset(context_ptr->handle, &fd_read, &fd_write, &fd_error, &fd_count);
856
+ if (context_ptr->code != CURLE_OK) {
857
+ return NULL;
858
+ }
859
+ tv.tv_sec = timeout / 1000;
860
+ tv.tv_usec = (timeout % 1000) * 1000;
861
+ select(fd_count + 1, &fd_read, &fd_write, &fd_error, &tv);
862
+ #endif
863
+
864
+ /* Let libcurl do its work, even if no file descriptor needs attention. This is necessary because some of its
865
+ activities can't be monitored using file descriptors. */
866
+ context_ptr->code = curl_multi_perform(context_ptr->handle, &pending);
867
+ if (context_ptr->code != CURLE_OK) {
868
+ return NULL;
869
+ }
870
+
871
+ /* Check if there are finished transfers. For each of them call the function that completes them, with the global
872
+ interpreter lock acquired, as it will call Ruby code. */
873
+ while ((message = curl_multi_info_read(context_ptr->handle, &count)) != NULL) {
874
+ if (message->msg == CURLMSG_DONE) {
875
+ /* Call the Ruby code that completes the transfer: */
876
+ rb_thread_call_with_gvl(ov_http_client_complete_task, message);
877
+
878
+ /* Remove the easy handle from the multi handle and discard it: */
879
+ curl_multi_remove_handle(context_ptr->handle, message->easy_handle);
880
+ curl_easy_cleanup(message->easy_handle);
881
+ }
882
+ }
883
+
884
+ /* Everything worked correctly: */
885
+ context_ptr->code = CURLE_OK;
886
+ return NULL;
887
+ }
888
+
889
+ static void ov_http_client_wait_cancel(void* data) {
890
+ ov_http_client_wait_context* context_ptr;
891
+
892
+ /* The passed data is the wait context: */
893
+ context_ptr = data;
894
+
895
+ /* Set the cancel flag so that the operation will be actually aborted in the next operation of the wait loop: */
896
+ context_ptr->cancel = true;
897
+ }
898
+
899
+ static void ov_http_client_prepare_handle(ov_http_client_object* client_ptr, ov_http_request_object* request_ptr,
900
+ struct curl_slist** headers, CURL* handle) {
901
+ VALUE header;
902
+ VALUE url;
903
+ int timeout;
904
+ int connect_timeout;
905
+
906
+ /* Configure sharing of cookies with other handlers created by the client: */
907
+ curl_easy_setopt(handle, CURLOPT_SHARE, client_ptr->share);
908
+ if (client_ptr->cookies != NULL && strlen(client_ptr->cookies) > 0) {
909
+ curl_easy_setopt(handle, CURLOPT_COOKIEFILE, client_ptr->cookies);
910
+ curl_easy_setopt(handle, CURLOPT_COOKIEJAR, client_ptr->cookies);
911
+ }
912
+
913
+ /* Configure TLS parameters: */
914
+ if (client_ptr->insecure) {
915
+ curl_easy_setopt(handle, CURLOPT_SSL_VERIFYPEER, 0L);
916
+ curl_easy_setopt(handle, CURLOPT_SSL_VERIFYHOST, 0L);
917
+ }
918
+ if (client_ptr->ca_file != NULL) {
919
+ curl_easy_setopt(handle, CURLOPT_CAINFO, client_ptr->ca_file);
920
+ }
921
+
922
+ /* Configure the total timeout: */
923
+ timeout = client_ptr->timeout;
924
+ if (!NIL_P(request_ptr->timeout)) {
925
+ timeout = NUM2INT(request_ptr->timeout);
926
+ }
927
+ curl_easy_setopt(handle, CURLOPT_TIMEOUT, timeout);
928
+
929
+ /* Configure the connect timeout: */
930
+ connect_timeout = client_ptr->connect_timeout;
931
+ if (!NIL_P(request_ptr->connect_timeout)) {
932
+ connect_timeout = NUM2INT(request_ptr->connect_timeout);
933
+ }
934
+ curl_easy_setopt(handle, CURLOPT_CONNECTTIMEOUT, connect_timeout);
935
+
936
+ /* Configure compression of responses (setting the value to zero length string means accepting all the
937
+ compression types that libcurl supports): */
938
+ if (client_ptr->compress) {
939
+ curl_easy_setopt(handle, CURLOPT_ENCODING, "");
940
+ }
941
+
942
+ /* Configure debug mode: */
943
+ if (client_ptr->debug) {
944
+ curl_easy_setopt(handle, CURLOPT_VERBOSE, 1L);
945
+ curl_easy_setopt(handle, CURLOPT_DEBUGFUNCTION, ov_http_client_debug_function);
946
+ }
947
+
948
+ /* Configure the proxy: */
949
+ if (client_ptr->proxy_url != NULL) {
950
+ curl_easy_setopt(handle, CURLOPT_PROXY, client_ptr->proxy_url);
951
+ if (client_ptr->proxy_username != NULL && client_ptr->proxy_password != NULL) {
952
+ curl_easy_setopt(handle, CURLOPT_PROXYUSERNAME, client_ptr->proxy_username);
953
+ curl_easy_setopt(handle, CURLOPT_PROXYPASSWORD, client_ptr->proxy_password);
954
+ }
955
+ }
956
+
957
+ /* Configure callbacks: */
958
+ curl_easy_setopt(handle, CURLOPT_READFUNCTION, ov_http_client_read_function);
959
+ curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, ov_http_client_write_function);
960
+ curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, ov_http_client_header_function);
961
+
962
+ /* Build and set the URL: */
963
+ url = ov_http_client_build_url(request_ptr->url, request_ptr->query);
964
+ curl_easy_setopt(handle, CURLOPT_URL, StringValueCStr(url));
965
+
966
+ /* Set the method: */
967
+ if (rb_eql(request_ptr->method, POST_SYMBOL)) {
968
+ *headers = curl_slist_append(*headers, "Transfer-Encoding: chunked");
969
+ *headers = curl_slist_append(*headers, "Expect:");
970
+ curl_easy_setopt(handle, CURLOPT_POST, 1L);
971
+ }
972
+ else if (rb_eql(request_ptr->method, PUT_SYMBOL)) {
973
+ *headers = curl_slist_append(*headers, "Expect:");
974
+ curl_easy_setopt(handle, CURLOPT_UPLOAD, 1L);
975
+ curl_easy_setopt(handle, CURLOPT_PUT, 1L);
976
+ }
977
+ else if (rb_eql(request_ptr->method, DELETE_SYMBOL)) {
978
+ curl_easy_setopt(handle, CURLOPT_HTTPGET, 1L);
979
+ curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "DELETE");
980
+ }
981
+ else if (rb_eql(request_ptr->method, GET_SYMBOL)) {
982
+ curl_easy_setopt(handle, CURLOPT_HTTPGET, 1L);
983
+ }
984
+
985
+ /* Set authentication details: */
986
+ if (!NIL_P(request_ptr->token)) {
987
+ header = rb_sprintf("Authorization: Bearer %"PRIsVALUE"", request_ptr->token);
988
+ *headers = curl_slist_append(*headers, StringValueCStr(header));
989
+ }
990
+ else if (!NIL_P(request_ptr->username) && !NIL_P(request_ptr->password)) {
991
+ curl_easy_setopt(handle, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
992
+ curl_easy_setopt(handle, CURLOPT_USERNAME, StringValueCStr(request_ptr->username));
993
+ curl_easy_setopt(handle, CURLOPT_PASSWORD, StringValueCStr(request_ptr->password));
994
+ }
995
+ else if (RTEST(request_ptr->kerberos)) {
996
+ curl_easy_setopt(handle, CURLOPT_HTTPAUTH, CURLAUTH_NEGOTIATE);
997
+ }
998
+
999
+ /* Set the headers: */
1000
+ if (!NIL_P(request_ptr->headers)) {
1001
+ rb_hash_foreach(request_ptr->headers, ov_http_client_add_header, (VALUE) headers);
1002
+ }
1003
+ curl_easy_setopt(handle, CURLOPT_HTTPHEADER, *headers);
1004
+
1005
+ /* Send a summary of the request to the log: */
1006
+ ov_http_client_log_info(
1007
+ client_ptr->log,
1008
+ "Sending %"PRIsVALUE" request to URL '%"PRIsVALUE"'.",
1009
+ request_ptr->method,
1010
+ url
1011
+ );
1012
+ }
1013
+
1014
+ static VALUE ov_http_client_submit(VALUE self, VALUE request) {
1015
+ CURL* handle;
1016
+ VALUE response;
1017
+ VALUE transfer;
1018
+ ov_http_client_object* ptr;
1019
+ ov_http_request_object* request_ptr;
1020
+ ov_http_transfer_object* transfer_ptr;
1021
+ struct curl_slist* headers;
1022
+
1023
+ /* Get the pointer to the native object and check that it isn't closed: */
1024
+ ov_http_client_ptr(self, ptr);
1025
+ ov_http_client_check_closed(ptr);
1026
+
1027
+ /* Check the type of request and get the pointer to the native object: */
1028
+ if (NIL_P(request)) {
1029
+ rb_raise(ov_error_class, "The 'request' parameter can't be nil");
1030
+ }
1031
+ if (!rb_obj_is_instance_of(request, ov_http_request_class)) {
1032
+ rb_raise(ov_error_class, "The 'request' parameter isn't an instance of class 'HttpRequest'");
1033
+ }
1034
+ ov_http_request_ptr(request, request_ptr);
1035
+
1036
+ /* Create the libcurl easy handle: */
1037
+ handle = curl_easy_init();
1038
+ if (ptr->handle == NULL) {
1039
+ rb_raise(ov_error_class, "Can't create libcurl object");
1040
+ }
1041
+
1042
+ /* The headers used by the libcurl easy handle can't be released till the handle is released itself, so we need
1043
+ to initialize here, and add it to the context so that we can release it later: */
1044
+ headers = NULL;
1045
+
1046
+ /* Configure the libcurl easy handle with the data from the client and from the request: */
1047
+ ov_http_client_prepare_handle(ptr, request_ptr, &headers, handle);
1048
+
1049
+ /* Allocate a ne empty response: */
1050
+ response = rb_class_new_instance(0, NULL, ov_http_response_class);
1051
+
1052
+ /* Allocate a new empty transfer: */
1053
+ transfer = rb_class_new_instance(0, NULL, ov_http_transfer_class);
1054
+ ov_http_transfer_ptr(transfer, transfer_ptr);
1055
+ transfer_ptr->client = self;
1056
+ transfer_ptr->request = request;
1057
+ transfer_ptr->response = response;
1058
+ transfer_ptr->headers = headers;
1059
+ transfer_ptr->cancel = false;
1060
+ if (NIL_P(request_ptr->body)) {
1061
+ transfer_ptr->in = rb_class_new_instance(0, NULL, STRING_IO_CLASS);
1062
+ }
1063
+ else {
1064
+ transfer_ptr->in = rb_class_new_instance(1, &request_ptr->body, STRING_IO_CLASS);
1065
+ }
1066
+ transfer_ptr->out = rb_class_new_instance(0, NULL, STRING_IO_CLASS);
1067
+
1068
+ /* Put the request and the transfer in the hash of pending transfers: */
1069
+ rb_hash_aset(ptr->pending, request, transfer);
1070
+
1071
+ /* Set the transfer as the data for all the callbacks, so we can access it from any place where it is needed: */
1072
+ curl_easy_setopt(handle, CURLOPT_PRIVATE, transfer);
1073
+ curl_easy_setopt(handle, CURLOPT_READDATA, transfer);
1074
+ curl_easy_setopt(handle, CURLOPT_WRITEDATA, transfer);
1075
+ curl_easy_setopt(handle, CURLOPT_HEADERDATA, transfer);
1076
+ curl_easy_setopt(handle, CURLOPT_DEBUGDATA, transfer);
1077
+
1078
+ /* Add the easy handle to the multi handle: */
1079
+ curl_multi_add_handle(ptr->handle, handle);
1080
+
1081
+ return Qnil;
1082
+ }
1083
+
1084
+ static VALUE ov_http_client_send(VALUE self, VALUE request) {
1085
+ ov_http_client_object* ptr;
1086
+
1087
+ /* Get the pointer to the native object and check that it isn't closed: */
1088
+ ov_http_client_ptr(self, ptr);
1089
+ ov_http_client_check_closed(ptr);
1090
+
1091
+ /* If the limit hasn't been reached then submit the request directly to libcurl, otherwise put it in the queue: */
1092
+ if (RHASH_SIZE(ptr->pending) < ptr->limit) {
1093
+ ov_http_client_submit(self, request);
1094
+ }
1095
+ else {
1096
+ rb_ary_push(ptr->queue, request);
1097
+ }
1098
+
1099
+ return Qnil;
1100
+ }
1101
+
1102
+ static VALUE ov_http_client_wait(VALUE self, VALUE request) {
1103
+ VALUE next;
1104
+ VALUE result;
1105
+ ov_http_client_object* ptr;
1106
+ ov_http_client_wait_context context;
1107
+
1108
+ /* Get the pointer to the native object and check that it isn't closed: */
1109
+ ov_http_client_ptr(self, ptr);
1110
+ ov_http_client_check_closed(ptr);
1111
+
1112
+ /* Work till the transfer has been completed. */
1113
+ context.handle = ptr->handle;
1114
+ context.code = CURLE_OK;
1115
+ context.cancel = false;
1116
+ for (;;) {
1117
+ /* Move requests from the queue to libcurl: */
1118
+ while (RARRAY_LEN(ptr->queue) > 0 && RHASH_SIZE(ptr->pending) < ptr->limit) {
1119
+ next = rb_ary_shift(ptr->queue);
1120
+ ov_http_client_submit(self, next);
1121
+ }
1122
+
1123
+ /* Check if the response is already available, if so then return it: */
1124
+ result = rb_hash_delete(ptr->completed, request);
1125
+ if (!NIL_P(result)) {
1126
+ return result;
1127
+ }
1128
+
1129
+ /* If the response isn't available yet, then do some real work: */
1130
+ rb_thread_call_without_gvl(
1131
+ ov_http_client_wait_task,
1132
+ &context,
1133
+ ov_http_client_wait_cancel,
1134
+ &context
1135
+ );
1136
+ if (context.cancel) {
1137
+ return Qnil;
1138
+ }
1139
+ if (context.code != CURLE_OK) {
1140
+ rb_raise(ov_error_class, "Unexpected error while waiting: %s", curl_easy_strerror(context.code));
1141
+ }
1142
+ }
1143
+
1144
+ return Qnil;
1145
+ }
1146
+
1147
+ static VALUE ov_http_client_inspect(VALUE self) {
1148
+ ov_http_client_object* ptr;
1149
+
1150
+ ov_http_client_ptr(self, ptr);
1151
+ return rb_sprintf("#<%"PRIsVALUE":%p>", ov_http_client_class, ptr);
1152
+ }
1153
+
1154
+ void ov_http_client_define(void) {
1155
+ CURLcode code;
1156
+
1157
+ /* Load requirements: */
1158
+ rb_require("stringio");
1159
+ rb_require("uri");
1160
+
1161
+ /* Define the class: */
1162
+ ov_http_client_class = rb_define_class_under(ov_module, "HttpClient", rb_cObject);
1163
+
1164
+ /* Define the constructor: */
1165
+ rb_define_alloc_func(ov_http_client_class, ov_http_client_alloc);
1166
+ rb_define_method(ov_http_client_class, "initialize", ov_http_client_initialize, -1);
1167
+
1168
+ /* Define the methods: */
1169
+ rb_define_method(ov_http_client_class, "close", ov_http_client_close, 0);
1170
+ rb_define_method(ov_http_client_class, "inspect", ov_http_client_inspect, 0);
1171
+ rb_define_method(ov_http_client_class, "send", ov_http_client_send, 1);
1172
+ rb_define_method(ov_http_client_class, "to_s", ov_http_client_inspect, 0);
1173
+ rb_define_method(ov_http_client_class, "wait", ov_http_client_wait, 1);
1174
+
1175
+ /* Define the symbols: */
1176
+ CA_FILE_SYMBOL = ID2SYM(rb_intern("ca_file"));
1177
+ COMPRESS_SYMBOL = ID2SYM(rb_intern("compress"));
1178
+ CONNECTIONS_SYMBOL = ID2SYM(rb_intern("connections"));
1179
+ DEBUG_SYMBOL = ID2SYM(rb_intern("debug"));
1180
+ INSECURE_SYMBOL = ID2SYM(rb_intern("insecure"));
1181
+ LOG_SYMBOL = ID2SYM(rb_intern("log"));
1182
+ PASSWORD_SYMBOL = ID2SYM(rb_intern("password"));
1183
+ PIPELINE_SYMBOL = ID2SYM(rb_intern("pipeline"));
1184
+ PROXY_PASSWORD_SYMBOL = ID2SYM(rb_intern("proxy_password"));
1185
+ PROXY_URL_SYMBOL = ID2SYM(rb_intern("proxy_url"));
1186
+ PROXY_USERNAME_SYMBOL = ID2SYM(rb_intern("proxy_username"));
1187
+ TIMEOUT_SYMBOL = ID2SYM(rb_intern("timeout"));
1188
+ CONNECT_TIMEOUT_SYMBOL = ID2SYM(rb_intern("connect_timeout"));
1189
+ COOKIES_SYMBOL = ID2SYM(rb_intern("cookies"));
1190
+
1191
+ /* Define the method identifiers: */
1192
+ COMPARE_BY_IDENTITY_ID = rb_intern("compare_by_identity");
1193
+ DEBUG_ID = rb_intern("debug");
1194
+ DOWNCASE_ID = rb_intern("downcase");
1195
+ ENCODE_WWW_FORM_ID = rb_intern("encode_www_form");
1196
+ INFO_ID = rb_intern("info");
1197
+ INFO_Q_ID = rb_intern("info?");
1198
+ READ_ID = rb_intern("read");
1199
+ STRING_ID = rb_intern("string");
1200
+ STRING_IO_ID = rb_intern("StringIO");
1201
+ URI_ID = rb_intern("URI");
1202
+ WARN_ID = rb_intern("warn");
1203
+ WARN_Q_ID = rb_intern("warn?");
1204
+ WRITE_ID = rb_intern("write");
1205
+
1206
+ /* Locate classes: */
1207
+ STRING_IO_CLASS = rb_const_get(rb_cObject, STRING_IO_ID);
1208
+ URI_CLASS = rb_const_get(rb_cObject, URI_ID);
1209
+
1210
+ /* Initialize libcurl: */
1211
+ code = curl_global_init(CURL_GLOBAL_DEFAULT);
1212
+ if (code != CURLE_OK) {
1213
+ rb_raise(ov_error_class, "Can't initialize libcurl: %s", curl_easy_strerror(code));
1214
+ }
1215
+
1216
+ /* Get the libcurl version: */
1217
+ libcurl_version = curl_version_info(CURLVERSION_NOW);
1218
+ }