jameskilton-patron 0.4.3

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2008 The Hive http://www.thehive.com/
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.txt ADDED
@@ -0,0 +1,56 @@
1
+ = Ruby HTTP Client
2
+
3
+ == SYNOPSIS
4
+
5
+ Patron is a Ruby HTTP client library based on libcurl. It does not try to expose
6
+ the full "power" (read complexity) of libcurl but instead tries to provide a
7
+ sane API while taking advantage of libcurl under the hood.
8
+
9
+
10
+ == USAGE
11
+
12
+ Usage is very simple. First, you instantiate a Session object. You can set a few
13
+ default options on the Session instance that will be used by all subsequent
14
+ requests:
15
+
16
+ sess = Patron::Session.new
17
+ sess.timeout = 10
18
+ sess.base_url = "http://myserver.com:9900"
19
+ sess.headers['User-Agent'] = 'myapp/1.0'
20
+
21
+ The Session is used to make HTTP requests.
22
+
23
+ resp = sess.get("/foo/bar")
24
+
25
+ Requests return a Response object:
26
+
27
+ if resp.status < 400
28
+ puts resp.body
29
+ end
30
+
31
+ The GET, HEAD, PUT, POST and DELETE operations are all supported.
32
+
33
+ sess.put("/foo/baz", "some data")
34
+ sess.delete("/foo/baz")
35
+
36
+ You can ship custom headers with a single request:
37
+
38
+ sess.post("/foo/stuff", "some data", {"Content-Type" => "text/plain"})
39
+
40
+ That is pretty much all there is to it.
41
+
42
+
43
+ == REQUIREMENTS
44
+
45
+ You need a recent version of libcurl in order to install this gem. On MacOS X
46
+ the provided libcurl is sufficient. You will have to install the libcurl
47
+ development packages on Debian or Ubuntu. Other Linux systems are probably
48
+ similar. Windows users are on your own. Good luck with that.
49
+
50
+
51
+ == INSTALL
52
+
53
+ sudo gem install patron
54
+
55
+
56
+ Copyright (c) 2008 The Hive
data/Rakefile ADDED
@@ -0,0 +1,131 @@
1
+ ## -------------------------------------------------------------------
2
+ ##
3
+ ## Copyright (c) 2008 The Hive http://www.thehive.com/
4
+ ##
5
+ ## Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ ## of this software and associated documentation files (the "Software"), to deal
7
+ ## in the Software without restriction, including without limitation the rights
8
+ ## to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ ## copies of the Software, and to permit persons to whom the Software is
10
+ ## furnished to do so, subject to the following conditions:
11
+ ##
12
+ ## The above copyright notice and this permission notice shall be included in
13
+ ## all copies or substantial portions of the Software.
14
+ ##
15
+ ## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ ## IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ ## FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ ## AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ ## LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ ## OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ ## THE SOFTWARE.
22
+ ##
23
+ ## -------------------------------------------------------------------
24
+ require 'yaml'
25
+ require 'rake/clean'
26
+ require 'rake/rdoctask'
27
+ require 'spec/rake/spectask'
28
+ require 'jeweler'
29
+ require 'yard'
30
+
31
+ require 'rbconfig'
32
+ include Config
33
+
34
+ EXT_DIR = 'ext/patron'
35
+ SESSION_SO = "#{EXT_DIR}/session_ext.#{CONFIG['DLEXT']}"
36
+ SESSION_SRC = "#{EXT_DIR}/session_ext.c"
37
+
38
+ CLEAN.include FileList["#{EXT_DIR}/*"].exclude(/^.*\.(rb|c)$/)
39
+ CLOBBER.include %w( doc coverage pkg )
40
+
41
+ module Git
42
+ class Lib
43
+ def tag(tag)
44
+ # Force an annotated tag
45
+ command('tag', [tag, '-a', '-m', tag])
46
+ end
47
+ end
48
+ end
49
+
50
+ Jeweler::Tasks.new do |s|
51
+ s.name = 'patron'
52
+ s.platform = Gem::Platform::RUBY
53
+ s.author = 'Phillip Toland'
54
+ s.email = 'phil.toland@gmail.com'
55
+ s.homepage = 'http://github.com/toland/Patron'
56
+ s.rubyforge_project = 'patron'
57
+ s.summary = 'Patron HTTP client'
58
+ s.description = 'Ruby HTTP client library based on libcurl'
59
+
60
+ s.extensions << 'ext/patron/extconf.rb'
61
+ s.require_paths << 'ext'
62
+
63
+ s.files = FileList['README.txt',
64
+ 'LICENSE',
65
+ 'Rakefile',
66
+ 'lib/**/*',
67
+ 'spec/*',
68
+ 'ext/patron/*.{rb,c}']
69
+
70
+ # rdoc
71
+ s.has_rdoc = true
72
+ s.extra_rdoc_files = ['README.txt']
73
+ s.rdoc_options = ['--quiet',
74
+ '--title', "Patron documentation",
75
+ '--opname', 'index.html',
76
+ '--line-numbers',
77
+ '--main', 'README.txt',
78
+ '--inline-source']
79
+ end
80
+
81
+ file SESSION_SO => SESSION_SRC do
82
+ cd EXT_DIR do
83
+ ruby 'extconf.rb'
84
+ sh 'make'
85
+ end
86
+ end
87
+
88
+ desc "Compile extension"
89
+ task :compile => SESSION_SO
90
+
91
+ desc "Start an IRB shell"
92
+ task :shell => :compile do
93
+ sh 'irb -I./lib -I./ext -r patron'
94
+ end
95
+
96
+ Rake::RDocTask.new do |rdoc|
97
+ rdoc.rdoc_dir = 'doc'
98
+ rdoc.title = "Patron documentation"
99
+ rdoc.main = 'README.txt'
100
+ rdoc.options << '--line-numbers' << '--inline-source'
101
+ rdoc.rdoc_files.include('README.txt')
102
+ rdoc.rdoc_files.include('lib/**/*.rb')
103
+ end
104
+
105
+ YARD::Rake::YardocTask.new do |t|
106
+ t.files = ['lib/**/*.rb']
107
+ t.options = ['--readme', 'README.txt']
108
+ end
109
+
110
+ desc "Run specs"
111
+ Spec::Rake::SpecTask.new(:spec) do |t|
112
+ t.spec_opts = ['--options', "spec/spec.opts"]
113
+ t.spec_files = FileList['spec/**/*_spec.rb']
114
+ end
115
+
116
+ task :spec => [:compile]
117
+
118
+ desc "Run specs with RCov"
119
+ Spec::Rake::SpecTask.new('spec:rcov') do |t|
120
+ t.spec_files = FileList['spec/**/*_spec.rb']
121
+ t.rcov = true
122
+ t.rcov_opts << '--sort coverage'
123
+ t.rcov_opts << '--comments'
124
+ t.rcov_opts << '--exclude spec'
125
+ t.rcov_opts << '--exclude lib/magneto.rb'
126
+ t.rcov_opts << '--exclude /Library/Ruby/Gems'
127
+ end
128
+
129
+ Jeweler::RubyforgeTasks.new
130
+
131
+ task :default => :spec
@@ -0,0 +1,43 @@
1
+ ## -------------------------------------------------------------------
2
+ ##
3
+ ## Copyright (c) 2008 The Hive http://www.thehive.com/
4
+ ##
5
+ ## Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ ## of this software and associated documentation files (the "Software"), to deal
7
+ ## in the Software without restriction, including without limitation the rights
8
+ ## to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ ## copies of the Software, and to permit persons to whom the Software is
10
+ ## furnished to do so, subject to the following conditions:
11
+ ##
12
+ ## The above copyright notice and this permission notice shall be included in
13
+ ## all copies or substantial portions of the Software.
14
+ ##
15
+ ## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ ## IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ ## FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ ## AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ ## LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ ## OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ ## THE SOFTWARE.
22
+ ##
23
+ ## -------------------------------------------------------------------
24
+ require 'mkmf'
25
+ require 'rbconfig'
26
+
27
+ if find_executable('curl-config')
28
+ $CFLAGS << " #{`curl-config --cflags`.strip}"
29
+ $LIBS << " #{`curl-config --libs`.strip}"
30
+ elsif !have_library('curl') or !have_header('curl/curl.h')
31
+ fail <<-EOM
32
+ Can't find libcurl or curl/curl.h
33
+
34
+ Try passing --with-curl-dir or --with-curl-lib and --with-curl-include
35
+ options to extconf.
36
+ EOM
37
+ end
38
+
39
+ if CONFIG['CC'] =~ /gcc/
40
+ $CFLAGS << ' -Wall'
41
+ end
42
+
43
+ create_makefile 'patron/session_ext'
@@ -0,0 +1,437 @@
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
+ #include <ruby.h>
26
+ #include <curl/curl.h>
27
+
28
+ static VALUE mPatron = Qnil;
29
+ static VALUE cSession = Qnil;
30
+ static VALUE cRequest = Qnil;
31
+ static VALUE ePatronError = Qnil;
32
+ static VALUE eUnsupportedProtocol = Qnil;
33
+ static VALUE eURLFormatError = Qnil;
34
+ static VALUE eHostResolutionError = Qnil;
35
+ static VALUE eConnectionFailed = Qnil;
36
+ static VALUE ePartialFileError = Qnil;
37
+ static VALUE eTimeoutError = Qnil;
38
+ static VALUE eTooManyRedirects = Qnil;
39
+
40
+
41
+ struct curl_state {
42
+ CURL* handle;
43
+ char* upload_buf;
44
+ FILE* download_file;
45
+ FILE* upload_file;
46
+ char error_buf[CURL_ERROR_SIZE];
47
+ struct curl_slist* headers;
48
+ };
49
+
50
+
51
+ //------------------------------------------------------------------------------
52
+ // Curl Callbacks
53
+ //
54
+
55
+ // Takes data streamed from libcurl and writes it to a Ruby string buffer.
56
+ static size_t session_write_handler(char* stream, size_t size, size_t nmemb, VALUE out) {
57
+ rb_str_buf_cat(out, stream, size * nmemb);
58
+ return size * nmemb;
59
+ }
60
+
61
+ static size_t session_read_handler(char* stream, size_t size, size_t nmemb, char **buffer) {
62
+ size_t result = 0;
63
+
64
+ if (buffer != NULL && *buffer != NULL) {
65
+ int len = size * nmemb;
66
+ char *s1 = strncpy(stream, *buffer, len);
67
+ result = strlen(s1);
68
+ *buffer += result;
69
+ }
70
+
71
+ return result;
72
+ }
73
+
74
+ //------------------------------------------------------------------------------
75
+ // Object allocation
76
+ //
77
+
78
+ // Cleans up the Curl handle when the Session object is garbage collected.
79
+ void session_free(struct curl_state *curl) {
80
+ curl_easy_cleanup(curl->handle);
81
+ free(curl);
82
+ }
83
+
84
+ // Allocates curl_state data needed for a new Session object.
85
+ VALUE session_alloc(VALUE klass) {
86
+ struct curl_state* curl;
87
+ VALUE obj = Data_Make_Struct(klass, struct curl_state, NULL, session_free, curl);
88
+ return obj;
89
+ }
90
+
91
+
92
+ //------------------------------------------------------------------------------
93
+ // Method implementations
94
+ //
95
+
96
+ // Returns the version of the embedded libcurl as a string.
97
+ VALUE libcurl_version(VALUE klass) {
98
+ char* value = curl_version();
99
+ return rb_str_new2(value);
100
+ }
101
+
102
+ // Initializes the libcurl handle on object initialization.
103
+ // NOTE: This must be called from Session#initialize.
104
+ VALUE session_ext_initialize(VALUE self) {
105
+ struct curl_state *state;
106
+ Data_Get_Struct(self, struct curl_state, state);
107
+
108
+ state->handle = curl_easy_init();
109
+
110
+ return self;
111
+ }
112
+
113
+ // URL escapes the provided string.
114
+ VALUE session_escape(VALUE self, VALUE value) {
115
+ struct curl_state *state;
116
+ Data_Get_Struct(self, struct curl_state, state);
117
+
118
+ VALUE string = StringValue(value);
119
+ char* escaped = curl_easy_escape(state->handle,
120
+ RSTRING_PTR(string),
121
+ RSTRING_LEN(string));
122
+
123
+ VALUE retval = rb_str_new2(escaped);
124
+ curl_free(escaped);
125
+
126
+ return retval;
127
+ }
128
+
129
+ // Unescapes the provided string.
130
+ VALUE session_unescape(VALUE self, VALUE value) {
131
+ struct curl_state *state;
132
+ Data_Get_Struct(self, struct curl_state, state);
133
+
134
+ VALUE string = StringValue(value);
135
+ char* unescaped = curl_easy_unescape(state->handle,
136
+ RSTRING_PTR(string),
137
+ RSTRING_LEN(string),
138
+ NULL);
139
+
140
+ VALUE retval = rb_str_new2(unescaped);
141
+ curl_free(unescaped);
142
+
143
+ return retval;
144
+ }
145
+
146
+ // Callback used to iterate over the HTTP headers and store them in an slist.
147
+ static VALUE each_http_header(VALUE header, VALUE self) {
148
+ struct curl_state *state;
149
+ Data_Get_Struct(self, struct curl_state, state);
150
+
151
+ VALUE name = rb_obj_as_string(rb_ary_entry(header, 0));
152
+ VALUE value = rb_obj_as_string(rb_ary_entry(header, 1));
153
+
154
+ VALUE header_str = Qnil;
155
+ header_str = rb_str_plus(name, rb_str_new2(": "));
156
+ header_str = rb_str_plus(header_str, value);
157
+
158
+ state->headers = curl_slist_append(state->headers, StringValuePtr(header_str));
159
+ return Qnil;
160
+ }
161
+
162
+ static void set_chunked_encoding(struct curl_state *state) {
163
+ state->headers = curl_slist_append(state->headers, "Transfer-Encoding: chunked");
164
+ }
165
+
166
+ static FILE* open_file(VALUE filename, char* perms) {
167
+ FILE* handle = fopen(StringValuePtr(filename), perms);
168
+ if (!handle) {
169
+ rb_raise(rb_eArgError, "Unable to open specified file.");
170
+ }
171
+
172
+ return handle;
173
+ }
174
+
175
+ // Set the options on the Curl handle from a Request object. Takes each field
176
+ // in the Request object and uses it to set the appropriate option on the Curl
177
+ // handle.
178
+ static void set_options_from_request(VALUE self, VALUE request) {
179
+ struct curl_state *state;
180
+ Data_Get_Struct(self, struct curl_state, state);
181
+
182
+ CURL* curl = state->handle;
183
+
184
+ VALUE headers = rb_iv_get(request, "@headers");
185
+ if (!NIL_P(headers)) {
186
+ if (rb_type(headers) != T_HASH) {
187
+ rb_raise(rb_eArgError, "Headers must be passed in a hash.");
188
+ }
189
+
190
+ rb_iterate(rb_each, headers, each_http_header, self);
191
+ }
192
+
193
+ ID action = SYM2ID(rb_iv_get(request, "@action"));
194
+ if (action == rb_intern("get")) {
195
+ curl_easy_setopt(curl, CURLOPT_HTTPGET, 1);
196
+
197
+ VALUE download_file = rb_iv_get(request, "@file_name");
198
+ if (!NIL_P(download_file)) {
199
+ state->download_file = open_file(download_file, "w");
200
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, state->download_file);
201
+ } else {
202
+ state->download_file = NULL;
203
+ }
204
+ } else if (action == rb_intern("post") || action == rb_intern("put")) {
205
+ VALUE data = rb_iv_get(request, "@upload_data");
206
+ VALUE filename = rb_iv_get(request, "@file_name");
207
+
208
+ if (!NIL_P(data)) {
209
+ state->upload_buf = StringValuePtr(data);
210
+ int len = RSTRING_LEN(data);
211
+
212
+ if (action == rb_intern("post")) {
213
+ curl_easy_setopt(curl, CURLOPT_POST, 1);
214
+ curl_easy_setopt(curl, CURLOPT_POSTFIELDS, state->upload_buf);
215
+ curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, len);
216
+ } else {
217
+ curl_easy_setopt(curl, CURLOPT_UPLOAD, 1);
218
+ curl_easy_setopt(curl, CURLOPT_READFUNCTION, &session_read_handler);
219
+ curl_easy_setopt(curl, CURLOPT_READDATA, &state->upload_buf);
220
+ curl_easy_setopt(curl, CURLOPT_INFILESIZE, len);
221
+ }
222
+ } else if (!NIL_P(filename)) {
223
+ set_chunked_encoding(state);
224
+
225
+ curl_easy_setopt(curl, CURLOPT_UPLOAD, 1);
226
+
227
+ if (action == rb_intern("post")) {
228
+ curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "POST");
229
+ }
230
+
231
+ state->upload_file = open_file(filename, "r");
232
+ curl_easy_setopt(curl, CURLOPT_READDATA, state->upload_file);
233
+ }
234
+ } else if (action == rb_intern("head")) {
235
+ curl_easy_setopt(curl, CURLOPT_NOBODY, 1);
236
+ } else {
237
+ VALUE action_name = rb_funcall(request, rb_intern("action_name"), 0);
238
+ curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, StringValuePtr(action_name));
239
+ }
240
+
241
+ curl_easy_setopt(curl, CURLOPT_HTTPHEADER, state->headers);
242
+ curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, state->error_buf);
243
+
244
+ VALUE url = rb_iv_get(request, "@url");
245
+ if (NIL_P(url)) {
246
+ rb_raise(rb_eArgError, "Must provide a URL");
247
+ }
248
+ curl_easy_setopt(curl, CURLOPT_URL, StringValuePtr(url));
249
+
250
+ VALUE timeout = rb_iv_get(request, "@timeout");
251
+ if (!NIL_P(timeout)) {
252
+ curl_easy_setopt(curl, CURLOPT_TIMEOUT, FIX2INT(timeout));
253
+ }
254
+
255
+ timeout = rb_iv_get(request, "@connect_timeout");
256
+ if (!NIL_P(timeout)) {
257
+ curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT_MS, FIX2INT(timeout));
258
+ }
259
+
260
+ VALUE redirects = rb_iv_get(request, "@max_redirects");
261
+ if (!NIL_P(redirects)) {
262
+ int r = FIX2INT(redirects);
263
+ curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, r == 0 ? 0 : 1);
264
+ curl_easy_setopt(curl, CURLOPT_MAXREDIRS, r);
265
+ }
266
+
267
+ VALUE proxy = rb_iv_get(request, "@proxy");
268
+ if (!NIL_P(proxy)) {
269
+ curl_easy_setopt(curl, CURLOPT_PROXY, StringValuePtr(proxy));
270
+ }
271
+
272
+ VALUE credentials = rb_funcall(request, rb_intern("credentials"), 0);
273
+ if (!NIL_P(credentials)) {
274
+ curl_easy_setopt(curl, CURLOPT_HTTPAUTH, rb_iv_get(request, "@auth_type"));
275
+ curl_easy_setopt(curl, CURLOPT_USERPWD, StringValuePtr(credentials));
276
+ }
277
+
278
+ VALUE insecure = rb_iv_get(request, "@insecure");
279
+ if(!NIL_P(insecure)) {
280
+ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
281
+ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 1);
282
+ }
283
+ }
284
+
285
+ // Use the info in a Curl handle to create a new Response object.
286
+ static VALUE create_response(CURL* curl) {
287
+ VALUE response = rb_class_new_instance(0, 0,
288
+ rb_const_get(mPatron, rb_intern("Response")));
289
+
290
+ char* url = NULL;
291
+ curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL, &url);
292
+ rb_iv_set(response, "@url", rb_str_new2(url));
293
+
294
+ long code = 0;
295
+ curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code);
296
+ rb_iv_set(response, "@status", INT2NUM(code));
297
+
298
+ long count = 0;
299
+ curl_easy_getinfo(curl, CURLINFO_REDIRECT_COUNT, &count);
300
+ rb_iv_set(response, "@redirect_count", INT2NUM(count));
301
+
302
+ return response;
303
+ }
304
+
305
+ // Raise an exception based on the Curl error code.
306
+ static VALUE select_error(CURLcode code) {
307
+ VALUE error = Qnil;
308
+ switch (code) {
309
+ case CURLE_UNSUPPORTED_PROTOCOL: error = eUnsupportedProtocol; break;
310
+ case CURLE_URL_MALFORMAT: error = eURLFormatError; break;
311
+ case CURLE_COULDNT_RESOLVE_HOST: error = eHostResolutionError; break;
312
+ case CURLE_COULDNT_CONNECT: error = eConnectionFailed; break;
313
+ case CURLE_PARTIAL_FILE: error = ePartialFileError; break;
314
+ case CURLE_OPERATION_TIMEDOUT: error = eTimeoutError; break;
315
+ case CURLE_TOO_MANY_REDIRECTS: error = eTooManyRedirects; break;
316
+
317
+ default: error = ePatronError;
318
+ }
319
+
320
+ return error;
321
+ }
322
+
323
+ // Perform the actual HTTP request by calling libcurl.
324
+ static VALUE perform_request(VALUE self) {
325
+ struct curl_state *state;
326
+ Data_Get_Struct(self, struct curl_state, state);
327
+
328
+ CURL* curl = state->handle;
329
+
330
+ // headers
331
+ VALUE header_buffer = rb_str_buf_new(32768);
332
+ curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, &session_write_handler);
333
+ curl_easy_setopt(curl, CURLOPT_HEADERDATA, header_buffer);
334
+
335
+ // body
336
+ VALUE body_buffer = Qnil;
337
+ if (!state->download_file) {
338
+ body_buffer = rb_str_buf_new(32768);
339
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &session_write_handler);
340
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, body_buffer);
341
+ }
342
+
343
+ CURLcode ret = curl_easy_perform(curl);
344
+ if (CURLE_OK == ret) {
345
+ VALUE response = create_response(curl);
346
+ if (!NIL_P(body_buffer)) {
347
+ rb_iv_set(response, "@body", body_buffer);
348
+ }
349
+ rb_funcall(response, rb_intern("parse_headers"), 1, header_buffer);
350
+ return response;
351
+ } else {
352
+ rb_raise(select_error(ret), state->error_buf);
353
+ }
354
+ }
355
+
356
+ // Cleanup after each request by resetting the Curl handle and deallocating all
357
+ // request related objects such as the header slist.
358
+ static VALUE cleanup(VALUE self) {
359
+ struct curl_state *state;
360
+ Data_Get_Struct(self, struct curl_state, state);
361
+
362
+ curl_easy_reset(state->handle);
363
+
364
+ if (state->headers) {
365
+ curl_slist_free_all(state->headers);
366
+ state->headers = NULL;
367
+ }
368
+
369
+ if (state->download_file) {
370
+ fclose(state->download_file);
371
+ state->download_file = NULL;
372
+ }
373
+
374
+ if (state->upload_file) {
375
+ fclose(state->upload_file);
376
+ state->upload_file = NULL;
377
+ }
378
+
379
+ state->upload_buf = NULL;
380
+
381
+ return Qnil;
382
+ }
383
+
384
+ VALUE session_handle_request(VALUE self, VALUE request) {
385
+ set_options_from_request(self, request);
386
+ return rb_ensure(&perform_request, self, &cleanup, self);
387
+ }
388
+
389
+ VALUE enable_cookie_session(VALUE self, VALUE file) {
390
+ struct curl_state *state;
391
+ Data_Get_Struct(self, struct curl_state, state);
392
+ CURL* curl = state->handle;
393
+ char *file_path = RSTRING_PTR(file);
394
+ if (file_path != "")
395
+ curl_easy_setopt(curl, CURLOPT_COOKIEJAR, file_path);
396
+ curl_easy_setopt(curl, CURLOPT_COOKIEFILE, file_path);
397
+ return Qnil;
398
+ }
399
+
400
+ //------------------------------------------------------------------------------
401
+ // Extension initialization
402
+ //
403
+
404
+ void Init_session_ext() {
405
+ curl_global_init(CURL_GLOBAL_ALL);
406
+ rb_require("patron/error");
407
+
408
+ mPatron = rb_define_module("Patron");
409
+
410
+ ePatronError = rb_const_get(mPatron, rb_intern("Error"));
411
+
412
+ eUnsupportedProtocol = rb_const_get(mPatron, rb_intern("UnsupportedProtocol"));
413
+ eURLFormatError = rb_const_get(mPatron, rb_intern("URLFormatError"));
414
+ eHostResolutionError = rb_const_get(mPatron, rb_intern("HostResolutionError"));
415
+ eConnectionFailed = rb_const_get(mPatron, rb_intern("ConnectionFailed"));
416
+ ePartialFileError = rb_const_get(mPatron, rb_intern("PartialFileError"));
417
+ eTimeoutError = rb_const_get(mPatron, rb_intern("TimeoutError"));
418
+ eTooManyRedirects = rb_const_get(mPatron, rb_intern("TooManyRedirects"));
419
+
420
+
421
+ rb_define_module_function(mPatron, "libcurl_version", libcurl_version, 0);
422
+
423
+ cSession = rb_define_class_under(mPatron, "Session", rb_cObject);
424
+ cRequest = rb_define_class_under(mPatron, "Request", rb_cObject);
425
+ rb_define_alloc_func(cSession, session_alloc);
426
+
427
+ rb_define_method(cSession, "ext_initialize", session_ext_initialize, 0);
428
+ rb_define_method(cSession, "escape", session_escape, 1);
429
+ rb_define_method(cSession, "unescape", session_unescape, 1);
430
+ rb_define_method(cSession, "handle_request", session_handle_request, 1);
431
+ rb_define_method(cSession, "enable_cookie_session", enable_cookie_session, 1);
432
+
433
+ rb_define_const(cRequest, "AuthBasic", CURLAUTH_BASIC);
434
+ rb_define_const(cRequest, "AuthDigest", CURLAUTH_DIGEST);
435
+ rb_define_const(cRequest, "AuthAny", CURLAUTH_ANY);
436
+
437
+ }