patron 0.4.16 → 0.4.17

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,29 +1,33 @@
1
- // -------------------------------------------------------------------
2
- //
3
- // Patron HTTP Client: Interface to libcurl
4
- // Copyright (c) 2008 The Hive http://www.thehive.com/
5
- //
6
- // Permission is hereby granted, free of charge, to any person obtaining a copy
7
- // of this software and associated documentation files (the "Software"), to deal
8
- // in the Software without restriction, including without limitation the rights
9
- // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
- // copies of the Software, and to permit persons to whom the Software is
11
- // furnished to do so, subject to the following conditions:
12
- //
13
- // The above copyright notice and this permission notice shall be included in
14
- // all copies or substantial portions of the Software.
15
- //
16
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
- // THE SOFTWARE.
23
- //
24
- // -------------------------------------------------------------------
1
+ /* -------------------------------------------------------------------------- *\
2
+ *
3
+ * Patron HTTP Client: Interface to libcurl
4
+ * Copyright (c) 2008 The Hive http://www.thehive.com/
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in
14
+ * all copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ * THE SOFTWARE.
23
+ *
24
+ \* -------------------------------------------------------------------------- */
25
25
  #include <ruby.h>
26
26
  #include <curl/curl.h>
27
+ #include "membuffer.h"
28
+ #include "sglib.h" /* Simple Generic Library -> http://sglib.sourceforge.net */
29
+
30
+ #define UNUSED_ARGUMENT(x) (void)x
27
31
 
28
32
  static VALUE mPatron = Qnil;
29
33
  static VALUE mProxyType = Qnil;
@@ -49,15 +53,23 @@ struct curl_state {
49
53
  struct curl_slist* headers;
50
54
  struct curl_httppost* post;
51
55
  struct curl_httppost* last;
56
+ membuffer header_buffer;
57
+ membuffer body_buffer;
58
+ int interrupt;
52
59
  };
53
60
 
54
- //------------------------------------------------------------------------------
55
- // Curl Callbacks
56
- //
57
61
 
