raptor-io 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. checksums.yaml +15 -0
  2. data/LICENSE +30 -0
  3. data/README.md +51 -0
  4. data/lib/rack/handler/raptor-io.rb +130 -0
  5. data/lib/raptor-io.rb +11 -0
  6. data/lib/raptor-io/error.rb +19 -0
  7. data/lib/raptor-io/protocol.rb +6 -0
  8. data/lib/raptor-io/protocol/error.rb +10 -0
  9. data/lib/raptor-io/protocol/http.rb +34 -0
  10. data/lib/raptor-io/protocol/http/client.rb +685 -0
  11. data/lib/raptor-io/protocol/http/error.rb +16 -0
  12. data/lib/raptor-io/protocol/http/headers.rb +132 -0
  13. data/lib/raptor-io/protocol/http/message.rb +67 -0
  14. data/lib/raptor-io/protocol/http/request.rb +307 -0
  15. data/lib/raptor-io/protocol/http/request/manipulator.rb +117 -0
  16. data/lib/raptor-io/protocol/http/request/manipulators.rb +217 -0
  17. data/lib/raptor-io/protocol/http/request/manipulators/authenticator.rb +110 -0
  18. data/lib/raptor-io/protocol/http/request/manipulators/authenticators/basic.rb +36 -0
  19. data/lib/raptor-io/protocol/http/request/manipulators/authenticators/digest.rb +135 -0
  20. data/lib/raptor-io/protocol/http/request/manipulators/authenticators/negotiate.rb +69 -0
  21. data/lib/raptor-io/protocol/http/request/manipulators/authenticators/ntlm.rb +29 -0
  22. data/lib/raptor-io/protocol/http/request/manipulators/redirect_follower.rb +65 -0
  23. data/lib/raptor-io/protocol/http/response.rb +166 -0
  24. data/lib/raptor-io/protocol/http/server.rb +446 -0
  25. data/lib/raptor-io/ruby.rb +4 -0
  26. data/lib/raptor-io/ruby/hash.rb +24 -0
  27. data/lib/raptor-io/ruby/ipaddr.rb +15 -0
  28. data/lib/raptor-io/ruby/openssl.rb +23 -0
  29. data/lib/raptor-io/ruby/string.rb +27 -0
  30. data/lib/raptor-io/socket.rb +175 -0
  31. data/lib/raptor-io/socket/comm.rb +143 -0
  32. data/lib/raptor-io/socket/comm/local.rb +94 -0
  33. data/lib/raptor-io/socket/comm/sapni.rb +75 -0
  34. data/lib/raptor-io/socket/comm/socks.rb +237 -0
  35. data/lib/raptor-io/socket/comm_chain.rb +30 -0
  36. data/lib/raptor-io/socket/error.rb +45 -0
  37. data/lib/raptor-io/socket/switch_board.rb +183 -0
  38. data/lib/raptor-io/socket/switch_board/route.rb +42 -0
  39. data/lib/raptor-io/socket/tcp.rb +231 -0
  40. data/lib/raptor-io/socket/tcp/ssl.rb +77 -0
  41. data/lib/raptor-io/socket/tcp_server.rb +16 -0
  42. data/lib/raptor-io/socket/tcp_server/ssl.rb +52 -0
  43. data/lib/raptor-io/socket/udp.rb +0 -0
  44. data/lib/raptor-io/version.rb +6 -0
  45. data/lib/tasks/yard.rake +26 -0
  46. data/spec/rack/handler/raptor_spec.rb +140 -0
  47. data/spec/raptor-io/protocol/http/client_spec.rb +671 -0
  48. data/spec/raptor-io/protocol/http/headers_spec.rb +189 -0
  49. data/spec/raptor-io/protocol/http/message_spec.rb +5 -0
  50. data/spec/raptor-io/protocol/http/request/manipulators/authenticator_spec.rb +193 -0
  51. data/spec/raptor-io/protocol/http/request/manipulators/authenticators/basic_spec.rb +32 -0
  52. data/spec/raptor-io/protocol/http/request/manipulators/authenticators/digest_spec.rb +76 -0
  53. data/spec/raptor-io/protocol/http/request/manipulators/authenticators/negotiate_spec.rb +52 -0
  54. data/spec/raptor-io/protocol/http/request/manipulators/authenticators/ntlm_spec.rb +37 -0
  55. data/spec/raptor-io/protocol/http/request/manipulators/redirect_follower_spec.rb +51 -0
  56. data/spec/raptor-io/protocol/http/request/manipulators_spec.rb +202 -0
  57. data/spec/raptor-io/protocol/http/request_spec.rb +965 -0
  58. data/spec/raptor-io/protocol/http/response_spec.rb +236 -0
  59. data/spec/raptor-io/protocol/http/server_spec.rb +345 -0
  60. data/spec/raptor-io/ruby/hash_spec.rb +20 -0
  61. data/spec/raptor-io/ruby/string_spec.rb +20 -0
  62. data/spec/raptor-io/socket/comm/local_spec.rb +50 -0
  63. data/spec/raptor-io/socket/switch_board/route_spec.rb +49 -0
  64. data/spec/raptor-io/socket/switch_board_spec.rb +87 -0
  65. data/spec/raptor-io/socket/tcp/ssl_spec.rb +18 -0
  66. data/spec/raptor-io/socket/tcp_server/ssl_spec.rb +59 -0
  67. data/spec/raptor-io/socket/tcp_server_spec.rb +19 -0
  68. data/spec/raptor-io/socket/tcp_spec.rb +14 -0
  69. data/spec/raptor-io/socket_spec.rb +16 -0
  70. data/spec/raptor-io/version_spec.rb +10 -0
  71. data/spec/spec_helper.rb +56 -0
  72. data/spec/support/fixtures/raptor/protocol/http/request/manipulators/manifoolators/fooer.rb +25 -0
  73. data/spec/support/fixtures/raptor/protocol/http/request/manipulators/niccolo_machiavelli.rb +20 -0
  74. data/spec/support/fixtures/raptor/protocol/http/request/manipulators/options_validator.rb +28 -0
  75. data/spec/support/fixtures/raptor/socket/ssl_server.crt +18 -0
  76. data/spec/support/fixtures/raptor/socket/ssl_server.key +15 -0
  77. data/spec/support/lib/path_helpers.rb +11 -0
  78. data/spec/support/lib/webserver_option_parser.rb +26 -0
  79. data/spec/support/lib/webservers.rb +120 -0
  80. data/spec/support/shared/contexts/with_ssl_server.rb +70 -0
  81. data/spec/support/shared/contexts/with_tcp_server.rb +58 -0
  82. data/spec/support/shared/examples/raptor/comm_examples.rb +26 -0
  83. data/spec/support/shared/examples/raptor/protocols/http/message.rb +106 -0
  84. data/spec/support/shared/examples/raptor/socket_examples.rb +135 -0
  85. data/spec/support/webservers/raptor/protocols/http/client.rb +100 -0
  86. data/spec/support/webservers/raptor/protocols/http/client_close_connection.rb +29 -0
  87. data/spec/support/webservers/raptor/protocols/http/client_https.rb +43 -0
  88. data/spec/support/webservers/raptor/protocols/http/request/manipulators/authenticators/basic.rb +9 -0
  89. data/spec/support/webservers/raptor/protocols/http/request/manipulators/authenticators/digest.rb +22 -0
  90. data/spec/support/webservers/raptor/protocols/http/request/manipulators/redirect_follower.rb +11 -0
  91. metadata +336 -0
