ovirt-engine-sdk 4.0.2 → 4.0.3

Sign up to get free protection for your applications and to get access to all the features.
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
+ }