experella-proxy 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
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>