curl_ffi 0.0.3 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- curl_ffi (0.0.2)
4
+ curl_ffi (0.0.4)
5
5
  ffi
6
6
 
7
7
  GEM
data/Rakefile CHANGED
@@ -13,7 +13,6 @@ task :gem => :gemspec
13
13
  desc %{Build the gemspec file.}
14
14
  task :gemspec do
15
15
  gemspec.validate
16
- File.open("#{gemspec.name}.gemspec", 'w'){|f| f.write gemspec.to_ruby }
17
16
  end
18
17
 
19
18
  desc %{Release the gem to RubyGems.org}
data/curl_ffi.gemspec CHANGED
@@ -1,34 +1,24 @@
1
1
  # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "curl_ffi/version"
2
4
 
3
5
  Gem::Specification.new do |s|
4
- s.name = %q{curl_ffi}
5
- s.version = "0.0.3"
6
+ s.name = "curl_ffi"
7
+ s.version = CurlFFI::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Arthur Schreiber", "Scott Gonyea"]
10
+ s.email = ["schreiber.arthur@gmail.com"]
11
+ s.homepage = "http://github.com/nokarma/curl-ffi"
12
+ s.summary = "An FFI based libCurl interface"
13
+ s.description = "An FFI based libCurl interface, intended to serve as a common backend for existing interfaces to libcurl"
6
14
 
7
- s.required_rubygems_version = Gem::Requirement.new(">= 1.3.6") if s.respond_to? :required_rubygems_version=
8
- s.authors = ["Arthur Schreiber", "Scott Gonyea"]
9
- s.date = %q{2010-11-06}
10
- s.description = %q{An FFI based libCurl interface, intended to serve as a common backend for existing interfaces to libcurl}
11
- s.email = ["schreiber.arthur@gmail.com"]
12
- s.files = [".gitignore", "Gemfile", "Gemfile.lock", "Rakefile", "curl_ffi.gemspec", "examples/evented_multi.rb", "examples/perform_multi.rb", "examples/select_multi.rb", "lib/bindings.rb", "lib/curl_ffi.rb", "lib/curl_ffi/easy.rb", "lib/curl_ffi/multi.rb", "lib/curl_ffi/version.rb", "spec/curl_ffi/easy_spec.rb", "spec/curl_ffi/multi_spec.rb", "spec/spec_helper.rb"]
13
- s.homepage = %q{http://github.com/nokarma/curl-ffi}
14
- s.require_paths = ["lib"]
15
- s.rubyforge_project = %q{curl-ffi}
16
- s.rubygems_version = %q{1.3.7}
17
- s.summary = %q{An FFI based libCurl interface}
15
+ s.required_rubygems_version = ">= 1.3.6"
16
+ s.rubyforge_project = "curl-ffi"
18
17
 
19
- if s.respond_to? :specification_version then
20
- current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
21
- s.specification_version = 3
18
+ s.add_dependency "ffi"
19
+ s.add_development_dependency "rspec"
22
20
 
23
- if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
24
- s.add_runtime_dependency(%q<ffi>, [">= 0"])
25
- s.add_development_dependency(%q<rspec>, [">= 0"])
26
- else
27
- s.add_dependency(%q<ffi>, [">= 0"])
28
- s.add_dependency(%q<rspec>, [">= 0"])
29
- end
30
- else
31
- s.add_dependency(%q<ffi>, [">= 0"])
32
- s.add_dependency(%q<rspec>, [">= 0"])
33
- end
21
+ s.files = `git ls-files`.split("\n")
22
+ s.test_files = `git ls-files -- {spec}/*`.split("\n")
23
+ s.require_paths = ["lib"]
34
24
  end
data/lib/bindings.rb CHANGED
@@ -14,6 +14,8 @@ module CurlFFI
14
14
  SOCKET_BAD = -1
15
15
  end
16
16
 
17
+ ERROR_SIZE = 256 + 1
18
+
17
19
  SOCKET_TIMEOUT = SOCKET_BAD
18
20
 
19
21
  OPTION_LONG = 0
@@ -889,9 +891,6 @@ module CurlFFI
889
891
  :LOCAL_PORT, INFO_LONG + 42
890
892
  ]