58
- // Takes data streamed from libcurl and writes it to a Ruby string buffer.
59
- static size_t session_write_handler(char* stream, size_t size, size_t nmemb, VALUE out) {
60
- rb_str_buf_cat(out, stream, size * nmemb);
62
+ /*----------------------------------------------------------------------------*/
63
+ /* Curl Callbacks */
64
+
65
+ /* Takes data streamed from libcurl and writes it to a Ruby string buffer. */
66
+ static size_t session_write_handler(char* stream, size_t size, size_t nmemb, membuffer* buf) {
67
+ int rc = membuffer_append(buf, stream, size * nmemb);
68
+
69
+ /* return 0 to signal that we could not append data to our buffer */
70
+ if (MB_OK != rc) { return 0; }
71
+
72
+ /* otherwise, return the number of bytes appended */
61
73
  return size * nmemb;
62
74
  }
63
75
 
@@ -65,133 +77,220 @@ static size_t session_read_handler(char* stream, size_t size, size_t nmemb, char
65
77
  size_t result = 0;
66
78
 
67
79
  if (buffer != NULL && *buffer != NULL) {
68
- int len = size * nmemb;
69
- char *s1 = strncpy(stream, *buffer, len);
70
- result = strlen(s1);
71
- *buffer += result;
80
+ size_t len = size * nmemb;
81
+ char *s1 = strncpy(stream, *buffer, len);
82
+ result = strlen(s1);
83
+ *buffer += result;
72
84
  }
73
85
 
74
86
  return result;
75
87
  }
76
88
 
77
- //------------------------------------------------------------------------------
78
- // Object allocation
79
- //
89
+ /* A non-zero return value from the progress handler will terminate the current
90
+ * request. We use this fact in order to interrupt any request when either the
91
+ * user calls the "interrupt" method on the session or when the Ruby interpreter
92
+ * is attempting to exit.
93
+ */
94
+ static int session_progress_handler(void *clientp, double dltotal, double dlnow, double ultotal, double ulnow) {
95
+ struct curl_state* state = (struct curl_state*) clientp;
96
+ UNUSED_ARGUMENT(dltotal);
97
+ UNUSED_ARGUMENT(dlnow);
98
+ UNUSED_ARGUMENT(ultotal);
99
+ UNUSED_ARGUMENT(ulnow);
100
+ return state->interrupt;
101
+ }
102
+
103
+
104
+ /*----------------------------------------------------------------------------*/
105
+ /* List of active curl sessions */
106
+
107
+ struct curl_state_list {
108
+ struct curl_state *state;
109
+ struct curl_state_list *next;
110
+ };
111
+
112
+ #define CS_LIST_COMPARATOR(p, _state_) (p->state == _state_)
113
+
114
+ static struct curl_state_list *cs_list = NULL;
115
+
116
+ static void cs_list_append( struct curl_state *state ) {
117
+ struct curl_state_list *item = NULL;
118
+
119
+ assert(state != NULL);
120
+ item = ruby_xmalloc(sizeof(struct curl_state_list));
121
+ item->state = state;
122
+ item->next = NULL;
123
+
124
+ SGLIB_LIST_ADD(struct curl_state_list, cs_list, item, next);
125
+ }
126
+
127
+ static void cs_list_remove(struct curl_state *state) {
128
+ struct curl_state_list *item = NULL;
129
+
130
+ assert(state != NULL);
131
+ SGLIB_LIST_FIND_MEMBER(struct curl_state_list, cs_list, state, CS_LIST_COMPARATOR, next, item);
132
+ if (item) {
133
+ SGLIB_LIST_DELETE(struct curl_state_list, cs_list, item, next);
134
+ ruby_xfree(item);
135
+ }
136
+ }
137
+
138
+ static void cs_list_interrupt(VALUE data) {
139
+ UNUSED_ARGUMENT(data);
140
+
141
+ SGLIB_LIST_MAP_ON_ELEMENTS(struct curl_state_list, cs_list, item, next, {
142
+ item->state->interrupt = 1;
143
+ });
144
+ }
80
145
 
81
- // Cleans up the Curl handle when the Session object is garbage collected.
82
- void session_free(struct curl_state *curl) {
83
- curl_easy_cleanup(curl->handle);
84
146
 
85
- if (curl->debug_file) {
147
+ /*----------------------------------------------------------------------------*/
148
+ /* Object allocation */
149
+
150
+ static void session_close_debug_file(struct curl_state *curl) {
151
+ if (curl->debug_file && stderr != curl->debug_file) {
86
152
  fclose(curl->debug_file);
87
- curl->debug_file = NULL;
153
+ }
154
+ curl->debug_file = NULL;
155
+ }
156
+
157
+ /* Cleans up the Curl handle when the Session object is garbage collected. */
158
+ void session_free(struct curl_state *curl) {
159
+ if (curl->handle) {
160
+ curl_easy_cleanup(curl->handle);
161
+ curl->handle = NULL;
88
162
  }
89
163
 
164
+ session_close_debug_file(curl);
165
+
166
+ membuffer_destroy( &curl->header_buffer );
167
+ membuffer_destroy( &curl->body_buffer );
168
+
169
+ cs_list_remove(curl);
170
+
90
171
  free(curl);
91
172
  }
92
173
 
93
- // Allocates curl_state data needed for a new Session object.
174
+ /* Allocates curl_state data needed for a new Session object. */
94
175
  VALUE session_alloc(VALUE klass) {
95
176
  struct curl_state* curl;
96
177
  VALUE obj = Data_Make_Struct(klass, struct curl_state, NULL, session_free, curl);
97
- return obj;
98
- }
99
-
100
178
 
101
- //------------------------------------------------------------------------------
102
- // Method implementations
103
- //
179
+ membuffer_init( &curl->header_buffer );
180
+ membuffer_init( &curl->body_buffer );
181
+ cs_list_append(curl);
104
182
 
105
- // Returns the version of the embedded libcurl as a string.
106
- VALUE libcurl_version(VALUE klass) {
107
- char* value = curl_version();
108
- return rb_str_new2(value);
183
+ return obj;
109
184
  }
110
185
 
111
- // Initializes the libcurl handle on object initialization.
112
- // NOTE: This must be called from Session#initialize.
113
- VALUE session_ext_initialize(VALUE self) {
186
+ /* Return the curl_state from the ruby VALUE which is the Session instance. */
187
+ static struct curl_state* get_curl_state(VALUE self) {
114
188
  struct curl_state *state;
115
189
  Data_Get_Struct(self, struct curl_state, state);
116
190
 
117
- state->handle = curl_easy_init();
118
- state->post = NULL;
119
- state->last = NULL;
191
+ if (NULL == state->handle) {
192
+ state->handle = curl_easy_init();
193
+ curl_easy_setopt(state->handle, CURLOPT_NOSIGNAL, 1);
194
+ curl_easy_setopt(state->handle, CURLOPT_NOPROGRESS, 0);
195
+ curl_easy_setopt(state->handle, CURLOPT_PROGRESSFUNCTION, &session_progress_handler);
196
+ curl_easy_setopt(state->handle, CURLOPT_PROGRESSDATA, state);
197
+ }
120
198
 
121
- return self;
199
+ return state;
122
200
  }
123
201
 
124
- // URL escapes the provided string.
125
- VALUE session_escape(VALUE self, VALUE value) {
126
- struct curl_state *state;
127
- Data_Get_Struct(self, struct curl_state, state);
128
202
 
203
+ /*----------------------------------------------------------------------------*/
204
+ /* Method implementations */
205
+
206
+ /* call-seq:
207
+ * Patron.libcurl_version -> version string
208
+ *
209
+ * Returns the version of the embedded libcurl as a string.
210
+ */
211
+ static VALUE libcurl_version(VALUE klass) {
212
+ char* value = curl_version();
213
+ UNUSED_ARGUMENT(klass);
214
+ return rb_str_new2(value);
215
+ }
216
+
217
+ /* call-seq:
218
+ * Session.escape( string ) -> escaped string
219
+ *
220
+ * URL escapes the provided string.
221
+ */
222
+ static VALUE session_escape(VALUE self, VALUE value) {
223
+ struct curl_state *state = get_curl_state(self);
129
224
  VALUE string = StringValue(value);
130
- char* escaped = curl_easy_escape(state->handle,
131
- RSTRING_PTR(string),
132
- RSTRING_LEN(string));
225
+ char* escaped = NULL;
226
+ VALUE retval = Qnil;
133
227
 
134
- VALUE retval = rb_str_new2(escaped);
228
+ escaped = curl_easy_escape(state->handle,
229
+ RSTRING_PTR(string),
230
+ (int) RSTRING_LEN(string));
231
+
232
+ retval = rb_str_new2(escaped);
135
233
  curl_free(escaped);
136
234
 
137
235
  return retval;
138
236
  }
139
237
 
140
- // Unescapes the provided string.
141
- VALUE session_unescape(VALUE self, VALUE value) {
142
- struct curl_state *state;
143
- Data_Get_Struct(self, struct curl_state, state);
144
-
238
+ /* call-seq:
239
+ * Session.unescape( string ) -> unescaped string
240
+ *
241
+ * Unescapes the provided string.
242
+ */
243
+ static VALUE session_unescape(VALUE self, VALUE value) {
244
+ struct curl_state *state = get_curl_state(self);
145
245
  VALUE string = StringValue(value);
146
- char* unescaped = curl_easy_unescape(state->handle,
147
- RSTRING_PTR(string),
148
- RSTRING_LEN(string),
149
- NULL);
246
+ char* unescaped = NULL;
247
+ VALUE retval = Qnil;
248
+
249
+ unescaped = curl_easy_unescape(state->handle,
250
+ RSTRING_PTR(string),
251
+ (int) RSTRING_LEN(string),
252
+ NULL);
150
253
 
151
- VALUE retval = rb_str_new2(unescaped);
254
+ retval = rb_str_new2(unescaped);
152
255
  curl_free(unescaped);
153
256
 
154
257
  return retval;
155
258
  }
156
259
 
157
- // Callback used to iterate over the HTTP headers and store them in an slist.
260
+ /* Callback used to iterate over the HTTP headers and store them in an slist. */
158
261
  static int each_http_header(VALUE header_key, VALUE header_value, VALUE self) {
159
- struct curl_state *state;
160
- Data_Get_Struct(self, struct curl_state, state);
161
-
262
+ struct curl_state *state = get_curl_state(self);
162
263
  VALUE name = rb_obj_as_string(header_key);
163
264
  VALUE value = rb_obj_as_string(header_value);
164
-
165
265
  VALUE header_str = Qnil;
266
+
166
267
  header_str = rb_str_plus(name, rb_str_new2(": "));
167
268
  header_str = rb_str_plus(header_str, value);
168
269
 
169
270
  state->headers = curl_slist_append(state->headers, StringValuePtr(header_str));
271
+
170
272
  return 0;
171
273
  }
172
274
 
173
275
  static int formadd_values(VALUE data_key, VALUE data_value, VALUE self) {
174
- struct curl_state *state;
175
- Data_Get_Struct(self, struct curl_state, state);
176
-
276
+ struct curl_state *state = get_curl_state(self);
177
277
  VALUE name = rb_obj_as_string(data_key);
178
278
  VALUE value = rb_obj_as_string(data_value);
179
279
 
180
280
  curl_formadd(&state->post, &state->last, CURLFORM_PTRNAME, RSTRING_PTR(name),
181
- CURLFORM_PTRCONTENTS, RSTRING_PTR(value), CURLFORM_END);
281
+ CURLFORM_PTRCONTENTS, RSTRING_PTR(value), CURLFORM_END);
282
+
182
283
  return 0;
183
284
  }
184
285
 
185
286
  static int formadd_files(VALUE data_key, VALUE data_value, VALUE self) {
186
- struct curl_state *state;
187
- Data_Get_Struct(self, struct curl_state, state);
188
-
287
+ struct curl_state *state = get_curl_state(self);
189
288
  VALUE name = rb_obj_as_string(data_key);
190
289
  VALUE value = rb_obj_as_string(data_value);
191
290
 
192
291
  curl_formadd(&state->post, &state->last, CURLFORM_PTRNAME, RSTRING_PTR(name),
193
- CURLFORM_FILE, RSTRING_PTR(value), CURLFORM_END);
194
-
292
+ CURLFORM_FILE, RSTRING_PTR(value), CURLFORM_END);
293
+
195
294
  return 0;
196
295
  }
197
296
 
@@ -199,7 +298,7 @@ static void set_chunked_encoding(struct curl_state *state) {
199
298
  state->headers = curl_slist_append(state->headers, "Transfer-Encoding: chunked");
200
299
  }
201
300
 
202
- static FILE* open_file(VALUE filename, char* perms) {
301
+ static FILE* open_file(VALUE filename, const char* perms) {
203
302
  FILE* handle = fopen(StringValuePtr(filename), perms);
204
303
  if (!handle) {
205
304
  rb_raise(rb_eArgError, "Unable to open specified file.");
@@ -208,16 +307,27 @@ static FILE* open_file(VALUE filename, char* perms) {
208
307
  return handle;
209
308
  }
210
309
 
211
- // Set the options on the Curl handle from a Request object. Takes each field
212
- // in the Request object and uses it to set the appropriate option on the Curl
213
- // handle.
310
+ /* Set the options on the Curl handle from a Request object. Takes each field
311
+ * in the Request object and uses it to set the appropriate option on the Curl
312
+ * handle.
313
+ */
214
314
  static void set_options_from_request(VALUE self, VALUE request) {
215
- struct curl_state *state;
216
- Data_Get_Struct(self, struct curl_state, state);
217
-
315
+ struct curl_state *state = get_curl_state(self);
218
316
  CURL* curl = state->handle;
219
317
 
220
- VALUE headers = rb_iv_get(request, "@headers");
318
+ ID action = Qnil;
319
+ VALUE headers = Qnil;
320
+ VALUE url = Qnil;
321
+ VALUE timeout = Qnil;
322
+ VALUE redirects = Qnil;
323
+ VALUE proxy = Qnil;
324
+ VALUE proxy_type = Qnil;
325
+ VALUE credentials = Qnil;
326
+ VALUE ignore_content_length = Qnil;
327
+ VALUE insecure = Qnil;
328
+ VALUE buffer_size = Qnil;
329
+
330
+ headers = rb_iv_get(request, "@headers");
221
331
  if (!NIL_P(headers)) {
222
332
  if (rb_type(headers) != T_HASH) {
223
333
  rb_raise(rb_eArgError, "Headers must be passed in a hash.");
@@ -225,11 +335,12 @@ static void set_options_from_request(VALUE self, VALUE request) {
225
335
 
226
336
  rb_hash_foreach(headers, each_http_header, self);
227
337
  }
228
- ID action = SYM2ID(rb_iv_get(request, "@action"));
229
- if (action == rb_intern("get")) {
230
- curl_easy_setopt(curl, CURLOPT_HTTPGET, 1);
231
338
 
339
+ action = SYM2ID(rb_iv_get(request, "@action"));
340
+ if (action == rb_intern("get")) {
232
341
  VALUE download_file = rb_iv_get(request, "@file_name");
342
+
343
+ curl_easy_setopt(curl, CURLOPT_HTTPGET, 1);
233
344
  if (!NIL_P(download_file)) {
234
345
  state->download_file = open_file(download_file, "w");
235
346
  curl_easy_setopt(curl, CURLOPT_WRITEDATA, state->download_file);
@@ -242,8 +353,9 @@ static void set_options_from_request(VALUE self, VALUE request) {
242
353
  VALUE multipart = rb_iv_get(request, "@multipart");
243
354
 
244
355
  if (!NIL_P(data) && NIL_P(multipart)) {
356
+ long len = RSTRING_LEN(data);
357
+
245
358
  state->upload_buf = StringValuePtr(data);
246
- int len = RSTRING_LEN(data);
247
359
 
248
360
  if (action == rb_intern("post")) {
249
361
  curl_easy_setopt(curl, CURLOPT_POST, 1);
@@ -275,11 +387,11 @@ static void set_options_from_request(VALUE self, VALUE request) {
275
387
  } else { rb_raise(rb_eArgError, "Data and Filename must be passed in a hash.");}
276
388
  }
277
389
  curl_easy_setopt(curl, CURLOPT_HTTPPOST, state->post);
278
-
390
+
279
391
  } else {
280
392
  rb_raise(rb_eArgError, "Multipart PUT not supported");
281
393
  }
282
-
394
+
283
395
  } else {
284
396
  rb_raise(rb_eArgError, "Must provide either data or a filename when doing a PUT or POST");
285
397
  }
@@ -293,13 +405,13 @@ static void set_options_from_request(VALUE self, VALUE request) {
293
405
  curl_easy_setopt(curl, CURLOPT_HTTPHEADER, state->headers);
294
406
  curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, state->error_buf);
295
407
 
296
- VALUE url = rb_iv_get(request, "@url");
408
+ url = rb_iv_get(request, "@url");
297
409
  if (NIL_P(url)) {
298
410
  rb_raise(rb_eArgError, "Must provide a URL");
299
411
  }
300
412
  curl_easy_setopt(curl, CURLOPT_URL, StringValuePtr(url));
301
413
 
302
- VALUE timeout = rb_iv_get(request, "@timeout");
414
+ timeout = rb_iv_get(request, "@timeout");
303
415
  if (!NIL_P(timeout)) {
304
416
  curl_easy_setopt(curl, CURLOPT_TIMEOUT, FIX2INT(timeout));
305
417
  }
@@ -309,41 +421,41 @@ static void set_options_from_request(VALUE self, VALUE request) {
309
421
  curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, FIX2INT(timeout));
310
422
  }
311
423
 
312
- VALUE redirects = rb_iv_get(request, "@max_redirects");
424
+ redirects = rb_iv_get(request, "@max_redirects");
313
425
  if (!NIL_P(redirects)) {
314
426
  int r = FIX2INT(redirects);
315
427
  curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, r == 0 ? 0 : 1);
316
428
  curl_easy_setopt(curl, CURLOPT_MAXREDIRS, r);
317
429
  }
318
430
 
319
- VALUE proxy = rb_iv_get(request, "@proxy");
431
+ proxy = rb_iv_get(request, "@proxy");
320
432
  if (!NIL_P(proxy)) {
321
433
  curl_easy_setopt(curl, CURLOPT_PROXY, StringValuePtr(proxy));
322
434
  }
323
435
 
324
- VALUE proxy_type = rb_iv_get(request, "@proxy_type");
436
+ proxy_type = rb_iv_get(request, "@proxy_type");
325
437
  if (!NIL_P(proxy_type)) {
326
438
  curl_easy_setopt(curl, CURLOPT_PROXYTYPE, FIX2INT(proxy_type));
327
439
  }
328
440
 
329
- VALUE credentials = rb_funcall(request, rb_intern("credentials"), 0);
441
+ credentials = rb_funcall(request, rb_intern("credentials"), 0);
330
442
  if (!NIL_P(credentials)) {
331
443
  curl_easy_setopt(curl, CURLOPT_HTTPAUTH, FIX2INT(rb_iv_get(request, "@auth_type")));
332
444
  curl_easy_setopt(curl, CURLOPT_USERPWD, StringValuePtr(credentials));
333
445
  }
334
446
 
335
- VALUE ignore_content_length = rb_iv_get(request, "@ignore_content_length");
447
+ ignore_content_length = rb_iv_get(request, "@ignore_content_length");
336
448
  if (!NIL_P(ignore_content_length)) {
337
449
  curl_easy_setopt(curl, CURLOPT_IGNORE_CONTENT_LENGTH, 1);
338
450
  }
339
451
 
340
- VALUE insecure = rb_iv_get(request, "@insecure");
452
+ insecure = rb_iv_get(request, "@insecure");
341
453
  if(!NIL_P(insecure)) {
342
454
  curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
343
455
  curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 1);
344
456
  }
345
457
 
346
- VALUE buffer_size = rb_iv_get(request, "@buffer_size");
458
+ buffer_size = rb_iv_get(request, "@buffer_size");
347
459
  if (!NIL_P(buffer_size)) {
348
460
  curl_easy_setopt(curl, CURLOPT_BUFFERSIZE, FIX2INT(buffer_size));
349
461
  }
@@ -354,29 +466,30 @@ static void set_options_from_request(VALUE self, VALUE request) {
354
466
  }
355
467
  }
356
468
 
357
- // Use the info in a Curl handle to create a new Response object.
469
+ /* Use the info in a Curl handle to create a new Response object. */
358
470
  static VALUE create_response(VALUE self, CURL* curl, VALUE header_buffer, VALUE body_buffer) {
471
+ VALUE args[6] = { Qnil, Qnil, Qnil, Qnil, Qnil, Qnil };
359
472
  char* effective_url = NULL;
473
+ long code = 0;
474
+ long count = 0;
475
+
360
476
  curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL, &effective_url);
361
- VALUE url = rb_str_new2(effective_url);
477
+ args[0] = rb_str_new2(effective_url);
362
478
 
363
- long code = 0;
364
479
  curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code);
365
- VALUE status = INT2NUM(code);
480
+ args[1] = INT2NUM(code);
366
481
 
367
- long count = 0;
368
482
  curl_easy_getinfo(curl, CURLINFO_REDIRECT_COUNT, &count);
369
- VALUE redirect_count = INT2NUM(count);
483
+ args[2] = INT2NUM(count);
370
484
 
371
- VALUE default_charset = rb_iv_get(self, "@default_response_charset");
485
+ args[3] = header_buffer;
486
+ args[4] = body_buffer;
487
+ args[5] = rb_iv_get(self, "@default_response_charset");
372
488
 
373
- VALUE args[6] = { url, status, redirect_count, header_buffer, body_buffer, default_charset };
374
-
375
- return rb_class_new_instance(6, args,
376
- rb_const_get(mPatron, rb_intern("Response")));
489
+ return rb_class_new_instance(6, args, rb_const_get(mPatron, rb_intern("Response")));
377
490
  }
378
491
 
379
- // Raise an exception based on the Curl error code.
492
+ /* Raise an exception based on the Curl error code. */
380
493
  static VALUE select_error(CURLcode code) {
381
494
  VALUE error = Qnil;
382
495
  switch (code) {
@@ -394,45 +507,57 @@ static VALUE select_error(CURLcode code) {
394
507
  return error;
395
508
  }
396
509
 
397
- // Perform the actual HTTP request by calling libcurl.
510
+ /* Perform the actual HTTP request by calling libcurl. */
398
511
  static VALUE perform_request(VALUE self) {
399
- struct curl_state *state;
400
- Data_Get_Struct(self, struct curl_state, state);
401
-
512
+ struct curl_state *state = get_curl_state(self);
402
513
  CURL* curl = state->handle;
514
+ membuffer* header_buffer = NULL;
515
+ membuffer* body_buffer = NULL;
516
+ CURLcode ret = 0;
403
517
 
404
- // headers
405
- VALUE header_buffer = rb_str_buf_new(32768);
518
+ state->interrupt = 0; /* clear any interrupt flags */
519
+
520
+ header_buffer = &state->header_buffer;
521
+ body_buffer = &state->body_buffer;
522
+
523
+ membuffer_clear(header_buffer);
524
+ membuffer_clear(body_buffer);
525
+
526
+ /* headers */
406
527
  curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, &session_write_handler);
407
528
  curl_easy_setopt(curl, CURLOPT_HEADERDATA, header_buffer);
408
529
 
409
- // body
410
- VALUE body_buffer = Qnil;
530
+ /* body */
411
531
  if (!state->download_file) {
412
- body_buffer = rb_str_buf_new(32768);
413
532
  curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &session_write_handler);
414
533
  curl_easy_setopt(curl, CURLOPT_WRITEDATA, body_buffer);
415
534
  }
416
535
 
417
536
  #if defined(HAVE_TBR) && defined(USE_TBR)
418
- CURLcode ret = rb_thread_blocking_region(curl_easy_perform, curl, RUBY_UBF_IO, 0);
537
+ ret = (CURLcode) rb_thread_blocking_region(
538
+ (rb_blocking_function_t*) curl_easy_perform, curl,
539
+ RUBY_UBF_IO, 0
540
+ );
419
541
  #else
420
- CURLcode ret = curl_easy_perform(curl);
542
+ ret = curl_easy_perform(curl);
421
543
  #endif
422
544
 
423
545
  if (CURLE_OK == ret) {
424
- return create_response(self, curl, header_buffer, body_buffer);
546
+ VALUE header_str = membuffer_to_rb_str(header_buffer);
547
+ VALUE body_str = Qnil;
548
+ if (!state->download_file) { body_str = membuffer_to_rb_str(body_buffer); }
549
+
550
+ return create_response(self, curl, header_str, body_str);
425
551
  } else {
426
552
  rb_raise(select_error(ret), "%s", state->error_buf);
427
553
  }
428
554
  }
429
555
 
430
- // Cleanup after each request by resetting the Curl handle and deallocating all
431
- // request related objects such as the header slist.
556
+ /* Cleanup after each request by resetting the Curl handle and deallocating
557
+ * all request related objects such as the header slist.
558
+ */
432
559
  static VALUE cleanup(VALUE self) {
433
- struct curl_state *state;
434
- Data_Get_Struct(self, struct curl_state, state);
435
-
560
+ struct curl_state *state = get_curl_state(self);
436
561
  curl_easy_reset(state->handle);
437
562
 
438
563
  if (state->headers) {
@@ -455,32 +580,86 @@ static VALUE cleanup(VALUE self) {
455
580
  return Qnil;
456
581
  }
457
582
 
458
- VALUE session_handle_request(VALUE self, VALUE request) {
583
+ /* call-seq:
584
+ * session.handle_request( request ) -> response
585
+ *
586
+ * Peform the actual HTTP request by calling libcurl. Each filed in the
587
+ * +request+ object will be used to set the appropriate option on the libcurl
588
+ * library. After the request completes, a Response object will be created and
589
+ * returned.
590
+ *
591
+ * In the event of an error in the libcurl library, a Ruby exception will be
592
+ * created and raised. The exception will return the libcurl error code and
593
+ * error message.
594
+ */
595
+ static VALUE session_handle_request(VALUE self, VALUE request) {
459
596
  set_options_from_request(self, request);
460
597
  return rb_ensure(&perform_request, self, &cleanup, self);
461
598
  }
462
599
 
463
- VALUE enable_cookie_session(VALUE self, VALUE file) {
600
+ /* call-seq:
601
+ * session.reset -> session
602
+ *
603
+ * Reset the underlying cURL session. This effectively closes all open
604
+ * connections and disables debug output.
605
+ */
606
+ static VALUE session_reset(VALUE self) {
464
607
  struct curl_state *state;
465
608
  Data_Get_Struct(self, struct curl_state, state);
609
+
610
+ if (NULL != state->handle) {
611
+ cleanup(self);
612
+ curl_easy_cleanup(state->handle);
613
+ state->handle = NULL;
614
+ session_close_debug_file(state);
615
+ }
616
+
617
+ return self;
618
+ }
619
+
620
+ /* call-seq:
621
+ * session.interrupt -> session
622
+ *
623
+ * Interrupt any currently executing request. This will cause the current
624
+ * request to error and raise an exception.
625
+ */
626
+ static VALUE session_interrupt(VALUE self) {
627
+ struct curl_state *state = get_curl_state(self);
628
+ state->interrupt = 1;
629
+ return self;
630
+ }
631
+
632
+ /* call-seq:
633
+ * session.enable_cookie_session( file ) -> session
634
+ *
635
+ * Turn on cookie handling for this session, storing them in memory by
636
+ * default or in +file+ if specified. The +file+ must be readable and
637
+ * writable. Calling multiple times will add more files.
638
+ */
639
+ static VALUE enable_cookie_session(VALUE self, VALUE file) {
640
+ struct curl_state *state = get_curl_state(self);
466
641
  CURL* curl = state->handle;
467
- char* file_path = RSTRING_PTR(file);
642
+ char* file_path = NULL;
643
+
644
+ file_path = RSTRING_PTR(file);
468
645
  if (file_path != NULL && strlen(file_path) != 0) {
469
646
  curl_easy_setopt(curl, CURLOPT_COOKIEJAR, file_path);
470
647
  }
471
648
  curl_easy_setopt(curl, CURLOPT_COOKIEFILE, file_path);
472
- return Qnil;
649
+
650
+ return self;
473
651
  }
474
652
 
475
- VALUE set_debug_file(VALUE self, VALUE file) {
476
- struct curl_state *state;
477
- Data_Get_Struct(self, struct curl_state, state);
653
+ /* call-seq:
654
+ * session.set_debug_file( file ) -> session
655
+ *
656
+ * Enable debug output to stderr or to specified +file+.
657
+ */
658
+ static VALUE set_debug_file(VALUE self, VALUE file) {
659
+ struct curl_state *state = get_curl_state(self);
478
660
  char* file_path = RSTRING_PTR(file);
479
661
 
480
- if(state->debug_file){
481
- fclose(state->debug_file);
482
- state->debug_file = NULL;
483
- }
662
+ session_close_debug_file(state);
484
663
 
485
664
  if(file_path != NULL && strlen(file_path) != 0) {
486
665
  state->debug_file = open_file(file, "w");
@@ -488,17 +667,19 @@ VALUE set_debug_file(VALUE self, VALUE file) {
488
667
  state->debug_file = stderr;
489
668
  }
490
669
 
491
- return Qnil;
670
+ return self;
492
671
  }
493
672
 
494
- //------------------------------------------------------------------------------
495
- // Extension initialization
496
- //
673
+
674
+ /*----------------------------------------------------------------------------*/
675
+ /* Extension initialization */
497
676
 
498
677
  void Init_session_ext() {
499
678
  curl_global_init(CURL_GLOBAL_ALL);
500
679
  rb_require("patron/error");
501
680
 
681
+ rb_set_end_proc(&cs_list_interrupt, Qnil);
682
+
502
683
  mPatron = rb_define_module("Patron");
503
684
 
504
685
  ePatronError = rb_const_get(mPatron, rb_intern("Error"));
@@ -517,12 +698,15 @@ void Init_session_ext() {
517
698
  cRequest = rb_define_class_under(mPatron, "Request", rb_cObject);
518
699
  rb_define_alloc_func(cSession, session_alloc);
519
700
 
520
- rb_define_method(cSession, "ext_initialize", session_ext_initialize, 0);
521
701
  rb_define_method(cSession, "escape", session_escape, 1);
522
702
  rb_define_method(cSession, "unescape", session_unescape, 1);
523
703
  rb_define_method(cSession, "handle_request", session_handle_request, 1);
704
+ rb_define_method(cSession, "reset", session_reset, 0);
705
+ rb_define_method(cSession, "interrupt", session_interrupt, 0);
524
706
  rb_define_method(cSession, "enable_cookie_session", enable_cookie_session, 1);
525
707
  rb_define_method(cSession, "set_debug_file", set_debug_file, 1);
708
+ rb_define_alias(cSession, "urlencode", "escape");
709
+ rb_define_alias(cSession, "urldecode", "unescape");
526
710
 
527
711
  rb_define_const(cRequest, "AuthBasic", INT2FIX(CURLAUTH_BASIC));
528
712
  rb_define_const(cRequest, "AuthDigest", INT2FIX(CURLAUTH_DIGEST));