ovirt-engine-sdk 4.0.2 → 4.0.3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ecdf7ff085e2d14fdca2923dc0b77b35597c2af9
4
- data.tar.gz: 620d7620903f1c24346b49e6b36c334e9b18ec29
3
+ metadata.gz: a8ba693baeea875805f523cf377e72079f1c7bbe
4
+ data.tar.gz: 3374d80e560ba7d07c0dc779d3f92b0846176fdf
5
5
  SHA512:
6
- metadata.gz: 7f9d24ced354d42aa6f9e63b2122c59a898331d442742363f81345ecd3b96f02d3c5ac02de08e4d5534fe2651c6dea28dba7e5424c4047d20845dbb0d018e7dc
7
- data.tar.gz: 8a14c4b538b101f78eee8f36e1862777f9574bb2c1bcf29664cb5b65b506fff966f3bfa10392968cf4b9c665d746a20d802532b79c8696aa67833c013d647dd9
6
+ metadata.gz: bb03ab99d0d8e493c162d1bd15204f6139c45c78210234e7855d9e9a518e2ef75cdd385f9e902539f822e70e1e8fe519184225c6f266b40d4f2608899dec831e
7
+ data.tar.gz: 3f4546e1db35dc8a15214f67681b4fa4d472dd200c45275b4e2ab7d8ae73d7f3ae1e3647e047ad58d8b2e15a2dda00b0a12a9612bb0e68f1dd6152cbb5ecd792
@@ -2,6 +2,12 @@
2
2
 
3
3
  This document describes the relevant changes between releases of the SDK.
4
4
 
5
+ == 4.0.3 / Oct 7 2016
6
+
7
+ New features:
8
+
9
+ * Removed dependency on the `curb` gem.
10
+
5
11
  == 4.0.2 / Sep 29 2016
6
12
 
7
13
  New features:
@@ -25,4 +25,10 @@ end
25
25
  $CPPFLAGS = "#{`xml2-config --cflags`.strip} #{$CPPFLAGS}"
26
26
  $LDFLAGS = "#{`xml2-config --libs`.strip} #{$LDFLAGS}"
27
27
 
28
+ # Check that "libcurl" is available:
29
+ unless pkg_config('libcurl')
30
+ raise 'The "libcurl" package isn\'t available.'
31
+ end
32
+
33
+ # Create the Makefile:
28
34
  create_makefile 'ovirtsdk4c'
