patron 0.4.16 → 0.4.17
Sign up to get free protection for your applications and to get access to all the features.
- 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));
|