patron 0.3.2

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.
@@ -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
@@ -0,0 +1,150 @@
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
+
30
+ require 'rbconfig'
31
+ include Config
32
+
33
+ EXT_DIR = 'ext/patron'
34
+ SESSION_SO = "#{EXT_DIR}/session_ext.#{CONFIG['DLEXT']}"
35
+ SESSION_SRC = "#{EXT_DIR}/session_ext.c"
36
+
37
+ CLEAN.include FileList["#{EXT_DIR}/*"].exclude(/^.*\.(rb|c)$/)
38
+ CLOBBER.include %w( doc coverage pkg )
39
+
40
+ module Git
41
+ class Lib
42
+ def tag(tag)
43
+ # Force an annotated tag
44
+ command('tag', [tag, '-a', '-m', tag])
45
+ end
46
+ end
47
+ end
48
+
49
+ Jeweler::Tasks.new do |s|
50
+ s.name = 'patron'
51
+ s.platform = Gem::Platform::RUBY
52
+ s.author = 'Phillip Toland'
53
+ s.email = 'phil.toland@gmail.com'
54
+ s.homepage = 'http://github.com/toland/Patron'
55
+ s.rubyforge_project = 'patron'
56
+ s.summary = 'Patron HTTP client'
57
+ s.description = 'Ruby HTTP client library based on libcurl'
58
+
59
+ s.extensions << 'ext/patron/extconf.rb'
60
+ s.require_paths << 'ext'
61
+
62
+ s.files = FileList['README.txt',
63
+ 'LICENSE',
64
+ 'Rakefile',
65
+ 'lib/**/*',
66
+ 'spec/*',
67
+ 'ext/patron/*.{rb,c}']
68
+
69
+ # rdoc
70
+ s.has_rdoc = true
71
+ s.extra_rdoc_files = ['README.txt']
72
+ s.rdoc_options = ['--quiet',
73
+ '--title', "Patron documentation",
74
+ '--opname', 'index.html',
75
+ '--line-numbers',
76
+ '--main', 'README.txt',
77
+ '--inline-source']
78
+ end
79
+
80
+ file SESSION_SO => SESSION_SRC do
81
+ cd EXT_DIR do
82
+ ruby 'extconf.rb'
83
+ sh 'make'
84
+ end
85
+ end
86
+
87
+ desc "Compile extension"
88
+ task :compile => SESSION_SO
89
+
90
+ desc "Start an IRB shell"
91
+ task :shell => :compile do
92
+ sh 'irb -I./lib -I./ext -r patron'
93
+ end
94
+
95
+ Rake::RDocTask.new do |rdoc|
96
+ rdoc.rdoc_dir = 'doc'
97
+ rdoc.title = "Patron documentation"
98
+ rdoc.main = 'README.txt'
99
+ rdoc.options << '--line-numbers' << '--inline-source'
100
+ rdoc.rdoc_files.include('README.txt')
101
+ rdoc.rdoc_files.include('lib/**/*.rb')
102
+ end
103
+
104
+ desc "Run specs"
105
+ Spec::Rake::SpecTask.new(:spec) do |t|
106
+ t.spec_opts = ['--options', "spec/spec.opts"]
107
+ t.spec_files = FileList['spec/**/*_spec.rb']
108
+ end
109
+
110
+ task :spec => [:compile]
111
+
112
+ desc "Run specs with RCov"
113
+ Spec::Rake::SpecTask.new('spec:rcov') do |t|
114
+ t.spec_files = FileList['spec/**/*_spec.rb']
115
+ t.rcov = true
116
+ t.rcov_opts << '--sort coverage'
117
+ t.rcov_opts << '--comments'
118
+ t.rcov_opts << '--exclude spec'
119
+ t.rcov_opts << '--exclude lib/magneto.rb'
120
+ t.rcov_opts << '--exclude /Library/Ruby/Gems'
121
+ end
122
+
123
+ # Rubyforge tasks
124
+ begin
125
+ require 'rake/contrib/sshpublisher'
126
+ namespace :rubyforge do
127
+
128
+ desc "Release gem and RDoc documentation to RubyForge"
129
+ task :release => ["rubyforge:release:gem", "rubyforge:release:docs"]
130
+
131
+ namespace :release do
132
+ desc "Publish RDoc to RubyForge."
133
+ task :docs => [:rdoc] do
134
+ config = YAML.load(
135
+ File.read(File.expand_path('~/.rubyforge/user-config.yml'))
136
+ )
137
+
138
+ host = "#{config['username']}@rubyforge.org"
139
+ remote_dir = "/var/www/gforge-projects/patron/"
140
+ local_dir = 'rdoc'
141
+
142
+ Rake::SshDirPublisher.new(host, remote_dir, local_dir).upload
143
+ end
144
+ end
145
+ end
146
+ rescue LoadError
147
+ puts "Rake SshDirPublisher is unavailable or your rubyforge environment is not configured."
148
+ end
149
+
150
+ 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,357 @@
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 ePatronError = Qnil;
31
+ static VALUE eUnsupportedProtocol = Qnil;
32
+ static VALUE eURLFormatError = Qnil;
33
+ static VALUE eHostResolutionError = Qnil;
34
+ static VALUE eConnectionFailed = Qnil;
35
+ static VALUE ePartialFileError = Qnil;
36
+ static VALUE eTimeoutError = Qnil;
37
+ static VALUE eTooManyRedirects = Qnil;
38
+
39
+
40
+ struct curl_state {
41
+ CURL* handle;
42
+ char* upload_buf;
43
+ char error_buf[CURL_ERROR_SIZE];
44
+ struct curl_slist* headers;
45
+ };
46
+
47
+
48
+ //------------------------------------------------------------------------------
49
+ // Curl Callbacks
50
+ //
51
+
52
+ // Takes data streamed from libcurl and writes it to a Ruby string buffer.
53
+ static size_t session_write_handler(char* stream, size_t size, size_t nmemb, VALUE out) {
54
+ rb_str_buf_cat(out, stream, size * nmemb);
55
+ return size * nmemb;
56
+ }
57
+
58
+ static size_t session_read_handler(char* stream, size_t size, size_t nmemb, char **buffer) {
59
+ size_t result = 0;
60
+
61
+ if (buffer != NULL && *buffer != NULL) {
62
+ int len = size * nmemb;
63
+ char *s1 = strncpy(stream, *buffer, len);
64
+ result = strlen(s1);
65
+ *buffer += result;
66
+ }
67
+
68
+ return result;
69
+ }
70
+
71
+ //------------------------------------------------------------------------------
72
+ // Object allocation
73
+ //
74
+
75
+ // Cleans up the Curl handle when the Session object is garbage collected.
76
+ void session_free(struct curl_state *curl) {
77
+ curl_easy_cleanup(curl->handle);
78
+ free(curl);
79
+ }
80
+
81
+ // Allocates curl_state data needed for a new Session object.
82
+ VALUE session_alloc(VALUE klass) {
83
+ struct curl_state* curl;
84
+ VALUE obj = Data_Make_Struct(klass, struct curl_state, NULL, session_free, curl);
85
+ return obj;
86
+ }
87
+
88
+
89
+ //------------------------------------------------------------------------------
90
+ // Method implementations
91
+ //
92
+
93
+ // Returns the version of the embedded libcurl as a string.
94
+ VALUE libcurl_version(VALUE klass) {
95
+ char* value = curl_version();
96
+ return rb_str_new2(value);
97
+ }
98
+
99
+ // Initializes the libcurl handle on object initialization.
100
+ // NOTE: This must be called from Session#initialize.
101
+ VALUE session_ext_initialize(VALUE self) {
102
+ struct curl_state *state;
103
+ Data_Get_Struct(self, struct curl_state, state);
104
+
105
+ state->handle = curl_easy_init();
106
+
107
+ return self;
108
+ }
109
+
110
+ // URL escapes the provided string.
111
+ VALUE session_escape(VALUE self, VALUE value) {
112
+ struct curl_state *state;
113
+ Data_Get_Struct(self, struct curl_state, state);
114
+
115
+ VALUE string = StringValue(value);
116
+ char* escaped = curl_easy_escape(state->handle,
117
+ RSTRING_PTR(string),
118
+ RSTRING_LEN(string));
119
+
120
+ VALUE retval = rb_str_new2(escaped);
121
+ curl_free(escaped);
122
+
123
+ return retval;
124
+ }
125
+
126
+ // Unescapes the provided string.
127
+ VALUE session_unescape(VALUE self, VALUE value) {
128
+ struct curl_state *state;
129
+ Data_Get_Struct(self, struct curl_state, state);
130
+
131
+ VALUE string = StringValue(value);
132
+ char* unescaped = curl_easy_unescape(state->handle,
133
+ RSTRING_PTR(string),
134
+ RSTRING_LEN(string),
135
+ NULL);
136
+
137
+ VALUE retval = rb_str_new2(unescaped);
138
+ curl_free(unescaped);
139
+
140
+ return retval;
141
+ }
142
+
143
+ // Callback used to iterate over the HTTP headers and store them in an slist.
144
+ static VALUE each_http_header(VALUE header, VALUE self) {
145
+ struct curl_state *state;
146
+ Data_Get_Struct(self, struct curl_state, state);
147
+
148
+ VALUE name = rb_obj_as_string(rb_ary_entry(header, 0));
149
+ VALUE value = rb_obj_as_string(rb_ary_entry(header, 1));
150
+
151
+ VALUE header_str = Qnil;
152
+ header_str = rb_str_plus(name, rb_str_new2(": "));
153
+ header_str = rb_str_plus(header_str, value);
154
+
155
+ state->headers = curl_slist_append(state->headers, StringValuePtr(header_str));
156
+ return Qnil;
157
+ }
158
+
159
+ // Set the options on the Curl handle from a Request object. Takes each field
160
+ // in the Request object and uses it to set the appropriate option on the Curl
161
+ // handle.
162
+ void set_options_from_request(VALUE self, VALUE request) {
163
+ struct curl_state *state;
164
+ Data_Get_Struct(self, struct curl_state, state);
165
+
166
+ CURL* curl = state->handle;
167
+
168
+ ID action = SYM2ID(rb_iv_get(request, "@action"));
169
+ if (action == rb_intern("get")) {
170
+ curl_easy_setopt(curl, CURLOPT_HTTPGET, 1);
171
+ } else if (action == rb_intern("post")) {
172
+ VALUE data = rb_iv_get(request, "@upload_data");
173
+
174
+ state->upload_buf = StringValuePtr(data);
175
+ int len = RSTRING_LEN(data);
176
+
177
+ curl_easy_setopt(curl, CURLOPT_POST, 1);
178
+ curl_easy_setopt(curl, CURLOPT_POSTFIELDS, state->upload_buf);
179
+ curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, len);
180
+ } else if (action == rb_intern("put")) {
181
+ VALUE data = rb_iv_get(request, "@upload_data");
182
+
183
+ state->upload_buf = StringValuePtr(data);
184
+ int len = RSTRING_LEN(data);
185
+
186
+ curl_easy_setopt(curl, CURLOPT_UPLOAD, 1);
187
+ curl_easy_setopt(curl, CURLOPT_READFUNCTION, &session_read_handler);
188
+ curl_easy_setopt(curl, CURLOPT_READDATA, &state->upload_buf);
189
+ curl_easy_setopt(curl, CURLOPT_INFILESIZE, len);
190
+ } else if (action == rb_intern("delete")) {
191
+ curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");
192
+ } else if (action == rb_intern("head")) {
193
+ curl_easy_setopt(curl, CURLOPT_NOBODY, 1);
194
+ }
195
+
196
+ curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, state->error_buf);
197
+
198
+ VALUE url = rb_iv_get(request, "@url");
199
+ if (NIL_P(url)) {
200
+ rb_raise(rb_eArgError, "Must provide a URL");
201
+ }
202
+ curl_easy_setopt(curl, CURLOPT_URL, StringValuePtr(url));
203
+
204
+ VALUE timeout = rb_iv_get(request, "@timeout");
205
+ if (!NIL_P(timeout)) {
206
+ curl_easy_setopt(curl, CURLOPT_TIMEOUT, FIX2INT(timeout));
207
+ }
208
+
209
+ timeout = rb_iv_get(request, "@connect_timeout");
210
+ if (!NIL_P(timeout)) {
211
+ curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT_MS, FIX2INT(timeout));
212
+ }
213
+
214
+ VALUE redirects = rb_iv_get(request, "@max_redirects");
215
+ if (!NIL_P(redirects)) {
216
+ int r = FIX2INT(redirects);
217
+ curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, r == 0 ? 0 : 1);
218
+ curl_easy_setopt(curl, CURLOPT_MAXREDIRS, r);
219
+ }
220
+
221
+ VALUE headers = rb_iv_get(request, "@headers");
222
+ if (!NIL_P(headers)) {
223
+ if (rb_type(headers) != T_HASH) {
224
+ rb_raise(rb_eArgError, "Headers must be passed in a hash.");
225
+ }
226
+
227
+ rb_iterate(rb_each, headers, each_http_header, self);
228
+ }
229
+
230
+ curl_easy_setopt(curl, CURLOPT_HTTPHEADER, state->headers);
231
+
232
+ VALUE credentials = rb_funcall(request, rb_intern("credentials"), 0);
233
+ if (!NIL_P(credentials)) {
234
+ curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
235
+ curl_easy_setopt(curl, CURLOPT_USERPWD, StringValuePtr(credentials));
236
+ }
237
+ }
238
+
239
+ // Use the info in a Curl handle to create a new Response object.
240
+ VALUE create_response(CURL* curl) {
241
+ VALUE response = rb_class_new_instance(0, 0,
242
+ rb_const_get(mPatron, rb_intern("Response")));
243
+
244
+ char* url = NULL;
245
+ curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL, &url);
246
+ rb_iv_set(response, "@url", rb_str_new2(url));
247
+
248
+ long code = 0;
249
+ curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code);
250
+ rb_iv_set(response, "@status", INT2NUM(code));
251
+
252
+ long count = 0;
253
+ curl_easy_getinfo(curl, CURLINFO_REDIRECT_COUNT, &count);
254
+ rb_iv_set(response, "@redirect_count", INT2NUM(count));
255
+
256
+ return response;
257
+ }
258
+
259
+ // Raise an exception based on the Curl error code.
260
+ VALUE select_error(CURLcode code) {
261
+ VALUE error = Qnil;
262
+ switch (code) {
263
+ case CURLE_UNSUPPORTED_PROTOCOL: error = eUnsupportedProtocol; break;
264
+ case CURLE_URL_MALFORMAT: error = eURLFormatError; break;
265
+ case CURLE_COULDNT_RESOLVE_HOST: error = eHostResolutionError; break;
266
+ case CURLE_COULDNT_CONNECT: error = eConnectionFailed; break;
267
+ case CURLE_PARTIAL_FILE: error = ePartialFileError; break;
268
+ case CURLE_OPERATION_TIMEDOUT: error = eTimeoutError; break;
269
+ case CURLE_TOO_MANY_REDIRECTS: error = eTooManyRedirects; break;
270
+
271
+ default: error = ePatronError;
272
+ }
273
+
274
+ return error;
275
+ }
276
+
277
+ // Perform the actual HTTP request by calling libcurl.
278
+ static VALUE perform_request(VALUE self) {
279
+ struct curl_state *state;
280
+ Data_Get_Struct(self, struct curl_state, state);
281
+
282
+ CURL* curl = state->handle;
283
+
284
+ // headers
285
+ VALUE header_buffer = rb_str_buf_new(32768);
286
+ curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, &session_write_handler);
287
+ curl_easy_setopt(curl, CURLOPT_HEADERDATA, header_buffer);
288
+
289
+ // body
290
+ VALUE body_buffer = rb_str_buf_new(32768);
291
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &session_write_handler);
292
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, body_buffer);
293
+
294
+ CURLcode ret = curl_easy_perform(curl);
295
+ if (CURLE_OK == ret) {
296
+ VALUE response = create_response(curl);
297
+ rb_iv_set(response, "@body", body_buffer);
298
+ rb_funcall(response, rb_intern("parse_headers"), 1, header_buffer);
299
+ return response;
300
+ } else {
301
+ rb_raise(select_error(ret), state->error_buf);
302
+ }
303
+ }
304
+
305
+ // Cleanup after each request by resetting the Curl handle and deallocating all
306
+ // request related objects such as the header slist.
307
+ static VALUE cleanup(VALUE self) {
308
+ struct curl_state *state;
309
+ Data_Get_Struct(self, struct curl_state, state);
310
+
311
+ curl_easy_reset(state->handle);
312
+
313
+ if (state->headers) {
314
+ curl_slist_free_all(state->headers);
315
+ state->headers = NULL;
316
+ }
317
+
318
+ state->upload_buf = NULL;
319
+
320
+ return Qnil;
321
+ }
322
+
323
+ VALUE session_handle_request(VALUE self, VALUE request) {
324
+ set_options_from_request(self, request);
325
+ return rb_ensure(&perform_request, self, &cleanup, self);
326
+ }
327
+
328
+ //------------------------------------------------------------------------------
329
+ // Extension initialization
330
+ //
331
+
332
+ void Init_session_ext() {
333
+ curl_global_init(CURL_GLOBAL_NOTHING);
334
+ rb_require("patron/error");
335
+
336
+ mPatron = rb_define_module("Patron");
337
+
338
+ ePatronError = rb_const_get(mPatron, rb_intern("Error"));
339
+
340
+ eUnsupportedProtocol = rb_const_get(mPatron, rb_intern("UnsupportedProtocol"));
341
+ eURLFormatError = rb_const_get(mPatron, rb_intern("URLFormatError"));
342
+ eHostResolutionError = rb_const_get(mPatron, rb_intern("HostResolutionError"));
343
+ eConnectionFailed = rb_const_get(mPatron, rb_intern("ConnectionFailed"));
344
+ ePartialFileError = rb_const_get(mPatron, rb_intern("PartialFileError"));
345
+ eTimeoutError = rb_const_get(mPatron, rb_intern("TimeoutError"));
346
+ eTooManyRedirects = rb_const_get(mPatron, rb_intern("TooManyRedirects"));
347
+
348
+ rb_define_module_function(mPatron, "libcurl_version", libcurl_version, 0);
349
+
350
+ cSession = rb_define_class_under(mPatron, "Session", rb_cObject);
351
+ rb_define_alloc_func(cSession, session_alloc);
352
+
353
+ rb_define_method(cSession, "ext_initialize", session_ext_initialize, 0);
354
+ rb_define_method(cSession, "escape", session_escape, 1);
355
+ rb_define_method(cSession, "unescape", session_unescape, 1);
356
+ rb_define_method(cSession, "handle_request", session_handle_request, 1);
357
+ }