experella-proxy 0.0.6

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 (43) hide show
  1. data/.gitignore +15 -0
  2. data/Gemfile +3 -0
  3. data/README.md +219 -0
  4. data/Rakefile +25 -0
  5. data/TODO.txt +20 -0
  6. data/bin/experella-proxy +54 -0
  7. data/config/default/404.html +16 -0
  8. data/config/default/503.html +18 -0
  9. data/config/default/config.rb +64 -0
  10. data/config/default/ssl/certs/experella-proxy.pem +18 -0
  11. data/config/default/ssl/private/experella-proxy.key +28 -0
  12. data/dev/experella-proxy +62 -0
  13. data/experella-proxy.gemspec +39 -0
  14. data/lib/experella-proxy/backend.rb +58 -0
  15. data/lib/experella-proxy/backend_server.rb +100 -0
  16. data/lib/experella-proxy/configuration.rb +154 -0
  17. data/lib/experella-proxy/connection.rb +557 -0
  18. data/lib/experella-proxy/connection_manager.rb +167 -0
  19. data/lib/experella-proxy/globals.rb +37 -0
  20. data/lib/experella-proxy/http_status_codes.rb +45 -0
  21. data/lib/experella-proxy/proxy.rb +61 -0
  22. data/lib/experella-proxy/request.rb +106 -0
  23. data/lib/experella-proxy/response.rb +204 -0
  24. data/lib/experella-proxy/server.rb +68 -0
  25. data/lib/experella-proxy/version.rb +15 -0
  26. data/lib/experella-proxy.rb +93 -0
  27. data/spec/echo-server/echo_server.rb +49 -0
  28. data/spec/experella-proxy/backend_server_spec.rb +101 -0
  29. data/spec/experella-proxy/configuration_spec.rb +27 -0
  30. data/spec/experella-proxy/connection_manager_spec.rb +159 -0
  31. data/spec/experella-proxy/experella-proxy_spec.rb +471 -0
  32. data/spec/experella-proxy/request_spec.rb +88 -0
  33. data/spec/experella-proxy/response_spec.rb +44 -0
  34. data/spec/fixtures/404.html +16 -0
  35. data/spec/fixtures/503.html +18 -0
  36. data/spec/fixtures/spec.log +331 -0
  37. data/spec/fixtures/test_config.rb +34 -0
  38. data/spec/spec.log +235 -0
  39. data/spec/spec_helper.rb +35 -0
  40. data/test/sinatra/hello_world_server.rb +17 -0
  41. data/test/sinatra/server_one.rb +89 -0
  42. data/test/sinatra/server_two.rb +89 -0
  43. metadata +296 -0
