patron 0.4.16 → 0.4.17
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.
- data/Gemfile.lock +14 -14
- data/Rakefile +1 -1
- data/ext/patron/extconf.rb +2 -1
- data/ext/patron/membuffer.c +85 -0
- data/ext/patron/membuffer.h +78 -0
- data/ext/patron/session_ext.c +344 -160
- data/ext/patron/sglib.h +1952 -0
- data/lib/patron/response.rb +4 -1
- data/lib/patron/session.rb +1 -8
- data/lib/patron/version.rb +1 -1
- data/patron.gemspec +3 -3
- data/pic.png +0 -0
- data/script/test_server +3 -111
- data/spec/response_spec.rb +6 -0
- data/spec/session_spec.rb +14 -1
- data/spec/spec_helper.rb +4 -0
- data/spec/support/test_server.rb +175 -0
- metadata +18 -13
data/ext/patron/session_ext.c
CHANGED
@@ -1,29 +1,33 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
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
|
-
|
59
|
-
|
60
|
-
|
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
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
103
|
-
|
179
|
+
membuffer_init( &curl->header_buffer );
|
180
|
+
membuffer_init( &curl->body_buffer );
|
181
|
+
cs_list_append(curl);
|
104
182
|
|
105
|
-
|
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
|
-
|
112
|
-
|
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
|
118
|
-
|
119
|
-
|
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
|
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 =
|
131
|
-
|
132
|
-
RSTRING_LEN(string));
|
225
|
+
char* escaped = NULL;
|
226
|
+
VALUE retval = Qnil;
|
133
227
|
|
134
|
-
|
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
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
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 =
|
147
|
-
|
148
|
-
|
149
|
-
|
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
|
-
|
254
|
+
retval = rb_str_new2(unescaped);
|
152
255
|
curl_free(unescaped);
|
153
256
|
|
154
257
|
return retval;
|
155
258
|
}
|
156
259
|
|
157
|
-
|
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
|
-
|
212
|
-
|
213
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
480
|
+
args[1] = INT2NUM(code);
|
366
481
|
|
367
|
-
long count = 0;
|
368
482
|
curl_easy_getinfo(curl, CURLINFO_REDIRECT_COUNT, &count);
|
369
|
-
|
483
|
+
args[2] = INT2NUM(count);
|
370
484
|
|
371
|
-
|
485
|
+
args[3] = header_buffer;
|
486
|
+
args[4] = body_buffer;
|
487
|
+
args[5] = rb_iv_get(self, "@default_response_charset");
|
372
488
|
|
373
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
405
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
542
|
+
ret = curl_easy_perform(curl);
|
421
543
|
#endif
|
422
544
|
|
423
545
|
if (CURLE_OK == ret) {
|
424
|
-
|
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
|
-
|
431
|
-
|
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
|
-
|
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
|
-
|
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 =
|
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
|
-
|
649
|
+
|
650
|
+
return self;
|
473
651
|
}
|
474
652
|
|
475
|
-
|
476
|
-
|
477
|
-
|
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
|
-
|
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
|
670
|
+
return self;
|
492
671
|
}
|
493
672
|
|
494
|
-
|
495
|
-
|
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));
|