ovirt-engine-sdk 4.0.1 → 4.4.1

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.
@@ -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
+ }