@@ -0,0 +1,471 @@
1
+ require 'spec_helper'
2
+
3
+ # Dirty integration tests for the proxy connection logic
4
+ #
5
+ # uses the simple echo server in spec/echo-server/echo_server.rb and the test_config.rb
6
+ #
7
+ # Proxy logfile in spec/log/spec.log
8
+ #
9
+ describe ExperellaProxy do
10
+ include POSIX::Spawn
11
+ include ExperellaProxy::Globals
12
+ let(:echo_server) {
13
+ File.expand_path("../../echo-server/echo_server.rb", __FILE__)
14
+ }
15
+ let(:experella_proxy) {
16
+ File.expand_path("../../../bin/experella-proxy", __FILE__)
17
+ }
18
+
19
+
20
+
21
+ describe "EchoServer" do
22
+ before :each do
23
+ @pid = spawn("ruby", "#{echo_server}", "127.0.0.10", "7654")
24
+ sleep(0.8) #let the server startup, specs may fail if this is set to low
25
+ end
26
+ after :each do
27
+ Process.kill('QUIT', @pid)
28
+ sleep(1.0) # give the kill command some time
29
+ end
30
+
31
+ it "should get response from the echoserver" do
32
+ lambda {
33
+ EM.run do
34
+ http = EventMachine::HttpRequest.new("http://127.0.0.10:7654").get({:connect_timeout => 1})
35
+ http.errback {
36
+ EventMachine.stop
37
+ raise "http request failed"
38
+ }
39
+ http.callback {
40
+ http.response.should start_with "you sent: "
41
+ EventMachine.stop
42
+ }
43
+ end
44
+ }.should_not raise_error
45
+ end
46
+ end
47
+
48
+ describe "Proxy" do
49
+ before :each do
50
+ @pid = spawn("ruby", "#{echo_server}", "127.0.0.10", "7654")
51
+ @pid2 = spawn("#{experella_proxy}", "run", "--", "--config=#{File.join(File.dirname(__FILE__),"/../fixtures/test_config.rb")}")
52
+ sleep(0.8) #let the server startup, specs may fail if this is set to low
53
+ ExperellaProxy.init(:configfile => File.join(File.dirname(__FILE__),"/../fixtures/test_config.rb"))
54
+ config.backends.each do |backend|
55
+ connection_manager.add_backend(ExperellaProxy::BackendServer.new(backend[:host], backend[:port], backend))
56
+ end
57
+ end
58
+ after :each do
59
+ log.close
60
+ Process.kill('QUIT', @pid)
61
+ Process.kill('TERM', @pid2)
62
+ sleep(1.0) # give the kill command some time
63
+ end
64
+
65
+ it "should get response from the echoserver via the proxy" do
66
+ log.info "should get response from the echoserver via the proxy"
67
+ EM.epoll
68
+ EM.run do
69
+ lambda {
70
+ EventMachine.add_timer(0.2) do
71
+ http = EventMachine::HttpRequest.new("http://#{config.proxy[0][:host]}:#{config.proxy[0][:port]}"
72
+ ).get({:connect_timeout => 1, :head => {"Host" => "experella.com"}})
73
+ http.errback {
74
+ EventMachine.stop
75
+ raise "http request failed"
76
+ }
77
+ http.callback {
78
+ http.response.should start_with "you sent: "
79
+ EventMachine.stop
80
+ }
81
+ end
82
+ }.should_not raise_error
83
+ end
84
+ end
85
+
86
+ it "should respond with 404" do
87
+ log.info "should respond with 404"
88
+ EM.epoll
89
+ EM.run do
90
+ lambda {
91
+ EventMachine.add_timer(0.2) do
92
+
93
+ multi = EventMachine::MultiRequest.new
94
+ multi_shuffle = []
95
+ multi_shuffle[0] = Proc.new {
96
+ multi.add :head, EventMachine::HttpRequest.new("http://#{config.proxy[0][:host]}:#{config.proxy[0][:port]}"
97
+ ).head({:connect_timeout => 1})
98
+ }
99
+ multi_shuffle[1] = Proc.new {
100
+ multi.add :get, EventMachine::HttpRequest.new("http://#{config.proxy[0][:host]}:#{config.proxy[0][:port]}"
101
+ ).get({:connect_timeout => 1})
102
+ }
103
+ multi_shuffle.shuffle!
104
+ multi_shuffle.each do |p|
105
+ p.call
106
+ end
107
+
108
+ multi.callback do
109
+ unless multi.responses[:errback].empty?
110
+ EventMachine.stop
111
+ raise "http request failed"
112
+ end
113
+ multi.responses[:callback][:head].response.empty?.should be_true
114
+ multi.responses[:callback][:head].response_header.status.should == 404
115
+ multi.responses[:callback][:get].response_header.status.should == 404
116
+ multi.responses[:callback][:get].response.should start_with "<!DOCTYPE html>"
117
+ EventMachine.stop
118
+ end
119
+
120
+
121
+ end
122
+ }.should_not raise_error
123
+ end
124
+ end
125
+
126
+ it "should respond with 400 on malformed request" do
127
+ log.info "should respond with 400 on malformed request"
128
+ EM.epoll
129
+ EM.run do
130
+ lambda {
131
+ EventMachine.add_timer(0.2) do
132
+ http = EventMachine::HttpRequest.new("http://#{config.proxy[0][:host]}:#{config.proxy[0][:port]}"
133
+ ).post({:connect_timeout => 1, :head => {"Host" => "experella.com", "Transfer-Encoding" => "chunked"},
134
+ :body => "9\r\nMalformed\r\na\r\nchunked da\r\n2\rta HERE\r\n0\r\n\r\n"})
135
+ http.errback {
136
+ EventMachine.stop
137
+ raise "http request failed"
138
+ }
139
+ http.callback {
140
+ http.response_header.status.should == 400
141
+ EventMachine.stop
142
+ }
143
+ end
144
+ }.should_not raise_error
145
+ end
146
+ end
147
+
148
+ it "should respond with 503" do
149
+ log.info "should respond with 503"
150
+ EM.epoll
151
+ EM.run do
152
+ lambda {
153
+ EventMachine.add_timer(0.2) do
154
+
155
+ multi = EventMachine::MultiRequest.new
156
+ multi_shuffle = []
157
+ multi_shuffle[0] = Proc.new {
158
+ multi.add :head, EventMachine::HttpRequest.new("http://#{config.proxy[0][:host]}:#{config.proxy[0][:port]}/oneroute"
159
+ ).head({:connect_timeout => 1, :head => {"Host" => "experella.com"}})
160
+ }
161
+ multi_shuffle[1] = Proc.new {
162
+ multi.add :get, EventMachine::HttpRequest.new("http://#{config.proxy[0][:host]}:#{config.proxy[0][:port]}/anotherpath"
163
+ ).get({:connect_timeout => 1, :head => {"Host" => "experella.com"}})
164
+ }
165
+ multi_shuffle.shuffle!
166
+ multi_shuffle.each do |p|
167
+ p.call
168
+ end
169
+
170
+ multi.callback do
171
+ unless multi.responses[:errback].empty?
172
+ EventMachine.stop
173
+ raise "http request failed"
174
+ end
175
+ multi.responses[:callback][:head].response.empty?.should be_true
176
+ multi.responses[:callback][:head].response_header.status.should == 503
177
+ multi.responses[:callback][:get].response_header.status.should == 503
178
+ multi.responses[:callback][:get].response.should start_with "<!DOCTYPE html>"
179
+ EventMachine.stop
180
+ end
181
+
182
+
183
+ end
184
+ }.should_not raise_error
185
+ end
186
+ end
187
+
188
+ it "should reuse keep-alive connections" do
189
+ log.info "should reuse keep-alive connections"
190
+ EM.epoll
191
+ EM.run do
192
+ lambda {
193
+ EventMachine.add_timer(0.2) do
194
+ conn = EventMachine::HttpRequest.new("http://#{config.proxy[0][:host]}:#{config.proxy[0][:port]}")
195
+ req1 = conn.get({:connect_timeout => 1, :keepalive => true, :head => {"Host" => "experella.com"}})
196
+
197
+ req1.errback {
198
+ EventMachine.stop
199
+ raise "http request 1 failed"
200
+ }
201
+ req1.callback {
202
+ req1.response.should start_with "you sent: "
203
+ req2 = conn.get({:path => '/about/', :connect_timeout => 1, :keepalive => true, :head => {"Host" => "experella.com"}})
204
+ req2.errback {
205
+ EventMachine.stop
206
+ raise "http request 2 failed"
207
+ }
208
+ req2.callback {
209
+ req2.response.should start_with "you sent: "
210
+ req3 = conn.get({:connect_timeout => 1, :head => {"Host" => "experella.com"}})
211
+ req3.errback {
212
+ EventMachine.stop
213
+ raise "http request 3 failed"
214
+ }
215
+ req3.callback {
216
+ req3.response.should start_with "you sent: "
217
+ EventMachine.stop
218
+ }
219
+ }
220
+ }
221
+ end
222
+ }.should_not raise_error
223
+ end
224
+ end
225
+
226
+ it "should handle chunked post requests and strip invalid Content-Length" do
227
+ log.info "should stream chunked post requests"
228
+ EM.epoll
229
+ EM.run do
230
+ lambda {
231
+ EventMachine.add_timer(0.2) do
232
+ # this will issue a Post Request with Content-Length AND Transfer-Encoding chunked, where the data is
233
+ # correctly encoded in chunks. The Proxy should therefor strip the Content-Length and forward the data
234
+ # in chunks.
235
+ expected_headers = []
236
+ expected_headers << "POST / HTTP/1.1"
237
+ expected_headers << "Host: experella.com"
238
+ expected_headers << "Connection: close"
239
+ expected_headers << "Via: 1.1 experella"
240
+ expected_headers << "User-Agent: EventMachine HttpClient"
241
+ expected_headers << "Transfer-Encoding: chunked"
242
+
243
+ # generate random chunked message
244
+ body = String.new
245
+ expected_body = String.new
246
+ chunks = 20 + rand(20)
247
+ # all alphanumeric characters
248
+ o = [('a'..'z'), ('A'..'Z'), ('0'..'9')].map { |i| i.to_a }.flatten
249
+ while chunks > 0 do
250
+ #chunksize 10 to 1510 characters
251
+ string = (0...(10 + rand(1500))).map { o[rand(o.length)] }.join
252
+ body << string.size.to_s(16)
253
+ body << "\r\n"
254
+ body << string
255
+ expected_body << string
256
+ body << "\r\n"
257
+ chunks -= 1
258
+ end
259
+ body << "0\r\n\r\n"
260
+
261
+ http = EventMachine::HttpRequest.new("http://#{config.proxy[0][:host]}:#{config.proxy[0][:port]}"
262
+ ).post({:connect_timeout => 1, :head => {"Host" => "experella.com", "Transfer-Encoding" => "chunked"},
263
+ :body => body})
264
+ http.errback {
265
+ EventMachine.stop
266
+ raise "http request failed"
267
+ }
268
+ http.callback {
269
+ # as the proxy can rechunk large data in different chunks, it's required to remove all chunk encoding
270
+ # characters and compare it to the unchunked random data saved in expected_body
271
+
272
+ # split header and body
273
+ response = http.response.partition("\\r\\n\\r\\n")
274
+ # split by chunked encoding delimiter
275
+ response[2] = response[2].split("\\r\\n").map { |i|
276
+ # delete all strings containing the hex-size information (FFF = length 3 = 4500 chars)
277
+ if i.length <= 3
278
+ i = ""
279
+ else
280
+ i = i
281
+ end
282
+ }.join
283
+ response = response.join
284
+ expected_headers.each do |header|
285
+ response.should include header
286
+ end
287
+ response.should include expected_body
288
+
289
+ EventMachine.stop
290
+ }
291
+ end
292
+ }.should_not raise_error
293
+ end
294
+
295
+ end
296
+
297
+ # check echo_server for the response
298
+ it "should rechunk and stream Transfer-Encoding chunked responses" do
299
+ log.info "should rechunk and stream Transfer-Encoding chunked responses"
300
+ EM.epoll
301
+ EM.run do
302
+ lambda {
303
+ EventMachine.add_timer(0.2) do
304
+ http = EventMachine::HttpRequest.new("http://#{config.proxy[0][:host]}:#{config.proxy[0][:port]}/chunked"
305
+ ).get({:connect_timeout => 1, :head => {"Host" => "experella.com"}})
306
+ http.errback {
307
+ EventMachine.stop
308
+ raise "http request failed"
309
+ }
310
+ received_chunks = ""
311
+ http.stream { |chunk|
312
+ received_chunks << chunk
313
+ }
314
+ http.callback {
315
+ true.should be_true
316
+ received_chunks.should == "chunk one chunk two chunk three chunk four chunk 123456789abcdef"
317
+ http.response_header["Transfer-Encoding"].should == "chunked"
318
+ EventMachine.stop
319
+ }
320
+ end
321
+ }.should_not raise_error
322
+ end
323
+
324
+ end
325
+
326
+ it "should timeout inactive connections after config.timeout" do
327
+ log.info "should timeout inactive connections after config.timeout"
328
+ EM.epoll
329
+ EM.run do
330
+
331
+ lambda {
332
+ EventMachine.add_timer(0.2) do
333
+ time = Time.now
334
+ conn = EventMachine::HttpRequest.new("http://#{config.proxy[0][:host]}:#{config.proxy[0][:port]}")
335
+ req1 = conn.get({:connect_timeout => 1, :inactivity_timeout => config.timeout + 5,
336
+ :keepalive => true, :head => {"Host" => "experella.com"}})
337
+ req1.errback {
338
+ time = Time.now - time
339
+ time.should >= config.timeout
340
+ time.should < config.timeout + 5
341
+ EventMachine.stop
342
+ raise "http request failed"
343
+ }
344
+ req1.callback {
345
+ req1.response.should start_with "you sent: "
346
+ }
347
+ #check for inactivity timeout
348
+ EventMachine.add_periodic_timer(1) do
349
+ if conn.conn.get_idle_time.nil?
350
+ time = Time.now - time
351
+ time.should >= config.timeout
352
+ time.should <= config.timeout + 5
353
+ EventMachine.stop
354
+ elsif Time.now - time > config.timeout + 6
355
+ EventMachine.stop
356
+ raise "Timeout failed completly"
357
+ end
358
+ end
359
+ end
360
+ }.should_not raise_error
361
+ end
362
+ end
363
+
364
+
365
+ it "should handle pipelined requests correctly" do
366
+ log.info "should handle pipelined requests correctly"
367
+ EM.epoll
368
+ EM.run do
369
+ lambda {
370
+ EventMachine.add_timer(0.2) do
371
+ conn = EventMachine::HttpRequest.new("http://#{config.proxy[0][:host]}:#{config.proxy[0][:port]}")
372
+
373
+ pipe1 = conn.get({:connect_timeout => 1, :keepalive => true, :head => {"Host" => "experella.com"}})
374
+ pipe2 = conn.get({:path => '/about/', :connect_timeout => 1, :keepalive => true, :head => {"Host" => "experella.com"}})
375
+ pipe3 = conn.get({:connect_timeout => 1, :head => {"Host" => "experella.com"}})
376
+ pipe1.errback {
377
+ EventMachine.stop
378
+ raise "http request 1 failed"
379
+ }
380
+ pipe1.callback {
381
+ pipe1.response.should start_with "you sent: "
382
+ pipe2.finished?.should be_false
383
+ pipe3.finished?.should be_false
384
+ }
385
+ pipe2.errback {
386
+ EventMachine.stop
387
+ raise "http request 2 failed"
388
+ }
389
+ pipe2.callback {
390
+ pipe2.response.should start_with "you sent: "
391
+ pipe1.finished?.should be_true
392
+ pipe3.finished?.should be_false
393
+ }
394
+ pipe3.errback {
395
+ EventMachine.stop
396
+ raise "http request 3 failed"
397
+ }
398
+ pipe3.callback {
399
+ pipe3.response.should start_with "you sent: "
400
+ pipe1.finished?.should be_true
401
+ pipe2.finished?.should be_true
402
+ EventMachine.stop
403
+ }
404
+ end
405
+ }.should_not raise_error
406
+ end
407
+ end
408
+
409
+ it "should accept requests on all set proxy domains" do
410
+ log.info "should accept requests on all set proxy domains"
411
+ EM.epoll
412
+ EM.run do
413
+ lambda {
414
+ EventMachine.add_timer(0.2) do
415
+
416
+ multi = EventMachine::MultiRequest.new
417
+ multi_shuffle = []
418
+ i = 0
419
+ while config.proxy.length > i do
420
+ multi_shuffle[i] = Proc.new { |i|
421
+ multi.add i, EventMachine::HttpRequest.new("http://#{config.proxy[i][:host]}:#{config.proxy[i][:port]}"
422
+ ).get({:connect_timeout => 1, :head => {"Host" => "experella.com"}})
423
+ }
424
+ i += 1
425
+ end
426
+ multi_shuffle.shuffle!
427
+ i = 0
428
+ multi_shuffle.each do |p|
429
+ p.call(i)
430
+ i += 1
431
+ end
432
+
433
+ multi.callback do
434
+ unless multi.responses[:errback].empty?
435
+ EventMachine.stop
436
+ raise "http request failed"
437
+ end
438
+ multi.responses[:callback].each_value do |resp|
439
+ resp.response.should start_with "you sent: "
440
+ end
441
+ EventMachine.stop
442
+ end
443
+ end
444
+ }.should_not raise_error
445
+ end
446
+ end
447
+
448
+ it "should be able to handle post requests" do
449
+ log.info "should be able to handle post requests"
450
+ EM.epoll
451
+ EM.run do
452
+ lambda {
453
+ EventMachine.add_timer(0.2) do
454
+ http = EventMachine::HttpRequest.new("http://#{config.proxy[0][:host]}:#{config.proxy[0][:port]}"
455
+ ).post({:connect_timeout => 1, :head => {"Host" => "experella.com"}, :body => "Message body"})
456
+ http.errback {
457
+ EventMachine.stop
458
+ raise "http post failed"
459
+ }
460
+ http.callback {
461
+ http.response.should start_with "you sent: "
462
+ http.response.should end_with "Message body\""
463
+ EventMachine.stop
464
+ }
465
+ end
466
+ }.should_not raise_error
467
+ end
468
+ end
469
+
470
+ end
471
+ end
@@ -0,0 +1,88 @@
1
+ require 'spec_helper'
2
+
3
+ describe ExperellaProxy::Request do
4
+
5
+ let(:request) {
6
+ ExperellaProxy::Request.new("conn")
7
+ }
8
+
9
+ describe "#new" do
10
+
11
+ it "initializes connection variable and reader" do
12
+ request.conn.should eql("conn")
13
+ end
14
+
15
+ it "initializes header hash and reader" do
16
+ request.header.should be_an_instance_of Hash
17
+ end
18
+
19
+ it "initializes uri hash and reader" do
20
+ request.uri.should be_an_instance_of Hash
21
+ end
22
+
23
+ it "initializes keep_alive boolean and reader" do
24
+ request.keep_alive.should be_true
25
+ request.keep_alive = false
26
+ request.keep_alive.should be_false
27
+ end
28
+
29
+ it "initializes chunked boolean and reader" do
30
+ request.chunked.should be_false
31
+ request.chunked = true
32
+ request.chunked.should be_true
33
+ end
34
+ end
35
+
36
+ describe "#<<" do
37
+ it "adds data to send_buffer" do
38
+ request << "hel"
39
+ request << "lo"
40
+ request.flush.should eql("hello")
41
+ end
42
+ end
43
+
44
+ describe "#flush" do
45
+ it "returns and clears the send_buffer" do
46
+ request << "hello"
47
+ request.flush.should eql("hello")
48
+ request.flushed?.should == true
49
+ end
50
+ end
51
+
52
+ describe "#add_uri" do
53
+ it "adds a hash to the URI hash" do
54
+ request.add_uri({:path => "/hello"})
55
+ request.uri[:path].should eql("/hello")
56
+ end
57
+ end
58
+
59
+ describe "#update_header" do
60
+ it "updates the header hash and symbolizes input keys" do
61
+ request.update_header("Host" => "xyz", :request_url => "abcd")
62
+ request.header[:Host].should eql("xyz")
63
+ request.header[:request_url].should eql("abcd")
64
+ request.header["Host"].should be_nil
65
+ end
66
+
67
+ it "overwrites values of existing keys" do
68
+ request.update_header("Host" => "xyz", :request_url => "abcd")
69
+ request.update_header("Host" => "abc")
70
+ request.header.should eql({:Host => "abc", :request_url => "abcd"})
71
+ end
72
+ end
73
+
74
+ describe "#reconstruct_header" do
75
+ it "writes a valid http header into send_buffer" do
76
+ request << "HeaderDummy\r\n\r\n"
77
+ request.update_header({:http_method => "GET", :request_url => "/index",
78
+ "Host" => "localhost", "Connection" => "keep-alive", :"Via-X" => ["Lukas", "Amy", "George"]})
79
+ request.reconstruct_header
80
+ data = request.flush
81
+ data.start_with?("GET /index HTTP/1.1\r\n").should be_true
82
+ data.should include("Connection: keep-alive\r\n")
83
+ data.should include("Via-X: Lukas,Amy,George\r\n")
84
+ data.end_with?("\r\n\r\n").should be_true
85
+ end
86
+ end
87
+
88
+ end
@@ -0,0 +1,44 @@
1
+ require 'spec_helper'
2
+
3
+ describe ExperellaProxy::Response do
4
+
5
+ let(:response) {
6
+ ExperellaProxy::Response.new(ExperellaProxy::Request.new("conn"))
7
+ }
8
+
9
+ describe "#new" do
10
+
11
+ it "initializes header hash and reader" do
12
+ response.header.should be_an_instance_of Hash
13
+ end
14
+ end
15
+
16
+ describe "#update_header" do
17
+ it "updates the header hash and symbolizes input keys" do
18
+ response.update_header("Host" => "xyz", :response_url => "abcd")
19
+ response.header[:Host].should eql("xyz")
20
+ response.header[:response_url].should eql("abcd")
21
+ response.header["Host"].should be_nil
22
+ end
23
+
24
+ it "overwrites values of existing keys" do
25
+ response.update_header("Host" => "xyz", :response_url => "abcd")
26
+ response.update_header("Host" => "abc")
27
+ response.header.should eql({:Host => "abc", :response_url => "abcd"})
28
+ end
29
+ end
30
+
31
+ describe "#reconstruct_header" do
32
+ it "writes a valid http header into send_buffer" do
33
+ response.update_header({"Connection" => "keep-alive",
34
+ :"Via-X" => ["Lukas", "Amy", "George"]})
35
+ response.reconstruct_header
36
+ data = response.flush
37
+ data.start_with?("HTTP/1.1 500 Internal Server Error\r\n").should be_true
38
+ data.should include("Connection: keep-alive\r\n")
39
+ data.should include("Via-X: Lukas,Amy,George\r\n")
40
+ data.end_with?("\r\n\r\n").should be_true
41
+ end
42
+ end
43
+
44
+ end
@@ -0,0 +1,16 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <style type="text/css"> body {
5
+ text-align: center;
6
+ font-family: helvetica, arial;
7
+ font-size: 22px;
8
+ color: #000;
9
+ margin: 20px
10
+ }
11
+ </style>
12
+ </head>
13
+ <body>
14
+ <h2>404 Page not found!</h2>
15
+ </body>
16
+ </html>
@@ -0,0 +1,18 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <style type="text/css"> body {
5
+ text-align: center;
6
+ font-family: helvetica, arial;
7
+ font-size: 22px;
8
+ color: #000;
9
+ margin: 20px
10
+
11
+ }
12
+
13
+ </style>
14
+ </head>
15
+ <body>
16
+ <h2> 503 Service unavailable!</h2>
17
+ </body>
18
+ </html>