streamly 0.1.3 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.4 (September 2nd, 2010)
4
+ * Streamly now respects Encoding.default_internal on 1.9
5
+ * fixed a random segfault when applying a NULL auth field
6
+ * take advantage of rb_thread_blocking_region on 1.9 and emulate it's behavior on 1.8 for Ruby-aware thread-saftey
7
+
3
8
  ## 0.1.3 (August 19th, 2009)
4
9
  * Fixed a bug where a username or password was specified, but not both
5
10
 
@@ -3,14 +3,13 @@
3
3
  == Features
4
4
  * rest-client like API
5
5
  * Streaming API allows the caller to be handed chunks of the response while it's being received
6
+ * uses Encoding.default_internal (otherwise falls back to utf-8) for strings it hands back in 1.9
6
7
 
7
8
  == How to install
8
9
 
9
- Install it like any other gem hosted at the Githubs like so:
10
+ Nothing special about it, just:
10
11
 
11
- (more instructions here: http://gems.github.com)
12
-
13
- sudo gem install brianmario-streamly
12
+ sudo gem install streamly
14
13
 
15
14
  == Example of use
16
15
 
@@ -1,4 +1,5 @@
1
1
  ---
2
- :minor: 1
3
- :patch: 3
2
+ :patch: 4
4
3
  :major: 0
4
+ :build:
5
+ :minor: 1
@@ -1,4 +1,5 @@
1
1
  # encoding: UTF-8
2
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/..')
2
3
  $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
3
4
 
4
5
  require 'rubygems'
@@ -9,23 +10,23 @@ require 'benchmark'
9
10
 
10
11
  url = ARGV[0]
11
12
 
12
- Benchmark.bm do |x|
13
- puts "Streamly"
13
+ Benchmark.bmbm do |x|
14
14
  x.report do
15
+ puts "Streamly"
15
16
  (ARGV[1] || 1).to_i.times do
16
17
  Streamly.get(url)
17
18
  end
18
19
  end
19
-
20
- puts "Shell out to curl"
20
+
21
21
  x.report do
22
+ puts "Shell out to curl"
22
23
  (ARGV[1] || 1).to_i.times do
23
24
  `curl -s --compressed #{url}`
24
25
  end
25
26
  end
26
-
27
- puts "rest-client"
27
+
28
28
  x.report do
29
+ puts "rest-client"
29
30
  (ARGV[1] || 1).to_i.times do
30
31
  RestClient.get(url, {"Accept-Encoding" => "identity, deflate, gzip"})
31
32
  end
@@ -1,4 +1,5 @@
1
1
  # encoding: UTF-8
2
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/..')
2
3
  $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
3
4
 
4
5
  require 'rubygems'
@@ -10,31 +11,31 @@ require 'benchmark'
10
11
 
11
12
  url = ARGV[0]
12
13
 
13
- Benchmark.bm do |x|
14
- puts "Streamly"
14
+ Benchmark.bmbm do |x|
15
15
  parser = Yajl::Parser.new
16
16
  parser.on_parse_complete = lambda {|obj| }
17
17
  x.report do
18
+ puts "Streamly"
18
19
  (ARGV[1] || 1).to_i.times do
19
20
  Streamly.get(url) do |chunk|
20
21
  parser << chunk
21
22
  end
22
23
  end
23
24
  end
24
-
25
- puts "Shell out to curl"
25
+
26
26
  parser = Yajl::Parser.new
27
27
  parser.on_parse_complete = lambda {|obj| }
28
28
  x.report do
29
+ puts "Shell out to curl"
29
30
  (ARGV[1] || 1).to_i.times do
30
31
  parser.parse `curl -s --compressed #{url}`
31
32
  end
32
33
  end
33
-
34
- puts "rest-client"
34
+
35
35
  parser = Yajl::Parser.new
36
36
  parser.on_parse_complete = lambda {|obj| }
37
37
  x.report do
38
+ puts "rest-client"
38
39
  (ARGV[1] || 1).to_i.times do
39
40
  parser.parse RestClient.get(url, {"Accept-Encoding" => "identity, deflate, gzip"})
40
41
  end
@@ -2,6 +2,9 @@
2
2
  require 'mkmf'
3
3
  require 'rbconfig'
4
4
 
5
+ # 1.9-only
6
+ have_func('rb_thread_blocking_region')
7
+
5
8
  $CFLAGS << ' -DHAVE_RBTRAP' if have_var('rb_trap_immediate', ['ruby.h', 'rubysig.h'])
6
9
  # add_define 'HAVE_RBTRAP' if have_var('rb_trap_immediate', ['ruby.h', 'rubysig.h'])
7
10
 
@@ -19,7 +22,7 @@ elsif !have_library('curl') or !have_header('curl/curl.h')
19
22
  EOM
20
23
  end
21
24
 
22
- $CFLAGS << ' -Wall'
25
+ $CFLAGS << ' -Wall -Wextra -funroll-loops'
23
26
  # $CFLAGS << ' -O0 -ggdb'
24
27
 
25
28
  create_makefile("streamly_ext")
@@ -1,13 +1,13 @@
1
1
  // static size_t upload_data_handler(char * stream, size_t size, size_t nmemb, VALUE upload_stream) {
2
2
  // size_t result = 0;
3
- //
3
+ //
4
4
  // // TODO
5
5
  // // Change upload_stream back to a VALUE
6
6
  // // if TYPE(upload_stream) == T_STRING - read at most "len" continuously
7
7
  // // if upload_stream is IO-like, read chunks of it
8
8
  // // OR
9
9
  // // if upload_stream responds to "each", use that?
10
- //
10
+ //
11
11
  // TRAP_BEG;
12
12
  // // if (upload_stream != NULL && *upload_stream != NULL) {
13
13
  // // int len = size * nmemb;
@@ -16,334 +16,412 @@
16
16
  // // *upload_stream += result;
17
17
  // // }
18
18
  // TRAP_END;
19
- //
19
+ //
20
20
  // return result;
21
21
  // }
22
22
 
23
23
  #include "streamly.h"
24
+ #ifdef HAVE_RUBY_ENCODING_H
25
+ #include <ruby/encoding.h>
26
+ static rb_encoding *utf8Encoding;
27
+ #endif
24
28
 
25
29
  static size_t header_handler(char * stream, size_t size, size_t nmemb, VALUE handler) {
26
- TRAP_BEG;
27
- if(TYPE(handler) == T_STRING) {
28
- rb_str_buf_cat(handler, stream, size * nmemb);
30
+ size_t str_len = size * nmemb;
31
+
32
+ if(TYPE(handler) == T_STRING) {
33
+ #ifdef HAVE_RUBY_ENCODING_H
34
+ rb_encoding *default_internal_enc = rb_default_internal_encoding();
35
+ if (default_internal_enc) {
36
+ handler = rb_str_export_to_enc(handler, default_internal_enc);
37
+ } else {
38
+ handler = rb_str_export_to_enc(handler, utf8Encoding);
39
+ }
40
+ #endif
41
+ rb_str_buf_cat(handler, stream, str_len);
42
+ } else {
43
+ VALUE chunk = rb_str_new(stream, str_len);
44
+ #ifdef HAVE_RUBY_ENCODING_H
45
+ rb_encoding *default_internal_enc = rb_default_internal_encoding();
46
+ if (default_internal_enc) {
47
+ chunk = rb_str_export_to_enc(chunk, default_internal_enc);
29
48
  } else {
30
- rb_funcall(handler, rb_intern("call"), 1, rb_str_new(stream, size * nmemb));
49
+ chunk = rb_str_export_to_enc(chunk, utf8Encoding);
31
50
  }
32
- TRAP_END;
33
- return size * nmemb;
51
+ #endif
52
+ rb_funcall(handler, rb_intern("call"), 1, chunk);
53
+ }
54
+ return str_len;
34
55
  }
35
56
 
36
57
  static size_t data_handler(char * stream, size_t size, size_t nmemb, VALUE handler) {
37
- TRAP_BEG;
38
- if(TYPE(handler) == T_STRING) {
39
- rb_str_buf_cat(handler, stream, size * nmemb);
58
+ size_t str_len = size * nmemb;
59
+
60
+ if(TYPE(handler) == T_STRING) {
61
+ #ifdef HAVE_RUBY_ENCODING_H
62
+ rb_encoding *default_internal_enc = rb_default_internal_encoding();
63
+ if (default_internal_enc) {
64
+ handler = rb_str_export_to_enc(handler, default_internal_enc);
40
65
  } else {
41
- rb_funcall(handler, rb_intern("call"), 1, rb_str_new(stream, size * nmemb));
66
+ handler = rb_str_export_to_enc(handler, utf8Encoding);
42
67
  }
43
- TRAP_END;
44
- return size * nmemb;
68
+ #endif
69
+ rb_str_buf_cat(handler, stream, str_len);
70
+ } else {
71
+ VALUE chunk = rb_str_new(stream, str_len);
72
+ #ifdef HAVE_RUBY_ENCODING_H
73
+ rb_encoding *default_internal_enc = rb_default_internal_encoding();
74
+ if (default_internal_enc) {
75
+ chunk = rb_str_export_to_enc(chunk, default_internal_enc);
76
+ } else {
77
+ chunk = rb_str_export_to_enc(chunk, utf8Encoding);
78
+ }
79
+ #endif
80
+ rb_funcall(handler, rb_intern("call"), 1, chunk);
81
+ }
82
+ return str_len;
45
83
  }
46
84
 
47
- void streamly_instance_mark(struct curl_instance * instance) {
48
- rb_gc_mark(instance->request_method);
49
- rb_gc_mark(instance->request_payload_handler);
50
- rb_gc_mark(instance->response_header_handler);
51
- rb_gc_mark(instance->response_body_handler);
52
- rb_gc_mark(instance->options);
85
+ static void streamly_instance_mark(struct curl_instance * instance) {
86
+ rb_gc_mark(instance->request_method);
87
+ rb_gc_mark(instance->request_payload_handler);
88
+ rb_gc_mark(instance->response_header_handler);
89
+ rb_gc_mark(instance->response_body_handler);
90
+ rb_gc_mark(instance->options);
53
91
  }
54
92
 
55
- void streamly_instance_free(struct curl_instance * instance) {
56
- curl_easy_cleanup(instance->handle);
57
- free(instance);
93
+ static void streamly_instance_free(struct curl_instance * instance) {
94
+ curl_easy_cleanup(instance->handle);
95
+ free(instance);
58
96
  }
59
97
 
60
98
  // Initially borrowed from Patron - http://github.com/toland/patron
61
99
  // slightly modified for Streamly
62
100
  static VALUE each_http_header(VALUE header, VALUE self) {
63
- struct curl_instance * instance;
64
- GetInstance(self, instance);
65
- VALUE header_str = rb_str_new2("");
66
-
67
- rb_str_buf_cat(header_str, RSTRING_PTR(rb_ary_entry(header, 0)), RSTRING_LEN(rb_ary_entry(header, 0)));
68
- rb_str_buf_cat(header_str, ": ", 2);
69
- rb_str_buf_cat(header_str, RSTRING_PTR(rb_ary_entry(header, 1)), RSTRING_LEN(rb_ary_entry(header, 1)));
70
-
71
- instance->request_headers = curl_slist_append(instance->request_headers, RSTRING_PTR(header_str));
72
- rb_gc_mark(header_str);
73
- return Qnil;
101
+ struct curl_instance * instance;
102
+ GetInstance(self, instance);
103
+ size_t key_len, val_len, header_str_len;
104
+ VALUE key, val;
105
+
106
+ key = rb_ary_entry(header, 0);
107
+ key_len = RSTRING_LEN(key);
108
+
109
+ val = rb_ary_entry(header, 1);
110
+ val_len = RSTRING_LEN(val);
111
+
112
+ header_str_len = (key_len + val_len + 3);
113
+ unsigned char header_str[header_str_len];
114
+
115
+ memcpy(header_str, RSTRING_PTR(key), key_len);
116
+ memcpy(header_str+2, ": ", 2);
117
+ memcpy(header_str+val_len, RSTRING_PTR(val), val_len);
118
+
119
+ header_str[header_str_len+1] = '\0';
120
+ instance->request_headers = curl_slist_append(instance->request_headers, (char *)header_str);
121
+ return Qnil;
74
122
  }
75
123
 
76
124
  // Initially borrowed from Patron - http://github.com/toland/patron
77
125
  // slightly modified for Streamly
78
126
  static VALUE select_error(CURLcode code) {
79
- VALUE error = Qnil;
80
-
81
- switch (code) {
82
- case CURLE_UNSUPPORTED_PROTOCOL:
83
- error = eUnsupportedProtocol;
84
- break;
85
- case CURLE_URL_MALFORMAT:
86
- error = eURLFormatError;
87
- break;
88
- case CURLE_COULDNT_RESOLVE_HOST:
89
- error = eHostResolutionError;
90
- break;
91
- case CURLE_COULDNT_CONNECT:
92
- error = eConnectionFailed;
93
- break;
94
- case CURLE_PARTIAL_FILE:
95
- error = ePartialFileError;
96
- break;
97
- case CURLE_OPERATION_TIMEDOUT:
98
- error = eTimeoutError;
99
- break;
100
- case CURLE_TOO_MANY_REDIRECTS:
101
- error = eTooManyRedirects;
102
- break;
103
- default:
104
- error = eStreamlyError;
105
- }
127
+ VALUE error = Qnil;
128
+
129
+ switch (code) {
130
+ case CURLE_UNSUPPORTED_PROTOCOL:
131
+ error = eUnsupportedProtocol;
132
+ break;
133
+ case CURLE_URL_MALFORMAT:
134
+ error = eURLFormatError;
135
+ break;
136
+ case CURLE_COULDNT_RESOLVE_HOST:
137
+ error = eHostResolutionError;
138
+ break;
139
+ case CURLE_COULDNT_CONNECT:
140
+ error = eConnectionFailed;
141
+ break;
142
+ case CURLE_PARTIAL_FILE:
143
+ error = ePartialFileError;
144
+ break;
145
+ case CURLE_OPERATION_TIMEDOUT:
146
+ error = eTimeoutError;
147
+ break;
148
+ case CURLE_TOO_MANY_REDIRECTS:
149
+ error = eTooManyRedirects;
150
+ break;
151
+ default:
152
+ error = eStreamlyError;
153
+ }
106
154
 
107
- return error;
155
+ return error;
108
156
  }
109
157
 
110
158
  /*
111
- * Document-class: Streamly::Request
112
- *
113
- * A streaming REST client for Ruby that uses libcurl to do the heavy lifting.
114
- * The API is almost exactly like rest-client, so users of that library should find it very familiar.
115
- */
159
+ * Document-class: Streamly::Request
160
+ *
161
+ * A streaming REST client for Ruby that uses libcurl to do the heavy lifting.
162
+ * The API is almost exactly like rest-client, so users of that library should find it very familiar.
163
+ */
116
164
  /*
117
- * Document-method: new
118
- *
119
- * call-seq: new(args)
120
- *
121
- * +args+ should be a Hash and is required
122
- * This Hash should at least contain +:url+ and +:method+ keys.
123
- * You may also provide the following optional keys:
124
- * +:headers+ - should be a Hash of name/value pairs
125
- * +:response_header_handler+ - can be a string or object that responds to #call
126
- * If an object was passed, it's #call method will be called and passed the current chunk of data
127
- * +:response_body_handler+ - can be a string or object that responds to #call
128
- * If an object was passed, it's #call method will be called and passed the current chunk of data
129
- * +:payload+ - If +:method+ is either +:post+ or +:put+ this will be used as the request body
130
- *
131
- */
132
- VALUE rb_streamly_new(int argc, VALUE * argv, VALUE klass) {
133
- struct curl_instance * instance;
134
- VALUE obj = Data_Make_Struct(klass, struct curl_instance, streamly_instance_mark, streamly_instance_free, instance);
135
- rb_obj_call_init(obj, argc, argv);
136
- return obj;
165
+ * Document-method: new
166
+ *
167
+ * call-seq: new(args)
168
+ *
169
+ * +args+ should be a Hash and is required
170
+ * This Hash should at least contain +:url+ and +:method+ keys.
171
+ * You may also provide the following optional keys:
172
+ * +:headers+ - should be a Hash of name/value pairs
173
+ * +:response_header_handler+ - can be a string or object that responds to #call
174
+ * If an object was passed, it's #call method will be called and passed the current chunk of data
175
+ * +:response_body_handler+ - can be a string or object that responds to #call
176
+ * If an object was passed, it's #call method will be called and passed the current chunk of data
177
+ * +:payload+ - If +:method+ is either +:post+ or +:put+ this will be used as the request body
178
+ *
179
+ */
180
+ static VALUE rb_streamly_new(int argc, VALUE * argv, VALUE klass) {
181
+ struct curl_instance * instance;
182
+ VALUE obj = Data_Make_Struct(klass, struct curl_instance, streamly_instance_mark, streamly_instance_free, instance);
183
+ rb_obj_call_init(obj, argc, argv);
184
+ return obj;
137
185
  }
138
186
 
139
187
  /*
140
- * Document-method: initialize
141
- *
142
- * call-seq: initialize(args)
143
- *
144
- * +args+ should be a Hash and is required
145
- * This Hash should at least contain +:url+ and +:method+ keys.
146
- * You may also provide the following optional keys:
147
- * +:headers+ - should be a Hash of name/value pairs
148
- * +:response_header_handler+ - can be a string or object that responds to #call
149
- * If an object was passed, it's #call method will be called and passed the current chunk of data
150
- * +:response_body_handler+ - can be a string or object that responds to #call
151
- * If an object was passed, it's #call method will be called and passed the current chunk of data
152
- * +:payload+ - If +:method+ is either +:post+ or +:put+ this will be used as the request body
153
- *
154
- */
155
- VALUE rb_streamly_init(int argc, VALUE * argv, VALUE self) {
156
- struct curl_instance * instance;
157
- char * credential_sep = ":";
158
- VALUE args, url, payload, headers, username, password, credentials;
159
-
160
- GetInstance(self, instance);
161
- instance->handle = curl_easy_init();
162
- instance->request_headers = NULL;
163
- instance->request_method = Qnil;
164
- instance->request_payload_handler = Qnil;
165
- instance->response_header_handler = Qnil;
166
- instance->response_body_handler = Qnil;
167
- instance->options = Qnil;
168
-
169
- rb_scan_args(argc, argv, "10", &args);
170
-
188
+ * Document-method: initialize
189
+ *
190
+ * call-seq: initialize(args)
191
+ *
192
+ * +args+ should be a Hash and is required
193
+ * This Hash should at least contain +:url+ and +:method+ keys.
194
+ * You may also provide the following optional keys:
195
+ * +:headers+ - should be a Hash of name/value pairs
196
+ * +:response_header_handler+ - can be a string or object that responds to #call
197
+ * If an object was passed, it's #call method will be called and passed the current chunk of data
198
+ * +:response_body_handler+ - can be a string or object that responds to #call
199
+ * If an object was passed, it's #call method will be called and passed the current chunk of data
200
+ * +:payload+ - If +:method+ is either +:post+ or +:put+ this will be used as the request body
201
+ *
202
+ */
203
+ static VALUE rb_streamly_init(int argc, VALUE * argv, VALUE self) {
204
+ struct curl_instance * instance;
205
+ VALUE args, url, payload, headers, username, password, credentials;
206
+
207
+ GetInstance(self, instance);
208
+ instance->handle = curl_easy_init();
209
+ instance->request_headers = NULL;
210
+ instance->request_method = Qnil;
211
+ instance->request_payload_handler = Qnil;
212
+ instance->response_header_handler = Qnil;
213
+ instance->response_body_handler = Qnil;
214
+ instance->options = Qnil;
215
+
216
+ rb_scan_args(argc, argv, "10", &args);
217
+
171
218
  // Ensure our args parameter is a hash
172
- Check_Type(args, T_HASH);
173
-
174
- instance->request_method = rb_hash_aref(args, sym_method);
175
- url = rb_hash_aref(args, sym_url);
176
- payload = rb_hash_aref(args, sym_payload);
177
- headers = rb_hash_aref(args, sym_headers);
178
- username = rb_hash_aref(args, sym_username);
179
- password = rb_hash_aref(args, sym_password);
180
- instance->response_header_handler = rb_hash_aref(args, sym_response_header_handler);
181
- instance->response_body_handler = rb_hash_aref(args, sym_response_body_handler);
182
-
219
+ Check_Type(args, T_HASH);
220
+
221
+ instance->request_method = rb_hash_aref(args, sym_method);
222
+ url = rb_hash_aref(args, sym_url);
223
+ payload = rb_hash_aref(args, sym_payload);
224
+ headers = rb_hash_aref(args, sym_headers);
225
+ username = rb_hash_aref(args, sym_username);
226
+ password = rb_hash_aref(args, sym_password);
227
+ instance->response_header_handler = rb_hash_aref(args, sym_response_header_handler);
228
+ instance->response_body_handler = rb_hash_aref(args, sym_response_body_handler);
229
+
183
230
  // First lets verify we have a :method key
184
- if (NIL_P(instance->request_method)) {
185
- rb_raise(eStreamlyError, "You must specify a :method");
186
- } else {
231
+ if (NIL_P(instance->request_method)) {
232
+ rb_raise(eStreamlyError, "You must specify a :method");
233
+ } else {
187
234
  // OK, a :method was specified, but if it's POST or PUT we require a :payload
188
- if (instance->request_method == sym_post || instance->request_method == sym_put) {
189
- if (NIL_P(payload)) {
190
- rb_raise(eStreamlyError, "You must specify a :payload for POST and PUT requests");
191
- }
192
- }
235
+ if (instance->request_method == sym_post || instance->request_method == sym_put) {
236
+ if (NIL_P(payload)) {
237
+ rb_raise(eStreamlyError, "You must specify a :payload for POST and PUT requests");
238
+ }
193
239
  }
194
-
240
+ }
241
+
195
242
  // Now verify a :url was provided
196
- if (NIL_P(url)) {
197
- rb_raise(eStreamlyError, "You must specify a :url to request");
198
- }
199
-
200
- if (NIL_P(instance->response_header_handler)) {
201
- instance->response_header_handler = rb_str_new2("");
202
- }
203
- if (instance->request_method != sym_head && NIL_P(instance->response_body_handler)) {
204
- instance->response_body_handler = rb_str_new2("");
243
+ if (NIL_P(url)) {
244
+ rb_raise(eStreamlyError, "You must specify a :url to request");
245
+ }
246
+
247
+ if (NIL_P(instance->response_header_handler)) {
248
+ instance->response_header_handler = rb_str_new2("");
249
+ #ifdef HAVE_RUBY_ENCODING_H
250
+ rb_encoding *default_internal_enc = rb_default_internal_encoding();
251
+ if (default_internal_enc) {
252
+ instance->response_header_handler = rb_str_export_to_enc(instance->response_header_handler, default_internal_enc);
253
+ } else {
254
+ instance->response_header_handler = rb_str_export_to_enc(instance->response_header_handler, utf8Encoding);
205
255
  }
206
-
207
- if (!NIL_P(headers)) {
208
- Check_Type(headers, T_HASH);
209
- rb_iterate(rb_each, headers, each_http_header, self);
210
- curl_easy_setopt(instance->handle, CURLOPT_HTTPHEADER, instance->request_headers);
256
+ #endif
257
+ }
258
+ if (instance->request_method != sym_head && NIL_P(instance->response_body_handler)) {
259
+ instance->response_body_handler = rb_str_new2("");
260
+ #ifdef HAVE_RUBY_ENCODING_H
261
+ rb_encoding *default_internal_enc = rb_default_internal_encoding();
262
+ if (default_internal_enc) {
263
+ instance->response_body_handler = rb_str_export_to_enc(instance->response_body_handler, default_internal_enc);
264
+ } else {
265
+ instance->response_body_handler = rb_str_export_to_enc(instance->response_body_handler, utf8Encoding);
211
266
  }
212
-
267
+ #endif
268
+ }
269
+
270
+ if (!NIL_P(headers)) {
271
+ Check_Type(headers, T_HASH);
272
+ rb_iterate(rb_each, headers, each_http_header, self);
273
+ curl_easy_setopt(instance->handle, CURLOPT_HTTPHEADER, instance->request_headers);
274
+ }
275
+
213
276
  // So far so good, lets start setting up our request
214
-
277
+
215
278
  // Set the type of request
216
- if (instance->request_method == sym_head) {
217
- curl_easy_setopt(instance->handle, CURLOPT_NOBODY, 1);
218
- } else if (instance->request_method == sym_get) {
219
- curl_easy_setopt(instance->handle, CURLOPT_HTTPGET, 1);
220
- } else if (instance->request_method == sym_post) {
221
- curl_easy_setopt(instance->handle, CURLOPT_POST, 1);
222
- curl_easy_setopt(instance->handle, CURLOPT_POSTFIELDS, RSTRING_PTR(payload));
223
- curl_easy_setopt(instance->handle, CURLOPT_POSTFIELDSIZE, RSTRING_LEN(payload));
224
-
225
- // (multipart)
226
- // curl_easy_setopt(instance->handle, CURLOPT_HTTPPOST, 1);
227
-
228
- // TODO: get streaming upload working
229
- // curl_easy_setopt(instance->handle, CURLOPT_READFUNCTION, &upload_data_handler);
230
- // curl_easy_setopt(instance->handle, CURLOPT_READDATA, &instance->upload_stream);
231
- // curl_easy_setopt(instance->handle, CURLOPT_INFILESIZE, len);
232
- } else if (instance->request_method == sym_put) {
233
- curl_easy_setopt(instance->handle, CURLOPT_CUSTOMREQUEST, "PUT");
234
- curl_easy_setopt(instance->handle, CURLOPT_POSTFIELDS, RSTRING_PTR(payload));
235
- curl_easy_setopt(instance->handle, CURLOPT_POSTFIELDSIZE, RSTRING_LEN(payload));
236
-
237
- // TODO: get streaming upload working
238
- // curl_easy_setopt(instance->handle, CURLOPT_UPLOAD, 1);
239
- // curl_easy_setopt(instance->handle, CURLOPT_READFUNCTION, &upload_data_handler);
240
- // curl_easy_setopt(instance->handle, CURLOPT_READDATA, &instance->upload_stream);
241
- // curl_easy_setopt(instance->handle, CURLOPT_INFILESIZE, len);
242
- } else if (instance->request_method == sym_delete) {
243
- curl_easy_setopt(instance->handle, CURLOPT_CUSTOMREQUEST, "DELETE");
244
- }
245
-
246
- // Other common options
247
- curl_easy_setopt(instance->handle, CURLOPT_URL, RSTRING_PTR(url));
248
- curl_easy_setopt(instance->handle, CURLOPT_FOLLOWLOCATION, 1);
249
- curl_easy_setopt(instance->handle, CURLOPT_MAXREDIRS, 3);
250
-
251
- // Response header handling
252
- curl_easy_setopt(instance->handle, CURLOPT_HEADERFUNCTION, &header_handler);
253
- curl_easy_setopt(instance->handle, CURLOPT_HEADERDATA, instance->response_header_handler);
254
-
255
- // Response body handling
256
- if (instance->request_method != sym_head) {
257
- curl_easy_setopt(instance->handle, CURLOPT_ENCODING, "identity, deflate, gzip");
258
- curl_easy_setopt(instance->handle, CURLOPT_WRITEFUNCTION, &data_handler);
259
- curl_easy_setopt(instance->handle, CURLOPT_WRITEDATA, instance->response_body_handler);
279
+ if (instance->request_method == sym_head) {
280
+ curl_easy_setopt(instance->handle, CURLOPT_NOBODY, 1);
281
+ } else if (instance->request_method == sym_get) {
282
+ curl_easy_setopt(instance->handle, CURLOPT_HTTPGET, 1);
283
+ } else if (instance->request_method == sym_post) {
284
+ curl_easy_setopt(instance->handle, CURLOPT_POST, 1);
285
+ curl_easy_setopt(instance->handle, CURLOPT_POSTFIELDS, RSTRING_PTR(payload));
286
+ curl_easy_setopt(instance->handle, CURLOPT_POSTFIELDSIZE, RSTRING_LEN(payload));
287
+
288
+ // (multipart)
289
+ // curl_easy_setopt(instance->handle, CURLOPT_HTTPPOST, 1);
290
+
291
+ // TODO: get streaming upload working
292
+ // curl_easy_setopt(instance->handle, CURLOPT_READFUNCTION, &upload_data_handler);
293
+ // curl_easy_setopt(instance->handle, CURLOPT_READDATA, &instance->upload_stream);
294
+ // curl_easy_setopt(instance->handle, CURLOPT_INFILESIZE, len);
295
+ } else if (instance->request_method == sym_put) {
296
+ curl_easy_setopt(instance->handle, CURLOPT_CUSTOMREQUEST, "PUT");
297
+ curl_easy_setopt(instance->handle, CURLOPT_POSTFIELDS, RSTRING_PTR(payload));
298
+ curl_easy_setopt(instance->handle, CURLOPT_POSTFIELDSIZE, RSTRING_LEN(payload));
299
+
300
+ // TODO: get streaming upload working
301
+ // curl_easy_setopt(instance->handle, CURLOPT_UPLOAD, 1);
302
+ // curl_easy_setopt(instance->handle, CURLOPT_READFUNCTION, &upload_data_handler);
303
+ // curl_easy_setopt(instance->handle, CURLOPT_READDATA, &instance->upload_stream);
304
+ // curl_easy_setopt(instance->handle, CURLOPT_INFILESIZE, len);
305
+ } else if (instance->request_method == sym_delete) {
306
+ curl_easy_setopt(instance->handle, CURLOPT_CUSTOMREQUEST, "DELETE");
307
+ }
308
+
309
+ // Other common options
310
+ curl_easy_setopt(instance->handle, CURLOPT_URL, RSTRING_PTR(url));
311
+ curl_easy_setopt(instance->handle, CURLOPT_FOLLOWLOCATION, 1);
312
+ curl_easy_setopt(instance->handle, CURLOPT_MAXREDIRS, 3);
313
+
314
+ // Response header handling
315
+ curl_easy_setopt(instance->handle, CURLOPT_HEADERFUNCTION, &header_handler);
316
+ curl_easy_setopt(instance->handle, CURLOPT_HEADERDATA, instance->response_header_handler);
317
+
318
+ // Response body handling
319
+ if (instance->request_method != sym_head) {
320
+ curl_easy_setopt(instance->handle, CURLOPT_ENCODING, "identity, deflate, gzip");
321
+ curl_easy_setopt(instance->handle, CURLOPT_WRITEFUNCTION, &data_handler);
322
+ curl_easy_setopt(instance->handle, CURLOPT_WRITEDATA, instance->response_body_handler);
323
+ }
324
+
325
+ if (!NIL_P(username) || !NIL_P(password)) {
326
+ credentials = rb_str_new2("");
327
+ if (!NIL_P(username)) {
328
+ rb_str_buf_cat(credentials, RSTRING_PTR(username), RSTRING_LEN(username));
260
329
  }
261
-
262
- curl_easy_setopt(instance, CURLOPT_USERPWD, NULL);
263
- if (!NIL_P(username) || !NIL_P(password)) {
264
- credentials = rb_str_new2("");
265
- if (!NIL_P(username)) {
266
- rb_str_buf_cat(credentials, RSTRING_PTR(username), RSTRING_LEN(username));
267
- }
268
- rb_str_buf_cat(credentials, credential_sep, 1);
269
- if (!NIL_P(password)) {
270
- rb_str_buf_cat(credentials, RSTRING_PTR(password), RSTRING_LEN(password));
271
- }
272
- curl_easy_setopt(instance->handle, CURLOPT_HTTPAUTH, CURLAUTH_BASIC | CURLAUTH_DIGEST);
273
- curl_easy_setopt(instance->handle, CURLOPT_USERPWD, RSTRING_PTR(credentials));
274
- rb_gc_mark(credentials);
330
+ rb_str_buf_cat(credentials, ":", 1);
331
+ if (!NIL_P(password)) {
332
+ rb_str_buf_cat(credentials, RSTRING_PTR(password), RSTRING_LEN(password));
275
333
  }
276
-
277
- curl_easy_setopt(instance->handle, CURLOPT_SSL_VERIFYPEER, 0);
278
- curl_easy_setopt(instance->handle, CURLOPT_SSL_VERIFYHOST, 0);
279
-
280
- curl_easy_setopt(instance->handle, CURLOPT_ERRORBUFFER, instance->error_buffer);
281
-
282
- return self;
334
+ curl_easy_setopt(instance->handle, CURLOPT_HTTPAUTH, CURLAUTH_BASIC | CURLAUTH_DIGEST);
335
+ curl_easy_setopt(instance->handle, CURLOPT_USERPWD, RSTRING_PTR(credentials));
336
+ rb_gc_mark(credentials);
337
+ }
338
+
339
+ curl_easy_setopt(instance->handle, CURLOPT_SSL_VERIFYPEER, 0);
340
+ curl_easy_setopt(instance->handle, CURLOPT_SSL_VERIFYHOST, 0);
341
+
342
+ curl_easy_setopt(instance->handle, CURLOPT_ERRORBUFFER, instance->error_buffer);
343
+
344
+ return self;
345
+ }
346
+
347
+ static VALUE nogvl_perform(void *handle) {
348
+ CURLcode res;
349
+ VALUE status = Qnil;
350
+
351
+ res = curl_easy_perform(handle);
352
+ if (CURLE_OK != res) {
353
+ status = select_error(res);
354
+ }
355
+
356
+ return status;
283
357
  }
284
358
 
285
359
  /*
286
- * Document-method: rb_streamly_execute
287
- *
288
- * call-seq: rb_streamly_execute
289
- */
290
- VALUE rb_streamly_execute(int argc, VALUE * argv, VALUE self) {
291
- CURLcode res;
292
- struct curl_instance * instance;
293
- GetInstance(self, instance);
294
-
295
- // Done setting up, lets do this!
296
- res = curl_easy_perform(instance->handle);
297
- if (CURLE_OK != res) {
298
- rb_raise(select_error(res), instance->error_buffer);
299
- }
300
-
301
- // Cleanup
302
- if (instance->request_headers != NULL) {
303
- curl_slist_free_all(instance->request_headers);
304
- instance->request_headers = NULL;
305
- }
306
- curl_easy_reset(instance->handle);
307
- instance->request_payload_handler = Qnil;
360
+ * Document-method: rb_streamly_execute
361
+ *
362
+ * call-seq: rb_streamly_execute
363
+ */
364
+ static VALUE rb_streamly_execute(RB_STREAMLY_UNUSED int argc, RB_STREAMLY_UNUSED VALUE * argv, VALUE self) {
365
+ VALUE status;
366
+ struct curl_instance * instance;
367
+ GetInstance(self, instance);
308
368
 
309
- if (instance->request_method == sym_head && TYPE(instance->response_header_handler) == T_STRING) {
310
- return instance->response_header_handler;
311
- } else if (TYPE(instance->response_body_handler) == T_STRING) {
312
- return instance->response_body_handler;
313
- } else {
314
- return Qnil;
315
- }
369
+ // Done setting up, lets do this!
370
+ status = rb_thread_blocking_region(nogvl_perform, instance->handle, RUBY_UBF_IO, 0);
371
+ if (!NIL_P(status)) {
372
+ rb_raise(status, "%s", instance->error_buffer);
373
+ }
374
+
375
+ // Cleanup
376
+ if (instance->request_headers != NULL) {
377
+ curl_slist_free_all(instance->request_headers);
378
+ instance->request_headers = NULL;
379
+ }
380
+ curl_easy_reset(instance->handle);
381
+ instance->request_payload_handler = Qnil;
382
+
383
+ if (instance->request_method == sym_head && TYPE(instance->response_header_handler) == T_STRING) {
384
+ return instance->response_header_handler;
385
+ } else if (TYPE(instance->response_body_handler) == T_STRING) {
386
+ return instance->response_body_handler;
387
+ } else {
388
+ return Qnil;
389
+ }
316
390
  }
317
391
 
318
392
  // Ruby Extension initializer
319
393
  void Init_streamly_ext() {
320
- mStreamly = rb_define_module("Streamly");
321
-
322
- cRequest = rb_define_class_under(mStreamly, "Request", rb_cObject);
323
- rb_define_singleton_method(cRequest, "new", rb_streamly_new, -1);
324
- rb_define_method(cRequest, "initialize", rb_streamly_init, -1);
325
- rb_define_method(cRequest, "execute", rb_streamly_execute, -1);
326
-
327
- eStreamlyError = rb_define_class_under(mStreamly, "Error", rb_eStandardError);
328
- eUnsupportedProtocol = rb_define_class_under(mStreamly, "UnsupportedProtocol", rb_eStandardError);
329
- eURLFormatError = rb_define_class_under(mStreamly, "URLFormatError", rb_eStandardError);
330
- eHostResolutionError = rb_define_class_under(mStreamly, "HostResolutionError", rb_eStandardError);
331
- eConnectionFailed = rb_define_class_under(mStreamly, "ConnectionFailed", rb_eStandardError);
332
- ePartialFileError = rb_define_class_under(mStreamly, "PartialFileError", rb_eStandardError);
333
- eTimeoutError = rb_define_class_under(mStreamly, "TimeoutError", rb_eStandardError);
334
- eTooManyRedirects = rb_define_class_under(mStreamly, "TooManyRedirects", rb_eStandardError);
335
-
336
- sym_method = ID2SYM(rb_intern("method"));
337
- sym_url = ID2SYM(rb_intern("url"));
338
- sym_payload = ID2SYM(rb_intern("payload"));
339
- sym_headers = ID2SYM(rb_intern("headers"));
340
- sym_head = ID2SYM(rb_intern("head"));
341
- sym_get = ID2SYM(rb_intern("get"));
342
- sym_post = ID2SYM(rb_intern("post"));
343
- sym_put = ID2SYM(rb_intern("put"));
344
- sym_delete = ID2SYM(rb_intern("delete"));
345
- sym_username = ID2SYM(rb_intern("username"));
346
- sym_password = ID2SYM(rb_intern("password"));
347
- sym_response_header_handler = ID2SYM(rb_intern("response_header_handler"));
348
- sym_response_body_handler = ID2SYM(rb_intern("response_body_handler"));
394
+ mStreamly = rb_define_module("Streamly");
395
+
396
+ cRequest = rb_define_class_under(mStreamly, "Request", rb_cObject);
397
+ rb_define_singleton_method(cRequest, "new", rb_streamly_new, -1);
398
+ rb_define_method(cRequest, "initialize", rb_streamly_init, -1);
399
+ rb_define_method(cRequest, "execute", rb_streamly_execute, -1);
400
+
401
+ eStreamlyError = rb_define_class_under(mStreamly, "Error", rb_eStandardError);
402
+ eUnsupportedProtocol = rb_define_class_under(mStreamly, "UnsupportedProtocol", rb_eStandardError);
403
+ eURLFormatError = rb_define_class_under(mStreamly, "URLFormatError", rb_eStandardError);
404
+ eHostResolutionError = rb_define_class_under(mStreamly, "HostResolutionError", rb_eStandardError);
405
+ eConnectionFailed = rb_define_class_under(mStreamly, "ConnectionFailed", rb_eStandardError);
406
+ ePartialFileError = rb_define_class_under(mStreamly, "PartialFileError", rb_eStandardError);
407
+ eTimeoutError = rb_define_class_under(mStreamly, "TimeoutError", rb_eStandardError);
408
+ eTooManyRedirects = rb_define_class_under(mStreamly, "TooManyRedirects", rb_eStandardError);
409
+
410
+ sym_method = ID2SYM(rb_intern("method"));
411
+ sym_url = ID2SYM(rb_intern("url"));
412
+ sym_payload = ID2SYM(rb_intern("payload"));
413
+ sym_headers = ID2SYM(rb_intern("headers"));
414
+ sym_head = ID2SYM(rb_intern("head"));
415
+ sym_get = ID2SYM(rb_intern("get"));
416
+ sym_post = ID2SYM(rb_intern("post"));
417
+ sym_put = ID2SYM(rb_intern("put"));
418
+ sym_delete = ID2SYM(rb_intern("delete"));
419
+ sym_username = ID2SYM(rb_intern("username"));
420
+ sym_password = ID2SYM(rb_intern("password"));
421
+ sym_response_header_handler = ID2SYM(rb_intern("response_header_handler"));
422
+ sym_response_body_handler = ID2SYM(rb_intern("response_body_handler"));
423
+
424
+ #ifdef HAVE_RUBY_ENCODING_H
425
+ utf8Encoding = rb_utf8_encoding();
426
+ #endif
349
427
  }