streamly_ffi 0.1.5
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/.gitignore +5 -0
- data/.rspec +1 -0
- data/CHANGELOG.md +18 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +115 -0
- data/Rakefile +43 -0
- data/VERSION.yml +5 -0
- data/benchmark/basic_request.rb +34 -0
- data/benchmark/streaming_json_request.rb +43 -0
- data/examples/basic/delete.rb +9 -0
- data/examples/basic/get.rb +9 -0
- data/examples/basic/head.rb +9 -0
- data/examples/basic/post.rb +11 -0
- data/examples/basic/put.rb +11 -0
- data/examples/streaming/delete.rb +13 -0
- data/examples/streaming/get.rb +13 -0
- data/examples/streaming/head.rb +13 -0
- data/examples/streaming/post.rb +15 -0
- data/examples/streaming/put.rb +16 -0
- data/ext/extconf.rb +28 -0
- data/ext/streamly.c +427 -0
- data/ext/streamly.h +66 -0
- data/lib/streamly_ffi.rb +122 -0
- data/lib/streamly_ffi/request.rb +163 -0
- data/lib/streamly_ffi/version.rb +3 -0
- data/spec/server.rb +18 -0
- data/spec/spec_helper.rb +7 -0
- data/spec/streamly_ffi/request_spec.rb +293 -0
- data/streamly_ffi.gemspec +41 -0
- metadata +96 -0
data/ext/streamly.c
ADDED
@@ -0,0 +1,427 @@
|
|
1
|
+
// static size_t upload_data_handler(char * stream, size_t size, size_t nmemb, VALUE upload_stream) {
|
2
|
+
// size_t result = 0;
|
3
|
+
//
|
4
|
+
// // TODO
|
5
|
+
// // Change upload_stream back to a VALUE
|
6
|
+
// // if TYPE(upload_stream) == T_STRING - read at most "len" continuously
|
7
|
+
// // if upload_stream is IO-like, read chunks of it
|
8
|
+
// // OR
|
9
|
+
// // if upload_stream responds to "each", use that?
|
10
|
+
//
|
11
|
+
// TRAP_BEG;
|
12
|
+
// // if (upload_stream != NULL && *upload_stream != NULL) {
|
13
|
+
// // int len = size * nmemb;
|
14
|
+
// // char *s1 = strncpy(stream, *upload_stream, len);
|
15
|
+
// // result = strlen(s1);
|
16
|
+
// // *upload_stream += result;
|
17
|
+
// // }
|
18
|
+
// TRAP_END;
|
19
|
+
//
|
20
|
+
// return result;
|
21
|
+
// }
|
22
|
+
|
23
|
+
#include "streamly.h"
|
24
|
+
#ifdef HAVE_RUBY_ENCODING_H
|
25
|
+
#include <ruby/encoding.h>
|
26
|
+
static rb_encoding *utf8Encoding;
|
27
|
+
#endif
|
28
|
+
|
29
|
+
static size_t header_handler(char * stream, size_t size, size_t nmemb, VALUE handler) {
|
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);
|
48
|
+
} else {
|
49
|
+
chunk = rb_str_export_to_enc(chunk, utf8Encoding);
|
50
|
+
}
|
51
|
+
#endif
|
52
|
+
rb_funcall(handler, rb_intern("call"), 1, chunk);
|
53
|
+
}
|
54
|
+
return str_len;
|
55
|
+
}
|
56
|
+
|
57
|
+
static size_t data_handler(char * stream, size_t size, size_t nmemb, VALUE handler) {
|
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);
|
65
|
+
} else {
|
66
|
+
handler = rb_str_export_to_enc(handler, utf8Encoding);
|
67
|
+
}
|
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;
|
83
|
+
}
|
84
|
+
|
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);
|
91
|
+
}
|
92
|
+
|
93
|
+
static void streamly_instance_free(struct curl_instance * instance) {
|
94
|
+
curl_easy_cleanup(instance->handle);
|
95
|
+
free(instance);
|
96
|
+
}
|
97
|
+
|
98
|
+
// Initially borrowed from Patron - http://github.com/toland/patron
|
99
|
+
// slightly modified for Streamly
|
100
|
+
static VALUE each_http_header(VALUE header, VALUE self) {
|
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;
|
122
|
+
}
|
123
|
+
|
124
|
+
// Initially borrowed from Patron - http://github.com/toland/patron
|
125
|
+
// slightly modified for Streamly
|
126
|
+
static VALUE select_error(CURLcode code) {
|
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
|
+
}
|
154
|
+
|
155
|
+
return error;
|
156
|
+
}
|
157
|
+
|
158
|
+
/*
|
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
|
+
*/
|
164
|
+
/*
|
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;
|
185
|
+
}
|
186
|
+
|
187
|
+
/*
|
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
|
+
|
218
|
+
// Ensure our args parameter is a hash
|
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
|
+
|
230
|
+
// First lets verify we have a :method key
|
231
|
+
if (NIL_P(instance->request_method)) {
|
232
|
+
rb_raise(eStreamlyError, "You must specify a :method");
|
233
|
+
} else {
|
234
|
+
// OK, a :method was specified, but if it's POST or PUT we require a :payload
|
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
|
+
}
|
239
|
+
}
|
240
|
+
}
|
241
|
+
|
242
|
+
// Now verify a :url was provided
|
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);
|
255
|
+
}
|
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);
|
266
|
+
}
|
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
|
+
|
276
|
+
// So far so good, lets start setting up our request
|
277
|
+
|
278
|
+
// Set the type of request
|
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));
|
329
|
+
}
|
330
|
+
rb_str_buf_cat(credentials, ":", 1);
|
331
|
+
if (!NIL_P(password)) {
|
332
|
+
rb_str_buf_cat(credentials, RSTRING_PTR(password), RSTRING_LEN(password));
|
333
|
+
}
|
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;
|
357
|
+
}
|
358
|
+
|
359
|
+
/*
|
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);
|
368
|
+
|
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
|
+
}
|
390
|
+
}
|
391
|
+
|
392
|
+
// Ruby Extension initializer
|
393
|
+
void Init_streamly_ext() {
|
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
|
427
|
+
}
|
data/ext/streamly.h
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
#include <curl/curl.h>
|
2
|
+
#include <ruby.h>
|
3
|
+
|
4
|
+
VALUE mStreamly, cRequest, eStreamlyError, eUnsupportedProtocol, eURLFormatError, eHostResolutionError;
|
5
|
+
VALUE eConnectionFailed, ePartialFileError, eTimeoutError, eTooManyRedirects;
|
6
|
+
VALUE sym_method, sym_url, sym_payload, sym_headers, sym_head, sym_get, sym_post, sym_put, sym_delete;
|
7
|
+
VALUE sym_response_header_handler, sym_response_body_handler, sym_username, sym_password;
|
8
|
+
|
9
|
+
#define GetInstance(obj, sval) (sval = (struct curl_instance*)DATA_PTR(obj));
|
10
|
+
|
11
|
+
struct curl_instance {
|
12
|
+
CURL* handle;
|
13
|
+
char error_buffer[CURL_ERROR_SIZE];
|
14
|
+
struct curl_slist* request_headers;
|
15
|
+
VALUE request_payload_handler;
|
16
|
+
VALUE response_header_handler;
|
17
|
+
VALUE response_body_handler;
|
18
|
+
VALUE request_method;
|
19
|
+
VALUE options;
|
20
|
+
};
|
21
|
+
|
22
|
+
// libcurl callbacks
|
23
|
+
static size_t header_handler(char * stream, size_t size, size_t nmemb, VALUE handler);
|
24
|
+
static size_t data_handler(char * stream, size_t size, size_t nmemb, VALUE handler);
|
25
|
+
// static size_t put_data_handler(char * stream, size_t size, size_t nmemb, VALUE upload_stream);
|
26
|
+
//
|
27
|
+
static VALUE select_error(CURLcode code);
|
28
|
+
static VALUE each_http_header(VALUE header, VALUE header_array);
|
29
|
+
static void streamly_instance_mark(struct curl_instance * instance);
|
30
|
+
static void streamly_instance_free(struct curl_instance * instance);
|
31
|
+
|
32
|
+
static VALUE rb_streamly_new(int argc, VALUE * argv, VALUE klass);
|
33
|
+
|
34
|
+
#if defined(__GNUC__) && (__GNUC__ >= 3)
|
35
|
+
#define RB_STREAMLY_UNUSED __attribute__ ((unused))
|
36
|
+
#else
|
37
|
+
#define RB_STREAMLY_UNUSED
|
38
|
+
#endif
|
39
|
+
|
40
|
+
/*
|
41
|
+
* partial emulation of the 1.9 rb_thread_blocking_region under 1.8,
|
42
|
+
* this is enough for dealing with blocking I/O functions in the
|
43
|
+
* presence of threads.
|
44
|
+
*/
|
45
|
+
#ifndef HAVE_RB_THREAD_BLOCKING_REGION
|
46
|
+
|
47
|
+
#include <rubysig.h>
|
48
|
+
#define RUBY_UBF_IO ((rb_unblock_function_t *)-1)
|
49
|
+
typedef void rb_unblock_function_t(void *);
|
50
|
+
typedef VALUE rb_blocking_function_t(void *);
|
51
|
+
static VALUE
|
52
|
+
rb_thread_blocking_region(
|
53
|
+
rb_blocking_function_t *func, void *data1,
|
54
|
+
RB_STREAMLY_UNUSED rb_unblock_function_t *ubf,
|
55
|
+
RB_STREAMLY_UNUSED void *data2)
|
56
|
+
{
|
57
|
+
VALUE rv;
|
58
|
+
|
59
|
+
TRAP_BEG;
|
60
|
+
rv = func(data1);
|
61
|
+
TRAP_END;
|
62
|
+
|
63
|
+
return rv;
|
64
|
+
}
|
65
|
+
|
66
|
+
#endif /* ! HAVE_RB_THREAD_BLOCKING_REGION */
|