streamly 0.1.3

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.
@@ -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