streamly_ffi 0.1.6 → 0.2.0

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/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in gsolr.gemspec
4
+ gemspec
5
+
6
+ group :test do
7
+ gem 'rspec'
8
+ gem 'rspec-core'
9
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,32 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ streamly_ffi (0.1.6)
5
+ curl_ffi
6
+
7
+ GEM
8
+ remote: http://rubygems.org/
9
+ specs:
10
+ curl_ffi (0.0.6)
11
+ ffi
12
+ diff-lcs (1.1.2)
13
+ ffi (0.6.3)
14
+ rake (>= 0.8.7)
15
+ rake (0.8.7)
16
+ rspec (2.1.0)
17
+ rspec-core (~> 2.1.0)
18
+ rspec-expectations (~> 2.1.0)
19
+ rspec-mocks (~> 2.1.0)
20
+ rspec-core (2.1.0)
21
+ rspec-expectations (2.1.0)
22
+ diff-lcs (~> 1.1.2)
23
+ rspec-mocks (2.1.0)
24
+
25
+ PLATFORMS
26
+ ruby
27
+
28
+ DEPENDENCIES
29
+ curl_ffi
30
+ rspec
31
+ rspec-core
32
+ streamly_ffi!
@@ -34,10 +34,18 @@ Benchmark.bmbm do |x|
34
34
  end
35
35
  end
36
36
 
37
+ x.report do
38
+ puts "StreamlyFFI2"
39
+ conn = StreamlyFFI::Connection.new
40
+ (ARGV[1] || 1).to_i.times do
41
+ conn.get(url)
42
+ end
43
+ end
44
+
37
45
  x.report do
38
46
  puts "`curl`"
39
47
  (ARGV[1] || 1).to_i.times do
40
- `curl -s --compressed #{url}`
48
+ `curl --compressed --silent #{url}`
41
49
  end
42
50
  end
43
51
 
data/lib/streamly_ffi.rb CHANGED
@@ -5,7 +5,10 @@ $:.unshift(File.dirname(__FILE__)) unless
5
5
  require 'streamly_ffi/version'
6
6
 
7
7
  module StreamlyFFI
8
- autoload :Request, "streamly_ffi/request"
8
+ autoload :Base, "streamly_ffi/base"
9
+ autoload :Connection, "streamly_ffi/connection"
10
+ autoload :Request, "streamly_ffi/request"
11
+ autoload :PersistentRequest, "streamly_ffi/persistent_request"
9
12
 
10
13
  class Error < StandardError; end
11
14
  class UnsupportedProtocol < StandardError; end
@@ -117,6 +120,22 @@ module StreamlyFFI
117
120
  opts.merge!({:response_body_handler => block}) if block_given?
118
121
  Request.execute(opts)
119
122
  end
120
- end
121
123
 
