ethon 0.15.0 → 0.17.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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +29 -1
  3. data/README.md +23 -0
  4. data/ethon.gemspec +6 -3
  5. data/lib/ethon/curls/classes.rb +12 -2
  6. data/lib/ethon/curls/codes.rb +3 -2
  7. data/lib/ethon/curls/options.rb +4 -3
  8. data/lib/ethon/easy/callbacks.rb +2 -1
  9. data/lib/ethon/easy/informations.rb +3 -0
  10. data/lib/ethon/easy/response_callbacks.rb +6 -1
  11. data/lib/ethon/version.rb +1 -1
  12. metadata +6 -85
  13. data/.github/workflows/ruby.yml +0 -41
  14. data/.gitignore +0 -8
  15. data/.rspec +0 -3
  16. data/Gemfile +0 -43
  17. data/Guardfile +0 -10
  18. data/Rakefile +0 -40
  19. data/profile/benchmarks.rb +0 -104
  20. data/profile/memory_leaks.rb +0 -114
  21. data/profile/perf_spec_helper.rb +0 -37
  22. data/profile/support/memory_test_helpers.rb +0 -76
  23. data/profile/support/os_memory_leak_tracker.rb +0 -48
  24. data/profile/support/ruby_object_leak_tracker.rb +0 -49
  25. data/spec/ethon/curl_spec.rb +0 -38
  26. data/spec/ethon/easy/callbacks_spec.rb +0 -59
  27. data/spec/ethon/easy/debug_info_spec.rb +0 -54
  28. data/spec/ethon/easy/features_spec.rb +0 -24
  29. data/spec/ethon/easy/form_spec.rb +0 -104
  30. data/spec/ethon/easy/header_spec.rb +0 -79
  31. data/spec/ethon/easy/http/custom_spec.rb +0 -177
  32. data/spec/ethon/easy/http/delete_spec.rb +0 -21
  33. data/spec/ethon/easy/http/get_spec.rb +0 -126
  34. data/spec/ethon/easy/http/head_spec.rb +0 -80
  35. data/spec/ethon/easy/http/options_spec.rb +0 -51
  36. data/spec/ethon/easy/http/patch_spec.rb +0 -51
  37. data/spec/ethon/easy/http/post_spec.rb +0 -317
  38. data/spec/ethon/easy/http/put_spec.rb +0 -168
  39. data/spec/ethon/easy/http_spec.rb +0 -64
  40. data/spec/ethon/easy/informations_spec.rb +0 -120
  41. data/spec/ethon/easy/mirror_spec.rb +0 -47
  42. data/spec/ethon/easy/operations_spec.rb +0 -268
  43. data/spec/ethon/easy/options_spec.rb +0 -193
  44. data/spec/ethon/easy/queryable_spec.rb +0 -235
  45. data/spec/ethon/easy/response_callbacks_spec.rb +0 -152
  46. data/spec/ethon/easy/util_spec.rb +0 -28
  47. data/spec/ethon/easy_spec.rb +0 -203
  48. data/spec/ethon/libc_spec.rb +0 -14
  49. data/spec/ethon/loggable_spec.rb +0 -22
  50. data/spec/ethon/multi/operations_spec.rb +0 -298
  51. data/spec/ethon/multi/options_spec.rb +0 -182
  52. data/spec/ethon/multi/stack_spec.rb +0 -80
  53. data/spec/ethon/multi_spec.rb +0 -152
  54. data/spec/spec_helper.rb +0 -28
  55. data/spec/support/localhost_server.rb +0 -95
  56. data/spec/support/server.rb +0 -115