891
893
 
892
-
893
-
894
-
895
894
  class MessageData < FFI::Union
896
895
  layout :whatever, :pointer,
897
896
  :result, :code
@@ -905,6 +904,10 @@ module CurlFFI
905
904
 
906
905
  # Returns a char * - needs to be freed manually using curl_free
907
906
  attach_function :easy_escape, :curl_easy_escape, [:pointer, :string, :int], :pointer
907
+ # Returns a char * - needs to be freed manually using curl_free
908
+ attach_function :easy_unescape, :curl_easy_unescape, [:pointer, :string, :int, :pointer], :pointer
909
+
910
+
908
911
  attach_function :easy_init, :curl_easy_init, [], :pointer
909
912
  attach_function :easy_cleanup, :curl_easy_cleanup, [:pointer], :void
910
913
  attach_function :easy_duphandle, :curl_easy_duphandle, [:pointer], :pointer
@@ -919,6 +922,13 @@ module CurlFFI
919
922
  attach_function :easy_setopt_string, :curl_easy_setopt, [:pointer, :option, :string], :code
920
923
  attach_function :easy_setopt_pointer, :curl_easy_setopt, [:pointer, :option, :pointer], :code
921
924
  attach_function :easy_setopt_curl_off_t, :curl_easy_setopt, [:pointer, :option, :curl_off_t], :code
925
+ attach_function :easy_strerror, :curl_easy_strerror, [:code], :string
926
+
927
+ callback :handler_function, [:string, :size_t, :size_t, :pointer], :size_t
928
+ attach_function :easy_setopt_handler_function, :curl_easy_setopt, [:pointer, :option, :handler_function], :code
929
+
930
+ callback :handler_string, [:string, :size_t, :size_t, :string], :size_t
931
+ attach_function :easy_setopt_handler_string, :curl_easy_setopt, [:pointer, :option, :handler_string], :code
922
932
 
923
933
  def self.easy_setopt(handle, option, value)
924
934
  option = OPTION[option] if option.is_a?(Symbol)
@@ -938,10 +948,15 @@ module CurlFFI
938
948
  end
939
949
  end
940
950
 
941
- attach_function :easy_strerror, :curl_easy_strerror, [:code], :string
951
+ # @TODO: checking to ensure that value is a proc?
952
+ def self.easy_setopt_handler(handle, option, value)
953
+ option = OPTION[option] if option.is_a?(Symbol)
954
+
955
+ if option >= OPTION_FUNCTIONPOINT
956
+ self.easy_setopt_handler_function(handle, option, value)
957
+ end
958
+ end
942
959
 
943
- # Returns a char * that has to be freed using curl_free
944
- attach_function :easy_unescape, :curl_easy_unescape, [:pointer, :string, :int, :pointer], :pointer
945
960
  attach_function :multi_add_handle, :curl_multi_add_handle, [:pointer, :pointer], :multi_code
946
961
  attach_function :multi_assign, :curl_multi_assign, [:pointer, :curl_socket_t, :pointer], :multi_code
947
962
  attach_function :multi_cleanup, :curl_multi_cleanup, [:pointer], :void
@@ -956,6 +971,10 @@ module CurlFFI
956
971
  attach_function :multi_setopt_pointer, :curl_multi_setopt, [:pointer, :multi_option, :pointer], :multi_code
957
972
  attach_function :multi_setopt_curl_off_t, :curl_multi_setopt, [:pointer, :multi_option, :curl_off_t], :multi_code
958
973
 
974
+ attach_function :multi_socket_action, :curl_multi_socket_action, [:pointer, :curl_socket_t, :int, :pointer], :multi_code
975
+ attach_function :multi_strerror, :curl_multi_strerror, [:multi_code], :string
976
+ attach_function :multi_timeout, :curl_multi_timeout, [:pointer, :pointer], :multi_code
977
+
959
978
  def self.multi_setopt(handle, option, value)
