curl_ffi 0.0.3 → 0.0.5

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