chrisjpowers-patron 0.4.0

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,156 @@
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
+ # Rubyforge tasks
130
+ begin
131
+ require 'rake/contrib/sshpublisher'
132
+ namespace :rubyforge do
133
+
134
+ desc "Release gem and RDoc documentation to RubyForge"
135
+ task :release => ["rubyforge:release:gem", "rubyforge:release:docs"]
136
+
137
+ namespace :release do
138
+ desc "Publish RDoc to RubyForge."
139
+ task :docs => [:rdoc] do
140
+ config = YAML.load(
141
+ File.read(File.expand_path('~/.rubyforge/user-config.yml'))
142
+ )
143
+
144
+ host = "#{config['username']}@rubyforge.org"
145
+ remote_dir = "/var/www/gforge-projects/patron/"
146
+ local_dir = 'doc'
147
+
148
+ Rake::SshDirPublisher.new(host, remote_dir, local_dir).upload
149
+ end
150
+ end
151
+ end
152
+ rescue LoadError
153
+ puts "Rake SshDirPublisher is unavailable or your rubyforge environment is not configured."
154
+ end
155
+
156
+ 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,411 @@
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
+ FILE* download_file;
44
+ FILE* upload_file;
45
+ char error_buf[CURL_ERROR_SIZE];
46
+ struct curl_slist* headers;
47
+ };
48
+
49
+
50
+ //------------------------------------------------------------------------------
51
+ // Curl Callbacks
52
+ //
53
+
54
+ // Takes data streamed from libcurl and writes it to a Ruby string buffer.
55
+ static size_t session_write_handler(char* stream, size_t size, size_t nmemb, VALUE out) {
56
+ rb_str_buf_cat(out, stream, size * nmemb);
57
+ return size * nmemb;
58
+ }
59
+
60
+ static size_t session_read_handler(char* stream, size_t size, size_t nmemb, char **buffer) {
61
+ size_t result = 0;
62
+
63
+ if (buffer != NULL && *buffer != NULL) {
64
+ int len = size * nmemb;
65
+ char *s1 = strncpy(stream, *buffer, len);
66
+ result = strlen(s1);
67
+ *buffer += result;
68
+ }
69
+
70
+ return result;
71
+ }
72
+
73
+ //------------------------------------------------------------------------------
74
+ // Object allocation
75
+ //
76
+
77
+ // Cleans up the Curl handle when the Session object is garbage collected.
78
+ void session_free(struct curl_state *curl) {
79
+ curl_easy_cleanup(curl->handle);
80
+ free(curl);
81
+ }
82
+
83
+ // Allocates curl_state data needed for a new Session object.
84
+ VALUE session_alloc(VALUE klass) {
85
+ struct curl_state* curl;
86
+ VALUE obj = Data_Make_Struct(klass, struct curl_state, NULL, session_free, curl);
87
+ return obj;
88
+ }
89
+
90
+
91
+ //------------------------------------------------------------------------------
92
+ // Method implementations
93
+ //
94
+
95
+ // Returns the version of the embedded libcurl as a string.
96
+ VALUE libcurl_version(VALUE klass) {
97
+ char* value = curl_version();
98
+ return rb_str_new2(value);
99
+ }
100
+
101
+ // Initializes the libcurl handle on object initialization.
102
+ // NOTE: This must be called from Session#initialize.
103
+ VALUE session_ext_initialize(VALUE self) {
104
+ struct curl_state *state;
105
+ Data_Get_Struct(self, struct curl_state, state);
106
+
107
+ state->handle = curl_easy_init();
108
+
109
+ return self;
110
+ }
111
+
112
+ // URL escapes the provided string.
113
+ VALUE session_escape(VALUE self, VALUE value) {
114
+ struct curl_state *state;
115
+ Data_Get_Struct(self, struct curl_state, state);
116
+
117
+ VALUE string = StringValue(value);
118
+ char* escaped = curl_easy_escape(state->handle,
119
+ RSTRING_PTR(string),
120
+ RSTRING_LEN(string));
121
+
122
+ VALUE retval = rb_str_new2(escaped);
123
+ curl_free(escaped);
124
+
125
+ return retval;
126
+ }
127
+
128
+ // Unescapes the provided string.
129
+ VALUE session_unescape(VALUE self, VALUE value) {
130
+ struct curl_state *state;
131
+ Data_Get_Struct(self, struct curl_state, state);
132
+
133
+ VALUE string = StringValue(value);
134
+ char* unescaped = curl_easy_unescape(state->handle,
135
+ RSTRING_PTR(string),
136
+ RSTRING_LEN(string),
137
+ NULL);
138
+
139
+ VALUE retval = rb_str_new2(unescaped);
140
+ curl_free(unescaped);
141
+
142
+ return retval;
143
+ }
144
+
145
+ // Callback used to iterate over the HTTP headers and store them in an slist.
146
+ static VALUE each_http_header(VALUE header, VALUE self) {
147
+ struct curl_state *state;
148
+ Data_Get_Struct(self, struct curl_state, state);
149
+
150
+ VALUE name = rb_obj_as_string(rb_ary_entry(header, 0));
151
+ VALUE value = rb_obj_as_string(rb_ary_entry(header, 1));
152
+
153
+ VALUE header_str = Qnil;
154
+ header_str = rb_str_plus(name, rb_str_new2(": "));
155
+ header_str = rb_str_plus(header_str, value);
156
+
157
+ state->headers = curl_slist_append(state->headers, StringValuePtr(header_str));
158
+ return Qnil;
159
+ }
160
+
161
+ static void set_chunked_encoding(struct curl_state *state) {
162
+ state->headers = curl_slist_append(state->headers, "Transfer-Encoding: chunked");
163
+ }
164
+
165
+ static FILE* open_file(VALUE filename, char* perms) {
166
+ FILE* handle = fopen(StringValuePtr(filename), perms);
167
+ if (!handle) {
168
+ rb_raise(rb_eArgError, "Unable to open specified file.");
169
+ }
170
+
171
+ return handle;
172
+ }
173
+
174
+ // Set the options on the Curl handle from a Request object. Takes each field
175
+ // in the Request object and uses it to set the appropriate option on the Curl
176
+ // handle.
177
+ static void set_options_from_request(VALUE self, VALUE request) {
178
+ struct curl_state *state;
179
+ Data_Get_Struct(self, struct curl_state, state);
180
+
181
+ CURL* curl = state->handle;
182
+
183
+ VALUE headers = rb_iv_get(request, "@headers");
184
+ if (!NIL_P(headers)) {
185
+ if (rb_type(headers) != T_HASH) {
186
+ rb_raise(rb_eArgError, "Headers must be passed in a hash.");
187
+ }
188
+
189
+ rb_iterate(rb_each, headers, each_http_header, self);
190
+ }
191
+
192
+ ID action = SYM2ID(rb_iv_get(request, "@action"));
193
+ if (action == rb_intern("get")) {
194
+ curl_easy_setopt(curl, CURLOPT_HTTPGET, 1);
195
+
196
+ VALUE download_file = rb_iv_get(request, "@file_name");
197
+ if (!NIL_P(download_file)) {
198
+ state->download_file = open_file(download_file, "w");
199
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, state->download_file);
200
+ } else {
201
+ state->download_file = NULL;
202
+ }
203
+ } else if (action == rb_intern("post") || action == rb_intern("put")) {
204
+ VALUE data = rb_iv_get(request, "@upload_data");
205
+ VALUE filename = rb_iv_get(request, "@file_name");
206
+
207
+ if (!NIL_P(data)) {
208
+ state->upload_buf = StringValuePtr(data);
209
+ int len = RSTRING_LEN(data);
210
+
211
+ if (action == rb_intern("post")) {
212
+ curl_easy_setopt(curl, CURLOPT_POST, 1);
213
+ curl_easy_setopt(curl, CURLOPT_POSTFIELDS, state->upload_buf);
214
+ curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, len);
215
+ } else {
216
+ curl_easy_setopt(curl, CURLOPT_UPLOAD, 1);
217
+ curl_easy_setopt(curl, CURLOPT_READFUNCTION, &session_read_handler);
218
+ curl_easy_setopt(curl, CURLOPT_READDATA, &state->upload_buf);
219
+ curl_easy_setopt(curl, CURLOPT_INFILESIZE, len);
220
+ }
221
+ } else if (!NIL_P(filename)) {
222
+ set_chunked_encoding(state);
223
+
224
+ curl_easy_setopt(curl, CURLOPT_UPLOAD, 1);
225
+
226
+ if (action == rb_intern("post")) {
227
+ curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "POST");
228
+ }
229
+
230
+ state->upload_file = open_file(filename, "r");
231
+ curl_easy_setopt(curl, CURLOPT_READDATA, state->upload_file);
232
+ }
233
+ } else if (action == rb_intern("head")) {
234
+ curl_easy_setopt(curl, CURLOPT_NOBODY, 1);
235
+ } else {
236
+ VALUE action_name = rb_funcall(request, rb_intern("action_name"), 0);
237
+ curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, StringValuePtr(action_name));
238
+ }
239
+
240
+ curl_easy_setopt(curl, CURLOPT_HTTPHEADER, state->headers);
241
+ curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, state->error_buf);
242
+
243
+ VALUE url = rb_iv_get(request, "@url");
244
+ if (NIL_P(url)) {
245
+ rb_raise(rb_eArgError, "Must provide a URL");
246
+ }
247
+ curl_easy_setopt(curl, CURLOPT_URL, StringValuePtr(url));
248
+
249
+ VALUE timeout = rb_iv_get(request, "@timeout");
250
+ if (!NIL_P(timeout)) {
251
+ curl_easy_setopt(curl, CURLOPT_TIMEOUT, FIX2INT(timeout));
252
+ }
253
+
254
+ timeout = rb_iv_get(request, "@connect_timeout");
255
+ if (!NIL_P(timeout)) {
256
+ curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT_MS, FIX2INT(timeout));
257
+ }
258
+
259
+ VALUE redirects = rb_iv_get(request, "@max_redirects");
260
+ if (!NIL_P(redirects)) {
261
+ int r = FIX2INT(redirects);
262
+ curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, r == 0 ? 0 : 1);
263
+ curl_easy_setopt(curl, CURLOPT_MAXREDIRS, r);
264
+ }
265
+
266
+ VALUE proxy = rb_iv_get(request, "@proxy");
267
+ if (!NIL_P(proxy)) {
268
+ curl_easy_setopt(curl, CURLOPT_PROXY, StringValuePtr(proxy));
269
+ }
270
+
271
+ VALUE credentials = rb_funcall(request, rb_intern("credentials"), 0);
272
+ if (!NIL_P(credentials)) {
273
+ curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
274
+ curl_easy_setopt(curl, CURLOPT_USERPWD, StringValuePtr(credentials));
275
+ }
276
+ }
277
+
278
+ // Use the info in a Curl handle to create a new Response object.
279
+ static VALUE create_response(CURL* curl) {
280
+ VALUE response = rb_class_new_instance(0, 0,
281
+ rb_const_get(mPatron, rb_intern("Response")));
282
+
283
+ char* url = NULL;
284
+ curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL, &url);
285
+ rb_iv_set(response, "@url", rb_str_new2(url));
286
+
287
+ long code = 0;
288
+ curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code);
289
+ rb_iv_set(response, "@status", INT2NUM(code));
290
+
291
+ long count = 0;
292
+ curl_easy_getinfo(curl, CURLINFO_REDIRECT_COUNT, &count);
293
+ rb_iv_set(response, "@redirect_count", INT2NUM(count));
294
+
295
+ return response;
296
+ }
297
+
298
+ // Raise an exception based on the Curl error code.
299
+ static VALUE select_error(CURLcode code) {
300
+ VALUE error = Qnil;
301
+ switch (code) {
302
+ case CURLE_UNSUPPORTED_PROTOCOL: error = eUnsupportedProtocol; break;
303
+ case CURLE_URL_MALFORMAT: error = eURLFormatError; break;
304
+ case CURLE_COULDNT_RESOLVE_HOST: error = eHostResolutionError; break;
305
+ case CURLE_COULDNT_CONNECT: error = eConnectionFailed; break;
306
+ case CURLE_PARTIAL_FILE: error = ePartialFileError; break;
307
+ case CURLE_OPERATION_TIMEDOUT: error = eTimeoutError; break;
308
+ case CURLE_TOO_MANY_REDIRECTS: error = eTooManyRedirects; break;
309
+
310
+ default: error = ePatronError;
311
+ }
312
+
313
+ return error;
314
+ }
315
+
316
+ // Perform the actual HTTP request by calling libcurl.
317
+ static VALUE perform_request(VALUE self) {
318
+ struct curl_state *state;
319
+ Data_Get_Struct(self, struct curl_state, state);
320
+
321
+ CURL* curl = state->handle;
322
+
323
+ // headers
324
+ VALUE header_buffer = rb_str_buf_new(32768);
325
+ curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, &session_write_handler);
326
+ curl_easy_setopt(curl, CURLOPT_HEADERDATA, header_buffer);
327
+
328
+ // body
329
+ VALUE body_buffer = Qnil;
330
+ if (!state->download_file) {
331
+ body_buffer = rb_str_buf_new(32768);
332
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &session_write_handler);
333
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, body_buffer);
334
+ }
335
+
336
+ CURLcode ret = curl_easy_perform(curl);
337
+ if (CURLE_OK == ret) {
338
+ VALUE response = create_response(curl);
339
+ if (!NIL_P(body_buffer)) {
340
+ rb_iv_set(response, "@body", body_buffer);
341
+ }
342
+ rb_funcall(response, rb_intern("parse_headers"), 1, header_buffer);
343
+ return response;
344
+ } else {
345
+ rb_raise(select_error(ret), state->error_buf);
346
+ }
347
+ }
348
+
349
+ // Cleanup after each request by resetting the Curl handle and deallocating all
350
+ // request related objects such as the header slist.
351
+ static VALUE cleanup(VALUE self) {
352
+ struct curl_state *state;
353
+ Data_Get_Struct(self, struct curl_state, state);
354
+
355
+ curl_easy_reset(state->handle);
356
+
357
+ if (state->headers) {
358
+ curl_slist_free_all(state->headers);
359
+ state->headers = NULL;
360
+ }
361
+
362
+ if (state->download_file) {
363
+ fclose(state->download_file);
364
+ state->download_file = NULL;
365
+ }
366
+
367
+ if (state->upload_file) {
368
+ fclose(state->upload_file);
369
+ state->upload_file = NULL;
370
+ }
371
+
372
+ state->upload_buf = NULL;
373
+
374
+ return Qnil;
375
+ }
376
+
377
+ VALUE session_handle_request(VALUE self, VALUE request) {
378
+ set_options_from_request(self, request);
379
+ return rb_ensure(&perform_request, self, &cleanup, self);
380
+ }
381
+
382
+ //------------------------------------------------------------------------------
383
+ // Extension initialization
384
+ //
385
+
386
+ void Init_session_ext() {
387
+ curl_global_init(CURL_GLOBAL_ALL);
388
+ rb_require("patron/error");
389
+
390
+ mPatron = rb_define_module("Patron");
391
+
392
+ ePatronError = rb_const_get(mPatron, rb_intern("Error"));
393
+
394
+ eUnsupportedProtocol = rb_const_get(mPatron, rb_intern("UnsupportedProtocol"));
395
+ eURLFormatError = rb_const_get(mPatron, rb_intern("URLFormatError"));
396
+ eHostResolutionError = rb_const_get(mPatron, rb_intern("HostResolutionError"));
397
+ eConnectionFailed = rb_const_get(mPatron, rb_intern("ConnectionFailed"));
398
+ ePartialFileError = rb_const_get(mPatron, rb_intern("PartialFileError"));
399
+ eTimeoutError = rb_const_get(mPatron, rb_intern("TimeoutError"));
400
+ eTooManyRedirects = rb_const_get(mPatron, rb_intern("TooManyRedirects"));
401
+
402
+ rb_define_module_function(mPatron, "libcurl_version", libcurl_version, 0);
403
+
404
+ cSession = rb_define_class_under(mPatron, "Session", rb_cObject);
405
+ rb_define_alloc_func(cSession, session_alloc);
406
+
407
+ rb_define_method(cSession, "ext_initialize", session_ext_initialize, 0);
408
+ rb_define_method(cSession, "escape", session_escape, 1);
409
+ rb_define_method(cSession, "unescape", session_unescape, 1);
410
+ rb_define_method(cSession, "handle_request", session_handle_request, 1);
411
+ }