960
979
  option = MULTI_OPTION[option] if option.is_a?(Symbol)
961
980
 
@@ -974,10 +993,6 @@ module CurlFFI
974
993
  end
975
994
  end
976
995
 
977
- attach_function :multi_socket_action, :curl_multi_socket_action, [:pointer, :curl_socket_t, :int, :pointer], :multi_code
978
- attach_function :multi_strerror, :curl_multi_strerror, [:multi_code], :string
979
- attach_function :multi_timeout, :curl_multi_timeout, [:pointer, :pointer], :multi_code
980
-
981
996
  attach_function :free, :curl_free, [:pointer], :void
982
997
 
983
998
  attach_function :slist_append, :curl_slist_append, [:pointer, :string], :pointer
data/lib/curl_ffi.rb CHANGED
@@ -1,3 +1,5 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
1
3
 
2
4
  require "ffi"
3
5
  require "bindings"
data/lib/curl_ffi/easy.rb CHANGED
@@ -2,6 +2,7 @@ require "curl_ffi"
2
2
 
3
3
  module CurlFFI
4
4
  class Easy
5
+
5
6
  attr_reader :pointer
6
7
 
7
8
  def initialize
@@ -43,6 +44,14 @@ module CurlFFI
43
44
  check_code(CurlFFI.easy_setopt(@pointer, option, value))
44
45
  end
45
46
 
47
+ def setopt_handler(option, value)
48
+ check_code(CurlFFI.easy_setopt_handler(@pointer, option, value))
49
+ end
50
+
51
+ def setopt_str_handler(option, value)
52
+ check_code(CurlFFI.easy_setopt_handler_string(@pointer, option, value))
53
+ end
54
+
46
55
  def getinfo(info)
47
56
  info = INFO[info] if info.is_a?(Symbol)
48
57
 
@@ -59,9 +68,11 @@ module CurlFFI
59
68
 
60
69
  protected
61
70
  def check_code(result)
71
+
62
72
  if result != :OK
63
- raise "Error - #{result}"
73
+ raise "Error - #{result}" unless result.nil?
64
74
  end
75
+ return result
65
76
  end
66
77
 
67
78
  def getinfo_double(info)
@@ -2,6 +2,7 @@ require "curl_ffi"
2
2
 
3
3
  module CurlFFI
4
4
  class Multi
5
+
5
6
  attr_reader :pointer, :running
6
7
 
7
8
  def initialize
@@ -1,3 +1,3 @@
1
1
  module CurlFFI
2
- VERSION = "0.0.3"
2
+ VERSION = "0.0.5"
3
3
  end
