ovirt-engine-sdk 4.1.5 → 4.1.6

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: 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);