streamly 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3 @@
1
+ *.bundle
2
+ *.o
3
+ Makefile
@@ -0,0 +1,13 @@
1
+ # Changelog
2
+
3
+ ## 0.1.3 (August 19th, 2009)
4
+ * Fixed a bug where a username or password was specified, but not both
5
+
6
+ ## 0.1.2 (July 28th, 2009)
7
+ * removing call to set CURLOPT_USERPWD to NULL as it would crash in linux
8
+
9
+ ## 0.1.1 (July 27th, 2009)
10
+ * Version bump for Github's gem builder
11
+
12
+ ## 0.1.0 (July 27th, 2009)
13
+ * Initial release
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008-2009 Brian Lopez - http://github.com/brianmario
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,116 @@
1
+ = A streaming REST client for Ruby that uses libcurl
2
+
3
+ == Features
4
+ * rest-client like API
5
+ * Streaming API allows the caller to be handed chunks of the response while it's being received
6
+
7
+ == How to install
8
+
9
+ Install it like any other gem hosted at the Githubs like so:
10
+
11
+ (more instructions here: http://gems.github.com)
12
+
13
+ sudo gem install brianmario-streamly
14
+
15
+ == Example of use
16
+
17
+ === A basic HEAD request
18
+
19
+ Streamly.head('www.somehost.com')
20
+
21
+ Or streaming
22
+
23
+ Streamly.head('www.somehost.com') do |header_chunk|
24
+ # do something with header_chunk
25
+ end
26
+
27
+ You can also pass a Hash of headers
28
+
29
+ Streamly.head('www.somehost.com', {"User-Agent" => "Your Mom"})
30
+
31
+ === A basic GET request
32
+
33
+ Streamly.get('www.somehost.com')
34
+
35
+ Or streaming
36
+
37
+ Streamly.get('www.somehost.com') do |body_chunk|
38
+ # do something with body_chunk
39
+ end
40
+
41
+ You can also pass a Hash of headers
42
+
43
+ Streamly.get('www.somehost.com', {"User-Agent" => "Your Mom"})
44
+
45
+ === A basic POST request
46
+
47
+ Streamly.post('www.somehost.com', 'blah=foo')
48
+
49
+ Or streaming
50
+
51
+ Streamly.post('www.somehost.com', 'blah=foo') do |body_chunk|
52
+ # do something with body_chunk
53
+ end
54
+
55
+ You can also pass a Hash of headers
56
+
57
+ Streamly.post('www.somehost.com', 'blah=foo', {"User-Agent" => "Your Mom"})
58
+
59
+ === A basic PUT request
60
+
61
+ Streamly.put('www.somehost.com', 'blah=foo')
62
+
63
+ Or streaming
64
+
65
+ Streamly.put('www.somehost.com', 'blah=foo') do |body_chunk|
66
+ # do something with body_chunk
67
+ end
68
+
69
+ You can also pass a Hash of headers
70
+
71
+ Streamly.put('www.somehost.com', 'blah=foo', {"User-Agent" => "Your Mom"})
72
+
73
+ === A basic DELETE request
74
+
75
+ Streamly.delete('www.somehost.com')
76
+
77
+ Or streaming
78
+
79
+ Streamly.delete('www.somehost.com') do |body_chunk|
80
+ # do something with body_chunk
81
+ end
82
+
83
+ You can also pass a Hash of headers
84
+
85
+ Streamly.delete('www.somehost.com', {"User-Agent" => "Your Mom"})
86
+
87
+ == Benchmarks
88
+
89
+ Fetching 2,405,005 bytes of JSON from a local lighttpd server
90
+
91
+ * Streamly: 0.011s
92
+ * Shell out to curl: 0.046s
93
+ * rest-client: 0.205s
94
+
95
+ Streaming, and parsing 2,405,005 bytes of JSON from a local lighttpd server
96
+
97
+ * Streamly: 0.231s
98
+ * Shell out to curl: 0.341s
99
+ * rest-client: 0.447s
100
+
101
+ == Other Notes
102
+
103
+ This library was basically an exercise in dealing with libcurl in C.
104
+
105
+ == Special Thanks
106
+
107
+ There are quite a few *extremely* nice REST client libraries out there for Ruby today. I especially owe thanks
108
+ to the following projects. Without them I probably would have never had the inspiration to even take the time
109
+ to write this library. In Streamly, you'll find snippets of code, API patterns and examples from all 3 of these projects.
110
+ I'll do my best to make sure I give credit where it's due *in* the source. Please let me know if I've missed something!
111
+
112
+ * rest-client - https://github.com/adamwiggins/rest-client
113
+ * curb - https://github.com/taf2/curb
114
+ * patron - https://github.com/toland/patron
115
+
116
+ And again, the Github crew for this amazing service!
@@ -0,0 +1,35 @@
1
+ # encoding: UTF-8
2
+ begin
3
+ require 'jeweler'
4
+ Jeweler::Tasks.new do |gem|
5
+ gem.name = "streamly"
6
+ gem.summary = "A streaming REST client for Ruby, in C."
7
+ gem.email = "seniorlopez@gmail.com"
8
+ gem.homepage = "http://github.com/brianmario/streamly"
9
+ gem.authors = ["Brian Lopez"]
10
+ gem.require_paths = ["lib", "ext"]
11
+ gem.extra_rdoc_files = `git ls-files *.rdoc`.split("\n")
12
+ gem.files = `git ls-files`.split("\n")
13
+ gem.extensions = ["ext/extconf.rb"]
14
+ gem.files.include %w(lib/jeweler/templates/.document lib/jeweler/templates/.gitignore)
15
+ gem.rubyforge_project = "streamly"
16
+ end
17
+ rescue LoadError
18
+ puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
19
+ end
20
+
21
+ require 'rake'
22
+ require 'spec/rake/spectask'
23
+
24
+ desc "Run all examples with RCov"
25
+ Spec::Rake::SpecTask.new('spec:rcov') do |t|
26
+ t.spec_files = FileList['spec/']
27
+ t.rcov = true
28
+ t.rcov_opts = lambda do
29
+ IO.readlines("spec/rcov.opts").map {|l| l.chomp.split " "}.flatten
30
+ end
31
+ end
32
+ Spec::Rake::SpecTask.new('spec') do |t|
33
+ t.spec_files = FileList['spec/']
34
+ t.spec_opts << '--options' << 'spec/spec.opts'
35
+ end
@@ -0,0 +1,4 @@
1
+ ---
2
+ :minor: 1
3
+ :patch: 3
4
+ :major: 0
@@ -0,0 +1,33 @@
1
+ # encoding: UTF-8
2
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
3
+
4
+ require 'rubygems'
5
+ require 'net/http'
6
+ require 'rest_client'
7
+ require 'streamly'
8
+ require 'benchmark'
9
+
10
+ url = ARGV[0]
11
+
12
+ Benchmark.bm do |x|
13
+ puts "Streamly"
14
+ x.report do
15
+ (ARGV[1] || 1).to_i.times do
16
+ Streamly.get(url)
17
+ end
18
+ end
19
+
20
+ puts "Shell out to curl"
21
+ x.report do
22
+ (ARGV[1] || 1).to_i.times do
23
+ `curl -s --compressed #{url}`
24
+ end
25
+ end
26
+
27
+ puts "rest-client"
28
+ x.report do
29
+ (ARGV[1] || 1).to_i.times do
30
+ RestClient.get(url, {"Accept-Encoding" => "identity, deflate, gzip"})
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,42 @@
1
+ # encoding: UTF-8
2
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
3
+
4
+ require 'rubygems'
5
+ require 'net/http'
6
+ require 'rest_client'
7
+ require 'streamly'
8
+ require 'yajl'
9
+ require 'benchmark'
10
+
11
+ url = ARGV[0]
12
+
13
+ Benchmark.bm do |x|
14
+ puts "Streamly"
15
+ parser = Yajl::Parser.new
16
+ parser.on_parse_complete = lambda {|obj| }
17
+ x.report do
18
+ (ARGV[1] || 1).to_i.times do
19
+ Streamly.get(url) do |chunk|
20
+ parser << chunk
21
+ end
22
+ end
23
+ end
24
+
25
+ puts "Shell out to curl"
26
+ parser = Yajl::Parser.new
27
+ parser.on_parse_complete = lambda {|obj| }
28
+ x.report do
29
+ (ARGV[1] || 1).to_i.times do
30
+ parser.parse `curl -s --compressed #{url}`
31
+ end
32
+ end
33
+
34
+ puts "rest-client"
35
+ parser = Yajl::Parser.new
36
+ parser.on_parse_complete = lambda {|obj| }
37
+ x.report do
38
+ (ARGV[1] || 1).to_i.times do
39
+ parser.parse RestClient.get(url, {"Accept-Encoding" => "identity, deflate, gzip"})
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,9 @@
1
+ # encoding: UTF-8
2
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../../lib')
3
+
4
+ require 'streamly'
5
+
6
+ raise Exception, "You must pass a URL to request" if ARGV[0].nil?
7
+
8
+ url = ARGV[0]
9
+ puts Streamly.delete(url)
@@ -0,0 +1,9 @@
1
+ # encoding: UTF-8
2
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../../lib')
3
+
4
+ require 'streamly'
5
+
6
+ raise Exception, "You must pass a URL to request" if ARGV[0].nil?
7
+
8
+ url = ARGV[0]
9
+ puts Streamly.get(url)
@@ -0,0 +1,9 @@
1
+ # encoding: UTF-8
2
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../../lib')
3
+
4
+ require 'streamly'
5
+
6
+ raise Exception, "You must pass a URL to request" if ARGV[0].nil?
7
+
8
+ url = ARGV[0]
9
+ puts Streamly.head(url)
@@ -0,0 +1,11 @@
1
+ # encoding: UTF-8
2
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../../lib')
3
+
4
+ require 'streamly'
5
+
6
+ raise Exception, "You must pass a URL to request" if ARGV[0].nil?
7
+ raise Exception, "You must pass a payload to POST" if ARGV[1].nil?
8
+
9
+ url = ARGV[0]
10
+ payload = ARGV[1]
11
+ puts Streamly.post(url, payload)
@@ -0,0 +1,11 @@
1
+ # encoding: UTF-8
2
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../../lib')
3
+
4
+ require 'streamly'
5
+
6
+ raise Exception, "You must pass a URL to request" if ARGV[0].nil?
7
+ raise Exception, "You must pass a payload to PUT" if ARGV[1].nil?
8
+
9
+ url = ARGV[0]
10
+ payload = ARGV[1]
11
+ puts Streamly.put(url, payload)
@@ -0,0 +1,13 @@
1
+ # encoding: UTF-8
2
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../../lib')
3
+
4
+ require 'streamly'
5
+
6
+ raise Exception, "You must pass a URL to request" if ARGV[0].nil?
7
+
8
+ url = ARGV[0]
9
+ Streamly.delete(url) do |chunk|
10
+ STDOUT.write chunk
11
+ STDOUT.flush
12
+ end
13
+ puts
@@ -0,0 +1,13 @@
1
+ # encoding: UTF-8
2
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../../lib')
3
+
4
+ require 'streamly'
5
+
6
+ raise Exception, "You must pass a URL to request" if ARGV[0].nil?
7
+
8
+ url = ARGV[0]
9
+ Streamly.get(url) do |chunk|
10
+ STDOUT.write chunk
11
+ STDOUT.flush
12
+ end
13
+ puts
@@ -0,0 +1,13 @@
1
+ # encoding: UTF-8
2
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../../lib')
3
+
4
+ require 'streamly'
5
+
6
+ raise Exception, "You must pass a URL to request" if ARGV[0].nil?
7
+
8
+ url = ARGV[0]
9
+ Streamly.head(url) do |chunk|
10
+ STDOUT.write chunk
11
+ STDOUT.flush
12
+ end
13
+ puts
@@ -0,0 +1,15 @@
1
+ # encoding: UTF-8
2
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../../lib')
3
+
4
+ require 'streamly'
5
+
6
+ raise Exception, "You must pass a URL to request" if ARGV[0].nil?
7
+ raise Exception, "You must pass a payload to POST" if ARGV[1].nil?
8
+
9
+ url = ARGV[0]
10
+ payload = ARGV[1]
11
+ Streamly.post(url, payload) do |chunk|
12
+ STDOUT.write chunk
13
+ STDOUT.flush
14
+ end
15
+ puts
@@ -0,0 +1,16 @@
1
+ # encoding: UTF-8
2
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../../lib')
3
+
4
+ require 'streamly'
5
+
6
+ raise Exception, "You must pass a URL to request" if ARGV[0].nil?
7
+ raise Exception, "You must pass a payload to PUT" if ARGV[1].nil?
8
+
9
+ url = ARGV[0]
10
+ payload = ARGV[1]
11
+ resp = ''
12
+ Streamly.put(url, payload) do |chunk|
13
+ STDOUT.write chunk
14
+ STDOUT.flush
15
+ end
16
+ puts
@@ -0,0 +1,25 @@
1
+ # encoding: UTF-8
2
+ require 'mkmf'
3
+ require 'rbconfig'
4
+
5
+ $CFLAGS << ' -DHAVE_RBTRAP' if have_var('rb_trap_immediate', ['ruby.h', 'rubysig.h'])
6
+ # add_define 'HAVE_RBTRAP' if have_var('rb_trap_immediate', ['ruby.h', 'rubysig.h'])
7
+
8
+ # Borrowed from taf2-curb
9
+ dir_config('curl')
10
+ if find_executable('curl-config')
11
+ $CFLAGS << " #{`curl-config --cflags`.strip}"
12
+ $LIBS << " #{`curl-config --libs`.strip}"
13
+ elsif !have_library('curl') or !have_header('curl/curl.h')
14
+ fail <<-EOM
15
+ Can't find libcurl or curl/curl.h
16
+
17
+ Try passing --with-curl-dir or --with-curl-lib and --with-curl-include
18
+ options to extconf.
19
+ EOM
20
+ end
21
+
22
+ $CFLAGS << ' -Wall'
23
+ # $CFLAGS << ' -O0 -ggdb'
24
+
25
+ create_makefile("streamly_ext")
@@ -0,0 +1,349 @@
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
+
25
+ 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);
29
+ } else {
30
+ rb_funcall(handler, rb_intern("call"), 1, rb_str_new(stream, size * nmemb));
31
+ }
32
+ TRAP_END;
33
+ return size * nmemb;
34
+ }
35
+
36
+ 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);
40
+ } else {
41
+ rb_funcall(handler, rb_intern("call"), 1, rb_str_new(stream, size * nmemb));
42
+ }
43
+ TRAP_END;
44
+ return size * nmemb;
45
+ }
46
+
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);
53
+ }
54
+
55
+ void streamly_instance_free(struct curl_instance * instance) {
56
+ curl_easy_cleanup(instance->handle);
57
+ free(instance);
58
+ }
59
+
60
+ // Initially borrowed from Patron - http://github.com/toland/patron
61
+ // slightly modified for Streamly
62
+ 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;
74
+ }
75
+
76
+ // Initially borrowed from Patron - http://github.com/toland/patron
77
+ // slightly modified for Streamly
78
+ 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
+ }
106
+
107
+ return error;
108
+ }
109
+
110
+ /*
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
+ */
116
+ /*
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;
137
+ }
138
+
139
+ /*
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
+
171
+ // 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
+
183
+ // 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 {
187
+ // 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
+ }
193
+ }
194
+
195
+ // 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("");
205
+ }
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);
211
+ }
212
+
213
+ // So far so good, lets start setting up our request
214
+
215
+ // 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);
260
+ }
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);
275
+ }
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;
283
+ }
284
+
285
+ /*
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;
308
+
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
+ }
316
+ }
317
+
318
+ // Ruby Extension initializer
319
+ 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"));
349
+ }
@@ -0,0 +1,43 @@
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
+ #ifdef HAVE_RBTRAP
12
+ #include <rubysig.h>
13
+ #else
14
+ void rb_enable_interrupt(void);
15
+ void rb_disable_interrupt(void);
16
+
17
+ #define TRAP_BEG rb_enable_interrupt();
18
+ #define TRAP_END do { rb_disable_interrupt(); rb_thread_check_ints(); } while(0);
19
+ #endif
20
+
21
+ struct curl_instance {
22
+ CURL* handle;
23
+ char error_buffer[CURL_ERROR_SIZE];
24
+ struct curl_slist* request_headers;
25
+ VALUE request_payload_handler;
26
+ VALUE response_header_handler;
27
+ VALUE response_body_handler;
28
+ VALUE request_method;
29
+ VALUE options;
30
+ };
31
+
32
+ // libcurl callbacks
33
+ static size_t header_handler(char * stream, size_t size, size_t nmemb, VALUE handler);
34
+ static size_t data_handler(char * stream, size_t size, size_t nmemb, VALUE handler);
35
+ // static size_t put_data_handler(char * stream, size_t size, size_t nmemb, VALUE upload_stream);
36
+ //
37
+ static VALUE select_error(CURLcode code);
38
+ static VALUE each_http_header(VALUE header, VALUE header_array);
39
+ void streamly_instance_mark(struct curl_instance * instance);
40
+ void streamly_instance_free(struct curl_instance * instance);
41
+
42
+ VALUE rb_streamly_new(int argc, VALUE * argv, VALUE klass);
43
+ VALUE rb_streamly_new(int argc, VALUE * argv, VALUE self);
@@ -0,0 +1,127 @@
1
+ # encoding: UTF-8
2
+ require 'streamly_ext'
3
+
4
+ module Streamly
5
+ VERSION = "0.1.3"
6
+
7
+ class Request
8
+ # A helper method to make your fire-and-forget requests easier
9
+ #
10
+ # Parameters:
11
+ # +args+ should be a Hash and is required
12
+ # This Hash should at least contain +:url+ and +:method+ keys.
13
+ # You may also provide the following optional keys:
14
+ # +:headers+ - should be a Hash of name/value pairs
15
+ # +:response_header_handler+ - can be a string or object that responds to #call
16
+ # If an object was passed, it's #call method will be called and passed the current chunk of data
17
+ # +:response_body_handler+ - can be a string or object that responds to #call
18
+ # If an object was passed, it's #call method will be called and passed the current chunk of data
19
+ # +:payload+ - If +:method+ is either +:post+ or +:put+ this will be used as the request body
20
+ #
21
+ def self.execute(args)
22
+ new(args).execute
23
+ end
24
+ end
25
+
26
+ # A helper method to make HEAD requests a dead-simple one-liner
27
+ #
28
+ # Example:
29
+ # Streamly.head("www.somehost.com/some_resource/1")
30
+ #
31
+ # Streamly.head("www.somehost.com/some_resource/1") do |header_chunk|
32
+ # # do something with _header_chunk_
33
+ # end
34
+ #
35
+ # Parameters:
36
+ # +url+ should be a String, the url to request
37
+ # +headers+ should be a Hash and is optional
38
+ #
39
+ # This method also accepts a block, which will stream the response headers in chunks to the caller
40
+ def self.head(url, headers=nil, &block)
41
+ opts = {:method => :head, :url => url, :headers => headers}
42
+ opts.merge!({:response_header_handler => block}) if block_given?
43
+ Request.execute(opts)
44
+ end
45
+
46
+ # A helper method to make HEAD requests a dead-simple one-liner
47
+ #
48
+ # Example:
49
+ # Streamly.get("www.somehost.com/some_resource/1")
50
+ #
51
+ # Streamly.get("www.somehost.com/some_resource/1") do |chunk|
52
+ # # do something with _chunk_
53
+ # end
54
+ #
55
+ # Parameters:
56
+ # +url+ should be a String, the url to request
57
+ # +headers+ should be a Hash and is optional
58
+ #
59
+ # This method also accepts a block, which will stream the response body in chunks to the caller
60
+ def self.get(url, headers=nil, &block)
61
+ opts = {:method => :get, :url => url, :headers => headers}
62
+ opts.merge!({:response_body_handler => block}) if block_given?
63
+ Request.execute(opts)
64
+ end
65
+
66
+ # A helper method to make HEAD requests a dead-simple one-liner
67
+ #
68
+ # Example:
69
+ # Streamly.post("www.somehost.com/some_resource", "asset[id]=2&asset[name]=bar")
70
+ #
71
+ # Streamly.post("www.somehost.com/some_resource", "asset[id]=2&asset[name]=bar") do |chunk|
72
+ # # do something with _chunk_
73
+ # end
74
+ #
75
+ # Parameters:
76
+ # +url+ should be a String (the url to request) and is required
77
+ # +payload+ should be a String and is required
78
+ # +headers+ should be a Hash and is optional
79
+ #
80
+ # This method also accepts a block, which will stream the response body in chunks to the caller
81
+ def self.post(url, payload, headers=nil, &block)
82
+ opts = {:method => :post, :url => url, :payload => payload, :headers => headers}
83
+ opts.merge!({:response_body_handler => block}) if block_given?
84
+ Request.execute(opts)
85
+ end
86
+
87
+ # A helper method to make HEAD requests a dead-simple one-liner
88
+ #
89
+ # Example:
90
+ # Streamly.put("www.somehost.com/some_resource/1", "asset[name]=foo")
91
+ #
92
+ # Streamly.put("www.somehost.com/some_resource/1", "asset[name]=foo") do |chunk|
93
+ # # do something with _chunk_
94
+ # end
95
+ #
96
+ # Parameters:
97
+ # +url+ should be a String (the url to request) and is required
98
+ # +payload+ should be a String and is required
99
+ # +headers+ should be a Hash and is optional
100
+ #
101
+ # This method also accepts a block, which will stream the response body in chunks to the caller
102
+ def self.put(url, payload, headers=nil, &block)
103
+ opts = {:method => :put, :url => url, :payload => payload, :headers => headers}
104
+ opts.merge!({:response_body_handler => block}) if block_given?
105
+ Request.execute(opts)
106
+ end
107
+
108
+ # A helper method to make HEAD requests a dead-simple one-liner
109
+ #
110
+ # Example:
111
+ # Streamly.delete("www.somehost.com/some_resource/1")
112
+ #
113
+ # Streamly.delete("www.somehost.com/some_resource/1") do |chunk|
114
+ # # do something with _chunk_
115
+ # end
116
+ #
117
+ # Parameters:
118
+ # +url+ should be a String, the url to request
119
+ # +headers+ should be a Hash and is optional
120
+ #
121
+ # This method also accepts a block, which will stream the response body in chunks to the caller
122
+ def self.delete(url, headers={}, &block)
123
+ opts = {:method => :delete, :url => url, :headers => headers}
124
+ opts.merge!({:response_body_handler => block}) if block_given?
125
+ Request.execute(opts)
126
+ end
127
+ end
@@ -0,0 +1,4 @@
1
+ --exclude spec,gem
2
+ --text-summary
3
+ --spec-only
4
+ --sort coverage --sort-reverse
@@ -0,0 +1,93 @@
1
+ # encoding: UTF-8
2
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper.rb')
3
+
4
+ describe "Streamly's REST API" do
5
+
6
+ before(:all) do
7
+ @response = "Hello, brian".strip
8
+ end
9
+
10
+ context "HEAD" do
11
+ it "should perform a basic request" do
12
+ resp = Streamly.head('localhost:4567')
13
+ resp.should_not be_empty
14
+ end
15
+
16
+ it "should perform a basic request and stream the response to the caller" do
17
+ streamed_response = ''
18
+ resp = Streamly.head('localhost:4567') do |chunk|
19
+ chunk.should_not be_empty
20
+ streamed_response << chunk
21
+ end
22
+ resp.should be_nil
23
+ streamed_response.should_not be_nil
24
+ end
25
+ end
26
+
27
+ context "GET" do
28
+ it "should perform a basic request" do
29
+ resp = Streamly.get('localhost:4567/?name=brian')
30
+ resp.should eql(@response)
31
+ end
32
+
33
+ it "should perform a basic request and stream the response to the caller" do
34
+ streamed_response = ''
35
+ resp = Streamly.get('localhost:4567/?name=brian') do |chunk|
36
+ chunk.should_not be_empty
37
+ streamed_response << chunk
38
+ end
39
+ resp.should be_nil
40
+ streamed_response.should eql(@response)
41
+ end
42
+ end
43
+
44
+ context "POST" do
45
+ it "should perform a basic request" do
46
+ resp = Streamly.post('localhost:4567', 'name=brian')
47
+ resp.should eql(@response)
48
+ end
49
+
50
+ it "should perform a basic request and stream the response to the caller" do
51
+ streamed_response = ''
52
+ resp = Streamly.post('localhost:4567', 'name=brian') do |chunk|
53
+ chunk.should_not be_empty
54
+ streamed_response << chunk
55
+ end
56
+ resp.should be_nil
57
+ streamed_response.should eql(@response)
58
+ end
59
+ end
60
+
61
+ context "PUT" do
62
+ it "should perform a basic request" do
63
+ resp = Streamly.put('localhost:4567', 'name=brian')
64
+ resp.should eql(@response)
65
+ end
66
+
67
+ it "should perform a basic request and stream the response to the caller" do
68
+ streamed_response = ''
69
+ resp = Streamly.put('localhost:4567', 'name=brian') do |chunk|
70
+ chunk.should_not be_empty
71
+ streamed_response << chunk
72
+ end
73
+ resp.should be_nil
74
+ streamed_response.should eql(@response)
75
+ end
76
+ end
77
+
78
+ context "DELETE" do
79
+ it "should perform a basic request" do
80
+ resp = Streamly.delete('localhost:4567/?name=brian').should eql(@response)
81
+ end
82
+
83
+ it "should perform a basic request and stream the response to the caller" do
84
+ streamed_response = ''
85
+ resp = Streamly.delete('localhost:4567/?name=brian') do |chunk|
86
+ chunk.should_not be_empty
87
+ streamed_response << chunk
88
+ end
89
+ resp.should be_nil
90
+ streamed_response.should eql(@response)
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,18 @@
1
+ require 'rubygems'
2
+ require 'sinatra'
3
+
4
+ get '/' do
5
+ "Hello, #{params[:name]}".strip
6
+ end
7
+
8
+ post '/' do
9
+ "Hello, #{params[:name]}".strip
10
+ end
11
+
12
+ put '/' do
13
+ "Hello, #{params[:name]}".strip
14
+ end
15
+
16
+ delete '/' do
17
+ "Hello, #{params[:name]}".strip
18
+ end
@@ -0,0 +1,2 @@
1
+ --format specdoc
2
+ --colour
@@ -0,0 +1,5 @@
1
+ # encoding: UTF-8
2
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
3
+
4
+ require 'rubygems'
5
+ require 'streamly'
@@ -0,0 +1,80 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{streamly}
8
+ s.version = "0.1.3"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Brian Lopez"]
12
+ s.date = %q{2010-02-11}
13
+ s.email = %q{seniorlopez@gmail.com}
14
+ s.extensions = ["ext/extconf.rb"]
15
+ s.extra_rdoc_files = [
16
+ "README.rdoc"
17
+ ]
18
+ s.files = [
19
+ ".gitignore",
20
+ "CHANGELOG.md",
21
+ "MIT-LICENSE",
22
+ "README.rdoc",
23
+ "Rakefile",
24
+ "VERSION.yml",
25
+ "benchmark/basic_request.rb",
26
+ "benchmark/streaming_json_request.rb",
27
+ "examples/basic/delete.rb",
28
+ "examples/basic/get.rb",
29
+ "examples/basic/head.rb",
30
+ "examples/basic/post.rb",
31
+ "examples/basic/put.rb",
32
+ "examples/streaming/delete.rb",
33
+ "examples/streaming/get.rb",
34
+ "examples/streaming/head.rb",
35
+ "examples/streaming/post.rb",
36
+ "examples/streaming/put.rb",
37
+ "ext/extconf.rb",
38
+ "ext/streamly.c",
39
+ "ext/streamly.h",
40
+ "lib/streamly.rb",
41
+ "spec/rcov.opts",
42
+ "spec/requests/request_spec.rb",
43
+ "spec/sinatra.rb",
44
+ "spec/spec.opts",
45
+ "spec/spec_helper.rb",
46
+ "streamly.gemspec"
47
+ ]
48
+ s.homepage = %q{http://github.com/brianmario/streamly}
49
+ s.rdoc_options = ["--charset=UTF-8"]
50
+ s.require_paths = ["lib", "ext"]
51
+ s.rubyforge_project = %q{streamly}
52
+ s.rubygems_version = %q{1.3.5}
53
+ s.summary = %q{A streaming REST client for Ruby, in C.}
54
+ s.test_files = [
55
+ "spec/requests/request_spec.rb",
56
+ "spec/sinatra.rb",
57
+ "spec/spec_helper.rb",
58
+ "examples/basic/delete.rb",
59
+ "examples/basic/get.rb",
60
+ "examples/basic/head.rb",
61
+ "examples/basic/post.rb",
62
+ "examples/basic/put.rb",
63
+ "examples/streaming/delete.rb",
64
+ "examples/streaming/get.rb",
65
+ "examples/streaming/head.rb",
66
+ "examples/streaming/post.rb",
67
+ "examples/streaming/put.rb"
68
+ ]
69
+
70
+ if s.respond_to? :specification_version then
71
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
72
+ s.specification_version = 3
73
+
74
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
75
+ else
76
+ end
77
+ else
78
+ end
79
+ end
80
+
metadata ADDED
@@ -0,0 +1,95 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: streamly
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.3
5
+ platform: ruby
6
+ authors:
7
+ - Brian Lopez
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-02-11 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description:
17
+ email: seniorlopez@gmail.com
18
+ executables: []
19
+
20
+ extensions:
21
+ - ext/extconf.rb
22
+ extra_rdoc_files:
23
+ - README.rdoc
24
+ files:
25
+ - .gitignore
26
+ - CHANGELOG.md
27
+ - MIT-LICENSE
28
+ - README.rdoc
29
+ - Rakefile
30
+ - VERSION.yml
31
+ - benchmark/basic_request.rb
32
+ - benchmark/streaming_json_request.rb
33
+ - examples/basic/delete.rb
34
+ - examples/basic/get.rb
35
+ - examples/basic/head.rb
36
+ - examples/basic/post.rb
37
+ - examples/basic/put.rb
38
+ - examples/streaming/delete.rb
39
+ - examples/streaming/get.rb
40
+ - examples/streaming/head.rb
41
+ - examples/streaming/post.rb
42
+ - examples/streaming/put.rb
43
+ - ext/extconf.rb
44
+ - ext/streamly.c
45
+ - ext/streamly.h
46
+ - lib/streamly.rb
47
+ - spec/rcov.opts
48
+ - spec/requests/request_spec.rb
49
+ - spec/sinatra.rb
50
+ - spec/spec.opts
51
+ - spec/spec_helper.rb
52
+ - streamly.gemspec
53
+ has_rdoc: true
54
+ homepage: http://github.com/brianmario/streamly
55
+ licenses: []
56
+
57
+ post_install_message:
58
+ rdoc_options:
59
+ - --charset=UTF-8
60
+ require_paths:
61
+ - lib
62
+ - ext
63
+ required_ruby_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: "0"
68
+ version:
69
+ required_rubygems_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: "0"
74
+ version:
75
+ requirements: []
76
+
77
+ rubyforge_project: streamly
78
+ rubygems_version: 1.3.5
79
+ signing_key:
80
+ specification_version: 3
81
+ summary: A streaming REST client for Ruby, in C.
82
+ test_files:
83
+ - spec/requests/request_spec.rb
84
+ - spec/sinatra.rb
85
+ - spec/spec_helper.rb
86
+ - examples/basic/delete.rb
87
+ - examples/basic/get.rb
88
+ - examples/basic/head.rb
89
+ - examples/basic/post.rb
90
+ - examples/basic/put.rb
91
+ - examples/streaming/delete.rb
92
+ - examples/streaming/get.rb
93
+ - examples/streaming/head.rb
94
+ - examples/streaming/post.rb
95
+ - examples/streaming/put.rb