ovirt-engine-sdk 4.1.5 → 4.1.6

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: 198793270c4f7de28a7676f4cd84785a1f3b8c66
4
- data.tar.gz: 29f34047600c900691d031141b4dc7e2241a9df7
3
+ metadata.gz: e8d3c58ff70eecb169d5103a4eeb2f7f9fed5a77
4
+ data.tar.gz: 302c8ab4c8c915eac19558b9813f91831d634687
5
5
  SHA512:
6
- metadata.gz: c294f903298a1558ee829b2589019928b3b05a2e91b337116186a853b088f335f33e15c80ca8c4a5a9a1c0af07cca36440ce76b011fdeb23d06aa72b5620f069
7
- data.tar.gz: d6a3bc588c2e0e91053b82d1053bd95b37ac1b4fea3167d81a6bfb31fec14069483271f9b46f45b5b460524fa3974a30705260a05560c4edb9d7f23232706545
6
+ metadata.gz: 508ab6b1baa739ef1837e0ca7548075d2874d98b4ba887ab1e2ffcc85c60370516d4a7e7e7c67ffd2a49bc669eeaa5a8581c98989d0611ad7a0d3372eefaac0a
7
+ data.tar.gz: 098cec88d486935c3b5aecb9b2435cd669f9369a700ca197824d7ce81e211d249335919aa87ad9144aff21e41bb752e785139fbd837cb422589133b868ea83f1
data/CHANGES.adoc CHANGED
@@ -2,6 +2,43 @@
2
2
 
3
3
  This document describes the relevant changes between releases of the SDK.
4
4
 
5
+ == 4.1.6 / May 31 2017
6
+
7
+ Update to model 4.1.35:
8
+
9
+ * Replace generic assigned networks services with services specific to
10
+ the type of object that they are assigned to, in particular data
11
+ centers and clusters.
12
+
13
+ * Add `driver` attribute to `HostDevice` type.
14
+
15
+ * Add common concepts document.
16
+
17
+ * Add appendix containing changes from version 3 to version 4 of
18
+ the API.
19
+
20
+ * Add `readOnly` attribute to the `DiskAttachment` type.
21
+
22
+ * Fix the type of the `Host.nics` link. It should be of type
23
+ `HostNic[]`, not `Nic[]`.
24
+
25
+ New features:
26
+
27
+ * Add support for asynchronous requests.
28
+
29
+ * Automatically replace bad tokens
30
+ https://bugzilla.redhat.com/1434831[#1434831].
31
+
32
+ * Improve error message for wrong content type
33
+ https://bugzilla.redhat.com/1440292[#1440292].
34
+
35
+ * Add `Error.code`
36
+ https://bugzilla.redhat.com/1443420[#1443420].
37
+
38
+ * Add `Error.fault`.
39
+
40
+ * Add support for request timeout.
41
+
5
42
  == 4.1.5 / Mar 15 2017
6
43
 
7
44
  Update to model 4.1.33:
@@ -1,5 +1,5 @@
1
1
  /*
2
- Copyright (c) 2016 Red Hat, Inc.
2
+ Copyright (c) 2016-2017 Red Hat, Inc.
3
3
 
4
4
  Licensed under the Apache License, Version 2.0 (the "License");
5
5
  you may not use this file except in compliance with the License.
@@ -26,9 +26,11 @@ limitations under the License.
26
26
 
27
27
  #include "ov_module.h"
28
28
  #include "ov_error.h"
29
+ #include "ov_string.h"
29
30
  #include "ov_http_client.h"
30
31
  #include "ov_http_request.h"
31
32
  #include "ov_http_response.h"
33
+ #include "ov_http_transfer.h"
32
34
 
33
35
  /* Class: */
34
36
  VALUE ov_http_client_class;
@@ -36,18 +38,22 @@ VALUE ov_http_client_class;
36
38
  /* Symbols: */
37
39
  static VALUE CA_FILE_SYMBOL;
38
40
  static VALUE COMPRESS_SYMBOL;
41
+ static VALUE CONNECTIONS_SYMBOL;
39
42
  static VALUE DEBUG_SYMBOL;
40
43
  static VALUE INSECURE_SYMBOL;
41
44
  static VALUE LOG_SYMBOL;
42
45
  static VALUE PASSWORD_SYMBOL;
43
- static VALUE TIMEOUT_SYMBOL;
44
- static VALUE USERNAME_SYMBOL;
46
+ static VALUE PIPELINE_SYMBOL;
47
+ static VALUE PROXY_PASSWORD_SYMBOL;
45
48
  static VALUE PROXY_URL_SYMBOL;
46
49
  static VALUE PROXY_USERNAME_SYMBOL;
47
- static VALUE PROXY_PASSWORD_SYMBOL;
50
+ static VALUE TIMEOUT_SYMBOL;
51
+ static VALUE USERNAME_SYMBOL;
48
52
 
49
53
  /* Method identifiers: */
54
+ static ID COMPARE_BY_IDENTITY_ID;
50
55
  static ID DEBUG_ID;
56
+ static ID DOWNCASE_ID;
51
57
  static ID ENCODE_WWW_FORM_ID;
52
58
  static ID INFO_ID;
53
59
  static ID INFO_Q_ID;
@@ -70,31 +76,22 @@ const char LF = '\x0A';
70
76
  #define CURLAUTH_NEGOTIATE CURLAUTH_GSSNEGOTIATE
71
77
  #endif
72
78
 
73
- typedef struct {
74
- CURL* curl;
75
- VALUE log; /* Logger */
76
- } ov_http_client_object;
77
-
78
- typedef struct {
79
- ov_http_client_object* object;
80
- ov_http_response_object* response;
81
- VALUE in; /* IO */
82
- VALUE out; /* IO */
83
- bool cancel;
84
- CURLcode result;
85
- } ov_http_client_perform_context;
79
+ /* Before version 7.43.0 of libcurl the CURLPIPE_HTTP1 constant didn't exist, the value 1 was used
80
+ directly instead: */
81
+ #ifndef CURLPIPE_HTTP1
82
+ #define CURLPIPE_HTTP1 1
83
+ #endif
86
84
 
87
85
  typedef struct {
88
- ov_http_client_object* object;
86
+ VALUE io; /* IO */
89
87
  char* ptr;
90
88
  size_t size;
91
89
  size_t nmemb;
92
- VALUE io; /* IO */
93
90
  size_t result;
94
91
  } ov_http_client_io_context;
95
92
 