@@ -0,0 +1,236 @@
1
+ #coding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe RaptorIO::Protocol::HTTP::Response do
5
+ it_should_behave_like 'RaptorIO::Protocol::HTTP::Message'
6
+
7
+ let(:url) { 'http://test.com' }
8
+
9
+ let(:response) do
10
+ "HTTP/1.1 404 Not Found\r\n" +
11
+ "Content-Type: text/html;charset=utf-8\r\n" +
12
+ "Content-Length: 27\r\n\r\n" +
13
+ "<!DOCTYPE html>\n" +
14
+ "More stuff\n".force_encoding( 'ASCII-8BIT')
15
+ end
16
+
17
+ let(:response_cr) do
18
+ "HTTP/1.1 404 Not Found\n" +
19
+ "Content-Type: text/html;charset=utf-8\n" +
20
+ "Content-Length: 27\n\n" +
21
+ "<!DOCTYPE html>\n" +
22
+ "More stuff\n".force_encoding( 'ASCII-8BIT')
23
+ end
24
+
25
+ it 'forces the body to UTF-8' do
26
+ described_class.parse( response ).body.encoding.to_s.should == 'UTF-8'
27
+ described_class.new( body: "stuff τεστblah" ).body.should == 'stuff τεστblah'
28
+ end
29
+
30
+ describe '#text?' do
31
+ context 'when the content-type is' do
32
+ context 'text/*' do
33
+ it 'returns true' do
34
+ h = {
35
+ headers: { 'Content-Type' => 'text/stuff' },
36
+ body: "stuff"
37
+ }
38
+ described_class.new( h ).text?.should be_true
39
+ end
40
+ end
41
+
42
+ context 'application/*' do
43
+ context 'and the response body is' do
44
+ context 'binary' do
45
+ it 'returns false' do
46
+ h = {
47
+ headers: { 'Content-Type' => 'application/stuff' },
48
+ body: "\00\00\00"
49
+ }
50
+ described_class.new( h ).text?.should be_false
51
+ end
52
+ end
53
+
54
+ context 'text' do
55
+ it 'returns true' do
56
+ h = {
57
+ headers: { 'Content-Type' => 'application/stuff' },
58
+ body: "stuff"
59
+ }
60
+ described_class.new( h ).text?.should be_true
61
+ end
62
+ end
63
+ end
64
+ end
65
+
66
+ context 'other' do
67
+ it 'returns false' do
68
+ h = {
69
+ headers: { 'Content-Type' => 'blah/stuff' },
70
+ body: "stuff"
71
+ }
72
+ described_class.new( h ).text?.should be_false
73
+ end
74
+ end
75
+
76
+ context nil do
77
+ context 'and the response body is' do
78
+ context 'binary' do
79
+ it 'returns false' do
80
+ h = { body: "\00\00\00" }
81
+ described_class.new( h ).text?.should be_false
82
+ end
83
+ end
84
+
85
+ context 'text' do
86
+ it 'returns true' do
87
+ h = { body: "stuff" }
88
+ described_class.new( h ).text?.should be_true
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
95
+
96
+ describe '#code' do
97
+ it 'returns the HTTP status code' do
98
+ described_class.new( url: url, code: 200 ).code.should == 200
99
+ end
100
+
101
+ it 'defaults to 0' do
102
+ described_class.new( url: url ).code.should == 0
103
+ end
104
+ end
105
+
106
+ describe '#body' do
107
+ it 'returns the HTTP response body' do
108
+ described_class.parse( response ).body.should == "<!DOCTYPE html>\nMore stuff\n"
109
+ end
110
+
111
+ it 'is forced to UTF8' do
112
+ described_class.parse( response ).body.encoding.to_s.should == 'UTF-8'
113
+ end
114
+ end
115
+
116
+ describe '#redirect?' do
117
+ context 'when the code is in the 3xx family' do
118
+ context 'and there is a Location in the headers' do
119
+ it 'returns true' do
120
+ 300.upto( 399 ) do |code|
121
+ described_class.new(
122
+ url: url,
123
+ code: code,
124
+ headers: { 'Location' => url }
125
+ ).redirect?.should be_true
126
+ end
127
+ end
128
+ end
129
+ context 'and there is no Location in the headers' do
130
+ it 'returns false' do
131
+ 300.upto( 399 ) do |code|
132
+ described_class.new(
133
+ url: url,
134
+ code: code
135
+ ).redirect?.should be_false
136
+ end
137
+ end
138
+ end
139
+ end
140
+
141
+ context 'when the code is not in the 3xx family' do
142
+ it 'returns false' do
143
+ described_class.new(
144
+ url: url,
145
+ code: 200
146
+ ).redirect?.should be_false
147
+ end
148
+
149
+ context 'and there is a Location in the headers' do
150
+ it 'returns true' do
151
+ described_class.new(
152
+ url: url,
153
+ code: 200,
154
+ headers: { 'Location' => url }
155
+ ).redirect?.should be_false
156
+ end
157
+ end
158
+ end
159
+ end
160
+
161
+ describe '#modified?' do
162
+ context 'when the code is 304' do
163
+ it 'returns false' do
164
+ described_class.new( url: url, code: 304 ).modified?.should be_false
165
+ end
166
+ end
167
+ context 'when the code is not 304' do
168
+ it 'returns true' do
169
+ described_class.new( url: url, code: 301 ).modified?.should be_true
170
+ end
171
+ end
172
+ end
173
+
174
+ describe '#request' do
175
+ it 'returns the assigned request' do
176
+ r = RaptorIO::Protocol::HTTP::Request.new( url: url )
177
+ described_class.new( url: url, request: r ).request.should == r
178
+ end
179
+ end
180
+
181
+ describe '.parse' do
182
+ it 'supports CRLF terminators' do
183
+ r = described_class.parse( response )
184
+ r.version.should == '1.1'
185
+ r.code.should == 404
186
+ r.message.should == 'Not Found'
187
+ r.body.should == "<!DOCTYPE html>\nMore stuff\n"
188
+ r.headers.should == {
189
+ 'Content-Type' => 'text/html;charset=utf-8',
190
+ 'Content-Length' => '27'
191
+ }
192
+ r.to_s.should == response
193
+ end
194
+
195
+ it 'supports CR terminators' do
196
+ r = described_class.parse( response_cr )
197
+ r.version.should == '1.1'
198
+ r.code.should == 404
199
+ r.message.should == 'Not Found'
200
+ r.body.should == "<!DOCTYPE html>\nMore stuff\n"
201
+ r.headers.should == {
202
+ 'Content-Type' => 'text/html;charset=utf-8',
203
+ 'Content-Length' => '27'
204
+ }
205
+ r.to_s.should == response
206
+ end
207
+
208
+ context 'when passed an empty string' do
209
+ it 'returns an empty response' do
210
+ r = described_class.parse( '' )
211
+ r.version.should == '1.1'
212
+ r.code.should == 0
213
+ r.message.should be_nil
214
+ r.body.should be_nil
215
+ r.headers.should == {}
216
+ end
217
+ end
218
+ end
219
+
220
+ describe '#to_s' do
221
+ it 'returns a String representation of the response' do
222
+ r = described_class.new(
223
+ version: '1.1',
224
+ code: 404,
225
+ message: 'Not Found',
226
+ body: "<!DOCTYPE html>\nMore stuff\n",
227
+ headers: {
228
+ 'Content-Type' => 'text/html;charset=utf-8',
229
+ 'Content-Length' => '431'
230
+ }
231
+ )
232
+
233
+ r.to_s.should == response
234
+ end
235
+ end
236
+ end
@@ -0,0 +1,345 @@
1
+ #coding: utf-8
2
+ require 'spec_helper'
3
+ require 'net/https'
4
+
5
+ describe RaptorIO::Protocol::HTTP::Server do
6
+
7
+ after :each do
8
+ next if !@server
9
+ @server.stop
10
+ @server = nil
11
+ end
12
+
13
+ def test_server( server_or_url )
14
+ test_request server_or_url
15
+ end
16
+
17
+ def test_request( server_or_url )
18
+ request( server_or_url ).code.should == '418'
19
+ end
20
+
21
+ def request( server_or_url )
22
+ uri = URI( argument_to_url( server_or_url ) )
23
+
24
+ https = Net::HTTP.new( uri.host, uri.port )
25
+ https.use_ssl = (uri.scheme == 'https')
26
+ https.verify_mode = OpenSSL::SSL::VERIFY_NONE
27
+
28
+ https.start { |cx| cx.request( Net::HTTP::Get.new( uri.path ) ) }
29
+ end
30
+
31
+ def test_request_with_body( server_or_url )
32
+ response = request_with_body( server_or_url )
33
+ response.code.should == '418'
34
+ response.body.should == 'name=value'
35
+ end
36
+
37
+ def request_with_body( server_or_url )
38
+ Net::HTTP.post_form( URI( argument_to_url( server_or_url ) ), 'name' => 'value' )
39
+ end
40
+
41
+ def argument_to_url( server_or_url )
42
+ server_or_url.is_a?( String ) ? server_or_url : server_or_url.url
43
+ end
44
+
45
+ let(:switch_board) { RaptorIO::Socket::SwitchBoard.new }
46
+ let(:ssl_context) do
47
+ ssl_context = OpenSSL::SSL::SSLContext.new( :TLSv1 )
48
+ ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
49
+
50
+ ca = OpenSSL::X509::Name.parse( "/C=US/ST=SomeState/L=SomeCity/O=Organization/OU=Unit/CN=localhost" )
51
+ key = OpenSSL::PKey::RSA.new( 1024 )
52
+ crt = OpenSSL::X509::Certificate.new
53
+
54
+ crt.version = 2
55
+ crt.serial = 1
56
+ crt.subject = ca
57
+ crt.issuer = ca
58
+ crt.public_key = key.public_key
59
+ crt.not_before = Time.now
60
+ crt.not_after = Time.now + 1 * 365 * 24 * 60 * 60 # 1 year
61
+
62
+ ssl_context.cert = crt
63
+ ssl_context.key = key
64
+ ssl_context
65
+ end
66
+
67
+ def new_server( options = {}, &block )
68
+ @server = described_class.new(
69
+ { switch_board: switch_board }.merge(options),
70
+ &block
71
+ )
72
+ end
73
+
74
+ describe '#run_nonblock' do
75
+ it 'starts the server but does not block' do
76
+ server = new_server
77
+ server.run_nonblock
78
+
79
+ test_server server
80
+ end
81
+ end
82
+
83
+ describe '#stop' do
84
+ it 'stops the server' do
85
+ server = new_server
86
+ server.run_nonblock
87
+
88
+ test_server server
89
+ server.stop
90
+
91
+ expect { request( server ) }.to raise_error Errno::ECONNREFUSED
92
+ end
93
+ end
94
+
95
+ context 'when a request has Transfer-Encoding' do
96
+ context 'other' do
97
+ it 'returns a 501 response' do
98
+ server = new_server
99
+ server.run_nonblock
100
+
101
+ socket = TCPSocket.new( server.address, server.port )
102
+ [
103
+ 'GET / HTTP/1.1',
104
+ 'Transfer-Encoding: Stuff'
105
+ ].each do |l|
106
+ socket.puts l
107
+ end
108
+ socket.puts
109
+
110
+ [
111
+ 'Data' * 12,
112
+ 'More data' * 20,
113
+ 'Even more data' * 102
114
+ ].each do |l|
115
+ socket << "#{l.size.to_s( 16 )}\r\n#{l}\r\n"
116
+ end
117
+ socket << "0\r\n\r\n"
118
+
119
+ buff = ''
120
+ while !(buff =~ RaptorIO::Protocol::HTTP::HEADER_SEPARATOR_PATTERN)
121
+ buff << socket.gets
122
+ end
123
+ socket.close
124
+
125
+ RaptorIO::Protocol::HTTP::Response.parse( buff ).code.should == 501
126
+ end
127
+ end
128
+
129
+ describe 'Chunked' do
130
+ it 'supports it' do
131
+ server = new_server
132
+ server.run_nonblock
133
+
134
+ socket = TCPSocket.new( server.address, server.port )
135
+ [
136
+ 'GET / HTTP/1.1',
137
+ 'Transfer-Encoding: Chunked'
138
+ ].each do |l|
139
+ socket.puts l
140
+ end
141
+ socket.puts
142
+
143
+ [
144
+ 'Data' * 12,
145
+ 'More data' * 20,
146
+ 'Even more data' * 102
147
+ ].each do |l|
148
+ socket << "#{l.size.to_s( 16 )}\r\n#{l}\r\n"
149
+ end
150
+ socket << "0\r\n\r\n"
151
+
152
+ buff = ''
153
+ while !(buff =~ RaptorIO::Protocol::HTTP::HEADER_SEPARATOR_PATTERN)
154
+ buff << socket.gets
155
+ end
156
+ socket.close
157
+
158
+ RaptorIO::Protocol::HTTP::Response.parse( buff ).code.should == 418
159
+ end
160
+ end
161
+ end
162
+
163
+ describe '#initialize' do
164
+ describe :switch_board do
165
+ it 'uses that switchboard' do
166
+ server = described_class.new( switch_board: switch_board )
167
+ server.switch_board.should == switch_board
168
+ end
169
+
170
+ context 'when nil' do
171
+ it 'raises ArgumentError' do
172
+ expect { described_class.new }.to raise_error ArgumentError
173
+ end
174
+ end
175
+ end
176
+
177
+ describe :ssl_context do
178
+ it 'uses it to establish an SSL stream' do
179
+ server = new_server( ssl_context: ssl_context )
180
+ server.url.should == "https://#{server.address}:#{server.port}/"
181
+ server.ssl?.should be_true
182
+
183
+ server.run_nonblock
184
+
185
+ test_server server
186
+ end
187
+ end
188
+
189
+ describe :address do
190
+ it 'binds to this address' do
191
+ address = '127.0.0.1'
192
+ server = new_server( address: address )
193
+ server.address.should == address
194
+ server.run_nonblock
195
+
196
+ test_server server
197
+ end
198
+
199
+ it 'defaults to 0.0.0.0' do
200
+ server = new_server
201
+ server.address.should == '0.0.0.0'
202
+
203
+ server.run_nonblock
204
+
205
+ test_server server
206
+ end
207
+ end
208
+
209
+ describe :port do
210
+ it 'binds to this port' do
211
+ server = new_server( port: 8080 )
212
+ server.port.should == 8080
213
+ server.run_nonblock
214
+
215
+ test_server server
216
+ end
217
+
218
+ it 'defaults to 4567' do
219
+ server = new_server
220
+ server.port.should == 4567
221
+
222
+ server.run_nonblock
223
+
224
+ test_server server
225
+ end
226
+ end
227
+
228
+ describe :timeout do
229
+ it 'defaults to 10', speed: 'slow' do
230
+ server = new_server
231
+ server.timeout.should == 10
232
+ server.run_nonblock
233
+
234
+ server.timeouts.should == 0
235
+
236
+ socket = Socket.new( Socket::Constants::AF_INET, Socket::Constants::SOCK_STREAM, 0 )
237
+ sockaddr = Socket.pack_sockaddr_in( server.port, server.address )
238
+ socket.connect( sockaddr )
239
+
240
+ sleep 9
241
+
242
+ server.timeouts.should == 0
243
+
244
+ sleep 11
245
+ socket.close
246
+
247
+ server.timeouts.should == 1
248
+ end
249
+
250
+ it 'sets the request timeout' do
251
+ server = new_server( timeout: 1 )
252
+ server.timeout.should == 1
253
+ server.run_nonblock
254
+
255
+ server.timeouts.should == 0
256
+
257
+ socket = Socket.new( Socket::Constants::AF_INET, Socket::Constants::SOCK_STREAM, 0 )
258
+ sockaddr = Socket.pack_sockaddr_in( server.port, server.address )
259
+ socket.connect( sockaddr )
260
+
261
+ sleep 2
262
+ socket.close
263
+
264
+ server.timeouts.should == 1
265
+ end
266
+ end
267
+
268
+ describe :request_mtu do
269
+ it 'reads bodies larger than :request_mtu in :request_mtu sized chunks' do
270
+ server = new_server( request_mtu: 1 )
271
+ server.request_mtu.should == 1
272
+ server.run_nonblock
273
+
274
+ test_request_with_body server
275
+ end
276
+ end
277
+
278
+ describe :response_mtu do
279
+ it 'sends responses larger than :response_mtu in :response_mtu sized chunks' do
280
+ server = new_server( response_mtu: 1 )
281
+ server.response_mtu.should == 1
282
+ server.run_nonblock
283
+
284
+ test_request_with_body server
285
+ end
286
+ end
287
+
288
+ describe :handler do
289
+ it 'passes each request to the given handler' do
290
+ request = nil
291
+
292
+ server = new_server do |response|
293
+ request = response.request
294
+ response.code = 200
295
+ response.body = 'Success!'
296
+ end
297
+
298
+ server.run_nonblock
299
+
300
+ response = request( server )
301
+ response.code.should == '200'
302
+ response.body.should == 'Success!'
303
+
304
+ request.should be_kind_of RaptorIO::Protocol::HTTP::Request
305
+ end
306
+ end
307
+ end
308
+
309
+ describe '#run' do
310
+ it 'starts the server' do
311
+ server = new_server
312
+ Thread.new { server.run }
313
+ sleep 0.1 while !server.running?
314
+
315
+ test_server server
316
+ end
317
+ end
318
+
319
+ describe '#running?' do
320
+ context 'when the server is running' do
321
+ it 'returns true' do
322
+ server = new_server
323
+ server.run_nonblock
324
+ server.running?.should be_true
325
+ end
326
+ end
327
+ context 'when the server is not running' do
328
+ it 'returns true' do
329
+ server = new_server
330
+ server.running?.should be_false
331
+ end
332
+ end
333
+ end
334
+
335
+ describe '#url' do
336
+ it 'returns the URL of the server' do
337
+ server = new_server
338
+ server.run_nonblock
339
+
340
+ server.url.should == "http://#{server.address}:#{server.port}/"
341
+ test_server server.url
342
+ end
343
+ end
344
+
345
+ end