122
- # require "streamly/request" # May need to do this? Not sure how autoload works with FFI yet
124
+ # A helper method to make HEAD requests a dead-simple one-liner
125
+ #
126
+ # Example:
127
+ # Streamly.delete("www.somehost.com/some_resource/1")
128
+ #
129
+ # Streamly.delete("www.somehost.com/some_resource/1") do |chunk|
130
+ # # do something with _chunk_
131
+ # end
132
+ #
133
+ # Parameters:
134
+ # +url+ should be a String, the url to request
135
+ # +headers+ should be a Hash and is optional
136
+ #
137
+ # This method also accepts a block, which will stream the response body in chunks to the caller
138
+ def self.connect
139
+ Connection.new
140
+ end
141
+ end
@@ -0,0 +1,155 @@
1
+ module StreamlyFFI
2
+ module Base
3
+ alias __method__ method
4
+
5
+ attr_accessor :url, :method, :default_write_handler, :default_header_handler
6
+
7
+ def execute(options={})
8
+ set_options(options).perform
9
+
10
+ CurlFFI.slist_free_all(@request_headers) if @request_headers
11
+
12
+ connection.reset
13
+
14
+ resp = if(options.has_key?(:response_header_handler) or options.has_key?(:response_body_handler))
15
+ nil
16
+ elsif(options[:method] == :head && response_header.respond_to?(:to_str))
17
+ response_header
18
+ elsif(response_body.is_a?(String))
19
+ response_body
20
+ else
21
+ nil
22
+ end
23
+
24
+ return resp
25
+ end
26
+
27
+ def perform
28
+ connection.perform
29
+ end
30
+
31
+ def set_options(options={})
32
+ @url = options[:url] if options.has_key?(:url) # Make sure @url is set, if not
33
+ @method = options[:method] if options.has_key?(:method) # Make sure @method is set, if not
34
+ @payload = options[:payload]
35
+
36
+ @response_body = nil
37
+ @response_header = nil
38
+ @custom_header_handler = nil
39
+ @custom_write_handler = nil
40
+
41
+ # url should be a string that doesn't suck
42
+ # method should be :post, :get, :put, :delete, :head
43
+ # options should contain any of the following keys:
44
+ # :headers, :response_header_handler, :response_body_handler, :payload (required if method = :post / :put)
45
+
46
+ case @method
47
+ when :get then connection.setopt :HTTPGET, 1
48
+ when :head then connection.setopt :NOBODY, 1
49
+ when :post then connection.setopt :POST, 1
50
+ connection.setopt :POSTFIELDS, @payload
51
+ connection.setopt :POSTFIELDSIZE, @payload.size
52
+ when :put then connection.setopt :CUSTOMREQUEST, "PUT"
53
+ connection.setopt :POSTFIELDS, @payload
54
+ connection.setopt :POSTFIELDSIZE, @payload.size
55
+ when :delete then connection.setopt :CUSTOMREQUEST, "DELETE"
56
+ # else I WILL CUT YOU
57
+ end
58
+
59
+ if options.has_key?(:headers) and not options[:headers].nil?
60
+ options[:headers].each_pair do |key_and_value|
61
+ self.request_headers = CurlFFI.slist_append(self.request_headers, key_and_value.join(": "))
62
+ end
63
+ connection.setopt :HTTPHEADER, @request_headers
64
+ end
65
+
66
+ if options.has_key?(:response_header_handler)
67
+ @custom_header_handler = options[:response_header_handler]
68
+ set_header_handler(:custom_header_callback)
69
+ else
70
+ set_header_handler
71
+ end
72
+
73
+ if options.has_key?(:response_body_handler)
74
+ @custom_write_handler = options[:response_body_handler]
75
+ set_write_handler(:custom_write_callback)
76
+ else
77
+ set_write_handler
78
+ end
79
+
80
+ connection.setopt :ENCODING, "identity, deflate, gzip" unless @method == :head
81
+ connection.setopt :URL, @url
82
+
83
+ # Other common options (blame streamly guy)
84
+ connection.setopt :FOLLOWLOCATION, 1
85
+ connection.setopt :MAXREDIRS, 3
86
+ # @TODO: This should be an option
87
+ connection.setopt :SSL_VERIFYPEER, 0
88
+ connection.setopt :SSL_VERIFYHOST, 0
89
+
90
+ connection.setopt :ERRORBUFFER, self.error_buffer
91
+
92
+ return self
93
+ end
94
+
95
+ def connection
96
+ @connection ||= CurlFFI::Easy.new
97
+ end
98
+
99
+ def error_buffer
100
+ @error_buffer ||= FFI::MemoryPointer.new(:char, CurlFFI::ERROR_SIZE, :clear)
101
+ end
102
+ alias :error :error_buffer
103
+
104
+ def request_headers
105
+ @request_headers ||= FFI::MemoryPointer.from_string("")
106
+ end
107
+
108
+ def response_body
109
+ @response_body ||= ""
110
+ end
111
+ alias :response :response_body
112
+ alias :body :response_body
113
+
114
+ def response_header
115
+ @response_header ||= ""
116
+ end
117
+ alias :headers :response_header
118
+
119
+ def set_write_handler(_callback=:default_write_callback)
120
+ connection.setopt(:WRITEFUNCTION, FFI::Function.new(:size_t, [:pointer, :size_t, :size_t,], &self.__method__(_callback)))
121
+ end
122
+
123
+ def set_header_handler(_callback=:default_header_callback)
124
+ connection.setopt(:HEADERFUNCTION, FFI::Function.new(:size_t, [:pointer, :size_t, :size_t], &self.__method__(_callback)))
125
+ end
126
+
127
+ def default_write_callback(string_ptr, size, nmemb)
128
+ length = size * nmemb
129
+ response_body << string_ptr.read_string(length)
130
+
131
+ return length
132
+ end
133
+
134
+ def default_header_callback(string_ptr, size, nmemb)
135
+ length = size * nmemb
136
+ response_header << string_ptr.read_string(length)
137
+
138
+ return length
139
+ end
140
+
141
+ def custom_write_callback(string_ptr, size, nmemb)
142
+ length = size * nmemb
143
+ @custom_write_handler.call(string_ptr.read_string(length))
144
+
145
+ return length
146
+ end
147
+
148
+ def custom_header_callback(string_ptr, size, nmemb)
149
+ length = size * nmemb
150
+ @custom_header_handler.call(string_ptr.read_string(length))
151
+
152
+ return length
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,112 @@
1
+ module StreamlyFFI
2
+ class Connection
3
+
4
+ attr_accessor :connection
5
+
6
+ def initialize
7
+ self.connection = StreamlyFFI::PersistentRequest.new
8
+ end
9
+
10
+ # A helper method to make HEAD requests a dead-simple one-liner
11
+ #
12
+ # Example:
13
+ # Streamly.head("www.somehost.com/some_resource/1")
14
+ #
15
+ # Streamly.head("www.somehost.com/some_resource/1") do |header_chunk|
16
+ # # do something with _header_chunk_
17
+ # end
18
+ #
19
+ # Parameters:
20
+ # +url+ should be a String, the url to request
21
+ # +headers+ should be a Hash and is optional
22
+ #
23
+ # This method also accepts a block, which will stream the response headers in chunks to the caller
24
+ def head(url, headers=nil, &block)
25
+ opts = {:method => :head, :url => url, :headers => headers}
26
+ opts[:response_header_handler] = block if block_given?
27
+ self.connection.execute(opts)
28
+ end
29
+
30
+ # A helper method to make HEAD requests a dead-simple one-liner
31
+ #
32
+ # Example:
33
+ # Streamly.get("www.somehost.com/some_resource/1")
34
+ #
35
+ # Streamly.get("www.somehost.com/some_resource/1") do |chunk|
36
+ # # do something with _chunk_
37
+ # end
38
+ #
39
+ # Parameters:
40
+ # +url+ should be a String, the url to request
41
+ # +headers+ should be a Hash and is optional
42
+ #
43
+ # This method also accepts a block, which will stream the response body in chunks to the caller
44
+ def get(url, headers=nil, &block)
45
+ opts = {:method => :get, :url => url, :headers => headers}
46
+ opts[:response_body_handler] = block if block_given?
47
+ self.connection.execute(opts)
48
+ end
49
+
50
+ # A helper method to make HEAD requests a dead-simple one-liner
51
+ #
52
+ # Example:
53
+ # Streamly.post("www.somehost.com/some_resource", "asset[id]=2&asset[name]=bar")
54
+ #
55
+ # Streamly.post("www.somehost.com/some_resource", "asset[id]=2&asset[name]=bar") do |chunk|
56
+ # # do something with _chunk_
57
+ # end
58
+ #
59
+ # Parameters:
60
+ # +url+ should be a String (the url to request) and is required
61
+ # +payload+ should be a String and is required
62
+ # +headers+ should be a Hash and is optional
63
+ #
64
+ # This method also accepts a block, which will stream the response body in chunks to the caller
65
+ def post(url, payload, headers=nil, &block)
66
+ opts = {:method => :post, :url => url, :payload => payload, :headers => headers}
67
+ opts[:response_body_handler] = block if block_given?
68
+ self.connection.execute(opts)
69
+ end
70
+
71
+ # A helper method to make HEAD requests a dead-simple one-liner
72
+ #
73
+ # Example:
74
+ # Streamly.put("www.somehost.com/some_resource/1", "asset[name]=foo")
75
+ #
76
+ # Streamly.put("www.somehost.com/some_resource/1", "asset[name]=foo") do |chunk|
77
+ # # do something with _chunk_
78
+ # end
79
+ #
80
+ # Parameters:
81
+ # +url+ should be a String (the url to request) and is required
82
+ # +payload+ should be a String and is required
83
+ # +headers+ should be a Hash and is optional
84
+ #
85
+ # This method also accepts a block, which will stream the response body in chunks to the caller
86
+ def put(url, payload, headers=nil, &block)
87
+ opts = {:method => :put, :url => url, :payload => payload, :headers => headers}
88
+ opts[:response_body_handler] = block if block_given?
89
+ self.connection.execute(opts)
90
+ end
91
+
92
+ # A helper method to make HEAD requests a dead-simple one-liner
93
+ #
94
+ # Example:
95
+ # Streamly.delete("www.somehost.com/some_resource/1")
96
+ #
97
+ # Streamly.delete("www.somehost.com/some_resource/1") do |chunk|
98
+ # # do something with _chunk_
99
+ # end
100
+ #
101
+ # Parameters:
102
+ # +url+ should be a String, the url to request
103
+ # +headers+ should be a Hash and is optional
104
+ #
105
+ # This method also accepts a block, which will stream the response body in chunks to the caller
106
+ def delete(url, headers={}, &block)
107
+ opts = {:method => :delete, :url => url, :headers => headers}
108
+ opts[:response_body_handler] = block if block_given?
109
+ self.connection.execute(opts)
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,15 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ require "curl_ffi"
5
+
6
+ module StreamlyFFI
7
+ class PersistentRequest
8
+ include StreamlyFFI::Base
9
+
10
+ def [](_sym)
11
+ send _sym
12
+ end
13
+
14
+ end
15
+ end
@@ -7,157 +7,14 @@ require "singleton"
7
7
  module StreamlyFFI