@@ -0,0 +1,680 @@
1
+ /*
2
+ Copyright (c) 2016 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 <stdbool.h>
23
+ #include <stdlib.h>
24
+ #include <string.h>
25
+
26
+ #include "ov_module.h"
27
+ #include "ov_error.h"
28
+ #include "ov_http_client.h"
29
+ #include "ov_http_request.h"
30
+ #include "ov_http_response.h"
31
+
32
+ /* Symbols: */
33
+ static VALUE CA_FILE_SYMBOL;
34
+ static VALUE COMPRESS_SYMBOL;
35
+ static VALUE DEBUG_SYMBOL;
36
+ static VALUE INSECURE_SYMBOL;
37
+ static VALUE LOG_SYMBOL;
38
+ static VALUE PASSWORD_SYMBOL;
39
+ static VALUE TIMEOUT_SYMBOL;
40
+ static VALUE USERNAME_SYMBOL;
41
+
42
+ /* Method identifiers: */
43
+ static ID DEBUG_ID;
44
+ static ID ENCODE_WWW_FORM_ID;
45
+ static ID READ_ID;
46
+ static ID STRING_ID;
47
+ static ID STRING_IO_ID;
48
+ static ID URI_ID;
49
+ static ID WRITE_ID;
50
+
51
+ /* References to classes: */
52
+ static VALUE URI_CLASS;
53
+ static VALUE STRING_IO_CLASS;
54
+
55
+ /* Constants: */
56
+ const char CR = '\x0D';
57
+ const char LF = '\x0A';
58
+
59
+ /* Before version 7.38.0 of libcurl the NEGOTIATE authentication method was named GSSNEGOTIATE: */
60
+ #ifndef CURLAUTH_NEGOTIATE
61
+ #define CURLAUTH_NEGOTIATE CURLAUTH_GSSNEGOTIATE
62
+ #endif
63
+
64
+ typedef struct {
65
+ CURL* curl;
66
+ VALUE log; /* Logger */
67
+ } ov_http_client_object;
68
+
69
+ typedef struct {
70
+ ov_http_client_object* object;
71
+ ov_http_response_object* response;
72
+ VALUE in; /* IO */
73
+ VALUE out; /* IO */
74
+ bool cancel;
75
+ CURLcode result;
76
+ } ov_http_client_perform_context;
77
+
78
+ typedef struct {
79
+ ov_http_client_object* object;
80
+ char* ptr;
81
+ size_t size;
82
+ size_t nmemb;
83
+ VALUE io; /* IO */
84
+ size_t result;
85
+ } ov_http_client_io_context;
86
+
87
+ typedef struct {
88
+ ov_http_response_object* response;
89
+ char* buffer;
90
+ size_t size;
91
+ size_t nitems;
92
+ size_t result;
93
+ } ov_http_client_header_context;
94
+
95
+ typedef struct {
96
+ ov_http_client_object* object;
97
+ curl_infotype type;
98
+ char* data;
99
+ size_t size;
100
+ } ov_http_client_debug_context;
101
+
102
+ static void ov_http_client_check_closed(ov_http_client_object* object) {
103
+ if (object->curl == NULL) {
104
+ rb_raise(ov_error_class, "The client is already closed");
105
+ }
106
+ }
107
+
108
+ static void ov_http_client_mark(ov_http_client_object *object) {
109
+ /* Nothing to mark. */
110
+ }
111
+
112
+ static void ov_http_client_free(ov_http_client_object *object) {
113
+ /* Release the resources used by libcurl. The callbacks need to be cleared before cleaning the libcurl handle
114
+ because libcurl calls them during the cleanup, and we can't call Ruby code from this method. */
115
+ if (object->curl != NULL) {
116
+ curl_easy_setopt(object->curl, CURLOPT_DEBUGFUNCTION, NULL);
117
+ curl_easy_setopt(object->curl, CURLOPT_WRITEFUNCTION, NULL);
118
+ curl_easy_setopt(object->curl, CURLOPT_HEADERFUNCTION, NULL);
119
+ curl_easy_cleanup(object->curl);
120
+ object->curl = NULL;
121
+ }
122
+
123
+ /* Free this object: */
124
+ xfree(object);
125
+ }
126
+
127
+ static VALUE ov_http_client_alloc(VALUE klass) {
128
+ ov_http_client_object* object;
129
+
130
+ object = ALLOC(ov_http_client_object);
131
+ object->curl = NULL;
132
+ object->log = Qnil;
133
+ return Data_Wrap_Struct(klass, ov_http_client_mark, ov_http_client_free, object);
134
+ }
135
+
136
+ static VALUE ov_http_client_close(VALUE self) {
137
+ ov_http_client_object* object;
138
+
139
+ /* Get the pointer to the native object and check that it isn't closed: */
140
+ Data_Get_Struct(self, ov_http_client_object, object);
141
+ ov_http_client_check_closed(object);
142
+
143
+ /* Release the resources used by libcurl. In this case we don't need to clear the callbacks in advance, because
144
+ we can safely call Ruby code from this method. */
145
+ curl_easy_cleanup(object->curl);
146
+ object->curl = NULL;
147
+
148
+ return Qnil;
149
+ }
150
+
151
+ static void* ov_http_client_read_task(void* data) {
152
+ VALUE bytes;
153
+ VALUE count;
154
+ ov_http_client_io_context* io_context = (ov_http_client_io_context*) data;
155
+
156
+ /* Read the data using the "read" method and write the raw bytes to the buffer provided by libcurl: */
157
+ count = INT2NUM(io_context->size * io_context->nmemb);
158
+ bytes = rb_funcall(io_context->io, READ_ID, 1, count);
159
+ if (NIL_P(bytes)) {
160
+ io_context->result = 0;
161
+ }
162
+ else {
163
+ io_context->result = RSTRING_LEN(bytes);
164
+ memcpy(io_context->ptr, StringValuePtr(bytes), io_context->result);
165
+ }
166
+
167
+ return NULL;
168
+ }
169
+
170
+ static size_t ov_http_client_read_function(char *ptr, size_t size, size_t nmemb, void *userdata) {
171
+ ov_http_client_perform_context* perform_context = (ov_http_client_perform_context*) userdata;
172
+ ov_http_client_io_context io_context;
173
+
174
+ /* Check if the operation has been cancelled, and return immediately, this will cause the perform method to
175
+ return an error to the caller: */
176
+ if (perform_context->cancel) {
177
+ return CURL_READFUNC_ABORT;
178
+ }
179
+
180
+ /* Execute the read with the global interpreter lock acquired, as it needs to call Ruby methods: */
181
+ io_context.object = perform_context->object;
182
+ io_context.ptr = ptr;
183
+ io_context.size = size;
184
+ io_context.nmemb = nmemb;
185
+ io_context.io = perform_context->in;
186
+ rb_thread_call_with_gvl(ov_http_client_read_task, &io_context);
187
+ return io_context.result;
188
+ }
189
+
190
+ static void* ov_http_client_write_task(void* data) {
191
+ VALUE bytes;
192
+ VALUE count;
193
+ ov_http_client_io_context* io_context = (ov_http_client_io_context*) data;
194
+
195
+ /* Convert the buffer to a Ruby string and write it to the IO object, using the "write" method: */
196
+ bytes = rb_str_new(io_context->ptr, io_context->size * io_context->nmemb);
197
+ count = rb_funcall(io_context->io, WRITE_ID, 1, bytes);
198
+ io_context->result = NUM2INT(count);
199
+
200
+ return NULL;
201
+ }
202
+
203
+ static size_t ov_http_client_write_function(char *ptr, size_t size, size_t nmemb, void *userdata) {
204
+ ov_http_client_perform_context* perform_context = (ov_http_client_perform_context*) userdata;
205
+ ov_http_client_io_context io_context;
206
+
207
+ /* Check if the operation has been cancelled, and return immediately, this will cause the perform method to
208
+ return an error to the caller: */
209
+ if (perform_context->cancel) {
210
+ return 0;
211
+ }
212
+
213
+ /* Execute the write with the global interpreter lock acquired, as it needs to call Ruby methods: */
214
+ io_context.object = perform_context->object;
215
+ io_context.ptr = ptr;
216
+ io_context.size = size;
217
+ io_context.nmemb = nmemb;
218
+ io_context.io = perform_context->out;
219
+ rb_thread_call_with_gvl(ov_http_client_write_task, &io_context);
220
+ return io_context.result;
221
+ }
222
+
223
+ static void* ov_http_client_header_task(void* data) {
224
+ VALUE name;
225
+ VALUE value;
226
+ char* buffer;
227
+ char* pointer;
228
+ size_t length;
229
+ ov_http_client_header_context* header_context = (ov_http_client_header_context*) data;
230
+
231
+ /* We should always tell the library that we processed all the data: */
232
+ header_context->result = header_context->size * header_context->nitems;
233
+
234
+ /* Remove trailing white space: */
235
+ length = header_context->result;
236
+ buffer = header_context->buffer;
237
+ while (length > 0 && isspace(buffer[length - 1])) {
238
+ length--;
239
+ }
240
+
241
+ /* Parse the header and add it to the response object: */
242
+ pointer = memchr(buffer, ':', length);
243
+ if (pointer != NULL) {
244
+ name = rb_str_new(buffer, pointer - buffer);
245
+ pointer++;
246
+ while (pointer - buffer < length && isspace(*pointer)) {
247
+ pointer++;
248
+ }
249
+ value = rb_str_new(pointer, length - (pointer - buffer));
250
+ rb_hash_aset(header_context->response->headers, name, value);
251
+ }
252
+
253
+ return NULL;
254
+ }
255
+
256
+ static size_t ov_http_client_header_function(char *buffer, size_t size, size_t nitems, void *userdata) {
257
+ ov_http_client_header_context header_context;
258
+ ov_http_client_perform_context* perform_context = (ov_http_client_perform_context*) userdata;
259
+
260
+ /* Check if the operation has been cancelled, and return immediately, this will cause the perform method to
261
+ return an error to the caller: */
262
+ if (perform_context->cancel) {
263
+ return 0;
264
+ }
265
+
266
+ /* Parse the header with the global intepreter lock acquired, as it needs to call Ruby methods: */
267
+ header_context.response = perform_context->response;
268
+ header_context.buffer = buffer;
269
+ header_context.size = size;
270
+ header_context.nitems = nitems;
271
+ rb_thread_call_with_gvl(ov_http_client_header_task, &header_context);
272
+ return header_context.result;
273
+ }
274
+
275
+ static void* ov_http_client_debug_task(void* data) {
276
+ VALUE line;
277
+ VALUE log;
278
+ int c;
279
+ char* text;
280
+ ov_http_client_debug_context* debug_context = (ov_http_client_debug_context*) data;
281
+ size_t i;
282
+ size_t j;
283
+ size_t size;
284
+ int s;
285
+
286
+ /* Do nothing if there is no log: */
287
+ log = debug_context->object->log;
288
+ if (NIL_P(log)) {
289
+ return NULL;
290
+ }
291
+
292
+ /* Split the text into lines, and send a debug message for each line: */
293
+ text = debug_context->data;
294
+ size = debug_context->size;
295
+ i = 0;
296
+ s = 0;
297
+ for (j = 0; j <= size; j++) {
298
+ c = j < size? text[j]: -1;
299
+ switch (s) {
300
+ case 0:
301
+ if (c == CR || c == LF || c == -1) {
302
+ line = rb_str_new(text + i, j - i);
303
+ rb_funcall(log, DEBUG_ID, 1, line);
304
+ i = j + 1;
305
+ s = 1;
306
+ }
307
+ break;
308
+ case 1:
309
+ if (c == CR || c == LF || c == -1) {
310
+ i++;
311
+ }
312
+ else {
313
+ s = 0;
314
+ }
315
+ break;
316
+ }
317
+ }
318
+
319
+ return NULL;
320
+ }
321
+
322
+ static int ov_http_client_debug_function(CURL* handle, curl_infotype type, char* data, size_t size, void* userptr) {
323
+ ov_http_client_debug_context debug_context;
324
+ ov_http_client_perform_context* perform_context = (ov_http_client_perform_context*) userptr;
325
+
326
+ /* Execute the debug code with the global interpreter lock acquired, as it needs to call Ruby methods: */
327
+ debug_context.object = perform_context->object;
328
+ debug_context.type = type;
329
+ debug_context.data = data;
330
+ debug_context.size = size;
331
+ rb_thread_call_with_gvl(ov_http_client_debug_task, &debug_context);
332
+ return 0;
333
+ }
334
+
335
+ static VALUE ov_http_client_initialize(int argc, VALUE* argv, VALUE self) {
336
+ VALUE opt;
337
+ VALUE opts;
338
+ bool compress;
339
+ bool debug;
340
+ bool insecure;
341
+ char* ca_file;
342
+ int timeout;
343
+ ov_http_client_object* object;
344
+
345
+ /* Get the pointer to the native object: */
346
+ Data_Get_Struct(self, ov_http_client_object, object);
347
+
348
+ /* Check the number of arguments: */
349
+ if (argc > 1) {
350
+ rb_raise(ov_error_class, "Expected at most one argument, 'opts', but received %d", argc);
351
+ }
352
+ opts = argc > 0? argv[0]: Qnil;
353
+ if (NIL_P(opts)) {
354
+ opts = rb_hash_new();
355
+ }
356
+ else {
357
+ Check_Type(opts, T_HASH);
358
+ }
359
+
360
+ /* Get the value of the 'insecure' parameter: */
361
+ opt = rb_hash_aref(opts, INSECURE_SYMBOL);
362
+ if (NIL_P(opt)) {
363
+ insecure = false;
364
+ }
365
+ else {
366
+ insecure = RTEST(opt);
367
+ }
368
+
369
+ /* Get the value of the 'ca_file' parameter: */
370
+ opt = rb_hash_aref(opts, CA_FILE_SYMBOL);
371
+ if (NIL_P(opt)) {
372
+ ca_file = NULL;
373
+ }
374
+ else {
375
+ Check_Type(opt, T_STRING);
376
+ ca_file = StringValueCStr(opt);
377
+ }
378
+
379
+ /* Get the value of the 'debug' parameter: */
380
+ opt = rb_hash_aref(opts, DEBUG_SYMBOL);
381
+ if (NIL_P(opt)) {
382
+ debug = false;
383
+ }
384
+ else {
385
+ debug = RTEST(opt);
386
+ }
387
+
388
+ /* Get the value of the 'log' parameter: */
389
+ opt = rb_hash_aref(opts, LOG_SYMBOL);
390
+ if (NIL_P(opt)) {
391
+ object->log = Qnil;
392
+ }
393
+ else {
394
+ object->log = opt;
395
+ }
396
+
397
+ /* Get the value of the 'timeout' parameter: */
398
+ opt = rb_hash_aref(opts, TIMEOUT_SYMBOL);
399
+ if (NIL_P(opt)) {
400
+ timeout = 0;
401
+ }
402
+ else {
403
+ Check_Type(opt, T_FIXNUM);
404
+ timeout = NUM2INT(opt);
405
+ }
406
+
407
+ /* Get the value of the 'compress' parameter: */
408
+ opt = rb_hash_aref(opts, COMPRESS_SYMBOL);
409
+ if (NIL_P(opt)) {
410
+ compress = false;
411
+ }
412
+ else {
413
+ compress = RTEST(opt);
414
+ }
415
+
416
+ /* Create the libcurl object: */
417
+ object->curl = curl_easy_init();
418
+ if (object->curl == NULL) {
419
+ rb_raise(ov_error_class, "Can't create libcurl object");
420
+ }
421
+
422
+ /* Configure TLS parameters: */
423
+ if (insecure) {
424
+ curl_easy_setopt(object->curl, CURLOPT_SSL_VERIFYPEER, 0L);
425
+ curl_easy_setopt(object->curl, CURLOPT_SSL_VERIFYHOST, 0L);
426
+ }
427
+ if (ca_file != NULL) {
428
+ curl_easy_setopt(object->curl, CURLOPT_CAINFO, ca_file);
429
+ }
430
+
431
+
432
+ /* Configure the timeout: */
433
+ curl_easy_setopt(object->curl, CURLOPT_TIMEOUT, timeout);
434
+
435
+ /* Configure compression of responses (setting the value to zero length string means accepting all the
436
+ compression types that libcurl supports): */
437
+ if (compress) {
438
+ curl_easy_setopt(object->curl, CURLOPT_ENCODING, "");
439
+ }
440
+
441
+ /* Configure debug mode: */
442
+ if (debug) {
443
+ curl_easy_setopt(object->curl, CURLOPT_VERBOSE, 1L);
444
+ curl_easy_setopt(object->curl, CURLOPT_DEBUGFUNCTION, ov_http_client_debug_function);
445
+ }
446
+
447
+ /* Configure callbacks: */
448
+ curl_easy_setopt(object->curl, CURLOPT_READFUNCTION, ov_http_client_read_function);
449
+ curl_easy_setopt(object->curl, CURLOPT_WRITEFUNCTION, ov_http_client_write_function);
450
+ curl_easy_setopt(object->curl, CURLOPT_HEADERFUNCTION, ov_http_client_header_function);
451
+
452
+ return self;
453
+ }
454
+
455
+ static VALUE ov_http_client_build_url(VALUE self, VALUE url, VALUE query) {
456
+ ov_http_client_object* object;
457
+
458
+ /* Get the pointer to the native object and check that it isn't closed: */
459
+ Data_Get_Struct(self, ov_http_client_object, object);
460
+ ov_http_client_check_closed(object);
461
+
462
+ /* Copy the URL: */
463
+ if (NIL_P(url)) {
464
+ rb_raise(ov_error_class, "The 'url' parameter can't be nil");
465
+ }
466
+ Check_Type(url, T_STRING);
467
+
468
+ /* Append the query: */
469
+ if (!NIL_P(query)) {
470
+ Check_Type(query, T_HASH);
471
+ if (RHASH_SIZE(query) > 0) {
472
+ url = rb_sprintf("%"PRIsVALUE"?%"PRIsVALUE"", url, rb_funcall(URI_CLASS, ENCODE_WWW_FORM_ID, 1, query));
473
+ }
474
+ }
475
+
476
+ return url;
477
+ }
478
+
479
+ static int ov_http_client_add_header(VALUE name, VALUE value, struct curl_slist** headers) {
480
+ VALUE header = Qnil;
481
+
482
+ header = rb_sprintf("%"PRIsVALUE": %"PRIsVALUE"", name, value);
483
+ *headers = curl_slist_append(*headers, StringValueCStr(header));
484
+
485
+ return ST_CONTINUE;
486
+ }
487
+
488
+ static void* ov_http_client_perform_task(void* data) {
489
+ ov_http_client_perform_context* perform_context = (ov_http_client_perform_context*) data;
490
+
491
+ /* Call the libcurl 'perform' method, and store the result in the context: */
492
+ perform_context->result = curl_easy_perform(perform_context->object->curl);
493
+
494
+ return NULL;
495
+ }
496
+
497
+ static void ov_http_client_perform_cancel(void* data) {
498
+ ov_http_client_perform_context* perform_context = (ov_http_client_perform_context*) data;
499
+
500
+ /* Set the cancel flag so that the operation will be actually aborted the next time that libcurl calls the write
501
+ function: */
502
+ perform_context->cancel = true;
503
+ }
504
+
505
+ static VALUE ov_http_client_send(VALUE self, VALUE request, VALUE response) {
506
+ VALUE header;
507
+ VALUE url;
508
+ long response_code;
509
+ ov_http_client_object* object;
510
+ ov_http_client_perform_context perform_context;
511
+ ov_http_request_object* request_object;
512
+ ov_http_response_object* response_object;
513
+ struct curl_slist* headers;
514
+
515
+ /* Get the pointer to the native object and check that it isn't closed: */
516
+ Data_Get_Struct(self, ov_http_client_object, object);
517
+ ov_http_client_check_closed(object);
518
+
519
+ /* Check the type of request and get the pointer to the native object: */
520
+ if (NIL_P(request)) {
521
+ rb_raise(ov_error_class, "The 'request' parameter can't be nil");
522
+ }
523
+ if (!rb_obj_is_instance_of(request, ov_http_request_class)) {
524
+ rb_raise(ov_error_class, "The 'request' parameter isn't an instance of class 'HttpRequest'");
525
+ }
526
+ Data_Get_Struct(request, ov_http_request_object, request_object);
527
+
528
+ /* Check the type of response and get the pointer to the native object: */
529
+ if (NIL_P(response)) {
530
+ rb_raise(ov_error_class, "The 'response' parameter can't be nil");
531
+ }
532
+ if (!rb_obj_is_instance_of(response, ov_http_response_class)) {
533
+ rb_raise(ov_error_class, "The 'response' parameter isn't an instance of class 'HttpResponse'");
534
+ }
535
+ Data_Get_Struct(response, ov_http_response_object, response_object);
536
+
537
+ /* Build and set the URL: */
538
+ url = ov_http_client_build_url(self, request_object->url, request_object->query);
539
+ curl_easy_setopt(object->curl, CURLOPT_URL, StringValueCStr(url));
540
+
541
+ /* Initialize the list of headers: */
542
+ headers = NULL;
543
+
544
+ /* Set the method: */
545
+ if (rb_eql(request_object->method, POST_SYMBOL)) {
546
+ headers = curl_slist_append(headers, "Transfer-Encoding: chunked");
547
+ headers = curl_slist_append(headers, "Expect:");
548
+ curl_easy_setopt(object->curl, CURLOPT_POST, 1L);
549
+ }
550
+ else if (rb_eql(request_object->method, PUT_SYMBOL)) {
551
+ curl_easy_setopt(object->curl, CURLOPT_UPLOAD, 1L);
552
+ curl_easy_setopt(object->curl, CURLOPT_PUT, 1L);
553
+ }
554
+ else if (rb_eql(request_object->method, DELETE_SYMBOL)) {
555
+ curl_easy_setopt(object->curl, CURLOPT_HTTPGET, 1L);
556
+ curl_easy_setopt(object->curl, CURLOPT_CUSTOMREQUEST, "DELETE");
557
+ }
558
+ else if (rb_eql(request_object->method, GET_SYMBOL)) {
559
+ curl_easy_setopt(object->curl, CURLOPT_HTTPGET, 1L);
560
+ }
561
+
562
+ /* Set authentication details: */
563
+ if (!NIL_P(request_object->token)) {
564
+ header = rb_sprintf("Authorization: Bearer %"PRIsVALUE"", request_object->token);
565
+ headers = curl_slist_append(headers, StringValueCStr(header));
566
+ }
567
+ else if (!NIL_P(request_object->username) && !NIL_P(request_object->password)) {
568
+ curl_easy_setopt(object->curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
569
+ curl_easy_setopt(object->curl, CURLOPT_USERNAME, StringValueCStr(request_object->username));
570
+ curl_easy_setopt(object->curl, CURLOPT_PASSWORD, StringValueCStr(request_object->password));
571
+ }
572
+ else if (RTEST(request_object->kerberos)) {
573
+ curl_easy_setopt(object->curl, CURLOPT_HTTPAUTH, CURLAUTH_NEGOTIATE);
574
+ }
575
+
576
+ /* Set the headers: */
577
+ if (!NIL_P(request_object->headers)) {
578
+ rb_hash_foreach(request_object->headers, ov_http_client_add_header, (VALUE) &headers);
579
+ }
580
+ curl_easy_setopt(object->curl, CURLOPT_HTTPHEADER, headers);
581
+
582
+ /* Performing the request is a potentially lengthy and blocking operation, so we need to make sure that it runs
583
+ without the global interpreter lock acquired as much as possible: */
584
+ perform_context.object = object;
585
+ perform_context.response = response_object;
586
+ perform_context.cancel = false;
587
+ if (NIL_P(request_object->body)) {
588
+ perform_context.in = rb_class_new_instance(0, NULL, STRING_IO_CLASS);
589
+ }
590
+ else {
591
+ perform_context.in = rb_class_new_instance(1, &request_object->body, STRING_IO_CLASS);
592
+ }
593
+ perform_context.out = rb_class_new_instance(0, NULL, STRING_IO_CLASS);
594
+ curl_easy_setopt(object->curl, CURLOPT_READDATA, &perform_context);
595
+ curl_easy_setopt(object->curl, CURLOPT_WRITEDATA, &perform_context);
596
+ curl_easy_setopt(object->curl, CURLOPT_HEADERDATA, &perform_context);
597
+ curl_easy_setopt(object->curl, CURLOPT_DEBUGDATA, &perform_context);
598
+ rb_thread_call_without_gvl(
599
+ ov_http_client_perform_task,
600
+ &perform_context,
601
+ ov_http_client_perform_cancel,
602
+ &perform_context
603
+ );
604
+
605
+ /* Free the headers and clear the libcurl object, regardless of the result of the request: */
606
+ curl_slist_free_all(headers);
607
+ curl_easy_setopt(object->curl, CURLOPT_URL, NULL);
608
+ curl_easy_setopt(object->curl, CURLOPT_READDATA, NULL);
609
+ curl_easy_setopt(object->curl, CURLOPT_WRITEDATA, NULL);
610
+ curl_easy_setopt(object->curl, CURLOPT_HEADERDATA, NULL);
611
+ curl_easy_setopt(object->curl, CURLOPT_DEBUGDATA, NULL);
612
+ curl_easy_setopt(object->curl, CURLOPT_CUSTOMREQUEST, NULL);
613
+ curl_easy_setopt(object->curl, CURLOPT_UPLOAD, 0L);
614
+ curl_easy_setopt(object->curl, CURLOPT_HTTPAUTH, 0L);
615
+ curl_easy_setopt(object->curl, CURLOPT_USERNAME, "");
616
+ curl_easy_setopt(object->curl, CURLOPT_PASSWORD, "");
617
+
618
+ /* Check the result of the request: */
619
+ if (perform_context.result != CURLE_OK) {
620
+ rb_raise(ov_error_class, "Can't send request: %s", curl_easy_strerror(perform_context.result));
621
+ }
622
+
623
+ /* Get the response code: */
624
+ curl_easy_getinfo(object->curl, CURLINFO_RESPONSE_CODE, &response_code);
625
+ response_object->code = LONG2NUM(response_code);
626
+
627
+ /* Get the response body: */
628
+ response_object->body = rb_funcall(perform_context.out, STRING_ID, 0);
629
+
630
+ return Qnil;
631
+ }
632
+
633
+ void ov_http_client_define(void) {
634
+ CURLcode code;
635
+
636
+ /* Load requirements: */
637
+ rb_require("stringio");
638
+ rb_require("uri");
639
+
640
+ /* Define the class: */
641
+ ov_http_client_class = rb_define_class_under(ov_module, "HttpClient", rb_cObject);
642
+
643
+ /* Define the constructor: */
644
+ rb_define_alloc_func(ov_http_client_class, ov_http_client_alloc);
645
+ rb_define_method(ov_http_client_class, "initialize", ov_http_client_initialize, -1);
646
+
647
+ /* Define the methods: */
648
+ rb_define_method(ov_http_client_class, "build_url", ov_http_client_build_url, 2);
649
+ rb_define_method(ov_http_client_class, "close", ov_http_client_close, 0);
650
+ rb_define_method(ov_http_client_class, "send", ov_http_client_send, 2);
651
+
652
+ /* Define the symbols: */
653
+ USERNAME_SYMBOL = ID2SYM(rb_intern("username"));
654
+ PASSWORD_SYMBOL = ID2SYM(rb_intern("password"));
655
+ INSECURE_SYMBOL = ID2SYM(rb_intern("insecure"));
656
+ CA_FILE_SYMBOL = ID2SYM(rb_intern("ca_file"));
657
+ DEBUG_SYMBOL = ID2SYM(rb_intern("debug"));
658
+ LOG_SYMBOL = ID2SYM(rb_intern("log"));
659
+ COMPRESS_SYMBOL = ID2SYM(rb_intern("compress"));
660
+ TIMEOUT_SYMBOL = ID2SYM(rb_intern("timeout"));
661
+
662
+ /* Define the method identifiers: */
663
+ DEBUG_ID = rb_intern("debug");
664
+ ENCODE_WWW_FORM_ID = rb_intern("encode_www_form");
665
+ READ_ID = rb_intern("read");
666
+ STRING_ID = rb_intern("string");
667
+ STRING_IO_ID = rb_intern("StringIO");
668
+ URI_ID = rb_intern("URI");
669
+ WRITE_ID = rb_intern("write");
670
+
671
+ /* Locate classes: */
672
+ STRING_IO_CLASS = rb_const_get(rb_cObject, STRING_IO_ID);
673
+ URI_CLASS = rb_const_get(rb_cObject, URI_ID);
674
+
675
+ /* Initialize libcurl: */
676
+ code = curl_global_init(CURL_GLOBAL_DEFAULT);
677
+ if (code != CURLE_OK) {
678
+ rb_raise(ov_error_class, "Can't initialize libcurl: %s", curl_easy_strerror(code));
679
+ }
680
+ }