ethon 0.6.1 → 0.6.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -4,8 +4,15 @@ rvm:
4
4
  - 1.8.7
5
5
  - 1.9.2
6
6
  - 1.9.3
7
+ - 2.0.0
8
+ - ruby-head
9
+ - ree
7
10
  - jruby-head
8
11
  - jruby-18mode
9
12
  - jruby-19mode
10
13
  - rbx-18mode
11
14
  - rbx-19mode
15
+ matrix:
16
+ allow_failures:
17
+ - rvm: ruby-head
18
+ - rvm: jruby-head
@@ -2,7 +2,19 @@
2
2
 
3
3
  ## Master
4
4
 
5
- [Full Changelog](https://github.com/typhoeus/ethon/compare/v0.6.0...master)
5
+ [Full Changelog](https://github.com/typhoeus/ethon/compare/v0.6.2...master)
6
+
7
+ ## 0.6.2
8
+
9
+ [Full Changelog](https://github.com/typhoeus/ethon/compare/v0.6.1...v0.6.2)
10
+
11
+ The changelog entries are coming soon!
12
+
13
+ ## 0.6.1
14
+
15
+ [Full Changelog](https://github.com/typhoeus/ethon/compare/v0.6.0...v0.6.1)
16
+
17
+ The changelog entries are coming soon!
6
18
 
7
19
  ## 0.6.0
8
20
 
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Ethon [![Build Status](https://secure.travis-ci.org/typhoeus/ethon.png?branch=master)](http://travis-ci.org/typhoeus/ethon)
1
+ # Ethon [![Build Status](https://secure.travis-ci.org/typhoeus/ethon.png?branch=master)](http://travis-ci.org/typhoeus/ethon) [![Gem Version](https://badge.fury.io/rb/ethon.png)](http://badge.fury.io/rb/ethon)
2
2
 
3
3
  In Greek mythology, Ethon, the son of Typhoeus and Echidna, is a gigantic eagle. So much for the history.
4
4
  In the modern world, Ethon is a very basic libcurl wrapper using ffi.
@@ -90,3 +90,7 @@ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
90
90
  OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
91
91
  ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
92
92
  OTHER DEALINGS IN THE SOFTWARE.
93
+
94
+
95
+ [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/typhoeus/ethon/trend.png)](https://bitdeli.com/free "Bitdeli Badge")
96
+
@@ -15,7 +15,7 @@ Gem::Specification.new do |s|
15
15
  s.description = "Very lightweight libcurl wrapper."
16
16
 
17
17
  s.required_rubygems_version = ">= 1.3.6"
18
- s.rubyforge_project = '[none]'
18
+ s.license = 'MIT'
19
19
 
20
20
  s.add_dependency('ffi', ['>= 1.3.0'])
21
21
  s.add_dependency('mime-types', ['~> 1.18'])
@@ -105,7 +105,8 @@ module Ethon
105
105
  # @return [ String ] The info.
106
106
  def get_info_string(option, handle)
107
107
  if easy_getinfo(handle, option, string_ptr) == :ok
108
- string_ptr.read_pointer.read_string
108
+ ptr=string_ptr.read_pointer
109
+ ptr.null? ? nil : ptr.read_string
109
110
  end
110
111
  end
111
112
 
@@ -10,7 +10,7 @@ module Ethon
10
10
  raise NameError, "Ethon::Curls::Options unknown type #{type}." unless respond_to?("#{type.to_s.downcase}_options")
11
11
  opthash=send("#{type.to_s.downcase}_options")
12
12
  raise Errors::InvalidOption.new(option) unless opthash.include?(option)
13
-
13
+
14
14
  case opthash[option][:type]
15
15
  when :none
16
16
  return if value.nil?
@@ -78,7 +78,7 @@ module Ethon
78
78
 
79
79
  send("#{type}_setopt_#{func}", handle, opthash[option][:opt], value)
80
80
  end
81
-
81
+
82
82
  OPTION_TYPE_BASE = {
83
83
  :long => 0,
84
84
  :objectpoint => 10000,
@@ -125,7 +125,7 @@ module Ethon
125
125
  elsif not opts.is_a? Hash then
126
126
  raise TypeError, "Ethon::Curls::Options #{ftype} #{name} Expected opts to be an Array or a Hash."
127
127
  end
128
-
128
+
129
129
  when :bitmask
130
130
  if opts.is_a? Array then
131
131
  if opts.last.is_a? Hash then
@@ -151,14 +151,14 @@ module Ethon
151
151
  opts[k]=v.map { |b| opts[b] }.inject :|
152
152
  end
153
153
  end
154
-
154
+
155
155
  when :buffer
156
156
  raise TypeError, "Ethon::Curls::Options #{ftype} #{name} Expected opts to be an Array or a Hash." unless opts.is_a? Integer
157
-
157
+
158
158
  else
159
159
  raise ArgumentError, "Ethon::Curls::Options #{ftype} #{name} Expected no opts." unless opts.nil?
160
160
  end
161
-
161
+
162
162
  opthash=const_get("#{ftype.to_s.upcase}_OPTIONS")
163
163
  opthash[name]={:type=>type, :opt=>OPTION_TYPE_BASE[OPTION_TYPE_MAP[type]]+num, :opts=>opts}
164
164
  end
@@ -178,12 +178,12 @@ module Ethon
178
178
  end
179
179
  >
180
180
  end
181
-
181
+
182
182
  # Curl multi options, refer
183
183
  # Defined @ https://github.com/bagder/curl/blob/master/include/curl/multi.h
184
184
  # Documentation @ http://curl.haxx.se/libcurl/c/curl_multi_setopt.html
185
185
  option_type :multi
186
-
186
+
187
187
  option :multi, :socketfunction, :callback, 1
188
188
  option :multi, :socketdata, :cbdata, 2
189
189
  option :multi, :pipelining, :bool, 3
@@ -280,7 +280,7 @@ module Ethon
280
280
  option :easy, :username, :string, 173
281
281
  option :easy, :password, :string, 174
282
282
  option :easy, :proxyusername, :string, 175
283
- option :easy, :proxypassword, :string, 176
283
+ option :easy, :proxypassword, :string, 176
284
284
  option :easy, :httpauth, :bitmask, 107, [:none, :basic, :digest, :gssnegotiate, :ntlm, :digest_ie, :ntlm_wb, {:only => 1<<31, :any => ~0x10, :anysafe => ~0x11, :auto => 0x1f}]
285
285
  option :easy, :tlsauth_type, :enum, 206, [:none, :srp]
286
286
  option :easy, :tlsauth_username, :string, 204
@@ -248,6 +248,8 @@ module Ethon
248
248
  @url = nil
249
249
  @hash = nil
250
250
  @on_complete = nil
251
+ @on_headers = nil
252
+ @on_body = nil
251
253
  @procs = nil
252
254
  @mirror = nil
253
255
  Curl.easy_reset(handle)
@@ -24,6 +24,7 @@ module Ethon
24
24
  Curl.set_option(:debugfunction, debug_callback, handle)
25
25
  @response_body = ""
26
26
  @response_headers = ""
27
+ @headers_called = false
27
28
  @debug_info = Ethon::Easy::DebugInfo.new
28
29
  end
29
30
 
@@ -35,7 +36,13 @@ module Ethon
35
36
  # @return [ Proc ] The callback.
36
37
  def body_write_callback
37
38
  @body_write_callback ||= proc {|stream, size, num, object|
38
- @response_body << stream.read_string(size * num)
39
+ unless @headers_called
40
+ @headers_called = true
41
+ headers
42
+ end
43
+ if :unyielded == body(chunk = stream.read_string(size * num))
44
+ @response_body << chunk
45
+ end
39
46
  size * num
40
47
  }
41
48
  end
@@ -22,6 +22,28 @@ module Ethon
22
22
  # #=> []
23
23
  module ResponseCallbacks
24
24
 
25
+ # Set on_headers callback.
26
+ #
27
+ # @example Set on_headers.
28
+ # request.on_headers { p "yay" }
29
+ #
30
+ # @param [ Block ] block The block to execute.
31
+ def on_headers(&block)
32
+ @on_headers ||= []
33
+ @on_headers << block if block_given?
34
+ @on_headers
35
+ end
36
+
37
+ # Execute on_headers callbacks.
38
+ #
39
+ # @example Execute on_headers.
40
+ # request.headers
41
+ def headers
42
+ if defined?(@on_headers) and not @on_headers.nil?
43
+ @on_headers.each{ |callback| callback.call(self) }
44
+ end
45
+ end
46
+
25
47
  # Set on_complete callback.
26
48
  #
27
49
  # @example Set on_complete.
@@ -39,10 +61,36 @@ module Ethon
39
61
  # @example Execute on_completes.
40
62
  # request.complete
41
63
  def complete
42
- if @on_complete
64
+ if defined?(@on_complete) and not @on_complete.nil?
43
65
  @on_complete.each{ |callback| callback.call(self) }
44
66
  end
45
67
  end
68
+
69
+ # Set on_body callback.
70
+ #
71
+ # @example Set on_body.
72
+ # request.on_body { |chunk| p "yay" }
73
+ #
74
+ # @param [ Block ] block The block to execute.
75
+ def on_body(&block)
76
+ @on_body ||= []
77
+ @on_body << block if block_given?
78
+ @on_body
79
+ end
80
+
81
+ # Execute on_body callbacks.
82
+ #
83
+ # @example Execute on_body.
84
+ # request.body("This data came from HTTP.")
85
+ #
86
+ # @return [ Object ] If there are no on_body callbacks, returns the symbol :unyielded.
87
+ def body(chunk)
88
+ if defined?(@on_body) and not @on_body.nil?
89
+ @on_body.each{ |callback| callback.call(chunk, self) }
90
+ else
91
+ :unyielded
92
+ end
93
+ end
46
94
  end
47
95
  end
48
96
  end
@@ -14,7 +14,7 @@ module Ethon
14
14
  #
15
15
  # @return [ void ]
16
16
  def maxconnects=(value)
17
- Curl.set_option(:maxconnects, value_for(value, :int), handle)
17
+ Curl.set_option(:maxconnects, value_for(value, :int), handle, :multi)
18
18
  end
19
19
 
20
20
  # Sets pipelining option.
@@ -26,7 +26,7 @@ module Ethon
26
26
  #
27
27
  # @return [ void ]
28
28
  def pipelining=(value)
29
- Curl.set_option(:pipelining, value_for(value, :bool), handle)
29
+ Curl.set_option(:pipelining, value_for(value, :bool), handle, :multi)
30
30
  end
31
31
 
32
32
  # Sets socketdata option.
@@ -38,7 +38,7 @@ module Ethon
38
38
  #
39
39
  # @return [ void ]
40
40
  def socketdata=(value)
41
- Curl.set_option(:socketdata, value_for(value, :string), handle)
41
+ Curl.set_option(:socketdata, value_for(value, :string), handle, :multi)
42
42
  end
43
43
 
44
44
  # Sets socketfunction option.
@@ -50,7 +50,7 @@ module Ethon
50
50
  #
51
51
  # @return [ void ]
52
52
  def socketfunction=(value)
53
- Curl.set_option(:socketfunction, value_for(value, :string), handle)
53
+ Curl.set_option(:socketfunction, value_for(value, :string), handle, :multi)
54
54
  end
55
55
 
56
56
  # Sets timerdata option.
@@ -62,7 +62,7 @@ module Ethon
62
62
  #
63
63
  # @return [ void ]
64
64
  def timerdata=(value)
65
- Curl.set_option(:timerdata, value_for(value, :string), handle)
65
+ Curl.set_option(:timerdata, value_for(value, :string), handle, :multi)
66
66
  end
67
67
 
68
68
  # Sets timerfunction option.
@@ -74,7 +74,7 @@ module Ethon
74
74
  #
75
75
  # @return [ void ]
76
76
  def timerfunction=(value)
77
- Curl.set_option(:timerfunction, value_for(value, :string), handle)
77
+ Curl.set_option(:timerfunction, value_for(value, :string), handle, :multi)
78
78
  end
79
79
 
80
80
  private
@@ -1,5 +1,5 @@
1
1
  module Ethon
2
2
 
3
3
  # Ethon version.
4
- VERSION = '0.6.1'
4
+ VERSION = '0.6.2'
5
5
  end
@@ -31,6 +31,25 @@ describe Ethon::Easy::Http do
31
31
  easy.perform
32
32
  expect(easy.response_body).to include("\"REQUEST_METHOD\":\"#{action.to_s.upcase}\"")
33
33
  end
34
+
35
+ it "streams the response body from the #{action.to_s.upcase} request" do
36
+ bytes_read = 0
37
+ easy.on_body { |chunk, response| bytes_read += chunk.bytesize }
38
+ easy.http_request(url, action, options)
39
+ easy.perform
40
+ content_length = ((easy.response_headers =~ /Content-Length: (\d+)/) && $1.to_i)
41
+ expect(bytes_read).to eq(content_length)
42
+ expect(easy.response_body).to eq("")
43
+ end
44
+
45
+ it "notifies when headers are ready" do
46
+ headers = []
47
+ easy.on_headers { |r| headers << r.response_headers }
48
+ easy.http_request(url, action, options)
49
+ easy.perform
50
+ expect(headers).to eq([easy.response_headers])
51
+ expect(headers.first).to match(/Content-Length: (\d+)/)
52
+ end
34
53
  end
35
54
  end
36
55
 
@@ -3,29 +3,31 @@ require 'spec_helper'
3
3
  describe Ethon::Easy::ResponseCallbacks do
4
4
  let(:easy) { Ethon::Easy.new }
5
5
 
6
- describe "#on_complete" do
7
- it "responds" do
8
- expect(easy).to respond_to(:on_complete)
9
- end
6
+ [:on_complete, :on_headers, :on_body].each do |callback_type|
7
+ describe "##{callback_type}" do
8
+ it "responds" do
9
+ expect(easy).to respond_to("#{callback_type}")
10
+ end
10
11
 
11
- context "when no block given" do
12
- it "returns @on_complete" do
13
- expect(easy.on_complete).to eq([])
12
+ context "when no block given" do
13
+ it "returns @#{callback_type}" do
14
+ expect(easy.send("#{callback_type}")).to eq([])
15
+ end
14
16
  end
15
- end
16
17
 
17
- context "when block given" do
18
- it "stores" do
19
- easy.on_complete { p 1 }
20
- expect(easy.instance_variable_get(:@on_complete)).to have(1).items
18
+ context "when block given" do
19
+ it "stores" do
20
+ easy.send(callback_type) { p 1 }
21
+ expect(easy.instance_variable_get("@#{callback_type}")).to have(1).items
22
+ end
21
23
  end
22
- end
23
24
 
24
- context "when multiple blocks given" do
25
- it "stores" do
26
- easy.on_complete { p 1 }
27
- easy.on_complete { p 2 }
28
- expect(easy.instance_variable_get(:@on_complete)).to have(2).items
25
+ context "when multiple blocks given" do
26
+ it "stores" do
27
+ easy.send(callback_type) { p 1 }
28
+ easy.send(callback_type) { p 2 }
29
+ expect(easy.instance_variable_get("@#{callback_type}")).to have(2).items
30
+ end
29
31
  end
30
32
  end
31
33
  end
@@ -47,4 +49,47 @@ describe Ethon::Easy::ResponseCallbacks do
47
49
  end
48
50
  end
49
51
  end
52
+
53
+ describe "#headers" do
54
+ before do
55
+ easy.on_headers {|r| String.new(r.url) }
56
+ end
57
+
58
+ it "executes blocks and passes self" do
59
+ String.should_receive(:new).with(easy.url)
60
+ easy.headers
61
+ end
62
+
63
+ context "when @on_headers nil" do
64
+ it "doesn't raise" do
65
+ easy.instance_variable_set(:@on_headers, nil)
66
+ expect{ easy.headers }.to_not raise_error(NoMethodError)
67
+ end
68
+ end
69
+ end
70
+
71
+ describe "#body" do
72
+ before do
73
+ @chunk = nil
74
+ @r = nil
75
+ easy.on_body { |chunk, r| @chunk = chunk ; @r = r }
76
+ end
77
+
78
+ it "executes blocks and passes self" do
79
+ easy.body("the chunk")
80
+ expect(@r).to be(easy)
81
+ end
82
+
83
+ it "executes blocks and passes chunk" do
84
+ easy.body("the chunk")
85
+ expect(@chunk).to eq("the chunk")
86
+ end
87
+
88
+ context "when @on_body nil" do
89
+ it "doesn't raise" do
90
+ easy.instance_variable_set(:@on_body, nil)
91
+ expect{ easy.body("the chunk") }.to_not raise_error(NoMethodError)
92
+ end
93
+ end
94
+ end
50
95
  end
@@ -82,6 +82,18 @@ describe Ethon::Easy do
82
82
  easy.reset
83
83
  expect(easy.on_complete).to be_empty
84
84
  end
85
+
86
+ it "resets on_headers" do
87
+ easy.on_headers { p 1 }
88
+ easy.reset
89
+ expect(easy.on_headers).to be_empty
90
+ end
91
+
92
+ it "resets on_body" do
93
+ easy.on_body { p 1 }
94
+ easy.reset
95
+ expect(easy.on_body).to be_empty
96
+ end
85
97
  end
86
98
 
87
99
  describe "#mirror" do
metadata CHANGED
@@ -1,18 +1,20 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ethon
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.1
4
+ version: 0.6.2
5
+ prerelease:
5
6
  platform: ruby
6
7
  authors:
7
8
  - Hans Hasselberg
8
9
  autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
11
- date: 2013-08-22 00:00:00.000000000 Z
12
+ date: 2013-12-19 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
15
  name: ffi
15
16
  requirement: !ruby/object:Gem::Requirement
17
+ none: false
16
18
  requirements:
17
19
  - - ! '>='
18
20
  - !ruby/object:Gem::Version
@@ -20,6 +22,7 @@ dependencies:
20
22
  type: :runtime
21
23
  prerelease: false
22
24
  version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
23
26
  requirements:
24
27
  - - ! '>='
25
28
  - !ruby/object:Gem::Version
@@ -27,6 +30,7 @@ dependencies:
27
30
  - !ruby/object:Gem::Dependency
28
31
  name: mime-types
29
32
  requirement: !ruby/object:Gem::Requirement
33
+ none: false
30
34
  requirements:
31
35
  - - ~>
32
36
  - !ruby/object:Gem::Version
@@ -34,6 +38,7 @@ dependencies:
34
38
  type: :runtime
35
39
  prerelease: false
36
40
  version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
37
42
  requirements:
38
43
  - - ~>
39
44
  - !ruby/object:Gem::Version
@@ -146,27 +151,32 @@ files:
146
151
  - spec/support/localhost_server.rb
147
152
  - spec/support/server.rb
148
153
  homepage: https://github.com/typhoeus/ethon
149
- licenses: []
150
- metadata: {}
154
+ licenses:
155
+ - MIT
151
156
  post_install_message:
152
157
  rdoc_options: []
153
158
  require_paths:
154
159
  - lib
155
160
  required_ruby_version: !ruby/object:Gem::Requirement
161
+ none: false
156
162
  requirements:
157
163
  - - ! '>='
158
164
  - !ruby/object:Gem::Version
159
165
  version: '0'
166
+ segments:
167
+ - 0
168
+ hash: 1717729668424310423
160
169
  required_rubygems_version: !ruby/object:Gem::Requirement
170
+ none: false
161
171
  requirements:
162
172
  - - ! '>='
163
173
  - !ruby/object:Gem::Version
164
174
  version: 1.3.6
165
175
  requirements: []
166
- rubyforge_project: ! '[none]'
167
- rubygems_version: 2.0.3
176
+ rubyforge_project:
177
+ rubygems_version: 1.8.23
168
178
  signing_key:
169
- specification_version: 4
179
+ specification_version: 3
170
180
  summary: Libcurl wrapper.
171
181
  test_files:
172
182
  - spec/ethon/curl_spec.rb
checksums.yaml DELETED
@@ -1,15 +0,0 @@
1
- ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- N2ZhYTYzYzNiNDAwMzdkYzk5YTcxYzEyMmNkZTI1ODliMGM4ZTViZg==
5
- data.tar.gz: !binary |-
6
- YzYwYmM2OTAzM2UyOTQ5YjMzNTRkNTc3NDE5ZjdiMTNmMGY3MmYxZg==
7
- !binary "U0hBNTEy":
8
- metadata.gz: !binary |-
9
- ZDJmYzExYjM4N2NkODQxMWE2YTMzNjAyNjg0MzFlMDFiZDI1ZDFiYzFlMWU2
10
- ZDU5ZjM5YTNlZDk4NTJkYTMxNzYwMzgwZDY1MWFkOTA1Y2FhNDJhZmVlMzVm
11
- NGMwODBlOTE0YjUyNjhjMTdiMDdmNDM2M2E5ZDQxNDdlOWMwOTc=
12
- data.tar.gz: !binary |-
13
- YmVkMmY3NjFkZTY0YTUzOWYyZGU2YjRlMjgwNDk4ZDkzMjk0MzI0ZTYxNWRh
14
- MDU3ZDliMGQ0ZTAwYTg0YmQ0NGVjNTJmZjM2YzNmZDg0MGY3NWU3MzMzYjdi
15
- YTIxMmQxMmI0OGY0N2ExYzAyOWY4YTU0YThmYWY0MWZlYmUzZWI=