8
8
  class Request
9
9
  include Singleton
10
+ include StreamlyFFI::Base
10
11
 
11
- alias __method__ method
12
-
13
- attr_reader :url, :method
14
-
15
- # @TODO: Argumenting Checking + Error Handling
16
12
  def initialize(options={})
17
- url = options[:url]
18
- method = options[:method]
19
-
20
- @response_body = nil
21
- @response_header = nil
22
- @custom_header_handler = nil
23
- @custom_write_handler = nil
24
-
25
- # url should be a string that doesn't suck
26
- # method should be :post, :get, :put, :delete, :head
27
- # options should contain any of the following keys:
28
- # :headers, :response_header_handler, :response_body_handler, :payload (required if method = :post / :put)
29
-
30
- case method
31
- when :get then connection.setopt :HTTPGET, 1
32
- when :head then connection.setopt :NOBODY, 1
33
- when :post then connection.setopt :POST, 1
34
- connection.setopt :POSTFIELDS, options[:payload]
35
- connection.setopt :POSTFIELDSIZE, options[:payload].size
36
- when :put then connection.setopt :CUSTOMREQUEST, "PUT"
37
- connection.setopt :POSTFIELDS, options[:payload]
38
- connection.setopt :POSTFIELDSIZE, options[:payload].size
39
- when :delete then connection.setopt :CUSTOMREQUEST, "DELETE"
40
- # else I WILL CUT YOU
41
- end
42
-
43
- if options[:headers].is_a? Hash and options[:headers].size > 0
44
- options[:headers].each_pair do |key_and_value|
45
- request_headers = CurlFFI.slist_append(request_headers, key_and_value.join(": "))
46
- end
47
- connection.setopt :HTTPHEADER, request_headers
48
- end
49
-
50
- @custom_header_handler = options[:response_header_handler] if options.has_key?(:response_header_handler)
51
- @custom_write_handler = options[:response_body_handler] if options.has_key?(:response_body_handler)
52
-
53
- default_header_handler
54
- default_write_handler
55
-
56
-
57
- connection.setopt :ENCODING, "identity, deflate, gzip" unless method == :head
58
- connection.setopt :URL, url
59
-
60
- # Other common options (blame streamly guy)
61
- connection.setopt :FOLLOWLOCATION, 1
62
- connection.setopt :MAXREDIRS, 3
63
- # @TODO: This should be an option
64
- connection.setopt :SSL_VERIFYPEER, 0
65
- connection.setopt :SSL_VERIFYHOST, 0
66
-
67
- connection.setopt :ERRORBUFFER, error_buffer
68
-
69
- return self # I am a terrible hack. I should abandon the singleton
70
- end
71
-
72
- def connection
73
- @connection ||= CurlFFI::Easy.new
74
- end
75
-
76
- def error_buffer
77
- @error_buffer ||= FFI::MemoryPointer.new(:char, CurlFFI::ERROR_SIZE, :clear)
13
+ self.set_options(options)
78
14
  end