96
93
  typedef struct {
97
- ov_http_response_object* response;
94
+ VALUE response; /* HttpResponse */
98
95
  char* buffer;
99
96
  size_t size;
100
97
  size_t nitems;
@@ -102,57 +99,104 @@ typedef struct {
102
99
  } ov_http_client_header_context;
103
100
 
104
101
  typedef struct {
105
- ov_http_client_object* object;
102
+ VALUE client; /* HttpClient */
106
103
  curl_infotype type;
107
104
  char* data;
108
105
  size_t size;
109
106
  } ov_http_client_debug_context;
110
107
 
108
+ typedef struct {
109
+ CURLM* handle;
110
+ CURLcode code;
111
+ bool cancel;
112
+ } ov_http_client_wait_context;
113
+
114
+
115
+ static void ov_http_client_log_info(VALUE log, const char* format, ...) {
116
+ VALUE enabled;
117
+ VALUE message;
118
+ va_list args;
119
+
120
+ if (!NIL_P(log)) {
121
+ enabled = rb_funcall(log, INFO_Q_ID, 0);
122
+ if (RTEST(enabled)) {
123
+ va_start(args, format);
124
+ message = rb_vsprintf(format, args);
125
+ rb_funcall(log, INFO_ID, 1, message);
126
+ va_end(args);
127
+ }
128
+ }
129
+ }
130
+
111
131
  static void ov_http_client_check_closed(ov_http_client_object* object) {
112
- if (object->curl == NULL) {
132
+ if (object->handle == NULL) {
113
133
  rb_raise(ov_error_class, "The client is already closed");
114
134
  }
115
135
  }
116
136
 
117
- static void ov_http_client_mark(ov_http_client_object *object) {
118
- /* Nothing to mark. */
137
+ static void ov_http_client_mark(void* vptr) {
138
+ ov_http_client_object* ptr;
139
+
140
+ ptr = vptr;
141
+ rb_gc_mark(ptr->log);
142
+ rb_gc_mark(ptr->pending);
143
+ rb_gc_mark(ptr->completed);
119
144
  }
120
145
 
121
- static void ov_http_client_free(ov_http_client_object *object) {
122
- /* Release the resources used by libcurl. The callbacks need to be cleared before cleaning the libcurl handle
123
- because libcurl calls them during the cleanup, and we can't call Ruby code from this method. */
124
- if (object->curl != NULL) {
125
- curl_easy_setopt(object->curl, CURLOPT_DEBUGFUNCTION, NULL);
126
- curl_easy_setopt(object->curl, CURLOPT_WRITEFUNCTION, NULL);
127
- curl_easy_setopt(object->curl, CURLOPT_HEADERFUNCTION, NULL);
128
- curl_easy_cleanup(object->curl);
129
- object->curl = NULL;
146
+ static void ov_http_client_free(void* vptr) {
147
+ ov_http_client_object* ptr;
148
+
149
+ /* Get the pointer to the object: */
150
+ ptr = vptr;
151
+
152
+ /* Release the resources used by libcurl: */
153
+ if (ptr->handle != NULL) {
154
+ curl_multi_cleanup(ptr->handle);
155
+ ptr->handle = NULL;
130
156
  }
131
157
 
158
+ /* Free the strings: */
159
+ ov_string_free(ptr->ca_file);
160
+ ov_string_free(ptr->proxy_url);
161
+ ov_string_free(ptr->proxy_username);
162
+ ov_string_free(ptr->proxy_password);
163
+
132
164
  /* Free this object: */
133
- xfree(object);
165
+ xfree(ptr);
134
166
  }
135
167
 
168
+ rb_data_type_t ov_http_client_type = {
169
+ .wrap_struct_name = "OVHTTPCLIENT",
170
+ .function = {
171
+ .dmark = ov_http_client_mark,
172
+ .dfree = ov_http_client_free,
173
+ .dsize = NULL,
174
+ .reserved = { NULL, NULL }
175
+ },
176
+ #ifdef RUBY_TYPED_FREE_IMMEDIATELY
177
+ .parent = NULL,
178
+ .data = NULL,
179
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY,
180
+ #endif
181
+ };
182
+
136
183
  static VALUE ov_http_client_alloc(VALUE klass) {
137
- ov_http_client_object* object;
184
+ ov_http_client_object* ptr;
138
185
 
139
- object = ALLOC(ov_http_client_object);
140
- object->curl = NULL;
141
- object->log = Qnil;
142
- return Data_Wrap_Struct(klass, ov_http_client_mark, ov_http_client_free, object);
186
+ ptr = ALLOC(ov_http_client_object);
187
+ return TypedData_Wrap_Struct(klass, &ov_http_client_type, ptr);
143
188
  }
144
189
 
145
190
  static VALUE ov_http_client_close(VALUE self) {
146
- ov_http_client_object* object;
191
+ ov_http_client_object* ptr;
147
192
 
148
193
  /* Get the pointer to the native object and check that it isn't closed: */
149
- Data_Get_Struct(self, ov_http_client_object, object);
150
- ov_http_client_check_closed(object);
194
+ ov_http_client_ptr(self, ptr);
195
+ ov_http_client_check_closed(ptr);
151
196
 
152
- /* Release the resources used by libcurl. In this case we don't need to clear the callbacks in advance, because
153
- we can safely call Ruby code from this method. */
154
- curl_easy_cleanup(object->curl);
155
- object->curl = NULL;
197
+ /* Release the resources used by libcurl: */
198
+ curl_multi_cleanup(ptr->handle);
199
+ ptr->handle = NULL;
156
200
 
157
201
  return Qnil;
158
202
  }
@@ -160,73 +204,91 @@ static VALUE ov_http_client_close(VALUE self) {
160
204
  static void* ov_http_client_read_task(void* data) {
161
205
  VALUE bytes;
162
206
  VALUE count;
163
- ov_http_client_io_context* io_context = (ov_http_client_io_context*) data;
207
+ ov_http_client_io_context* context_ptr;
208
+
209
+ /* The passed data is a pointer to the IO context: */
210
+ context_ptr = (ov_http_client_io_context*) data;
164
211
 
165
212
  /* Read the data using the "read" method and write the raw bytes to the buffer provided by libcurl: */
166
- count = INT2NUM(io_context->size * io_context->nmemb);
167
- bytes = rb_funcall(io_context->io, READ_ID, 1, count);
213
+ count = INT2NUM(context_ptr->size * context_ptr->nmemb);
214
+ bytes = rb_funcall(context_ptr->io, READ_ID, 1, count);
168
215
  if (NIL_P(bytes)) {
169
- io_context->result = 0;
216
+ context_ptr->result = 0;
170
217
  }
171
218
  else {
172
- io_context->result = RSTRING_LEN(bytes);
173
- memcpy(io_context->ptr, StringValuePtr(bytes), io_context->result);
219
+ context_ptr->result = RSTRING_LEN(bytes);
220
+ memcpy(context_ptr->ptr, StringValuePtr(bytes), context_ptr->result);
174
221
  }
175
222
 
176
223
  return NULL;
177
224
  }
178
225
 
179
226
  static size_t ov_http_client_read_function(char *ptr, size_t size, size_t nmemb, void *userdata) {
180
- ov_http_client_perform_context* perform_context = (ov_http_client_perform_context*) userdata;
181
- ov_http_client_io_context io_context;
227
+ VALUE transfer;
228
+ ov_http_client_io_context context;
229
+ ov_http_transfer_object* transfer_ptr;
230
+
231
+ /* The passed user data is the transfer: */
232
+ transfer = (VALUE) userdata;
233
+
234
+ /* Get the pointer to the transfer: */
235
+ ov_http_transfer_ptr(transfer, transfer_ptr);
182
236
 
183
237
  /* Check if the operation has been cancelled, and return immediately, this will cause the perform method to
184
238
  return an error to the caller: */
185
- if (perform_context->cancel) {
239
+ if (transfer_ptr->cancel) {
186
240
  return CURL_READFUNC_ABORT;
187
241
  }
188
242
 
189
243
  /* Execute the read with the global interpreter lock acquired, as it needs to call Ruby methods: */
190
- io_context.object = perform_context->object;
191
- io_context.ptr = ptr;
192
- io_context.size = size;
193
- io_context.nmemb = nmemb;
194
- io_context.io = perform_context->in;
195
- rb_thread_call_with_gvl(ov_http_client_read_task, &io_context);
196
- return io_context.result;
244
+ context.ptr = ptr;
245
+ context.size = size;
246
+ context.nmemb = nmemb;
247
+ context.io = transfer_ptr->in;
248
+ rb_thread_call_with_gvl(ov_http_client_read_task, &context);
249
+ return context.result;
197
250
  }
198
251
 
199
252
  static void* ov_http_client_write_task(void* data) {
200
253
  VALUE bytes;
201
254
  VALUE count;
202
- ov_http_client_io_context* io_context = (ov_http_client_io_context*) data;
255
+ ov_http_client_io_context* context_ptr;
256
+
257
+ /* The passed data is a pointer to the IO context: */
258
+ context_ptr = (ov_http_client_io_context*) data;
203
259
 
204
260
  /* Convert the buffer to a Ruby string and write it to the IO object, using the "write" method: */
205
- bytes = rb_str_new(io_context->ptr, io_context->size * io_context->nmemb);
206
- count = rb_funcall(io_context->io, WRITE_ID, 1, bytes);
207
- io_context->result = NUM2INT(count);
261
+ bytes = rb_str_new(context_ptr->ptr, context_ptr->size * context_ptr->nmemb);
262
+ count = rb_funcall(context_ptr->io, WRITE_ID, 1, bytes);
263
+ context_ptr->result = NUM2INT(count);
208
264
 
209
265
  return NULL;
210
266
  }
211
267
 
212
268
  static size_t ov_http_client_write_function(char *ptr, size_t size, size_t nmemb, void *userdata) {
213
- ov_http_client_perform_context* perform_context = (ov_http_client_perform_context*) userdata;
214
- ov_http_client_io_context io_context;
269
+ VALUE transfer;
270
+ ov_http_client_io_context context;
271
+ ov_http_transfer_object* transfer_ptr;
272
+
273
+ /* The passed user data is the transfer: */
274
+ transfer = (VALUE) userdata;
275
+
276
+ /* Get the pointer to the transfer: */
277
+ ov_http_transfer_ptr(transfer, transfer_ptr);
215
278
 
216
279
  /* Check if the operation has been cancelled, and return immediately, this will cause the perform method to
217
280
  return an error to the caller: */
218
- if (perform_context->cancel) {
281
+ if (transfer_ptr->cancel) {
219
282
  return 0;
220
283
  }
221
284
 
222
285
  /* Execute the write with the global interpreter lock acquired, as it needs to call Ruby methods: */
223
- io_context.object = perform_context->object;
224
- io_context.ptr = ptr;
225
- io_context.size = size;
226
- io_context.nmemb = nmemb;
227
- io_context.io = perform_context->out;
228
- rb_thread_call_with_gvl(ov_http_client_write_task, &io_context);
229
- return io_context.result;
286
+ context.ptr = ptr;
287
+ context.size = size;
288
+ context.nmemb = nmemb;
289
+ context.io = transfer_ptr->out;
290
+ rb_thread_call_with_gvl(ov_http_client_write_task, &context);
291
+ return context.result;
230
292
  }
231
293
 
232
294
  static void* ov_http_client_header_task(void* data) {
@@ -234,15 +296,22 @@ static void* ov_http_client_header_task(void* data) {
234
296
  VALUE value;
235
297
  char* buffer;
236
298
  char* pointer;
299
+ ov_http_client_header_context* context_ptr;
300
+ ov_http_response_object* response_ptr;
237
301
  size_t length;
238
- ov_http_client_header_context* header_context = (ov_http_client_header_context*) data;
302
+
303
+ /* The passed data is the pointer to the context: */
304
+ context_ptr = (ov_http_client_header_context*) data;
305
+
306
+ /* Get the pointer to the response: */
307
+ ov_http_response_ptr(context_ptr->response, response_ptr);
239
308
 
240
309
  /* We should always tell the library that we processed all the data: */
241
- header_context->result = header_context->size * header_context->nitems;
310
+ context_ptr->result = context_ptr->size * context_ptr->nitems;
242
311
 
243
312
  /* Remove trailing white space: */
244
- length = header_context->result;
245
- buffer = header_context->buffer;
313
+ length = context_ptr->result;
314
+ buffer = context_ptr->buffer;
246
315
  while (length > 0 && isspace(buffer[length - 1])) {
247
316
  length--;
248
317
  }
@@ -251,56 +320,71 @@ static void* ov_http_client_header_task(void* data) {
251
320
  pointer = memchr(buffer, ':', length);
252
321
  if (pointer != NULL) {
253
322
  name = rb_str_new(buffer, pointer - buffer);
323
+ name = rb_funcall(name, DOWNCASE_ID, 0);
254
324
  pointer++;
255
325
  while (pointer - buffer < length && isspace(*pointer)) {
256
326
  pointer++;
257
327
  }
258
328
  value = rb_str_new(pointer, length - (pointer - buffer));
259
- rb_hash_aset(header_context->response->headers, name, value);
329
+ rb_hash_aset(response_ptr->headers, name, value);
260
330
  }
261
331
 
262
332
  return NULL;
263
333
  }
264
334
 
265
335
  static size_t ov_http_client_header_function(char *buffer, size_t size, size_t nitems, void *userdata) {
266
- ov_http_client_header_context header_context;
267
- ov_http_client_perform_context* perform_context = (ov_http_client_perform_context*) userdata;
336
+ VALUE transfer;
337
+ ov_http_client_header_context context;
338
+ ov_http_transfer_object* transfer_ptr;
339
+
340
+ /* The passed user data is a pointer to the transfer: */
341
+ transfer = (VALUE) userdata;
342
+
343
+ /* Get the pointer to the transfer: */
344
+ ov_http_transfer_ptr(transfer, transfer_ptr);
268
345
 
269
346
  /* Check if the operation has been cancelled, and return immediately, this will cause the perform method to
270
347
  return an error to the caller: */
271
- if (perform_context->cancel) {
348
+ if (transfer_ptr->cancel) {
272
349
  return 0;
273
350
  }
274
351
 
275
- /* Parse the header with the global intepreter lock acquired, as it needs to call Ruby methods: */
276
- header_context.response = perform_context->response;
277
- header_context.buffer = buffer;
278
- header_context.size = size;
279
- header_context.nitems = nitems;
280
- rb_thread_call_with_gvl(ov_http_client_header_task, &header_context);
281
- return header_context.result;
352
+ /* Parse the header with the global interpreter lock acquired, as it needs to call Ruby methods: */
353
+ context.response = transfer_ptr->response;
354
+ context.buffer = buffer;
355
+ context.size = size;
356
+ context.nitems = nitems;
357
+ rb_thread_call_with_gvl(ov_http_client_header_task, &context);
358
+ return context.result;
282
359
  }
283
360
 
284
361
  static void* ov_http_client_debug_task(void* data) {
285
362
  VALUE line;
286
363
  VALUE log;
287
- int c;
288
364
  char* text;
289
- ov_http_client_debug_context* debug_context = (ov_http_client_debug_context*) data;
365
+ int c;
366
+ int s;
367
+ ov_http_client_debug_context* context_ptr;
368
+ ov_http_client_object* client_ptr;
290
369
  size_t i;
291
370
  size_t j;
292
371
  size_t size;
293
- int s;
372
+
373
+ /* The passed data is a pointer to the context: */
374
+ context_ptr = (ov_http_client_debug_context*) data;
375
+
376
+ /* Get the pointer to the client: */
377
+ ov_http_client_ptr(context_ptr->client, client_ptr);
294
378
 
295
379
  /* Do nothing if there is no log: */
296
- log = debug_context->object->log;
380
+ log = client_ptr->log;
297
381
  if (NIL_P(log)) {
298
382
  return NULL;
299
383
  }
300
384
 
301
385
  /* Split the text into lines, and send a debug message for each line: */
302
- text = debug_context->data;
303
- size = debug_context->size;
386
+ text = context_ptr->data;
387
+ size = context_ptr->size;
304
388
  i = 0;
305
389
  s = 0;
306
390
  for (j = 0; j <= size; j++) {
@@ -329,33 +413,34 @@ static void* ov_http_client_debug_task(void* data) {
329
413
  }
330
414
 
331
415
  static int ov_http_client_debug_function(CURL* handle, curl_infotype type, char* data, size_t size, void* userptr) {
332
- ov_http_client_debug_context debug_context;
333
- ov_http_client_perform_context* perform_context = (ov_http_client_perform_context*) userptr;
416
+ VALUE transfer;
417
+ ov_http_client_debug_context context;
418
+ ov_http_transfer_object* transfer_ptr;
419
+
420
+ /* The passed user pointer is the transfer: */
421
+ transfer = (VALUE) userptr;
422
+
423
+ /* Get the pointer to the transfer: */
424
+ ov_http_transfer_ptr(transfer, transfer_ptr);
334
425
 
335
426
  /* Execute the debug code with the global interpreter lock acquired, as it needs to call Ruby methods: */
336
- debug_context.object = perform_context->object;
337
- debug_context.type = type;
338
- debug_context.data = data;
339
- debug_context.size = size;
340
- rb_thread_call_with_gvl(ov_http_client_debug_task, &debug_context);
427
+ context.client = transfer_ptr->client;
428
+ context.type = type;
429
+ context.data = data;
430
+ context.size = size;
431
+ rb_thread_call_with_gvl(ov_http_client_debug_task, &context);
341
432
  return 0;
342
433
  }
343
434
 
344
435
  static VALUE ov_http_client_initialize(int argc, VALUE* argv, VALUE self) {
345
436
  VALUE opt;
346
437
  VALUE opts;
347
- bool compress;
348
- bool debug;
349
- bool insecure;
350
- char* ca_file;
351
- char* proxy_password;
352
- char* proxy_url;
353
- char* proxy_username;
354
- int timeout;
355
- ov_http_client_object* object;
438
+ long connections;
439
+ long pipeline;
440
+ ov_http_client_object* ptr;
356
441
 
357
442
  /* Get the pointer to the native object: */
358
- Data_Get_Struct(self, ov_http_client_object, object);
443
+ ov_http_client_ptr(self, ptr);
359
444
 
360
445
  /* Check the number of arguments: */
361
446
  if (argc > 1) {
@@ -369,146 +454,96 @@ static VALUE ov_http_client_initialize(int argc, VALUE* argv, VALUE self) {
369
454
  Check_Type(opts, T_HASH);
370
455
  }
371
456
 
372
- /* Get the value of the 'insecure' parameter: */
373
- opt = rb_hash_aref(opts, INSECURE_SYMBOL);
374
- if (NIL_P(opt)) {
375
- insecure = false;
376
- }
377
- else {
378
- insecure = RTEST(opt);
379
- }
380
-
381
457
  /* Get the value of the 'ca_file' parameter: */
382
458
  opt = rb_hash_aref(opts, CA_FILE_SYMBOL);
383
- if (NIL_P(opt)) {
384
- ca_file = NULL;
385
- }
386
- else {
387
- Check_Type(opt, T_STRING);
388
- ca_file = StringValueCStr(opt);
389
- }
459
+ ptr->ca_file = ov_string_dup(opt);
460
+
461
+ /* Get the value of the 'insecure' parameter: */
462
+ opt = rb_hash_aref(opts, INSECURE_SYMBOL);
463
+ ptr->insecure = NIL_P(opt)? false: RTEST(opt);
390
464
 
391
465
  /* Get the value of the 'debug' parameter: */
392
466
  opt = rb_hash_aref(opts, DEBUG_SYMBOL);
393
- if (NIL_P(opt)) {
394
- debug = false;
395
- }
396
- else {
397
- debug = RTEST(opt);
398
- }
467
+ ptr->debug = NIL_P(opt)? false: RTEST(opt);
399
468
 
400
- /* Get the value of the 'log' parameter: */
401
- opt = rb_hash_aref(opts, LOG_SYMBOL);
402
- if (NIL_P(opt)) {
403
- object->log = Qnil;
404
- }
405
- else {
406
- object->log = opt;
407
- }
469
+ /* Get the value of the 'compress' parameter: */
470
+ opt = rb_hash_aref(opts, COMPRESS_SYMBOL);
471
+ ptr->compress = NIL_P(opt)? true: RTEST(opt);
408
472
 
409
473
  /* Get the value of the 'timeout' parameter: */
410
474
  opt = rb_hash_aref(opts, TIMEOUT_SYMBOL);
411
475
  if (NIL_P(opt)) {
412
- timeout = 0;
476
+ ptr->timeout = 0;
413
477
  }
414
478
  else {
415
479
  Check_Type(opt, T_FIXNUM);
416
- timeout = NUM2INT(opt);
417
- }
418
-
419
- /* Get the value of the 'compress' parameter: */
420
- opt = rb_hash_aref(opts, COMPRESS_SYMBOL);
421
- if (NIL_P(opt)) {
422
- compress = false;
423
- }
424
- else {
425
- compress = RTEST(opt);
480
+ ptr->timeout = NUM2INT(opt);
426
481
  }
427
482
 
428
483
  /* Get the value of the 'proxy_url' parameter: */
429
484
  opt = rb_hash_aref(opts, PROXY_URL_SYMBOL);
430
- if (NIL_P(opt)) {
431
- proxy_url = NULL;
432
- }
433
- else {
434
- Check_Type(opt, T_STRING);
435
- proxy_url = StringValueCStr(opt);
436
- }
485
+ ptr->proxy_url = ov_string_dup(opt);
437
486
 
438
487
  /* Get the value of the 'proxy_username' parameter: */
439
488
  opt = rb_hash_aref(opts, PROXY_USERNAME_SYMBOL);
440
- if (NIL_P(opt)) {
441
- proxy_username = NULL;
442
- }
443
- else {
444
- Check_Type(opt, T_STRING);
445
- proxy_username = StringValueCStr(opt);
446
- }
489
+ ptr->proxy_username = ov_string_dup(opt);
447
490
 
448
491
  /* Get the value of the 'proxy_password' parameter: */
449
492
  opt = rb_hash_aref(opts, PROXY_PASSWORD_SYMBOL);
493
+ ptr->proxy_password = ov_string_dup(opt);
494
+
495
+ /* Get the value of the 'log' parameter: */
496
+ opt = rb_hash_aref(opts, LOG_SYMBOL);
497
+ ptr->log = opt;
498
+
499
+ /* Get the value of the 'pipeline' parameter: */
500
+ opt = rb_hash_aref(opts, PIPELINE_SYMBOL);
450
501
  if (NIL_P(opt)) {
451
- proxy_password = NULL;
502
+ pipeline = 0;
452
503
  }
453
504
  else {
454
- Check_Type(opt, T_STRING);
455
- proxy_password = StringValueCStr(opt);
456
- }
457
-
458
- /* Create the libcurl object: */
459
- object->curl = curl_easy_init();
460
- if (object->curl == NULL) {
461
- rb_raise(ov_error_class, "Can't create libcurl object");
505
+ Check_Type(opt, T_FIXNUM);
506
+ pipeline = NUM2LONG(opt);
462
507
  }
463
508
 
464
- /* Configure TLS parameters: */
465
- if (insecure) {
466
- curl_easy_setopt(object->curl, CURLOPT_SSL_VERIFYPEER, 0L);
467
- curl_easy_setopt(object->curl, CURLOPT_SSL_VERIFYHOST, 0L);
509
+ /* Get the value of the 'connections' parameter: */
510
+ opt = rb_hash_aref(opts, CONNECTIONS_SYMBOL);
511
+ if (NIL_P(opt)) {
512
+ connections = 0;
468
513
  }
469
- if (ca_file != NULL) {
470
- curl_easy_setopt(object->curl, CURLOPT_CAINFO, ca_file);
514
+ else {
515
+ Check_Type(opt, T_FIXNUM);
516
+ connections = NUM2LONG(opt);
471
517
  }
472
518
 
473
- /* Configure the timeout: */
474
- curl_easy_setopt(object->curl, CURLOPT_TIMEOUT, timeout);
519
+ /* Create the hash that contains the transfers are pending an completed. Both use the identity of the request
520
+ as key. */
521
+ ptr->completed = rb_funcall(rb_hash_new(), COMPARE_BY_IDENTITY_ID, 0);
522
+ ptr->pending = rb_funcall(rb_hash_new(), COMPARE_BY_IDENTITY_ID, 0);
475
523
 
476
- /* Configure compression of responses (setting the value to zero length string means accepting all the
477
- compression types that libcurl supports): */
478
- if (compress) {
479
- curl_easy_setopt(object->curl, CURLOPT_ENCODING, "");
524
+ /* Create the libcurl multi handle: */
525
+ ptr->handle = curl_multi_init();
526
+ if (ptr->handle == NULL) {
527
+ rb_raise(ov_error_class, "Can't create libcurl object");
480
528
  }
481
529
 
482
- /* Configure debug mode: */
483
- if (debug) {
484
- curl_easy_setopt(object->curl, CURLOPT_VERBOSE, 1L);
485
- curl_easy_setopt(object->curl, CURLOPT_DEBUGFUNCTION, ov_http_client_debug_function);
530
+ /* Enable pipelining: */
531
+ if (pipeline > 0) {
532
+ curl_multi_setopt(ptr->handle, CURLMOPT_PIPELINING, CURLPIPE_HTTP1);
533
+ #ifdef CURLMOPT_MAX_PIPELINE_LENGTH
534
+ curl_multi_setopt(ptr->handle, CURLMOPT_MAX_PIPELINE_LENGTH, pipeline);
535
+ #endif
486
536
  }
487
-
488
- /* Configure the proxy: */
489
- if (proxy_url != NULL) {
490
- curl_easy_setopt(object->curl, CURLOPT_PROXY, proxy_url);
491
- if (proxy_username != NULL && proxy_password != NULL) {
492
- curl_easy_setopt(object->curl, CURLOPT_PROXYUSERNAME, proxy_username);
493
- curl_easy_setopt(object->curl, CURLOPT_PROXYPASSWORD, proxy_password);
494
- }
537
+ if (connections > 0) {
538
+ #ifdef CURLMOPT_MAX_HOST_CONNECTIONS
539
+ curl_multi_setopt(ptr->handle, CURLMOPT_MAX_HOST_CONNECTIONS, connections);
540
+ #endif
495
541
  }
496
542
 
497
- /* Configure callbacks: */
498
- curl_easy_setopt(object->curl, CURLOPT_READFUNCTION, ov_http_client_read_function);
499
- curl_easy_setopt(object->curl, CURLOPT_WRITEFUNCTION, ov_http_client_write_function);
500
- curl_easy_setopt(object->curl, CURLOPT_HEADERFUNCTION, ov_http_client_header_function);
501
-
502
543
  return self;
503
544
  }
504
545
 
505
- static VALUE ov_http_client_build_url(VALUE self, VALUE url, VALUE query) {
506
- ov_http_client_object* object;
507
-
508
- /* Get the pointer to the native object and check that it isn't closed: */
509
- Data_Get_Struct(self, ov_http_client_object, object);
510
- ov_http_client_check_closed(object);
511
-
546
+ static VALUE ov_http_client_build_url( VALUE url, VALUE query) {
512
547
  /* Copy the URL: */
513
548
  if (NIL_P(url)) {
514
549
  rb_raise(ov_error_class, "The 'url' parameter can't be nil");
@@ -535,178 +570,317 @@ static int ov_http_client_add_header(VALUE name, VALUE value, struct curl_slist*
535
570
  return ST_CONTINUE;
536
571
  }
537
572
 
538
- static void* ov_http_client_perform_task(void* data) {
539
- ov_http_client_perform_context* perform_context = (ov_http_client_perform_context*) data;
573
+ static void* ov_http_client_complete_task(void* data) {
574
+ CURLM* handle;
575
+ CURLMsg* message;
576
+ VALUE error;
577
+ VALUE transfer;
578
+ long code;
579
+ ov_http_client_object* client_ptr;
580
+ ov_http_response_object* response_ptr;
581
+ ov_http_transfer_object* transfer_ptr;
582
+
583
+ /* The passed pointer is the libcurl message describing the completed transfer: */
584
+ message = (CURLMsg*) data;
585
+ handle = message->easy_handle;
586
+
587
+ /* The transfer is stored as the private data of the libcurl easy handle: */
588
+ curl_easy_getinfo(handle, CURLINFO_PRIVATE, &transfer);
589
+
590
+ /* Get the pointers to the transfer, client and response: */
591
+ ov_http_transfer_ptr(transfer, transfer_ptr);
592
+ ov_http_client_ptr(transfer_ptr->client, client_ptr);
593
+ ov_http_response_ptr(transfer_ptr->response, response_ptr);
594
+
595
+ /* Remove the transfer from the pending hash: */
596
+ rb_hash_delete(client_ptr->pending, transfer_ptr->request);
597
+
598
+ if (message->data.result == CURLE_OK) {
599
+ /* Copy the response code and the response body to the response object: */
600
+ curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &code);
601
+ response_ptr->code = LONG2NUM(code);
602
+ response_ptr->body = rb_funcall(transfer_ptr->out, STRING_ID, 0);
603
+
604
+ /* Put the request and the response in the completed transfers hash: */
605
+ rb_hash_aset(client_ptr->completed, transfer_ptr->request, transfer_ptr->response);
606
+
607
+ /* Send a summary of the response to the log: */
608
+ ov_http_client_log_info(
609
+ client_ptr->log,
610
+ "Received response code '%"PRIsVALUE"'.",
611
+ response_ptr->code
612
+ );
613
+ }
614
+ else {
615
+ /* Put the request and error in the completed transfers hash: */
616
+ error = rb_sprintf("Can't send request: %s", curl_easy_strerror(message->data.result));
617
+ error = rb_class_new_instance(1, &error, ov_error_class);
618
+ rb_hash_aset(client_ptr->completed, transfer_ptr->request, error);
619
+ }
540
620
 
541
- /* Call the libcurl 'perform' method, and store the result in the context: */
542
- perform_context->result = curl_easy_perform(perform_context->object->curl);
621
+ /* Now that the libcurl easy handle is released, we can release the headers as well: */
622
+ curl_slist_free_all(transfer_ptr->headers);
543
623
 
544
624
  return NULL;
545
625
  }
546
626
 
547
- static void ov_http_client_perform_cancel(void* data) {
548
- ov_http_client_perform_context* perform_context = (ov_http_client_perform_context*) data;
627
+ static void* ov_http_client_wait_task(void* data) {
628
+ CURLMsg* message;
629
+ int count;
630
+ int pending;
631
+ long timeout;
632
+ ov_http_client_wait_context* context_ptr;
549
633
 
550
- /* Set the cancel flag so that the operation will be actually aborted the next time that libcurl calls the write
551
- function: */
552
- perform_context->cancel = true;
553
- }
634
+ /* The passed data is the wait context: */
635
+ context_ptr = data;
554
636
 
555
- static void ov_http_client_log_info(VALUE log, const char* format, ...) {
556
- VALUE enabled;
557
- VALUE message;
558
- va_list args;
637
+ /* Get the timeout preferred by libcurl, or one second by default: */
638
+ curl_multi_timeout(context_ptr->handle, &timeout);
639
+ if (timeout < 0) {
640
+ timeout = 1000;
641
+ }
559
642
 
560
- if (!NIL_P(log)) {
561
- enabled = rb_funcall(log, INFO_Q_ID, 0);
562
- if (RTEST(enabled)) {
563
- va_start(args, format);
564
- message = rb_vsprintf(format, args);
565
- rb_funcall(log, INFO_ID, 1, message);
566
- va_end(args);
643
+ /* Wait till there is activity: */
644
+ context_ptr->code = curl_multi_wait(context_ptr->handle, NULL, 0, timeout, NULL);
645
+ if (context_ptr->code != CURLE_OK) {
646
+ return NULL;
647
+ }
648
+
649
+ /* Let libcurl do its work, even if no file descriptor needs attention. This is necessary because some of its
650
+ activities can't be monitored using file descriptors. */
651
+ context_ptr->code = curl_multi_perform(context_ptr->handle, &pending);
652
+ if (context_ptr->code != CURLE_OK) {
653
+ return NULL;
654
+ }
655
+
656
+ /* Check if there are finished transfers. For each of them call the function that completes them, with the global
657
+ interpreter lock acquired, as it will call Ruby code. */
658
+ while ((message = curl_multi_info_read(context_ptr->handle, &count)) != NULL) {
659
+ if (message->msg == CURLMSG_DONE) {
660
+ /* Call the Ruby code that completes the transfer: */
661
+ rb_thread_call_with_gvl(ov_http_client_complete_task, message);
662
+
663
+ /* Remove the easy handle from the multi handle and discard it: */
664
+ curl_multi_remove_handle(context_ptr->handle, message->easy_handle);
665
+ curl_easy_cleanup(message->easy_handle);
567
666
  }
568
667
  }
668
+
669
+ /* Everything worked correctly: */
670
+ context_ptr->code = CURLE_OK;
671
+ return NULL;
672
+ }
673
+
674
+ static void ov_http_client_wait_cancel(void* data) {
675
+ ov_http_client_wait_context* context_ptr;
676
+
677
+ /* The passed data is the wait context: */
678
+ context_ptr = data;
679
+
680
+ /* Set the cancel flag so that the operation will be actually aborted in the next operation of the wait loop: */
681
+ context_ptr->cancel = true;
569
682
  }
570
683
 
571
- static VALUE ov_http_client_send(VALUE self, VALUE request, VALUE response) {
684
+ static void ov_http_client_prepare_handle(ov_http_client_object* client_ptr, ov_http_request_object* request_ptr,
685
+ struct curl_slist** headers, CURL* handle) {
572
686
  VALUE header;
573
687
  VALUE url;
574
- long response_code;
575
- ov_http_client_object* object;
576
- ov_http_client_perform_context perform_context;
577
- ov_http_request_object* request_object;
578
- ov_http_response_object* response_object;
579
- struct curl_slist* headers;
688
+ int timeout;
580
689
 
581
- /* Get the pointer to the native object and check that it isn't closed: */
582
- Data_Get_Struct(self, ov_http_client_object, object);
583
- ov_http_client_check_closed(object);
690
+ /* Configure TLS parameters: */
691
+ if (client_ptr->insecure) {
692
+ curl_easy_setopt(handle, CURLOPT_SSL_VERIFYPEER, 0L);
693
+ curl_easy_setopt(handle, CURLOPT_SSL_VERIFYHOST, 0L);
694
+ }
695
+ if (client_ptr->ca_file != NULL) {
696
+ curl_easy_setopt(handle, CURLOPT_CAINFO, client_ptr->ca_file);
697
+ }
584
698
 
585
- /* Check the type of request and get the pointer to the native object: */
586
- if (NIL_P(request)) {
587
- rb_raise(ov_error_class, "The 'request' parameter can't be nil");
699
+ /* Configure the timeout: */
700
+ timeout = client_ptr->timeout;
701
+ if (!NIL_P(request_ptr->timeout)) {
702
+ timeout = NUM2INT(request_ptr->timeout);
588
703
  }
589
- if (!rb_obj_is_instance_of(request, ov_http_request_class)) {
590
- rb_raise(ov_error_class, "The 'request' parameter isn't an instance of class 'HttpRequest'");
704
+ curl_easy_setopt(handle, CURLOPT_TIMEOUT, timeout);
705
+
706
+ /* Configure compression of responses (setting the value to zero length string means accepting all the
707
+ compression types that libcurl supports): */
708
+ if (client_ptr->compress) {
709
+ curl_easy_setopt(handle, CURLOPT_ENCODING, "");
591
710
  }
592
- Data_Get_Struct(request, ov_http_request_object, request_object);
593
711
 
594
- /* Check the type of response and get the pointer to the native object: */
595
- if (NIL_P(response)) {
596
- rb_raise(ov_error_class, "The 'response' parameter can't be nil");
712
+ /* Configure debug mode: */
713
+ if (client_ptr->debug) {
714
+ curl_easy_setopt(handle, CURLOPT_VERBOSE, 1L);
715
+ curl_easy_setopt(handle, CURLOPT_DEBUGFUNCTION, ov_http_client_debug_function);
597
716
  }
598
- if (!rb_obj_is_instance_of(response, ov_http_response_class)) {
599
- rb_raise(ov_error_class, "The 'response' parameter isn't an instance of class 'HttpResponse'");
717
+
718
+ /* Configure the proxy: */
719
+ if (client_ptr->proxy_url != NULL) {
720
+ curl_easy_setopt(handle, CURLOPT_PROXY, client_ptr->proxy_url);
721
+ if (client_ptr->proxy_username != NULL && client_ptr->proxy_password != NULL) {
722
+ curl_easy_setopt(handle, CURLOPT_PROXYUSERNAME, client_ptr->proxy_username);
723
+ curl_easy_setopt(handle, CURLOPT_PROXYPASSWORD, client_ptr->proxy_password);
724
+ }
600
725
  }
601
- Data_Get_Struct(response, ov_http_response_object, response_object);
602
726
 
603
- /* Build and set the URL: */
604
- url = ov_http_client_build_url(self, request_object->url, request_object->query);
605
- curl_easy_setopt(object->curl, CURLOPT_URL, StringValueCStr(url));
727
+ /* Configure callbacks: */
728
+ curl_easy_setopt(handle, CURLOPT_READFUNCTION, ov_http_client_read_function);
729
+ curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, ov_http_client_write_function);
730
+ curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, ov_http_client_header_function);
606
731
 
607
- /* Initialize the list of headers: */
608
- headers = NULL;
732
+ /* Build and set the URL: */
733
+ url = ov_http_client_build_url(request_ptr->url, request_ptr->query);
734
+ curl_easy_setopt(handle, CURLOPT_URL, StringValueCStr(url));
609
735
 
610
736
  /* Set the method: */
611
- if (rb_eql(request_object->method, POST_SYMBOL)) {
612
- headers = curl_slist_append(headers, "Transfer-Encoding: chunked");
613
- headers = curl_slist_append(headers, "Expect:");
614
- curl_easy_setopt(object->curl, CURLOPT_POST, 1L);
737
+ if (rb_eql(request_ptr->method, POST_SYMBOL)) {
738
+ *headers = curl_slist_append(*headers, "Transfer-Encoding: chunked");
739
+ *headers = curl_slist_append(*headers, "Expect:");
740
+ curl_easy_setopt(handle, CURLOPT_POST, 1L);
615
741
  }
616
- else if (rb_eql(request_object->method, PUT_SYMBOL)) {
617
- curl_easy_setopt(object->curl, CURLOPT_UPLOAD, 1L);
618
- curl_easy_setopt(object->curl, CURLOPT_PUT, 1L);
742
+ else if (rb_eql(request_ptr->method, PUT_SYMBOL)) {
743
+ curl_easy_setopt(handle, CURLOPT_UPLOAD, 1L);
744
+ curl_easy_setopt(handle, CURLOPT_PUT, 1L);
619
745
  }
620
- else if (rb_eql(request_object->method, DELETE_SYMBOL)) {
621
- curl_easy_setopt(object->curl, CURLOPT_HTTPGET, 1L);
622
- curl_easy_setopt(object->curl, CURLOPT_CUSTOMREQUEST, "DELETE");
746
+ else if (rb_eql(request_ptr->method, DELETE_SYMBOL)) {
747
+ curl_easy_setopt(handle, CURLOPT_HTTPGET, 1L);
748
+ curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "DELETE");
623
749
  }
624
- else if (rb_eql(request_object->method, GET_SYMBOL)) {
625
- curl_easy_setopt(object->curl, CURLOPT_HTTPGET, 1L);
750
+ else if (rb_eql(request_ptr->method, GET_SYMBOL)) {
751
+ curl_easy_setopt(handle, CURLOPT_HTTPGET, 1L);
626
752
  }
627
753
 
628
754
  /* Set authentication details: */
629
- if (!NIL_P(request_object->token)) {
630
- header = rb_sprintf("Authorization: Bearer %"PRIsVALUE"", request_object->token);
631
- headers = curl_slist_append(headers, StringValueCStr(header));
755
+ if (!NIL_P(request_ptr->token)) {
756
+ header = rb_sprintf("Authorization: Bearer %"PRIsVALUE"", request_ptr->token);
757
+ *headers = curl_slist_append(*headers, StringValueCStr(header));
632
758
  }
633
- else if (!NIL_P(request_object->username) && !NIL_P(request_object->password)) {
634
- curl_easy_setopt(object->curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
635
- curl_easy_setopt(object->curl, CURLOPT_USERNAME, StringValueCStr(request_object->username));
636
- curl_easy_setopt(object->curl, CURLOPT_PASSWORD, StringValueCStr(request_object->password));
759
+ else if (!NIL_P(request_ptr->username) && !NIL_P(request_ptr->password)) {
760
+ curl_easy_setopt(handle, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
761
+ curl_easy_setopt(handle, CURLOPT_USERNAME, StringValueCStr(request_ptr->username));
762
+ curl_easy_setopt(handle, CURLOPT_PASSWORD, StringValueCStr(request_ptr->password));
637
763
  }
638
- else if (RTEST(request_object->kerberos)) {
639
- curl_easy_setopt(object->curl, CURLOPT_HTTPAUTH, CURLAUTH_NEGOTIATE);
764
+ else if (RTEST(request_ptr->kerberos)) {
765
+ curl_easy_setopt(handle, CURLOPT_HTTPAUTH, CURLAUTH_NEGOTIATE);
640
766
  }
641
767
 
642
768
  /* Set the headers: */
643
- if (!NIL_P(request_object->headers)) {
644
- rb_hash_foreach(request_object->headers, ov_http_client_add_header, (VALUE) &headers);
769
+ if (!NIL_P(request_ptr->headers)) {
770
+ rb_hash_foreach(request_ptr->headers, ov_http_client_add_header, (VALUE) headers);
645
771
  }
646
- curl_easy_setopt(object->curl, CURLOPT_HTTPHEADER, headers);
772
+ curl_easy_setopt(handle, CURLOPT_HTTPHEADER, *headers);
647
773
 
648
774
  /* Send a summary of the request to the log: */
649
775
  ov_http_client_log_info(
650
- object->log,
776
+ client_ptr->log,
651
777
  "Sending '%"PRIsVALUE"' request to URL '%"PRIsVALUE"'.",
652
- request_object->method,
778
+ request_ptr->method,
653
779
  url
654
780
  );
781
+ }
782
+
783
+ static VALUE ov_http_client_send(VALUE self, VALUE request) {
784
+ CURL* handle;
785
+ VALUE response;
786
+ VALUE transfer;
787
+ ov_http_client_object* ptr;
788
+ ov_http_request_object* request_ptr;
789
+ ov_http_transfer_object* transfer_ptr;
790
+ struct curl_slist* headers;
655
791
 
656
- /* Performing the request is a potentially lengthy and blocking operation, so we need to make sure that it runs
657
- without the global interpreter lock acquired as much as possible: */
658
- perform_context.object = object;
659
- perform_context.response = response_object;
660
- perform_context.cancel = false;
661
- if (NIL_P(request_object->body)) {
662
- perform_context.in = rb_class_new_instance(0, NULL, STRING_IO_CLASS);
792
+ /* Get the pointer to the native object and check that it isn't closed: */
793
+ ov_http_client_ptr(self, ptr);
794
+ ov_http_client_check_closed(ptr);
795
+
796
+ /* Check the type of request and get the pointer to the native object: */
797
+ if (NIL_P(request)) {
798
+ rb_raise(ov_error_class, "The 'request' parameter can't be nil");
663
799
  }
664
- else {
665
- perform_context.in = rb_class_new_instance(1, &request_object->body, STRING_IO_CLASS);
666
- }
667
- perform_context.out = rb_class_new_instance(0, NULL, STRING_IO_CLASS);
668
- curl_easy_setopt(object->curl, CURLOPT_READDATA, &perform_context);
669
- curl_easy_setopt(object->curl, CURLOPT_WRITEDATA, &perform_context);
670
- curl_easy_setopt(object->curl, CURLOPT_HEADERDATA, &perform_context);
671
- curl_easy_setopt(object->curl, CURLOPT_DEBUGDATA, &perform_context);
672
- rb_thread_call_without_gvl(
673
- ov_http_client_perform_task,
674
- &perform_context,
675
- ov_http_client_perform_cancel,
676
- &perform_context
677
- );
800
+ if (!rb_obj_is_instance_of(request, ov_http_request_class)) {
801
+ rb_raise(ov_error_class, "The 'request' parameter isn't an instance of class 'HttpRequest'");
802
+ }
803
+ ov_http_request_ptr(request, request_ptr);
804
+
805
+ /* Create the libcurl easy handle: */
806
+ handle = curl_easy_init();
807
+ if (ptr->handle == NULL) {
808
+ rb_raise(ov_error_class, "Can't create libcurl object");
809
+ }
810
+
811
+ /* The headers used by the libcurl easy handle can't be released till the handle is released itself, so we need
812
+ to initialize here, and add it to the context so that we can release it later: */
813
+ headers = NULL;
814
+
815
+ /* Configure the libcurl easy handle with the data from the client and from the request: */
816
+ ov_http_client_prepare_handle(ptr, request_ptr, &headers, handle);
678
817
 
679
- /* Free the headers and clear the libcurl object, regardless of the result of the request: */
680
- curl_slist_free_all(headers);
681
- curl_easy_setopt(object->curl, CURLOPT_URL, NULL);
682
- curl_easy_setopt(object->curl, CURLOPT_READDATA, NULL);
683
- curl_easy_setopt(object->curl, CURLOPT_WRITEDATA, NULL);
684
- curl_easy_setopt(object->curl, CURLOPT_HEADERDATA, NULL);
685
- curl_easy_setopt(object->curl, CURLOPT_DEBUGDATA, NULL);
686
- curl_easy_setopt(object->curl, CURLOPT_CUSTOMREQUEST, NULL);
687
- curl_easy_setopt(object->curl, CURLOPT_UPLOAD, 0L);
688
- curl_easy_setopt(object->curl, CURLOPT_HTTPAUTH, 0L);
689
- curl_easy_setopt(object->curl, CURLOPT_USERNAME, "");
690
- curl_easy_setopt(object->curl, CURLOPT_PASSWORD, "");
818
+ /* Allocate a ne empty response: */
819
+ response = rb_class_new_instance(0, NULL, ov_http_response_class);
691
820
 
692
- /* Check the result of the request: */
693
- if (perform_context.result != CURLE_OK) {
694
- rb_raise(ov_error_class, "Can't send request: %s", curl_easy_strerror(perform_context.result));
821
+ /* Allocate a new empty transfer: */
822
+ transfer = rb_class_new_instance(0, NULL, ov_http_transfer_class);
823
+ ov_http_transfer_ptr(transfer, transfer_ptr);
824
+ transfer_ptr->client = self;
825
+ transfer_ptr->request = request;
826
+ transfer_ptr->response = response;
827
+ transfer_ptr->headers = headers;
828
+ transfer_ptr->cancel = false;
829
+ if (NIL_P(request_ptr->body)) {
830
+ transfer_ptr->in = rb_class_new_instance(0, NULL, STRING_IO_CLASS);
831
+ }
832
+ else {
833
+ transfer_ptr->in = rb_class_new_instance(1, &request_ptr->body, STRING_IO_CLASS);
695
834
  }
835
+ transfer_ptr->out = rb_class_new_instance(0, NULL, STRING_IO_CLASS);
696
836
 
697
- /* Get the response code: */
698
- curl_easy_getinfo(object->curl, CURLINFO_RESPONSE_CODE, &response_code);
699
- response_object->code = LONG2NUM(response_code);
837
+ /* Put the request and the transfer in the hash of pending transfers: */
838
+ rb_hash_aset(ptr->pending, request, transfer);
700
839
 
701
- /* Get the response body: */
702
- response_object->body = rb_funcall(perform_context.out, STRING_ID, 0);
840
+ /* Set the transfer as the data for all the callbacks, so we can access it from any place where it is needed: */
841
+ curl_easy_setopt(handle, CURLOPT_PRIVATE, transfer);
842
+ curl_easy_setopt(handle, CURLOPT_READDATA, transfer);
843
+ curl_easy_setopt(handle, CURLOPT_WRITEDATA, transfer);
844
+ curl_easy_setopt(handle, CURLOPT_HEADERDATA, transfer);
845
+ curl_easy_setopt(handle, CURLOPT_DEBUGDATA, transfer);
703
846
 
704
- /* Send a summary of the response to the log: */
705
- ov_http_client_log_info(
706
- object->log,
707
- "Received response code '%"PRIsVALUE"'.",
708
- response_object->code
709
- );
847
+ /* Add the easy handle to the multi handle: */
848
+ curl_multi_add_handle(ptr->handle, handle);
849
+
850
+ return Qnil;
851
+ }
852
+
853
+ static VALUE ov_http_client_wait(VALUE self, VALUE request) {
854
+ VALUE result;
855
+ ov_http_client_object* ptr;
856
+ ov_http_client_wait_context context;
857
+
858
+ /* Get the pointer to the native object and check that it isn't closed: */
859
+ ov_http_client_ptr(self, ptr);
860
+ ov_http_client_check_closed(ptr);
861
+
862
+ /* Work till the transfer has been completed: */
863
+ context.handle = ptr->handle;
864
+ context.code = CURLE_OK;
865
+ context.cancel = false;
866
+ for (;;) {
867
+ result = rb_hash_delete(ptr->completed, request);
868
+ if (!NIL_P(result)) {
869
+ return result;
870
+ }
871
+ rb_thread_call_without_gvl(
872
+ ov_http_client_wait_task,
873
+ &context,
874
+ ov_http_client_wait_cancel,
875
+ &context
876
+ );
877
+ if (context.cancel) {
878
+ return Qnil;
879
+ }
880
+ if (context.code != CURLE_OK) {
881
+ rb_raise(ov_error_class, "Unexpected error while waiting: %s", curl_easy_strerror(context.code));
882
+ }
883
+ }
710
884
 
711
885
  return Qnil;
712
886
  }
@@ -726,33 +900,37 @@ void ov_http_client_define(void) {
726
900
  rb_define_method(ov_http_client_class, "initialize", ov_http_client_initialize, -1);
727
901
 
728
902
  /* Define the methods: */
729
- rb_define_method(ov_http_client_class, "build_url", ov_http_client_build_url, 2);
730
- rb_define_method(ov_http_client_class, "close", ov_http_client_close, 0);
731
- rb_define_method(ov_http_client_class, "send", ov_http_client_send, 2);
903
+ rb_define_method(ov_http_client_class, "close", ov_http_client_close, 0);
904
+ rb_define_method(ov_http_client_class, "send", ov_http_client_send, 1);
905
+ rb_define_method(ov_http_client_class, "wait", ov_http_client_wait, 1);
732
906
 
733
907
  /* Define the symbols: */
734
- USERNAME_SYMBOL = ID2SYM(rb_intern("username"));
735
- PASSWORD_SYMBOL = ID2SYM(rb_intern("password"));
736
- INSECURE_SYMBOL = ID2SYM(rb_intern("insecure"));
737
908
  CA_FILE_SYMBOL = ID2SYM(rb_intern("ca_file"));
909
+ COMPRESS_SYMBOL = ID2SYM(rb_intern("compress"));
910
+ CONNECTIONS_SYMBOL = ID2SYM(rb_intern("connections"));
738
911
  DEBUG_SYMBOL = ID2SYM(rb_intern("debug"));
912
+ INSECURE_SYMBOL = ID2SYM(rb_intern("insecure"));
739
913
  LOG_SYMBOL = ID2SYM(rb_intern("log"));
740
- COMPRESS_SYMBOL = ID2SYM(rb_intern("compress"));
741
- TIMEOUT_SYMBOL = ID2SYM(rb_intern("timeout"));
914
+ PASSWORD_SYMBOL = ID2SYM(rb_intern("password"));
915
+ PIPELINE_SYMBOL = ID2SYM(rb_intern("pipeline"));
916
+ PROXY_PASSWORD_SYMBOL = ID2SYM(rb_intern("proxy_password"));
742
917
  PROXY_URL_SYMBOL = ID2SYM(rb_intern("proxy_url"));
743
918
  PROXY_USERNAME_SYMBOL = ID2SYM(rb_intern("proxy_username"));
744
- PROXY_PASSWORD_SYMBOL = ID2SYM(rb_intern("proxy_password"));
919
+ TIMEOUT_SYMBOL = ID2SYM(rb_intern("timeout"));
920
+ USERNAME_SYMBOL = ID2SYM(rb_intern("username"));
745
921
 
746
922
  /* Define the method identifiers: */
747
- DEBUG_ID = rb_intern("debug");
748
- ENCODE_WWW_FORM_ID = rb_intern("encode_www_form");
749
- INFO_ID = rb_intern("info");
750
- INFO_Q_ID = rb_intern("info?");
751
- READ_ID = rb_intern("read");
752
- STRING_ID = rb_intern("string");
753
- STRING_IO_ID = rb_intern("StringIO");
754
- URI_ID = rb_intern("URI");
755
- WRITE_ID = rb_intern("write");
923
+ COMPARE_BY_IDENTITY_ID = rb_intern("compare_by_identity");
924
+ DEBUG_ID = rb_intern("debug");
925
+ DOWNCASE_ID = rb_intern("downcase");
926
+ ENCODE_WWW_FORM_ID = rb_intern("encode_www_form");
927
+ INFO_ID = rb_intern("info");
928
+ INFO_Q_ID = rb_intern("info?");
929
+ READ_ID = rb_intern("read");
930
+ STRING_ID = rb_intern("string");
931
+ STRING_IO_ID = rb_intern("StringIO");
932
+ URI_ID = rb_intern("URI");
933
+ WRITE_ID = rb_intern("write");
756
934
 
757
935
  /* Locate classes: */
758
936
  STRING_IO_CLASS = rb_const_get(rb_cObject, STRING_IO_ID);