data/lib/streamly.rb ADDED
@@ -0,0 +1,124 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ require "ffi"
5
+ require "curl_ffi"
6
+
7
+ module Streamly
8
+ extend FFI::Library
9
+
10
+ autoload :Request, "streamly/request"
11
+
12
+ class Error < StandardError; end
13
+ class UnsupportedProtocol < StandardError; end
14
+ class URLFormatError < StandardError; end
15
+ class HostResolutionError < StandardError; end
16
+ class ConnectionFailed < StandardError; end
17
+ class PartialFileError < StandardError; end
18
+ class TimeoutError < StandardError; end
19
+ class TooManyRedirects < StandardError; end
20
+
21
+ # A helper method to make HEAD requests a dead-simple one-liner
22
+ #
23
+ # Example:
24
+ # Streamly.head("www.somehost.com/some_resource/1")
25
+ #
26
+ # Streamly.head("www.somehost.com/some_resource/1") do |header_chunk|
27
+ # # do something with _header_chunk_
28
+ # end
29
+ #
30
+ # Parameters:
31
+ # +url+ should be a String, the url to request
32
+ # +headers+ should be a Hash and is optional
33
+ #
34
+ # This method also accepts a block, which will stream the response headers in chunks to the caller
35
+ def self.head(url, headers=nil, &block)
36
+ opts = {:method => :head, :url => url, :headers => headers}
37
+ opts.merge!({:response_header_handler => block}) if block_given?
38
+ Request.execute(opts)
39
+ end
40
+
41
+ # A helper method to make HEAD requests a dead-simple one-liner
42
+ #
43
+ # Example:
44
+ # Streamly.get("www.somehost.com/some_resource/1")
45
+ #
46
+ # Streamly.get("www.somehost.com/some_resource/1") do |chunk|
47
+ # # do something with _chunk_
48
+ # end
49
+ #
50
+ # Parameters:
51
+ # +url+ should be a String, the url to request
52
+ # +headers+ should be a Hash and is optional
53
+ #
54
+ # This method also accepts a block, which will stream the response body in chunks to the caller
55
+ def self.get(url, headers=nil, &block)
56
+ opts = {:headers => headers}
57
+ opts.merge!({:response_body_handler => block}) if block_given?
58
+ Request.execute(url, :get, opts)
59
+ end
60
+
61
+ # A helper method to make HEAD requests a dead-simple one-liner
62
+ #
63
+ # Example:
64
+ # Streamly.post("www.somehost.com/some_resource", "asset[id]=2&asset[name]=bar")
65
+ #
66
+ # Streamly.post("www.somehost.com/some_resource", "asset[id]=2&asset[name]=bar") do |chunk|
67
+ # # do something with _chunk_
68
+ # end
69
+ #
70
+ # Parameters:
71
+ # +url+ should be a String (the url to request) and is required
72
+ # +payload+ should be a String and is required
73
+ # +headers+ should be a Hash and is optional
74
+ #
75
+ # This method also accepts a block, which will stream the response body in chunks to the caller
76
+ def self.post(url, payload, headers=nil, &block)
77
+ opts = {:payload => payload, :headers => headers}
78
+ opts.merge!({:response_body_handler => block}) if block_given?
79
+ Request.execute(url, :post, opts)
80
+ end
81
+
82
+ # A helper method to make HEAD requests a dead-simple one-liner
83
+ #
84
+ # Example:
85
+ # Streamly.put("www.somehost.com/some_resource/1", "asset[name]=foo")
86
+ #
87
+ # Streamly.put("www.somehost.com/some_resource/1", "asset[name]=foo") do |chunk|
88
+ # # do something with _chunk_
89
+ # end
90
+ #
91
+ # Parameters:
92
+ # +url+ should be a String (the url to request) and is required
93
+ # +payload+ should be a String and is required
94
+ # +headers+ should be a Hash and is optional
95
+ #
96
+ # This method also accepts a block, which will stream the response body in chunks to the caller
97
+ def self.put(url, payload, headers=nil, &block)
98
+ opts = {:payload => payload, :headers => headers}
99
+ opts.merge!({:response_body_handler => block}) if block_given?
100
+ Request.execute(url, :put, opts)
101
+ end
102
+
103
+ # A helper method to make HEAD requests a dead-simple one-liner
104
+ #
105
+ # Example:
106
+ # Streamly.delete("www.somehost.com/some_resource/1")
107
+ #
108
+ # Streamly.delete("www.somehost.com/some_resource/1") do |chunk|
109
+ # # do something with _chunk_
110
+ # end
111
+ #
112
+ # Parameters:
113
+ # +url+ should be a String, the url to request
114
+ # +headers+ should be a Hash and is optional
115
+ #
116
+ # This method also accepts a block, which will stream the response body in chunks to the caller
117
+ def self.delete(url, headers={}, &block)
118
+ opts = {:method => :delete, :url => url, :headers => headers}
119
+ opts.merge!({:response_body_handler => block}) if block_given?
120
+ Request.execute(opts)
121
+ end
122
+ end
123
+
124
+ # require "streamly/request" # May need to do this? Not sure how autoload works with FFI yet
@@ -0,0 +1,136 @@
1
+ require "ffi"
2
+ require "singleton"
3
+
4
+ module Streamly
5
+ class Request
6
+ include Singleton
7
+
8
+ attr_reader :response_header_handler, :response_body_handler
9
+
10
+ CallHandler = Proc.new do |stream, size, nmemb, handler|
11
+ handler.call(stream)
12
+ size * nmemb
13
+ end
14
+
15
+ StringHandler = Proc.new do |stream, size, nmemb, handler|
16
+ if handler.nil?
17
+ handler = stream.clone
18
+ else
19
+ handler << stream
20
+ end
21
+ size * nmemb
22
+ end
23
+
24
+ DataHandler = Proc.new do |stream, size, nmemb, handler|
25
+ case handler
26
+ when String then handler << stream
27
+ else handler.call(stream)
28
+ end
29
+ size * nmemb
30
+ end
31
+
32
+ # @TODO: Argumenting Checking + Error Handling
33
+ def initialize(url, method=:get, options={})
34
+ # url should be a string that doesn't suck
35
+ # method should be :post, :get, :put, :delete, :head
36
+ # options should contain any of the following keys:
37
+ # :headers, :response_header_handler, :response_body_handler, :payload (required if method = :post / :put)
38
+
39
+ # @response_header_handler ||= options[:response_header_handler] || FFI::MemoryPointer.from_string("")
40
+ # @response_body_handler ||= options[:response_body_handler] || FFI::MemoryPointer.from_string("")
41
+
42
+ case method
43
+ when :get then connection.setopt :HTTPGET, 1
44
+ when :head then connection.setopt :NOBODY, 1
45
+ when :post then connection.setopt :POST, 1
46
+ connection.setopt :POSTFIELDS, options[:payload]
47
+ connection.setopt :POSTFIELDSIZE, options[:payload].size
48
+ when :put then connection.setopt :CUSTOMREQUEST, "PUT"
49
+ connection.setopt :POSTFIELDS, options[:payload]
50
+ connection.setopt :POSTFIELDSIZE, options[:payload].size
51
+ when :delete then connection.setopt :CUSTOMREQUEST, "DELETE"
52
+ # else I WILL CUT YOU
53
+ end
54
+
55
+ if options[:headers].is_a? Hash and options[:headers].size > 0
56
+ options[:headers].each_pair do |key_and_value|
57
+ @request_headers = CurlFFI.slist_append(request_headers, key_and_value.join(": "))
58
+ end
59
+ connection.setopt :HTTPHEADER, @request_headers
60
+ end
61
+
62
+ if options[:response_header_handler].nil?
63
+ # @response_header_handler = FFI::MemoryPointer.from_string("")
64
+ @response_header_handler = FFI::MemoryPointer.new(:pointer)
65
+ connection.setopt_str_handler :HEADERFUNCTION, StringHandler
66
+ connection.setopt_str_handler :WRITEHEADER, @response_header_handler
67
+ else
68
+ @response_header_handler = options[:response_header_handler]
69
+ connection.setopt_handler :HEADERFUNCTION, CallHandler
70
+ connection.setopt_handler :WRITEHEADER, @response_header_handler
71
+ end
72
+
73
+ unless method == :head
74
+ connection.setopt :ENCODING, "identity, deflate, gzip"
75
+
76
+ if options[:response_body_handler].nil?
77
+ # @response_body_handler = FFI::MemoryPointer.from_string("")
78
+ @response_body_handler = FFI::MemoryPointer.new(:pointer)
79
+ connection.setopt_str_handler :WRITEFUNCTION, StringHandler
80
+ connection.setopt_str_handler :FILE, @response_body_handler
81
+ else
82
+ @response_body_handler = options[:response_body_handler]
83
+ connection.setopt_handler :WRITEFUNCTION, CallHandler
84
+ connection.setopt_handler :FILE, @response_body_handler
85
+ end
86
+ end
87
+
88
+ connection.setopt :URL, FFI::MemoryPointer.from_string(url)
89
+
90
+ # Other common options (blame streamly guy)
91
+ connection.setopt :FOLLOWLOCATION, 1
92
+ connection.setopt :MAXREDIRS, 3
93
+
94
+ # This should be an option
95
+ connection.setopt :SSL_VERIFYPEER, 0
96
+ connection.setopt :SSL_VERIFYHOST, 0
97
+
98
+ connection.setopt :ERRORBUFFER, error_buffer
99
+
100
+ return self
101
+ end
102
+
103
+ def connection
104
+ @connection ||= CurlFFI::Easy.new
105
+ end
106
+
107
+ def error_buffer
108
+ @error_buffer ||= FFI::MemoryPointer.new(:char, CurlFFI::ERROR_SIZE, :clear)
109
+ end
110
+
111
+ def request_headers
112
+ @request_headers ||= FFI::MemoryPointer.from_string("")
113
+ end
114
+ =begin
115
+
116
+ =end
117
+ def execute
118
+ connection.perform
119
+ return response_body_handler
120
+ end
121
+
122
+ def self.execute(url, method=:get, options={})
123
+ new(url, method, options).execute
124
+ end
125
+
126
+ # streamly's .c internal methods:
127
+ # @TODO: header_handler
128
+ # @TODO: data_handler
129
+ # @TODO: each_http_header
130
+ # @TODO: select_error
131
+ # @TODO: rb_streamly_new
132
+ # @TODO: rb_streamly_init
133
+ # @TODO: nogvl_perform
134
+ # @TODO: rb_streamly_execute
135
+ end
136
+ end
@@ -26,7 +26,7 @@ describe CurlFFI::Multi do
26
26
  end
