streamly_ffi 0.1.6 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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