79
-
80
- def request_headers
81
- @request_headers ||= FFI::MemoryPointer.from_string("")
82
- end
83
-
84
- def response_body
85
- @response_body ||= ""
86
- end
87
-
88
- def response_header
89
- @response_header ||= ""
90
- end
91
-
92
- def execute
93
- status = connection.perform
94
-
95
- # @TODO: Intelligent error stuff
96
- # raise Streamly::Error if status
97
15
 
98
- CurlFFI.slist_free_all(@request_headers) if @request_headers
99
-
100
- @connection.reset
101
- end
102
-
103
16
  def self.execute(options={})
104
- request = new(options)
105
-
106
- request.execute
107
-
108
- return nil if(options.has_key?(:response_header_handler) or options.has_key?(:response_body_handler))
109
-
110
- resp = if(options[:method] == :head && request.response_header.respond_to?(:to_str))
111
- request.response_header
112
- elsif(request.response_body.is_a?(String))
113
- request.response_body
114
- else
115
- nil
116
- end
117
-
118
- return resp
119
- end
120
-
121
- def default_write_handler
122
- connection.setopt(:WRITEFUNCTION, FFI::Function.new(:size_t, [:pointer, :size_t, :size_t,], &self.__method__(:write_callback)))
123
- end
124
-
125
- def default_header_handler
126
- connection.setopt(:HEADERFUNCTION, FFI::Function.new(:size_t, [:pointer, :size_t, :size_t], &self.__method__(:header_callback)))
17
+ new(options).execute
127
18
  end