27
27
 
28
28
  it "should return CurlFFI::Message objects when messages are available" do
29
- @easy.setopt(CurlFFI::OPTION[:URL], "http://google.de")
29
+ @easy.setopt(CurlFFI::OPTION[:URL], "http://www.google.de/")
30
30
  @multi.add_handle(@easy)
31
31
 
32
32
  @multi.perform while @multi.running != 0
@@ -42,7 +42,7 @@ describe CurlFFI::Multi do
42
42
  end
43
43
 
44
44
  it "should return an array of CurlFFI::Message objects when messages are available" do
45
- @easy.setopt(CurlFFI::OPTION[:URL], "http://google.de")
45
+ @easy.setopt(CurlFFI::OPTION[:URL], "http://www.google.de/")
46
46
  @multi.add_handle(@easy)
47
47
 
48
48
  @multi.perform while @multi.running != 0
@@ -60,7 +60,7 @@ describe CurlFFI::Multi do
60
60
  end
61
61
 
62
62
  it "should return the timeout till the next call to #perform" do
63
- @easy.setopt(CurlFFI::OPTION[:URL], "http://google.de")
63
+ @easy.setopt(CurlFFI::OPTION[:URL], "http://www.google.de/")
64
64
  @multi.add_handle(@easy)
65
65
 
66
66
  @multi.timeout.should == 1
@@ -71,7 +71,7 @@ describe CurlFFI::Multi do
71
71
 
72
72
  describe "#perform" do
73
73
  before :each do
74
- @easy.setopt(CurlFFI::OPTION[:URL], "http://google.de")
74
+ @easy.setopt(CurlFFI::OPTION[:URL], "http://www.google.de/")
75
75
  @multi.add_handle(@easy)
76
76
  end
77
77
 
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 0
8
- - 3
9
- version: 0.0.3
8
+ - 5
9
+ version: 0.0.5
10
10
  platform: ruby
11
11
  authors:
12
12
  - Arthur Schreiber
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-11-06 00:00:00 -07:00
18
+ date: 2010-11-08 00:00:00 -08:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -67,6 +67,8 @@ files:
67
67
  - lib/curl_ffi/easy.rb
68
68
  - lib/curl_ffi/multi.rb
69
69
  - lib/curl_ffi/version.rb
70
+ - lib/streamly.rb
71
+ - lib/streamly/request.rb
70
72
  - spec/curl_ffi/easy_spec.rb
71
73
  - spec/curl_ffi/multi_spec.rb
72
74
  - spec/spec_helper.rb