@@ -1,182 +0,0 @@
1
- # frozen_string_literal: true
2
- require 'spec_helper'
3
-
4
- describe Ethon::Multi::Options do
5
- let(:multi) { Ethon::Multi.new }
6
-
7
- [
8
- :maxconnects, :pipelining, :socketdata, :socketfunction,
9
- :timerdata, :timerfunction, :max_total_connections
10
- ].each do |name|
11
- describe "#{name}=" do
12
- it "responds_to" do
13
- expect(multi).to respond_to("#{name}=")
14
- end
15
-
16
- it "sets option" do
17
- expect(Ethon::Curl).to receive(:set_option).with(name, anything, anything, anything)
18
- multi.method("#{name}=").call(1)
19
- end
20
- end
21
- end
22
-
23
- context "socket_action mode" do
24
- let(:multi) { Ethon::Multi.new(execution_mode: :socket_action) }
25
-
26
- describe "#socketfunction callbacks" do
27
- it "allows multi_code return values" do
28
- calls = []
29
- multi.socketfunction = proc do |handle, sock, what, userp, socketp|
30
- calls << what
31
- :ok
32
- end
33
-
34
- easy = Ethon::Easy.new
35
- easy.url = "http://localhost:3001/?delay=1"
36
- multi.add(easy)
37
- expect(calls).to eq([])
38
- 5.times do
39
- multi.socket_action
40
- break unless calls.empty?
41
- sleep 0.1
42
- end
43
- expect(calls.last).to eq(:in).or(eq(:out))
44
- multi.delete(easy)
45
- expect(calls.last).to eq(:remove)
46
- end
47
-
48
- it "allows integer return values (compatibility)" do
49
- called = false
50
- multi.socketfunction = proc do |handle, sock, what, userp, socketp|
51
- called = true
52
- 0
53
- end
54
-
55
- easy = Ethon::Easy.new
56
- easy.url = "http://localhost:3001/?delay=1"
57
- multi.add(easy)
58
- 5.times do
59
- multi.socket_action
60
- break if called
61
- sleep 0.1
62
- end
63
- multi.delete(easy)
64
-
65
- expect(called).to be_truthy
66
- end
67
-
68
- it "errors on invalid return codes" do
69
- called = false
70
- multi.socketfunction = proc do |handle, sock, what, userp, socketp|
71
- called = true
72
- "hi"
73
- end
74
-
75
- easy = Ethon::Easy.new
76
- easy.url = "http://localhost:3001/?delay=1"
77
- multi.add(easy)
78
- expect {
79
- 5.times do
80
- multi.socket_action
81
- break if called
82
- sleep 0.1
83
- end
84
- }.to raise_error(ArgumentError)
85
- expect { multi.delete(easy) }.to raise_error(ArgumentError)
86
- end
87
- end
88
-
89
- describe "#timerfunction callbacks" do
90
- it "allows multi_code return values" do
91
- calls = []
92
- multi.timerfunction = proc do |handle, timeout_ms, userp|
93
- calls << timeout_ms
94
- :ok
95
- end
96
-
97
- easy = Ethon::Easy.new
98
- easy.url = "http://localhost:3001/?delay=1"
99
- multi.add(easy)
100
- expect(calls.last).to be >= 0 # adds an immediate timeout
101
-
102
- multi.delete(easy)
103
- expect(calls.last).to eq(-1) # cancels the timer
104
- end
105
-
106
- it "allows integer return values (compatibility)" do
107
- called = false
108
- multi.timerfunction = proc do |handle, timeout_ms, userp|
109
- called = true
110
- 0
111
- end
112
-
113
- easy = Ethon::Easy.new
114
- easy.url = "http://localhost:3001/?delay=1"
115
- multi.add(easy)
116
- multi.socket_action
117
- multi.delete(easy)
118
-
119
- expect(called).to be_truthy
120
- end
121
-
122
- it "errors on invalid return codes" do
123
- called = false
124
- multi.timerfunction = proc do |handle, timeout_ms, userp|
125
- called = true
126
- "hi"
127
- end
128
-
129
- easy = Ethon::Easy.new
130
- easy.url = "http://localhost:3001/?delay=1"
131
- expect { multi.add(easy) }.to raise_error(ArgumentError)
132
- end
133
- end
134
- end
135
-
136
- describe "#value_for" do
137
- context "when option in bool" do
138
- context "when value true" do
139
- let(:value) { true }
140
-
141
- it "returns 1" do
142
- expect(multi.method(:value_for).call(value, :bool)).to eq(1)
143
- end
144
- end
145
-
146
- context "when value false" do
147
- let(:value) { false }
148
-
149
- it "returns 0" do
150
- expect(multi.method(:value_for).call(value, :bool)).to eq(0)
151
- end
152
- end
153
- end
154
-
155
-
156
- context "when value in int" do
157
- let(:value) { "2" }
158
-
159
- it "returns value casted to int" do
160
- expect(multi.method(:value_for).call(value, :int)).to eq(2)
161
- end
162
- end
163
-
164
- context "when value in unspecific_options" do
165
- context "when value a string" do
166
- let(:value) { "www.example.\0com" }
167
-
168
- it "returns zero byte escaped string" do
169
- expect(multi.method(:value_for).call(value, nil)).to eq("www.example.\\0com")
170
- end
171
- end
172
-
173
- context "when value not a string" do
174
- let(:value) { 1 }
175
-
176
- it "returns value" do
177
- expect(multi.method(:value_for).call(value, nil)).to eq(1)
178
- end
179
- end
180
- end
181
- end
182
- end
@@ -1,80 +0,0 @@
1
- # frozen_string_literal: true
2
- require 'spec_helper'
3
-
4
- describe Ethon::Multi::Stack do
5
- let(:multi) { Ethon::Multi.new }
6
- let(:easy) { Ethon::Easy.new }
7
-
8
- describe "#add" do
9
- context "when easy already added" do
10
- before { multi.add(easy) }
11
-
12
- it "returns nil" do
13
- expect(multi.add(easy)).to be_nil
14
- end
15
- end
16
-
17
- context "when easy new" do
18
- it "adds easy to multi" do
19
- expect(Ethon::Curl).to receive(:multi_add_handle).and_return(:ok)
20
- multi.add(easy)
21
- end
22
-
23
- it "adds easy to easy_handles" do
24
- multi.add(easy)
25
- expect(multi.easy_handles).to include(easy)
26
- end
27
- end
28
-
29
- context "when multi_add_handle fails" do
30
- it "raises multi add error" do
31
- expect(Ethon::Curl).to receive(:multi_add_handle).and_return(:bad_easy_handle)
32
- expect{ multi.add(easy) }.to raise_error(Ethon::Errors::MultiAdd)
33
- end
34
- end
35
-
36
- context "when multi cleaned up before" do
37
- it "raises multi add error" do
38
- Ethon::Curl.multi_cleanup(multi.handle)
39
- expect{ multi.add(easy) }.to raise_error(Ethon::Errors::MultiAdd)
40
- end
41
- end
42
- end
43
-
44
- describe "#delete" do
45
- context "when easy in easy_handles" do
46
- before { multi.add(easy) }
47
-
48
- it "deletes easy from multi" do
49
- expect(Ethon::Curl).to receive(:multi_remove_handle).and_return(:ok)
50
- multi.delete(easy)
51
- end
52
-
53
- it "deletes easy from easy_handles" do
54
- multi.delete(easy)
55
- expect(multi.easy_handles).to_not include(easy)
56
- end
57
- end
58
-
59
- context "when easy is not in easy_handles" do
60
- it "does nothing" do
61
- expect(Ethon::Curl).to receive(:multi_add_handle).and_return(:ok)
62
- multi.add(easy)
63
- end
64
-
65
- it "adds easy to easy_handles" do
66
- multi.add(easy)
67
- expect(multi.easy_handles).to include(easy)
68
- end
69
- end
70
-
71
- context "when multi_remove_handle fails" do
72
- before { multi.add(easy) }
73
-
74
- it "raises multi remove error" do
75
- expect(Ethon::Curl).to receive(:multi_remove_handle).and_return(:bad_easy_handle)
76
- expect{ multi.delete(easy) }.to raise_error(Ethon::Errors::MultiRemove)
77
- end
78
- end
79
- end
80
- end
@@ -1,152 +0,0 @@
1
- # frozen_string_literal: true
2
- require 'spec_helper'
3
-
4
- describe Ethon::Multi do
5
- describe ".new" do
6
- it "inits curl" do
7
- expect(Ethon::Curl).to receive(:init)
8
- Ethon::Multi.new
9
- end
10
-
11
- context "with default options" do
12
- it "allows running #perform with the default execution_mode" do
13
- Ethon::Multi.new.perform
14
- end
15
-
16
- it "refuses to run #socket_action" do
17
- expect { Ethon::Multi.new.socket_action }.to raise_error(ArgumentError)
18
- end
19
- end
20
-
21
- context "when options not empty" do
22
- context "when pipelining is set" do
23
- let(:options) { { :pipelining => true } }
24
-
25
- it "sets pipelining" do
26
- expect_any_instance_of(Ethon::Multi).to receive(:pipelining=).with(true)
27
- Ethon::Multi.new(options)
28
- end
29
- end
30
-
31
- context "when execution_mode option is :socket_action" do
32
- let(:options) { { :execution_mode => :socket_action } }
33
- let(:multi) { Ethon::Multi.new(options) }
34
-
35
- it "refuses to run #perform" do
36
- expect { multi.perform }.to raise_error(ArgumentError)
37
- end
38
-
39
- it "allows running #socket_action" do
40
- multi.socket_action
41
- end
42
- end
43
- end
44
- end
45
-
46
- describe "#socket_action" do
47
- let(:options) { { :execution_mode => :socket_action } }
48
- let(:select_state) { { :readers => [], :writers => [], :timeout => 0 } }
49
- let(:multi) {
50
- multi = Ethon::Multi.new(options)
51
- multi.timerfunction = proc do |handle, timeout_ms, userp|
52
- timeout_ms = nil if timeout_ms == -1
53
- select_state[:timeout] = timeout_ms
54
- :ok
55
- end
56
- multi.socketfunction = proc do |handle, sock, what, userp, socketp|
57
- case what
58
- when :remove
59
- select_state[:readers].delete(sock)
60
- select_state[:writers].delete(sock)
61
- when :in
62
- select_state[:readers].push(sock) unless select_state[:readers].include? sock
63
- select_state[:writers].delete(sock)
64
- when :out
65
- select_state[:readers].delete(sock)
66
- select_state[:writers].push(sock) unless select_state[:writers].include? sock
67
- when :inout
68
- select_state[:readers].push(sock) unless select_state[:readers].include? sock
69
- select_state[:writers].push(sock) unless select_state[:writers].include? sock
70
- else
71
- raise ArgumentError, "invalid value for 'what' in socketfunction callback"
72
- end
73
- :ok
74
- end
75
- multi
76
- }
77
-
78
- def fds_to_ios(fds)
79
- fds.map do |fd|
80
- IO.for_fd(fd).tap { |io| io.autoclose = false }
81
- end
82
- end
83
-
84
- def perform_socket_action_until_complete
85
- multi.socket_action # start things off
86
-
87
- while multi.ongoing?
88
- readers, writers, _ = IO.select(
89
- fds_to_ios(select_state[:readers]),
90
- fds_to_ios(select_state[:writers]),
91
- [],
92
- select_state[:timeout]
93
- )
94
-
95
- to_notify = Hash.new { |hash, key| hash[key] = [] }
96
- unless readers.nil?
97
- readers.each do |reader|
98
- to_notify[reader] << :in
99
- end
100
- end
101
- unless writers.nil?
102
- writers.each do |writer|
103
- to_notify[writer] << :out
104
- end
105
- end
106
-
107
- to_notify.each do |io, readiness|
108
- multi.socket_action(io, readiness)
109
- end
110
-
111
- # if we didn't have anything to notify, then we timed out
112
- multi.socket_action if to_notify.empty?
113
- end
114
- ensure
115
- multi.easy_handles.dup.each do |h|
116
- multi.delete(h)
117
- end
118
- end
119
-
120
- it "supports an end-to-end request" do
121
- easy = Ethon::Easy.new
122
- easy.url = "http://localhost:3001/"
123
- multi.add(easy)
124
-
125
- perform_socket_action_until_complete
126
-
127
- expect(multi.ongoing?).to eq(false)
128
- end
129
-
130
- it "supports multiple concurrent requests" do
131
- handles = []
132
- 10.times do
133
- easy = Ethon::Easy.new
134
- easy.url = "http://localhost:3001/?delay=1"
135
- multi.add(easy)
136
- handles << easy
137
- end
138
-
139
- start = Time.now
140
- perform_socket_action_until_complete
141
- duration = Time.now - start
142
-
143
- # these should have happened concurrently
144
- expect(duration).to be < 2
145
- expect(multi.ongoing?).to eq(false)
146
-
147
- handles.each do |handle|
148
- expect(handle.response_code).to eq(200)
149
- end
150
- end
151
- end
152
- end
data/spec/spec_helper.rb DELETED
@@ -1,28 +0,0 @@
1
- # frozen_string_literal: true
2
- $LOAD_PATH.unshift(File.dirname(__FILE__))
3
- $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
4
-
5
- require 'bundler'
6
- Bundler.setup
7
- require "ethon"
8
- require 'rspec'
9
-
10
- if defined? require_relative
11
- require_relative 'support/localhost_server'
12
- require_relative 'support/server'
13
- else
14
- require 'support/localhost_server'
15
- require 'support/server'
16
- end
17
-
18
- # Ethon.logger = Logger.new($stdout).tap do |log|
19
- # log.level = Logger::DEBUG
20
- # end
21
-
22
- RSpec.configure do |config|
23
- # config.order = :rand
24
-
25
- config.before(:suite) do
26
- LocalhostServer.new(TESTSERVER.new, 3001)
27
- end
28
- end
@@ -1,95 +0,0 @@
1
- # frozen_string_literal: true
2
- require 'rack'
3
- require 'rack/handler/webrick'
4
- require 'net/http'
5
-
6
- # The code for this is inspired by Capybara's server:
7
- # http://github.com/jnicklas/capybara/blob/0.3.9/lib/capybara/server.rb
8
- class LocalhostServer
9
- READY_MESSAGE = "Server ready"
10
-
11
- class Identify
12
- def initialize(app)
13
- @app = app
14
- end
15
-
16
- def call(env)
17
- if env["PATH_INFO"] == "/__identify__"
18
- [200, {}, [LocalhostServer::READY_MESSAGE]]
19
- else
20
- @app.call(env)
21
- end
22
- end
23
- end
24
-
25
- attr_reader :port
26
-
27
- def initialize(rack_app, port = nil)
28
- @port = port || find_available_port
29
- @rack_app = rack_app
30
- concurrently { boot }
31
- wait_until(10, "Boot failed.") { booted? }
32
- end
33
-
34
- private
35
-
36
- def find_available_port
37
- server = TCPServer.new('127.0.0.1', 0)
38
- server.addr[1]
39
- ensure
40
- server.close if server
41
- end
42
-
43
- def boot
44
- # Use WEBrick since it's part of the ruby standard library and is available on all ruby interpreters.
45
- options = { :Port => port }
46
- options.merge!(:AccessLog => [], :Logger => WEBrick::BasicLog.new(StringIO.new)) unless ENV['VERBOSE_SERVER']
47
- Rack::Handler::WEBrick.run(Identify.new(@rack_app), **options)
48
- end
49
-
50
- def booted?
51
- res = ::Net::HTTP.get_response("localhost", '/__identify__', port)
52
- if res.is_a?(::Net::HTTPSuccess) or res.is_a?(::Net::HTTPRedirection)
53
- return res.body == READY_MESSAGE
54
- end
55
- rescue Errno::ECONNREFUSED, Errno::EBADF
56
- return false
57
- end
58
-
59
- def concurrently
60
- if should_use_subprocess?
61
- pid = Process.fork do
62
- trap(:INT) { ::Rack::Handler::WEBrick.shutdown }
63
- yield
64
- exit # manually exit; otherwise this sub-process will re-run the specs that haven't run yet.
65
- end
66
-
67
- at_exit do
68
- Process.kill('INT', pid)
69
- begin
70
- Process.wait(pid)
71
- rescue Errno::ECHILD
72
- # ignore this error...I think it means the child process has already exited.
73
- end
74
- end
75
- else
76
- Thread.new { yield }
77
- end
78
- end
79
-
80
- def should_use_subprocess?
81
- # !ENV['THREADED']
82
- false
83
- end
84
-
85
- def wait_until(timeout, error_message, &block)
86
- start_time = Time.now
87
-
88
- while true
89
- return if yield
90
- raise TimeoutError.new(error_message) if (Time.now - start_time) > timeout
91
- sleep(0.05)
92
- end
93
- end
94
- end
95
-
@@ -1,115 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
- require 'json'
4
- require 'zlib'
5
- require 'sinatra/base'
6
-
7
- TESTSERVER = Sinatra.new do
8
- set :logging, nil
9
-
10
- fail_count = 0
11
-
12
- post '/file' do
13
- {
14
- 'content-type' => params[:file][:type],
15
- 'filename' => params[:file][:filename],
16
- 'content' => params[:file][:tempfile].read,
17
- 'request-content-type' => request.env['CONTENT_TYPE']
18
- }.to_json
19
- end
20
-
21
- get '/multiple-headers' do
22
- [200, { 'Set-Cookie' => %w[ foo bar ], 'Content-Type' => 'text/plain' }, ['']]
23
- end
24
-
25
- get '/fail/:number' do
26
- if fail_count >= params[:number].to_i
27
- "ok"
28
- else
29
- fail_count += 1
30
- error 500, "oh noes!"
31
- end
32
- end
33
-
34
- get '/fail_forever' do
35
- error 500, "oh noes!"
36
- end
37
-
38
- get '/redirect' do
39
- redirect '/'
40
- end
41
-
42
- post '/redirect' do
43
- redirect '/'
44
- end
45
-
46
- get '/bad_redirect' do
47
- redirect '/bad_redirect'
48
- end
49
-
50
- get '/auth_basic/:username/:password' do
51
- @auth ||= Rack::Auth::Basic::Request.new(request.env)
52
- # Check that we've got a basic auth, and that it's credentials match the ones
53
- # provided in the request
54
- if @auth.provided? && @auth.basic? && @auth.credentials == [ params[:username], params[:password] ]
55
- # auth is valid - confirm it
56
- true
57
- else
58
- # invalid auth - request the authentication
59
- response['WWW-Authenticate'] = %(Basic realm="Testing HTTP Auth")
60
- throw(:halt, [401, "Not authorized\n"])
61
- end
62
- end
63
-
64
- get '/auth_ntlm' do
65
- # we're just checking for the existence if NTLM auth header here. It's validation
66
- # is too troublesome and really doesn't bother is much, it's up to libcurl to make
67
- # it valid
68
- response['WWW-Authenticate'] = 'NTLM'
69
- is_ntlm_auth = /^NTLM/ =~ request.env['HTTP_AUTHORIZATION']
70
- true if is_ntlm_auth
71
- throw(:halt, [401, "Not authorized\n"]) if !is_ntlm_auth
72
- end
73
-
74
- get '/gzipped' do
75
- req_env = request.env.to_json
76
- z = Zlib::Deflate.new
77
- gzipped_env = z.deflate(req_env, Zlib::FINISH)
78
- z.close
79
- response['Content-Encoding'] = 'gzip'
80
- gzipped_env
81
- end
82
-
83
- get '/**' do
84
- sleep params["delay"].to_i if params.has_key?("delay")
85
- request.env.merge!(:body => request.body.read).to_json
86
- end
87
-
88
- head '/**' do
89
- sleep params["delay"].to_i if params.has_key?("delay")
90
- end
91
-
92
- put '/**' do
93
- request.env.merge!(:body => request.body.read).to_json
94
- end
95
-
96
- post '/**' do
97
- request.env.merge!(:body => request.body.read).to_json
98
- end
99
-
100
- delete '/**' do
101
- request.env.merge!(:body => request.body.read).to_json
102
- end
103
-
104
- patch '/**' do
105
- request.env.merge!(:body => request.body.read).to_json
106
- end
107
-
108
- options '/**' do
109
- request.env.merge!(:body => request.body.read).to_json
110
- end
111
-
112
- route 'PURGE', '/**' do
113
- request.env.merge!(:body => request.body.read).to_json
114
- end
115
- end