128
-
129
- def write_callback(string_ptr, size, nmemb)
130
- length = size * nmemb
131
-
132
- if(@custom_write_handler)
133
- @custom_write_handler.call(string_ptr.read_string(length))
134
- else
135
- response_body << string_ptr.read_string(length)
136
- end
137
-
138
- return length
139
- end
140
-
141
- def header_callback(string_ptr, size, nmemb)
142
- length = size * nmemb
143
-
144
- if(@custom_header_handler)
145
- @custom_header_handler.call(string_ptr.read_string(length))
146
- else
147
- response_header << string_ptr.read_string(length)
148
- end
149
-
150
- return length
151
- end
152
-
153
- # streamly's .c internal methods:
154
- # @TODO: header_handler
155
- # @TODO: data_handler
156
- # @TODO: each_http_header
157
- # @TODO: select_error
158
- # @TODO: rb_streamly_new
159
- # @TODO: rb_streamly_init
160
- # @TODO: nogvl_perform
161
- # @TODO: rb_streamly_execute
162
19
  end
163
20
  end
@@ -1,3 +1,3 @@
1
1
  module StreamlyFFI
2
- VERSION = "0.1.6"
2
+ VERSION = "0.2.0"
3
3
  end
data/streamly_ffi.gemspec CHANGED
@@ -18,7 +18,6 @@ Gem::Specification.new do |s|
18
18
  s.email = %q{seniorlopez@gmail.com}
19
19
  s.homepage = %q{http://github.com/aitrus/streamly_ffi}
20
20
 
21
- s.platform = Gem::Platform::RUBY
22
21
  s.add_dependency "curl_ffi"
23
22
  s.add_development_dependency "rspec"
24
23
 
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 1
8
- - 6
9
- version: 0.1.6
7
+ - 2
8
+ - 0
9
+ version: 0.2.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - Brian Lopez
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-11-09 00:00:00 -08:00
18
+ date: 2010-11-10 00:00:00 -08:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -56,6 +56,8 @@ files:
56
56
  - .gitignore
57
57
  - .rspec
58
58
  - CHANGELOG.md
59
+ - Gemfile
60
+ - Gemfile.lock
59
61
  - MIT-LICENSE
60
62
  - README.rdoc
61
63
  - Rakefile
@@ -76,6 +78,9 @@ files:
76
78
  - ext/streamly.c
77
79
  - ext/streamly.h
78
80
  - lib/streamly_ffi.rb
81
+ - lib/streamly_ffi/base.rb
82
+ - lib/streamly_ffi/connection.rb
83
+ - lib/streamly_ffi/persistent_request.rb
79
84
  - lib/streamly_ffi/request.rb
80
85
  - lib/streamly_ffi/version.rb
81
86
  - spec/server.rb