raptor-io 0.0.1
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.
- checksums.yaml +15 -0
- data/LICENSE +30 -0
- data/README.md +51 -0
- data/lib/rack/handler/raptor-io.rb +130 -0
- data/lib/raptor-io.rb +11 -0
- data/lib/raptor-io/error.rb +19 -0
- data/lib/raptor-io/protocol.rb +6 -0
- data/lib/raptor-io/protocol/error.rb +10 -0
- data/lib/raptor-io/protocol/http.rb +34 -0
- data/lib/raptor-io/protocol/http/client.rb +685 -0
- data/lib/raptor-io/protocol/http/error.rb +16 -0
- data/lib/raptor-io/protocol/http/headers.rb +132 -0
- data/lib/raptor-io/protocol/http/message.rb +67 -0
- data/lib/raptor-io/protocol/http/request.rb +307 -0
- data/lib/raptor-io/protocol/http/request/manipulator.rb +117 -0
- data/lib/raptor-io/protocol/http/request/manipulators.rb +217 -0
- data/lib/raptor-io/protocol/http/request/manipulators/authenticator.rb +110 -0
- data/lib/raptor-io/protocol/http/request/manipulators/authenticators/basic.rb +36 -0
- data/lib/raptor-io/protocol/http/request/manipulators/authenticators/digest.rb +135 -0
- data/lib/raptor-io/protocol/http/request/manipulators/authenticators/negotiate.rb +69 -0
- data/lib/raptor-io/protocol/http/request/manipulators/authenticators/ntlm.rb +29 -0
- data/lib/raptor-io/protocol/http/request/manipulators/redirect_follower.rb +65 -0
- data/lib/raptor-io/protocol/http/response.rb +166 -0
- data/lib/raptor-io/protocol/http/server.rb +446 -0
- data/lib/raptor-io/ruby.rb +4 -0
- data/lib/raptor-io/ruby/hash.rb +24 -0
- data/lib/raptor-io/ruby/ipaddr.rb +15 -0
- data/lib/raptor-io/ruby/openssl.rb +23 -0
- data/lib/raptor-io/ruby/string.rb +27 -0
- data/lib/raptor-io/socket.rb +175 -0
- data/lib/raptor-io/socket/comm.rb +143 -0
- data/lib/raptor-io/socket/comm/local.rb +94 -0
- data/lib/raptor-io/socket/comm/sapni.rb +75 -0
- data/lib/raptor-io/socket/comm/socks.rb +237 -0
- data/lib/raptor-io/socket/comm_chain.rb +30 -0
- data/lib/raptor-io/socket/error.rb +45 -0
- data/lib/raptor-io/socket/switch_board.rb +183 -0
- data/lib/raptor-io/socket/switch_board/route.rb +42 -0
- data/lib/raptor-io/socket/tcp.rb +231 -0
- data/lib/raptor-io/socket/tcp/ssl.rb +77 -0
- data/lib/raptor-io/socket/tcp_server.rb +16 -0
- data/lib/raptor-io/socket/tcp_server/ssl.rb +52 -0
- data/lib/raptor-io/socket/udp.rb +0 -0
- data/lib/raptor-io/version.rb +6 -0
- data/lib/tasks/yard.rake +26 -0
- data/spec/rack/handler/raptor_spec.rb +140 -0
- data/spec/raptor-io/protocol/http/client_spec.rb +671 -0
- data/spec/raptor-io/protocol/http/headers_spec.rb +189 -0
- data/spec/raptor-io/protocol/http/message_spec.rb +5 -0
- data/spec/raptor-io/protocol/http/request/manipulators/authenticator_spec.rb +193 -0
- data/spec/raptor-io/protocol/http/request/manipulators/authenticators/basic_spec.rb +32 -0
- data/spec/raptor-io/protocol/http/request/manipulators/authenticators/digest_spec.rb +76 -0
- data/spec/raptor-io/protocol/http/request/manipulators/authenticators/negotiate_spec.rb +52 -0
- data/spec/raptor-io/protocol/http/request/manipulators/authenticators/ntlm_spec.rb +37 -0
- data/spec/raptor-io/protocol/http/request/manipulators/redirect_follower_spec.rb +51 -0
- data/spec/raptor-io/protocol/http/request/manipulators_spec.rb +202 -0
- data/spec/raptor-io/protocol/http/request_spec.rb +965 -0
- data/spec/raptor-io/protocol/http/response_spec.rb +236 -0
- data/spec/raptor-io/protocol/http/server_spec.rb +345 -0
- data/spec/raptor-io/ruby/hash_spec.rb +20 -0
- data/spec/raptor-io/ruby/string_spec.rb +20 -0
- data/spec/raptor-io/socket/comm/local_spec.rb +50 -0
- data/spec/raptor-io/socket/switch_board/route_spec.rb +49 -0
- data/spec/raptor-io/socket/switch_board_spec.rb +87 -0
- data/spec/raptor-io/socket/tcp/ssl_spec.rb +18 -0
- data/spec/raptor-io/socket/tcp_server/ssl_spec.rb +59 -0
- data/spec/raptor-io/socket/tcp_server_spec.rb +19 -0
- data/spec/raptor-io/socket/tcp_spec.rb +14 -0
- data/spec/raptor-io/socket_spec.rb +16 -0
- data/spec/raptor-io/version_spec.rb +10 -0
- data/spec/spec_helper.rb +56 -0
- data/spec/support/fixtures/raptor/protocol/http/request/manipulators/manifoolators/fooer.rb +25 -0
- data/spec/support/fixtures/raptor/protocol/http/request/manipulators/niccolo_machiavelli.rb +20 -0
- data/spec/support/fixtures/raptor/protocol/http/request/manipulators/options_validator.rb +28 -0
- data/spec/support/fixtures/raptor/socket/ssl_server.crt +18 -0
- data/spec/support/fixtures/raptor/socket/ssl_server.key +15 -0
- data/spec/support/lib/path_helpers.rb +11 -0
- data/spec/support/lib/webserver_option_parser.rb +26 -0
- data/spec/support/lib/webservers.rb +120 -0
- data/spec/support/shared/contexts/with_ssl_server.rb +70 -0
- data/spec/support/shared/contexts/with_tcp_server.rb +58 -0
- data/spec/support/shared/examples/raptor/comm_examples.rb +26 -0
- data/spec/support/shared/examples/raptor/protocols/http/message.rb +106 -0
- data/spec/support/shared/examples/raptor/socket_examples.rb +135 -0
- data/spec/support/webservers/raptor/protocols/http/client.rb +100 -0
- data/spec/support/webservers/raptor/protocols/http/client_close_connection.rb +29 -0
- data/spec/support/webservers/raptor/protocols/http/client_https.rb +43 -0
- data/spec/support/webservers/raptor/protocols/http/request/manipulators/authenticators/basic.rb +9 -0
- data/spec/support/webservers/raptor/protocols/http/request/manipulators/authenticators/digest.rb +22 -0
- data/spec/support/webservers/raptor/protocols/http/request/manipulators/redirect_follower.rb +11 -0
- metadata +336 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'rack/handler/raptor-io'
|
|
3
|
+
|
|
4
|
+
class RackValidatorApp
|
|
5
|
+
def call( environment )
|
|
6
|
+
|
|
7
|
+
environment.delete( 'rack.input' ).class.should == StringIO
|
|
8
|
+
environment.delete( 'rack.errors' ).class.should == IO
|
|
9
|
+
|
|
10
|
+
request = environment.delete( 'raptor.request' )
|
|
11
|
+
|
|
12
|
+
http_version = environment.delete( 'HTTP_VERSION' )
|
|
13
|
+
|
|
14
|
+
http_version.should == 'HTTP/1.1'
|
|
15
|
+
environment.delete( 'SERVER_NAME' ).should == environment['HTTP_HOST'].split( ':' ).first
|
|
16
|
+
|
|
17
|
+
environment.dup.each do |k, v|
|
|
18
|
+
next if !k.start_with?( 'HTTP_' )
|
|
19
|
+
|
|
20
|
+
environment.delete( k ).should ==
|
|
21
|
+
request.headers[ k.gsub( 'HTTP_', '' ).gsub( '_', '-' ) ]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
environment.should == ({
|
|
25
|
+
'REQUEST_METHOD' => 'GET',
|
|
26
|
+
'SCRIPT_NAME' => '',
|
|
27
|
+
'PATH_INFO' => '/',
|
|
28
|
+
'REQUEST_PATH' => '/',
|
|
29
|
+
'QUERY_STRING' => '',
|
|
30
|
+
'SERVER_PORT' => '9292',
|
|
31
|
+
'SERVER_PROTOCOL' => http_version,
|
|
32
|
+
'REMOTE_ADDR' => '127.0.0.1',
|
|
33
|
+
'rack.version' => [ 1, 2 ],
|
|
34
|
+
'rack.multithread' => true,
|
|
35
|
+
'rack.multiprocess' => false,
|
|
36
|
+
'rack.run_once' => false,
|
|
37
|
+
'rack.url_scheme' => 'http',
|
|
38
|
+
'rack.hijack?' => false
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
[ 200, { 'Content-Type' => 'text/html' }, 'Hello Rack!' ]
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
class ErrorRackApp
|
|
46
|
+
def call( environment )
|
|
47
|
+
raise 'Stuff'
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
describe Rack::Handler::RaptorIO do
|
|
52
|
+
|
|
53
|
+
def argument_to_url( server_or_url )
|
|
54
|
+
server_or_url.is_a?( String ) ? server_or_url : server_or_url.url
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def request( server_or_url )
|
|
58
|
+
Net::HTTP.get_response( URI( argument_to_url( server_or_url ) ) )
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def run_server( app, options = {}, &block )
|
|
62
|
+
options = { switch_board: RaptorIO::Socket::SwitchBoard.new }.merge( options )
|
|
63
|
+
described_class.run( app, options, &block )
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
describe '.run' do
|
|
67
|
+
it 'starts the server' do
|
|
68
|
+
server = nil
|
|
69
|
+
|
|
70
|
+
t = Thread.new do
|
|
71
|
+
run_server( RackValidatorApp.new, Host: '0.0.0.0', Port: 9292 ) { |s| server = s }
|
|
72
|
+
end
|
|
73
|
+
sleep 1
|
|
74
|
+
|
|
75
|
+
request( server ).body.should == 'Hello Rack!'
|
|
76
|
+
|
|
77
|
+
described_class.shutdown
|
|
78
|
+
t.join
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
context 'when the Rack application raises an exception' do
|
|
82
|
+
it 'returns the error in the response body' do
|
|
83
|
+
server = nil
|
|
84
|
+
t = Thread.new do
|
|
85
|
+
run_server( ErrorRackApp.new, Host: '0.0.0.0', Port: 9292 ) { |s| server = s }
|
|
86
|
+
end
|
|
87
|
+
sleep 1
|
|
88
|
+
|
|
89
|
+
request( server ).body.should == 'Stuff (RuntimeError)'
|
|
90
|
+
|
|
91
|
+
described_class.shutdown
|
|
92
|
+
t.join
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
describe '.shutdown' do
|
|
98
|
+
it 'stops the server' do
|
|
99
|
+
server = nil
|
|
100
|
+
t = Thread.new do
|
|
101
|
+
run_server( RackValidatorApp.new, Host: '0.0.0.0', Port: 9292 ) { |s| server = s }
|
|
102
|
+
end
|
|
103
|
+
sleep 1
|
|
104
|
+
|
|
105
|
+
request( server ).body.should == 'Hello Rack!'
|
|
106
|
+
|
|
107
|
+
described_class.shutdown
|
|
108
|
+
t.join
|
|
109
|
+
|
|
110
|
+
expect { request( server ) }.to raise_error Errno::ECONNREFUSED
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
describe '.default_host' do
|
|
115
|
+
context 'by default' do
|
|
116
|
+
it 'returns localhost' do
|
|
117
|
+
ENV['RACK_ENV'] = nil
|
|
118
|
+
described_class.default_host.should == 'localhost'
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
context 'when RACK_ENV is' do
|
|
123
|
+
context 'development' do
|
|
124
|
+
it 'returns localhost' do
|
|
125
|
+
ENV['RACK_ENV'] = 'development'
|
|
126
|
+
described_class.default_host.should == 'localhost'
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
context 'else' do
|
|
131
|
+
it 'returns 0.0.0.0' do
|
|
132
|
+
ENV['RACK_ENV'] = 'stuff'
|
|
133
|
+
described_class.default_host.should == '0.0.0.0'
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
end
|
|
@@ -0,0 +1,671 @@
|
|
|
1
|
+
#coding: utf-8
|
|
2
|
+
require 'spec_helper'
|
|
3
|
+
require 'raptor-io/socket'
|
|
4
|
+
|
|
5
|
+
describe RaptorIO::Protocol::HTTP::Client do
|
|
6
|
+
|
|
7
|
+
before :all do
|
|
8
|
+
WebServers.start :client_close_connection
|
|
9
|
+
WebServers.start :client_https
|
|
10
|
+
WebServers.start :client
|
|
11
|
+
|
|
12
|
+
@url = WebServers.url_for( :client )
|
|
13
|
+
@https_url = WebServers.url_for( :client_https ).gsub( 'http', 'https' )
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
before( :each ) do
|
|
17
|
+
RaptorIO::Protocol::HTTP::Request::Manipulators.reset
|
|
18
|
+
RaptorIO::Protocol::HTTP::Request::Manipulators.library = manipulator_fixtures_path
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
let(:url) { 'http://test.com' }
|
|
22
|
+
let(:switch_board) { RaptorIO::Socket::SwitchBoard.new }
|
|
23
|
+
let(:options) { {} }
|
|
24
|
+
subject(:client) do
|
|
25
|
+
described_class.new(
|
|
26
|
+
{ switch_board: switch_board }.merge(options)
|
|
27
|
+
)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
describe '#initialize' do
|
|
31
|
+
|
|
32
|
+
describe :ssl_version do
|
|
33
|
+
let(:options) do
|
|
34
|
+
{ ssl_version: 'stuff' }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it 'sets the SSL version to use' do
|
|
38
|
+
client.ssl_version.should == 'stuff'
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
describe :ssl_verify_mode do
|
|
43
|
+
let(:options) do
|
|
44
|
+
{ ssl_verify_mode: 'stuff' }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it 'sets the SSL version to use' do
|
|
48
|
+
client.ssl_verify_mode.should == 'stuff'
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
describe :ssl_context do
|
|
53
|
+
let(:options) do
|
|
54
|
+
{ ssl_context: 'stuff' }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it 'sets the SSL version to use' do
|
|
58
|
+
client.ssl_context.should == 'stuff'
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
describe :manipulators do
|
|
63
|
+
it 'defaults to an empty Hash' do
|
|
64
|
+
client.manipulators.should == {}
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
context 'when the options are invalid' do
|
|
68
|
+
it 'raises RaptorIO::Protocol::HTTP::Request::Manipulator::Error::InvalidOptions' do
|
|
69
|
+
expect do
|
|
70
|
+
described_class.new(
|
|
71
|
+
switch_board: switch_board,
|
|
72
|
+
manipulators: {
|
|
73
|
+
options_validator: { mandatory_string: 12 }
|
|
74
|
+
}
|
|
75
|
+
)
|
|
76
|
+
end.to raise_error RaptorIO::Protocol::HTTP::Request::Manipulator::Error::InvalidOptions
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
context 'with manipulators' do
|
|
81
|
+
let(:manipulators) do
|
|
82
|
+
{ 'manifoolators/fooer' => { times: 15 } }
|
|
83
|
+
end
|
|
84
|
+
let(:options) do
|
|
85
|
+
{ manipulators: manipulators }
|
|
86
|
+
end
|
|
87
|
+
it 'sets the manipulators option' do
|
|
88
|
+
client.manipulators.should == manipulators
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
context 'when a request is queued' do
|
|
92
|
+
it 'runs the configured manipulators' do
|
|
93
|
+
request = RaptorIO::Protocol::HTTP::Request.new( url: "#{@url}/" )
|
|
94
|
+
client.queue( request )
|
|
95
|
+
request.url.should == "#{@url}/" + ('foo' * 15)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
describe :timeout do
|
|
102
|
+
context 'without a value' do
|
|
103
|
+
it 'defaults to 10' do
|
|
104
|
+
client.timeout.should == 10
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
context 'with a value' do
|
|
109
|
+
let(:options) do
|
|
110
|
+
{ timeout: 1 }
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
it 'sets the timeout option' do
|
|
114
|
+
client.timeout.should == 1
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
context 'when a timeout occurs' do
|
|
118
|
+
it 'raises RaptorIO::Error::Timeout', speed: 'slow' do
|
|
119
|
+
expect {
|
|
120
|
+
client.get( "#{@url}/sleep", mode: :sync )
|
|
121
|
+
}.to raise_error RaptorIO::Error::Timeout
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
describe :concurrency do
|
|
129
|
+
context 'without a value' do
|
|
130
|
+
it 'defaults to 20' do
|
|
131
|
+
client.concurrency.should == 20
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
context 'with a value' do
|
|
135
|
+
let(:options) do
|
|
136
|
+
{ concurrency: 10 }
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
it 'sets the request concurrency option' do
|
|
140
|
+
client.concurrency.should == 10
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
it 'sets the amount of maximum open connections at any given time', speed: 'slow' do
|
|
144
|
+
cnt = 0
|
|
145
|
+
times = 10
|
|
146
|
+
|
|
147
|
+
url = "#{@url}/sleep"
|
|
148
|
+
|
|
149
|
+
client.concurrency = 1
|
|
150
|
+
times.times do
|
|
151
|
+
client.get url do
|
|
152
|
+
cnt += 1
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
t = Time.now
|
|
157
|
+
client.run
|
|
158
|
+
runtime_1 = Time.now - t
|
|
159
|
+
cnt.should == times
|
|
160
|
+
|
|
161
|
+
cnt = 0
|
|
162
|
+
client.concurrency = 20
|
|
163
|
+
times.times do
|
|
164
|
+
client.get url do
|
|
165
|
+
cnt += 1
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
t = Time.now
|
|
170
|
+
client.run
|
|
171
|
+
runtime_2 = Time.now - t
|
|
172
|
+
|
|
173
|
+
cnt.should == times
|
|
174
|
+
runtime_1.should > runtime_2
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
describe :user_agent do
|
|
180
|
+
context 'without a value' do
|
|
181
|
+
it "defaults to 'RaptorIO::HTTP/#{RaptorIO::VERSION}'" do
|
|
182
|
+
client.user_agent.should == "RaptorIO::HTTP/#{RaptorIO::VERSION}"
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
context 'with a value' do
|
|
187
|
+
let(:ua) { 'Stuff' }
|
|
188
|
+
let(:options) do
|
|
189
|
+
{ user_agent: ua }
|
|
190
|
+
end
|
|
191
|
+
it 'sets the user-agent option' do
|
|
192
|
+
client.user_agent.should == ua
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
it 'sets the User-Agent for the requests' do
|
|
196
|
+
client.request( @url ).headers['User-Agent'].should == ua
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
describe '#update_manipulators' do
|
|
204
|
+
context 'when the options are invalid' do
|
|
205
|
+
it 'raises RaptorIO::Protocol::HTTP::Request::Manipulator::Error::InvalidOptions' do
|
|
206
|
+
expect do
|
|
207
|
+
client.update_manipulators( options_validator: { mandatory_string: 12 } )
|
|
208
|
+
end.to raise_error RaptorIO::Protocol::HTTP::Request::Manipulator::Error::InvalidOptions
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
it 'updates the client-wide manipulators' do
|
|
213
|
+
manipulators = { 'manifoolators/fooer' => { times: 16 } }
|
|
214
|
+
client.update_manipulators( manipulators )
|
|
215
|
+
client.manipulators.should == manipulators
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
describe '#datastore' do
|
|
220
|
+
it 'returns a hash' do
|
|
221
|
+
client.datastore.should == {}
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
it 'has empty hashes as default values' do
|
|
225
|
+
client.datastore['stuff'].should == {}
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
describe '#concurrency=' do
|
|
230
|
+
it 'sets the concurrency option' do
|
|
231
|
+
client.concurrency.should_not == 10
|
|
232
|
+
client.concurrency = 10
|
|
233
|
+
client.concurrency.should == 10
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
describe '#user_agent=' do
|
|
238
|
+
it 'sets the user_agent option' do
|
|
239
|
+
ua = 'stuff'
|
|
240
|
+
client.user_agent.should_not == ua
|
|
241
|
+
client.user_agent = ua
|
|
242
|
+
client.user_agent.should == ua
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
describe '#request' do
|
|
247
|
+
it 'supports SSL' do
|
|
248
|
+
res = client.get( @https_url, mode: :sync )
|
|
249
|
+
res.should be_kind_of RaptorIO::Protocol::HTTP::Response
|
|
250
|
+
res.code.should == 200
|
|
251
|
+
res.body.should == 'Stuff...'
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
it 'handles responses without body (1xx, 204, 304)' do
|
|
255
|
+
client.get( "#{@url}/204", mode: :sync ).should be_kind_of RaptorIO::Protocol::HTTP::Response
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
it 'properly transmits raw binary data' do
|
|
259
|
+
client.get( "#{@url}/echo_body",
|
|
260
|
+
raw: true,
|
|
261
|
+
mode: :sync,
|
|
262
|
+
body: "\ff\ff"
|
|
263
|
+
).body.should == "\ff\ff"
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
it 'properly transmits raw multibyte data' do
|
|
267
|
+
client.get( "#{@url}/echo_body",
|
|
268
|
+
raw: true,
|
|
269
|
+
mode: :sync,
|
|
270
|
+
body: 'τεστ'
|
|
271
|
+
).body.should == 'τεστ'
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
it 'forwards the given options to the Request object' do
|
|
275
|
+
options = { parameters: { 'name' => 'value' }}
|
|
276
|
+
client.request( '/blah/', options ).parameters.should == options[:parameters]
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
context 'when passed a block' do
|
|
280
|
+
it 'sets it as a callback' do
|
|
281
|
+
passed_response = nil
|
|
282
|
+
response = RaptorIO::Protocol::HTTP::Response.new( url: url )
|
|
283
|
+
|
|
284
|
+
request = client.request( '/blah/' ) { |res| passed_response = res }
|
|
285
|
+
request.handle_response( response )
|
|
286
|
+
passed_response.should == response
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
describe 'option' do
|
|
291
|
+
describe :manipulators do
|
|
292
|
+
context 'when the options are invalid' do
|
|
293
|
+
it 'raises RaptorIO::Protocol::HTTP::Request::Manipulator::Error::InvalidOptions' do
|
|
294
|
+
expect do
|
|
295
|
+
client.get( "#{@url}/",
|
|
296
|
+
manipulators: { options_validator: { mandatory_string: 12 } }
|
|
297
|
+
)
|
|
298
|
+
end.to raise_error RaptorIO::Protocol::HTTP::Request::Manipulator::Error::InvalidOptions
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
it 'loads and configures the given manipulators' do
|
|
303
|
+
request = client.get( "#{@url}/",
|
|
304
|
+
manipulators: {
|
|
305
|
+
'manifoolators/fooer' => { times: 10 }
|
|
306
|
+
}
|
|
307
|
+
)
|
|
308
|
+
request.url.should == "#{@url}/" + ('foo' * 10)
|
|
309
|
+
end
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
describe :cookies do
|
|
313
|
+
context Hash do
|
|
314
|
+
it 'formats and sets request cookies' do
|
|
315
|
+
client.get( "#{@url}/cookies",
|
|
316
|
+
cookies: {
|
|
317
|
+
'name' => 'value',
|
|
318
|
+
'name2' => 'value2'
|
|
319
|
+
},
|
|
320
|
+
mode: :sync
|
|
321
|
+
).body.should == 'name=value;name2=value2'
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
context String do
|
|
326
|
+
it 'sets request cookies' do
|
|
327
|
+
client.get( "#{@url}/cookies",
|
|
328
|
+
cookies: 'name=value;name2=value2',
|
|
329
|
+
mode: :sync
|
|
330
|
+
).body.should == 'name=value;name2=value2'
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
describe :continue do
|
|
337
|
+
context 'default' do
|
|
338
|
+
it 'handles responses with status "100" automatically' do
|
|
339
|
+
body = 'stuff-here'
|
|
340
|
+
client.get( "#{@url}/100",
|
|
341
|
+
headers: {
|
|
342
|
+
'Expect' => '100-continue'
|
|
343
|
+
},
|
|
344
|
+
body: body,
|
|
345
|
+
mode: :sync
|
|
346
|
+
).body.should == body
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
context true do
|
|
351
|
+
it 'handles responses with status "100" automatically' do
|
|
352
|
+
body = 'stuff-here'
|
|
353
|
+
client.get( "#{@url}/100",
|
|
354
|
+
headers: {
|
|
355
|
+
'Expect' => '100-continue'
|
|
356
|
+
},
|
|
357
|
+
body: body,
|
|
358
|
+
continue: true,
|
|
359
|
+
mode: :sync
|
|
360
|
+
).body.should == body
|
|
361
|
+
end
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
context false do
|
|
365
|
+
it 'does not handle responses with status "100" automatically' do
|
|
366
|
+
body = 'stuff-here'
|
|
367
|
+
client.get( "#{@url}/100",
|
|
368
|
+
headers: {
|
|
369
|
+
'Expect' => '100-continue'
|
|
370
|
+
},
|
|
371
|
+
body: body,
|
|
372
|
+
continue: false,
|
|
373
|
+
mode: :sync
|
|
374
|
+
).code.should == 100
|
|
375
|
+
end
|
|
376
|
+
end
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
describe :timeout do
|
|
380
|
+
it 'handles timeouts progressively and in groups', speed: 'slow' do
|
|
381
|
+
|
|
382
|
+
2.times do
|
|
383
|
+
client.get( "#{@url}/long-sleep", timeout: 20 ) do |response|
|
|
384
|
+
response.error.should be_nil
|
|
385
|
+
end
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
2.times do
|
|
389
|
+
client.get( "#{@url}/long-sleep", timeout: 2 ) do |response|
|
|
390
|
+
response.error.should be_kind_of RaptorIO::Error::Timeout
|
|
391
|
+
end
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
2.times do
|
|
395
|
+
client.get( "#{@url}/long-sleep", timeout: 3 ) do |response|
|
|
396
|
+
response.error.should be_kind_of RaptorIO::Error::Timeout
|
|
397
|
+
end
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
2.times do
|
|
401
|
+
client.get( "#{@url}/long-sleep", timeout: 7 ) do |response|
|
|
402
|
+
response.error.should be_nil
|
|
403
|
+
end
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
2.times do
|
|
407
|
+
client.get( "#{@url}/long-sleep", timeout: 10 ) do |response|
|
|
408
|
+
response.error.should be_nil
|
|
409
|
+
end
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
t = Time.now
|
|
413
|
+
client.run
|
|
414
|
+
runtime = Time.now - t
|
|
415
|
+
|
|
416
|
+
(runtime >= 5.0 || runtime < 6.0).should be_true
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
context 'when a timeout occurs' do
|
|
420
|
+
let (:options) do
|
|
421
|
+
{ timeout: 1 }
|
|
422
|
+
end
|
|
423
|
+
it 'raises RaptorIO::Error::Timeout', speed: 'slow' do
|
|
424
|
+
expect {
|
|
425
|
+
client.get( "#{@url}/sleep", mode: :sync )
|
|
426
|
+
}.to raise_error RaptorIO::Error::Timeout
|
|
427
|
+
end
|
|
428
|
+
end
|
|
429
|
+
end
|
|
430
|
+
describe :mode do
|
|
431
|
+
describe :sync do
|
|
432
|
+
it 'performs the request synchronously and returns the response' do
|
|
433
|
+
options = {
|
|
434
|
+
parameters: { 'name' => 'value' },
|
|
435
|
+
mode: :sync
|
|
436
|
+
}
|
|
437
|
+
response = client.request( @url, options )
|
|
438
|
+
response.should be_kind_of RaptorIO::Protocol::HTTP::Response
|
|
439
|
+
response.request.parameters.should == options[:parameters]
|
|
440
|
+
end
|
|
441
|
+
end
|
|
442
|
+
end
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
it 'increments the queue size' do
|
|
446
|
+
client.queue_size.should == 0
|
|
447
|
+
client.request( '/blah/' )
|
|
448
|
+
client.queue_size.should == 1
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
it 'returns the request' do
|
|
452
|
+
client.request( '/blah/' ).should be_kind_of RaptorIO::Protocol::HTTP::Request
|
|
453
|
+
end
|
|
454
|
+
|
|
455
|
+
it 'treats a closed connection as a signal for end of a response' do
|
|
456
|
+
url = WebServers.url_for( :client_close_connection )
|
|
457
|
+
client.get( url, mode: :sync ).body.should == "Success\n.\n"
|
|
458
|
+
client.get( url, mode: :sync ).body.should == "Success\n.\n"
|
|
459
|
+
client.get( url, mode: :sync ).body.should == "Success\n.\n"
|
|
460
|
+
end
|
|
461
|
+
|
|
462
|
+
describe 'Content-Encoding' do
|
|
463
|
+
it 'supports gzip' do
|
|
464
|
+
client.get( "#{@url}/gzip", mode: :sync ).body.should == 'gzip'
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
it 'supports deflate' do
|
|
468
|
+
client.get( "#{@url}/deflate", mode: :sync ).body.should == 'deflate'
|
|
469
|
+
end
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
describe 'Content-Encoding' do
|
|
473
|
+
context 'supports' do
|
|
474
|
+
it 'chunked', speed: 'slow' do
|
|
475
|
+
res = client.get( "#{@url}/chunked", mode: :sync )
|
|
476
|
+
res.body.should == "foo\nbara\rbaraf\r\n"
|
|
477
|
+
res.headers.should_not include 'Transfer-Encoding'
|
|
478
|
+
res.headers['Content-Length'].to_i.should == res.body.size
|
|
479
|
+
end
|
|
480
|
+
end
|
|
481
|
+
end
|
|
482
|
+
end
|
|
483
|
+
|
|
484
|
+
describe '#get' do
|
|
485
|
+
it 'queues a GET request' do
|
|
486
|
+
client.get( '/blah/' ).http_method.should == :get
|
|
487
|
+
end
|
|
488
|
+
end
|
|
489
|
+
|
|
490
|
+
describe '#post' do
|
|
491
|
+
it 'queues a POST request' do
|
|
492
|
+
client.post( '/blah/' ).http_method.should == :post
|
|
493
|
+
end
|
|
494
|
+
end
|
|
495
|
+
|
|
496
|
+
describe '#queue_size' do
|
|
497
|
+
it 'returns the amount of queued requests' do
|
|
498
|
+
client.queue_size.should == 0
|
|
499
|
+
10.times { client.request( '/' ) }
|
|
500
|
+
client.queue_size.should == 10
|
|
501
|
+
end
|
|
502
|
+
end
|
|
503
|
+
|
|
504
|
+
describe '#queue' do
|
|
505
|
+
it 'queues a request' do
|
|
506
|
+
client.queue_size.should == 0
|
|
507
|
+
client.queue( RaptorIO::Protocol::HTTP::Request.new( url: url ) )
|
|
508
|
+
client.queue_size.should == 1
|
|
509
|
+
end
|
|
510
|
+
it 'returns the queued request' do
|
|
511
|
+
request = RaptorIO::Protocol::HTTP::Request.new( url: url )
|
|
512
|
+
client.queue(request).should == request
|
|
513
|
+
end
|
|
514
|
+
|
|
515
|
+
describe :manipulators do
|
|
516
|
+
context 'when the options are invalid' do
|
|
517
|
+
it 'raises RaptorIO::Protocol::HTTP::Request::Manipulator::Error::InvalidOptions' do
|
|
518
|
+
expect do
|
|
519
|
+
request = RaptorIO::Protocol::HTTP::Request.new( url: "#{@url}/" )
|
|
520
|
+
client.queue( request, options_validator: { mandatory_string: 12 } )
|
|
521
|
+
end.to raise_error RaptorIO::Protocol::HTTP::Request::Manipulator::Error::InvalidOptions
|
|
522
|
+
end
|
|
523
|
+
end
|
|
524
|
+
|
|
525
|
+
it 'loads and configures the given manipulators' do
|
|
526
|
+
request = RaptorIO::Protocol::HTTP::Request.new( url: "#{@url}/" )
|
|
527
|
+
client.queue( request, 'manifoolators/fooer' => { times: 10 } )
|
|
528
|
+
request.url.should == "#{@url}/" + ('foo' * 10)
|
|
529
|
+
end
|
|
530
|
+
end
|
|
531
|
+
|
|
532
|
+
end
|
|
533
|
+
|
|
534
|
+
describe '#<<' do
|
|
535
|
+
it 'alias of #queue' do
|
|
536
|
+
client.queue_size.should == 0
|
|
537
|
+
client << RaptorIO::Protocol::HTTP::Request.new( url: url )
|
|
538
|
+
client.queue_size.should == 1
|
|
539
|
+
end
|
|
540
|
+
end
|
|
541
|
+
|
|
542
|
+
describe '#run' do
|
|
543
|
+
context 'when a request fails' do
|
|
544
|
+
context 'in asynchronous mode' do
|
|
545
|
+
context 'due to a closed port' do
|
|
546
|
+
it 'passes the callback an empty response' do
|
|
547
|
+
url = 'http://localhost:9696969'
|
|
548
|
+
|
|
549
|
+
response = nil
|
|
550
|
+
client.get( url ){ |r| response = r }
|
|
551
|
+
client.run
|
|
552
|
+
|
|
553
|
+
response.version.should == '1.1'
|
|
554
|
+
response.code.should == 0
|
|
555
|
+
response.message.should be_nil
|
|
556
|
+
response.body.should be_nil
|
|
557
|
+
response.headers.should == {}
|
|
558
|
+
end
|
|
559
|
+
|
|
560
|
+
it 'assigns RaptorIO::Socket::Error::ConnectionError to #error' do
|
|
561
|
+
url = 'http://localhost:9696969'
|
|
562
|
+
|
|
563
|
+
response = nil
|
|
564
|
+
client.get( url ){ |r| response = r }
|
|
565
|
+
client.run
|
|
566
|
+
|
|
567
|
+
response.error.should be_kind_of RaptorIO::Socket::Error::ConnectionError
|
|
568
|
+
end
|
|
569
|
+
|
|
570
|
+
end
|
|
571
|
+
|
|
572
|
+
context 'due to an invalid IP address' do
|
|
573
|
+
it 'passes the callback an empty response', speed: 'slow' do
|
|
574
|
+
url = 'http://10.11.12.13'
|
|
575
|
+
|
|
576
|
+
response = nil
|
|
577
|
+
client.get( url ){ |r| response = r }
|
|
578
|
+
client.run
|
|
579
|
+
|
|
580
|
+
response.version.should == '1.1'
|
|
581
|
+
response.code.should == 0
|
|
582
|
+
response.message.should be_nil
|
|
583
|
+
response.body.should be_nil
|
|
584
|
+
response.headers.should == {}
|
|
585
|
+
end
|
|
586
|
+
|
|
587
|
+
it 'assigns RaptorIO::Socket::Error::ConnectionError to #error', speed: 'slow' do
|
|
588
|
+
url = 'http://10.11.12.13'
|
|
589
|
+
|
|
590
|
+
response = nil
|
|
591
|
+
client.get( url ){ |r| response = r }
|
|
592
|
+
client.run
|
|
593
|
+
|
|
594
|
+
response.error.should be_kind_of RaptorIO::Socket::Error::ConnectionError
|
|
595
|
+
end
|
|
596
|
+
end
|
|
597
|
+
|
|
598
|
+
context 'due to an invalid hostname' do
|
|
599
|
+
it 'passes the callback an empty response' do
|
|
600
|
+
url = 'http://stuffhereblahblahblah'
|
|
601
|
+
|
|
602
|
+
response = nil
|
|
603
|
+
client.get( url ){ |r| response = r }
|
|
604
|
+
client.run
|
|
605
|
+
|
|
606
|
+
response.version.should == '1.1'
|
|
607
|
+
response.code.should == 0
|
|
608
|
+
response.message.should be_nil
|
|
609
|
+
response.body.should be_nil
|
|
610
|
+
response.headers.should == {}
|
|
611
|
+
end
|
|
612
|
+
|
|
613
|
+
it 'assigns RaptorIO::Socket::Error::CouldNotResolve to #error' do
|
|
614
|
+
url = 'http://stuffhereblahblahblah'
|
|
615
|
+
|
|
616
|
+
response = nil
|
|
617
|
+
client.get( url ){ |r| response = r }
|
|
618
|
+
client.run
|
|
619
|
+
|
|
620
|
+
response.error.should be_kind_of RaptorIO::Socket::Error::CouldNotResolve
|
|
621
|
+
end
|
|
622
|
+
end
|
|
623
|
+
end
|
|
624
|
+
|
|
625
|
+
context 'in synchronous mode' do
|
|
626
|
+
context 'due to a closed port' do
|
|
627
|
+
it 'raises RaptorIO::Socket::Error::ConnectionRefused' do
|
|
628
|
+
expect {
|
|
629
|
+
client.get( 'http://localhost:858589', mode: :sync )
|
|
630
|
+
}.to raise_error RaptorIO::Socket::Error::ConnectionRefused
|
|
631
|
+
end
|
|
632
|
+
end
|
|
633
|
+
|
|
634
|
+
context 'due to an invalid address' do
|
|
635
|
+
it 'raises RaptorIO::Socket::Error::CouldNotResolve' do
|
|
636
|
+
expect {
|
|
637
|
+
client.get( 'http://stuffhereblahblahblah', mode: :sync )
|
|
638
|
+
}.to raise_error RaptorIO::Socket::Error::CouldNotResolve
|
|
639
|
+
end
|
|
640
|
+
end
|
|
641
|
+
end
|
|
642
|
+
|
|
643
|
+
end
|
|
644
|
+
|
|
645
|
+
it 'runs all the queued requests' do
|
|
646
|
+
cnt = 0
|
|
647
|
+
times = 2
|
|
648
|
+
|
|
649
|
+
times.times do
|
|
650
|
+
client.get @url do |r|
|
|
651
|
+
cnt += 1
|
|
652
|
+
end
|
|
653
|
+
end
|
|
654
|
+
|
|
655
|
+
client.run
|
|
656
|
+
cnt.should == times
|
|
657
|
+
end
|
|
658
|
+
|
|
659
|
+
it 'runs requests queued via callbacks' do
|
|
660
|
+
called = false
|
|
661
|
+
client.get @url do
|
|
662
|
+
client.get @url do
|
|
663
|
+
called = true
|
|
664
|
+
end
|
|
665
|
+
end
|
|
666
|
+
|
|
667
|
+
client.run
|
|
668
|
+
called.should be_true
|
|
669
|
+
end
|
|
670
|
+
end
|
|
671
|
+
end
|