streamly 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG.md +5 -0
- data/README.rdoc +3 -4
- data/VERSION.yml +3 -2
- data/benchmark/basic_request.rb +7 -6
- data/benchmark/streaming_json_request.rb +7 -6
- data/ext/extconf.rb +4 -1
- data/ext/streamly.c +356 -278
- data/ext/streamly.h +45 -22
- data/lib/streamly.rb +1 -1
- data/spec/requests/request_spec.rb +255 -65
- data/spec/{sinatra.rb → server.rb} +0 -0
- data/spec/spec_helper.rb +1 -0
- data/streamly.gemspec +6 -6
- metadata +19 -7
data/CHANGELOG.md
CHANGED
@@ -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
|
|
data/README.rdoc
CHANGED
@@ -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
|
-
|
10
|
+
Nothing special about it, just:
|
10
11
|
|
11
|
-
|
12
|
-
|
13
|
-
sudo gem install brianmario-streamly
|
12
|
+
sudo gem install streamly
|
14
13
|
|
15
14
|
== Example of use
|
16
15
|
|
data/VERSION.yml
CHANGED
data/benchmark/basic_request.rb
CHANGED
@@ -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.
|
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.
|
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
|
data/ext/extconf.rb
CHANGED
@@ -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")
|
data/ext/streamly.c
CHANGED
@@ -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
|
-
|
27
|
-
|
28
|
-
|
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
|
-
|
49
|
+
chunk = rb_str_export_to_enc(chunk, utf8Encoding);
|
31
50
|
}
|
32
|
-
|
33
|
-
|
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
|
-
|
38
|
-
|
39
|
-
|
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
|
-
|
66
|
+
handler = rb_str_export_to_enc(handler, utf8Encoding);
|
42
67
|
}
|
43
|
-
|
44
|
-
|
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
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
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
|
-
|
57
|
-
|
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
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
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
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
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
|
-
|
155
|
+
return error;
|
108
156
|
}
|
109
157
|
|
110
158
|
/*
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
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
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
VALUE rb_streamly_new(int argc, VALUE * argv, VALUE klass) {
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
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
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
VALUE rb_streamly_init(int argc, VALUE * argv, VALUE self) {
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
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
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
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
|
-
|
185
|
-
|
186
|
-
|
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
|
-
|
189
|
-
|
190
|
-
|
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
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
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
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
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
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
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
|
-
|
263
|
-
|
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,
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
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
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
VALUE rb_streamly_execute(int argc, VALUE * argv, VALUE self) {
|
291
|
-
|
292
|
-
|
293
|
-
|
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
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
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
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
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
|
}
|