puffing-billy 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,2 @@
1
+ node_modules/
2
+ log/test.log
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in puffing-billy.gemspec
4
+ gemspec
@@ -0,0 +1,96 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ puffing-billy (0.0.1)
5
+ em-http-request
6
+ eventmachine
7
+ eventmachine_httpserver
8
+ http_parser.rb
9
+ yajl-ruby
10
+
11
+ GEM
12
+ remote: https://rubygems.org/
13
+ specs:
14
+ addressable (2.3.2)
15
+ capybara (1.1.2)
16
+ mime-types (>= 1.16)
17
+ nokogiri (>= 1.3.3)
18
+ rack (>= 1.0.0)
19
+ rack-test (>= 0.5.4)
20
+ selenium-webdriver (~> 2.0)
21
+ xpath (~> 0.1.4)
22
+ capybara-webkit (0.12.1)
23
+ capybara (>= 1.0.0, < 1.2)
24
+ json
25
+ childprocess (0.3.5)
26
+ ffi (~> 1.0, >= 1.0.6)
27
+ cookiejar (0.3.0)
28
+ daemons (1.1.9)
29
+ diff-lcs (1.1.3)
30
+ em-http-request (1.0.3)
31
+ addressable (>= 2.2.3)
32
+ cookiejar
33
+ em-socksify
34
+ eventmachine (>= 1.0.0.beta.4)
35
+ http_parser.rb (>= 0.5.3)
36
+ em-socksify (0.2.1)
37
+ eventmachine (>= 1.0.0.beta.4)
38
+ eventmachine (1.0.0)
39
+ eventmachine_httpserver (0.2.1)
40
+ faraday (0.8.4)
41
+ multipart-post (~> 1.1)
42
+ faye-websocket (0.4.6)
43
+ eventmachine (>= 0.12.0)
44
+ ffi (1.1.5)
45
+ http_parser.rb (0.5.3)
46
+ json (1.7.5)
47
+ libwebsocket (0.1.5)
48
+ addressable
49
+ mime-types (1.19)
50
+ multi_json (1.3.6)
51
+ multipart-post (1.1.5)
52
+ nokogiri (1.5.5)
53
+ poltergeist (0.7.0)
54
+ capybara (~> 1.1)
55
+ childprocess (~> 0.3)
56
+ faye-websocket (~> 0.4, >= 0.4.4)
57
+ http_parser.rb (~> 0.5.3)
58
+ multi_json (~> 1.0)
59
+ rack (1.4.1)
60
+ rack-test (0.6.2)
61
+ rack (>= 1.0)
62
+ rspec (2.11.0)
63
+ rspec-core (~> 2.11.0)
64
+ rspec-expectations (~> 2.11.0)
65
+ rspec-mocks (~> 2.11.0)
66
+ rspec-core (2.11.1)
67
+ rspec-expectations (2.11.3)
68
+ diff-lcs (~> 1.1.3)
69
+ rspec-mocks (2.11.3)
70
+ rubyzip (0.9.9)
71
+ selenium-webdriver (2.25.0)
72
+ childprocess (>= 0.2.5)
73
+ libwebsocket (~> 0.1.3)
74
+ multi_json (~> 1.0)
75
+ rubyzip
76
+ thin (1.4.1)
77
+ daemons (>= 1.0.9)
78
+ eventmachine (>= 0.12.6)
79
+ rack (>= 1.0.0)
80
+ xpath (0.1.4)
81
+ nokogiri (~> 1.3)
82
+ yajl-ruby (1.1.0)
83
+
84
+ PLATFORMS
85
+ ruby
86
+
87
+ DEPENDENCIES
88
+ capybara
89
+ capybara-webkit
90
+ faraday
91
+ poltergeist
92
+ puffing-billy!
93
+ rack
94
+ rspec
95
+ selenium-webdriver
96
+ thin
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Olly Smith
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,78 @@
1
+ # Puffing Billy
2
+
3
+ A stubbing proxy server for ruby. Connect it to your browser in integration tests to fake
4
+ interactions with remote HTTP(S) servers.
5
+
6
+ ![](http://upload.wikimedia.org/wikipedia/commons/0/01/Puffing_Billy_1862.jpg)
7
+
8
+ ## Overview
9
+
10
+ The thirty second version:
11
+
12
+ ```ruby
13
+ it 'should stub google' do
14
+ proxy.stub('http://www.google.com').and_return(:text => "I'm not Google!")
15
+ visit 'http://www.google.com'
16
+ page.should have_content("I'm not Google!")
17
+ end
18
+ ```
19
+
20
+ ## Installation
21
+
22
+ Add this line to your application's Gemfile:
23
+
24
+ gem 'puffing-billy', :require => 'billy'
25
+
26
+ And then execute:
27
+
28
+ $ bundle
29
+
30
+ Or install it yourself as:
31
+
32
+ $ gem install puffing-billy
33
+
34
+ ## Usage
35
+
36
+ In your `spec_helper.rb`:
37
+
38
+ ```ruby
39
+ require 'billy/rspec'
40
+
41
+ # select a driver for your chosen browser environment
42
+ Capybara.javascript_driver = :selenium_billy
43
+ # Capybara.javascript_driver = :webkit_billy
44
+ # Capybara.javascript_driver = :poltergeist_billy
45
+ ```
46
+
47
+ In your tests:
48
+
49
+ ```ruby
50
+ # stub and return text, json, jsonp (or anything else)
51
+ proxy.stub('http://example.com/text').and_return(:text => 'Foobar')
52
+ proxy.stub('http://example.com/json').and_return(:json => { :foo => 'bar' })
53
+ proxy.stub('http://example.com/jsonp').and_return(:jsonp => { :foo => 'bar' })
54
+ proxy.stub('http://example.com/wtf').and_return(:body => 'WTF!?', :content_type => 'text/wtf')
55
+
56
+ # stub redirections and other return codes
57
+ proxy.stub('http://example.com/redirect').and_return(:redirect_to => 'http://example.com/other')
58
+ proxy.stub('http://example.com/missing').and_return(:code => 404, :body => 'Not found')
59
+
60
+ # even stub HTTPS!
61
+ proxy.stub('https://example.com/secure').and_return(:text => 'secrets!!1!')
62
+ ```
63
+
64
+ Stubs are reset between tests. Any requests that are not stubbed will be
65
+ proxied to the remote server.
66
+
67
+ ## Contributing
68
+
69
+ 1. Fork it
70
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
71
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
72
+ 4. Push to the branch (`git push origin my-new-feature`)
73
+ 5. Create new Pull Request
74
+
75
+ ## TODO
76
+
77
+ 1. Integration for test frameworks other than rspec.
78
+ 2. Caching (for super awesome improved test performance).
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1 @@
1
+ See example specs in `spec/requests/examples`.
@@ -0,0 +1,59 @@
1
+ <div id="fb-root"></div>
2
+ <script>
3
+ // Load the SDK Asynchronously
4
+ (function(d){
5
+ var js, id = 'facebook-jssdk', ref = d.getElementsByTagName('script')[0];
6
+ if (d.getElementById(id)) {return;}
7
+ js = d.createElement('script'); js.id = id; js.async = true;
8
+ js.src = "//connect.facebook.net/en_US/all.js";
9
+ ref.parentNode.insertBefore(js, ref);
10
+ }(document));
11
+
12
+ // Init the SDK upon load
13
+ window.fbAsyncInit = function() {
14
+ FB.init({
15
+ appId : '408416075843608', // App ID
16
+ //channelUrl : '//'+window.location.hostname+'/channel', // Path to your Channel File
17
+ status : true, // check login status
18
+ cookie : true, // enable cookies to allow the server to access the session
19
+ xfbml : true // parse XFBML
20
+ });
21
+
22
+ // listen for and handle auth.statusChange events
23
+ FB.Event.subscribe('auth.statusChange', function(response) {
24
+ if (response.authResponse) {
25
+ // user has auth'd your app and is logged into Facebook
26
+ FB.api('/me', function(me){
27
+ if (me.name) {
28
+ document.getElementById('auth-displayname').innerHTML = me.name;
29
+ }
30
+ })
31
+ document.getElementById('auth-loggedout').style.display = 'none';
32
+ document.getElementById('auth-loggedin').style.display = 'block';
33
+ } else {
34
+ // user has not auth'd your app, or is not logged into Facebook
35
+ document.getElementById('auth-loggedout').style.display = 'block';
36
+ document.getElementById('auth-loggedin').style.display = 'none';
37
+ }
38
+ });
39
+
40
+ // respond to clicks on the login and logout links
41
+ document.getElementById('auth-loginlink').addEventListener('click', function(){
42
+ FB.login();
43
+ });
44
+ document.getElementById('auth-logoutlink').addEventListener('click', function(){
45
+ FB.logout();
46
+ });
47
+ }
48
+ </script>
49
+
50
+ <h1>Facebook Client-side Authentication Example</h1>
51
+ <div id="auth-status">
52
+ <div id="auth-loggedout">
53
+ <a href="#" id="auth-loginlink">Login</a>
54
+ </div>
55
+ <div id="auth-loggedin" style="display:none">
56
+ Hi, <span id="auth-displayname"></span>
57
+ (<a href="#" id="auth-logoutlink">logout</a>)
58
+ </div>
59
+ </div>
@@ -0,0 +1,5 @@
1
+ require "billy/version"
2
+ require "billy/config"
3
+ require "billy/proxy_request_stub"
4
+ require "billy/proxy"
5
+ require "billy/proxy_connection"
@@ -0,0 +1,28 @@
1
+ require 'logger'
2
+
3
+ module Billy
4
+ class Config
5
+ attr_accessor :logger
6
+
7
+ def initialize
8
+ @logger = defined?(Rails) ? Rails.logger : Logger.new(STDOUT)
9
+ end
10
+ end
11
+
12
+ def self.configure
13
+ yield config if block_given?
14
+ config
15
+ end
16
+
17
+ def self.log(*args)
18
+ unless config.logger.nil?
19
+ config.logger.send(*args)
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def self.config
26
+ @config ||= Config.new
27
+ end
28
+ end
@@ -0,0 +1,22 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIDojCCAooCCQCYeVsjl+UFxzANBgkqhkiG9w0BAQUFADCBkjELMAkGA1UEBhMC
3
+ VVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MSEwHwYDVQQK
4
+ ExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxFjAUBgNVBAMTDVB1ZmZpbmcgQmls
5
+ bHkxIzAhBgkqhkiG9w0BCQEWFG9sbHkuc21pdGhAZ21haWwuY29tMB4XDTEyMTAw
6
+ MjA2NDIxMVoXDTEzMTAwMjA2NDIxMVowgZIxCzAJBgNVBAYTAlVTMQswCQYDVQQI
7
+ EwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEhMB8GA1UEChMYSW50ZXJuZXQg
8
+ V2lkZ2l0cyBQdHkgTHRkMRYwFAYDVQQDEw1QdWZmaW5nIEJpbGx5MSMwIQYJKoZI
9
+ hvcNAQkBFhRvbGx5LnNtaXRoQGdtYWlsLmNvbTCCASIwDQYJKoZIhvcNAQEBBQAD
10
+ ggEPADCCAQoCggEBALW7WQIIGCgUk28x1eIV0+VDG6SZV8dosJPU3WM4zLT0KLsv
11
+ 2+5KNO373KyXhIDQqkv+4+1myVYj5wkvDSFMLYvBTNmypS7Xh6+wyKjQQ5wupXoB
12
+ BNrJw5CbfUX9Kwq31H6aO1ItWZ6QHjW4dpE3AOu7ohwwyyL2Q0QkzTeP5b6SofbV
13
+ 9y1DpeH5L2an84LIjm1XrNbswY0p0CDT7TTsmYoXnw0MD5I73rT4arOOKWdVrD/G
14
+ lbvfcZN6c+nN713tGI9XfM5cQEXUsbSY4NKuF+jn7B2tsx+QRVwUyMwS8ZVpDt5q
15
+ DMBwROskWTMLKxeuxxpmjFWHlLuBlJ0M/rKII98CAwEAATANBgkqhkiG9w0BAQUF
16
+ AAOCAQEAMkhR7JDQKcYDy5Yb80cPIjw5kaEfTb+/Vh6DLLEtwEVIc2PcIvXCBZRo
17
+ 79Esa42BaNEaWPxRVAs0Doubh4IdGk9qp9a/71gqhi3iNe//depX5VLf1+5LnQtV
18
+ BbFruXUkzOF4kykm5Fcf9yQ9W951d0qaT6K9LefSsaoMrQjgKCeFAcIOXjqHyWns
19
+ KAO34C1kzAFemMY20RcgZf9/+09Zs/ZiDBeoWTc+juaa9zCgvg/1FKHqsA/iLg6/
20
+ VcdOquI9fJ5hRTZFkPoFLpxf2O1H3IpyNXmpcJDFvKfh7wIcHkLWIg5BFXc2S4+v
21
+ bNFEKqipZH+D9++jXGyumxA8VoZOOA==
22
+ -----END CERTIFICATE-----
@@ -0,0 +1,27 @@
1
+ -----BEGIN RSA PRIVATE KEY-----
2
+ MIIEowIBAAKCAQEAtbtZAggYKBSTbzHV4hXT5UMbpJlXx2iwk9TdYzjMtPQouy/b
3
+ 7ko07fvcrJeEgNCqS/7j7WbJViPnCS8NIUwti8FM2bKlLteHr7DIqNBDnC6legEE
4
+ 2snDkJt9Rf0rCrfUfpo7Ui1ZnpAeNbh2kTcA67uiHDDLIvZDRCTNN4/lvpKh9tX3
5
+ LUOl4fkvZqfzgsiObVes1uzBjSnQINPtNOyZihefDQwPkjvetPhqs44pZ1WsP8aV
6
+ u99xk3pz6c3vXe0Yj1d8zlxARdSxtJjg0q4X6OfsHa2zH5BFXBTIzBLxlWkO3moM
7
+ wHBE6yRZMwsrF67HGmaMVYeUu4GUnQz+sogj3wIDAQABAoIBAEM8cF7vDbjue+m8
8
+ 32wJNV9yJ60LSs2tLv9S1yHZpusgFl3DBDSyYcjW0TtNx6k9CnSZdkykJcNn/xeH
9
+ v+zc2VEGkF9O2Axvk3TuDB9hBlKnc3OjIt+rnF5JGN0nIKCTiNvaRi5ONwUSPwsT
10
+ F1L8rauJvR1+8/kYcaSplP+EjrSlvaKtUPNKfB8V6IktVLHiBUFs7nWoOGSaEB4/
11
+ 8jlji4s8xzVammTRg175mhOkgqdI+cWfXnn8PhOJZ2XjZz5eFOWxdvNldBbsm9Yc
12
+ g3URB9FpOPMY8DvgZNrAYp6/7C9ACecAIZAnDK0/aQOa95UeeCEbmAv59F2OqOc2
13
+ 35HUSrkCgYEA55e+o7pxUY7YPVopBz27JWANk/zwMN1ZgDlR87Q0ENmrjOB8kPmz
14
+ 02SHuVoXMPj/SD/S4J1NY2o7u5ofSpxUsdofCETsCeCbqhshRLqCu7yCjGagHknY
15
+ M4dJdRoFPN03gy+qHB7EKT0U89lvwRsjShc8h56wXsRHP6qM2uVgGyUCgYEAyOJh
16
+ ZPKBa0WVG9NxFS4Ej7CDpmWA415PyCrTK9cA2LCA0sWXHAhJ9guUx4zlqwLkfYBr
17
+ mmobAKjllFOUiECFLs49Oy/vD9Kw/ga9KO6eU9E/j9XHLVYupkPEVWeM7jzaQBul
18
+ CvMcPGgXX/84Xgtjjim8RuAPIgmRHSDfqRFUtbMCgYEAmQwxEiZuKMXLpY/luUFU
19
+ Yfi+QGRRnxlIwnIe9HzMQ651rl3UNEKwUi0HfLhKxzRmECsNgx6xO9fCrdHGiBoT
20
+ 5o0NIPvbORPUC3BuZesT5llHtN1FR37pf/QR2W9essBGpU1kj7zNSatyI0w4jFcQ
21
+ 1S/R8pYuXBI+O5bMCwS2pHkCgYBqFKG53RXav/PtrcqZlKNz/ZKH3DIj3zniSjsZ
22
+ e4BG7W4Z353cf8QO2i7G8fCWTgC7BYXNFRsNTiNuIHTfPrMV9HMBPl7PzEMK4iQh
23
+ 6WBSgr0+B3YWytv3kPGs5/HUHO5jzDVrgtX2UEGHwA7UGs+H0yJJiyhyoPqwlxuE
24
+ /FHvYQKBgEJaI/DaSpxJotq56lBHzTte1icbD8IFXMlbmWyIFxONWxlz98tQ2ypp
25
+ WhMGIzMsFE1X+P8vRbc865t36UMpucjRuAJ3MTeeCa60EghmdfajzA2ZWh/MnEyA
26
+ yvTr72g5gj5Yi/Zbiy1xeTo3UeaBxiCi8xyWAGodYg8891UVKqJu
27
+ -----END RSA PRIVATE KEY-----
@@ -0,0 +1,67 @@
1
+ require 'cgi'
2
+ require 'uri'
3
+ require 'eventmachine'
4
+
5
+ module Billy
6
+ class Proxy
7
+ def initialize
8
+ reset
9
+ end
10
+
11
+ def start
12
+ Thread.new do
13
+ EM.run do
14
+ EM.error_handler do |e|
15
+ puts e.class.name, e
16
+ puts e.backtrace.join("\n")
17
+ end
18
+
19
+ @signature = EM.start_server('127.0.0.1', 0, ProxyConnection) do |p|
20
+ p.handler = self
21
+ end
22
+ end
23
+ end
24
+ sleep(0.01) while @signature.nil?
25
+ end
26
+
27
+ def url
28
+ "http://#{host}:#{port}"
29
+ end
30
+
31
+ def host
32
+ 'localhost'
33
+ end
34
+
35
+ def port
36
+ Socket.unpack_sockaddr_in(EM.get_sockname(@signature)).first
37
+ end
38
+
39
+ def call(method, url, headers, body)
40
+ stub = find_stub(method, url)
41
+ unless stub.nil?
42
+ query_string = URI.parse(url).query || ""
43
+ params = CGI.parse(query_string)
44
+ stub.call(params, headers, body)
45
+ end
46
+ end
47
+
48
+ def stub(url, options = {})
49
+ ret = ProxyRequestStub.new(url, options)
50
+ @stubs << ret
51
+ ret
52
+ end
53
+
54
+ def reset
55
+ @stubs = []
56
+ end
57
+
58
+ protected
59
+
60
+ def find_stub(method, url)
61
+ @stubs.each do |stub|
62
+ return stub if stub.matches?(method, url)
63
+ end
64
+ nil
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,115 @@
1
+ require 'eventmachine'
2
+ require 'http/parser'
3
+ require 'em-http'
4
+ require 'evma_httpserver'
5
+
6
+ module Billy
7
+ class ProxyConnection < EventMachine::Connection
8
+ attr_accessor :handler
9
+
10
+ def post_init
11
+ @parser = Http::Parser.new(self)
12
+ @header_data = ""
13
+ end
14
+
15
+ def receive_data(data)
16
+ @header_data << data if @headers.nil?
17
+ begin
18
+ @parser << data
19
+ rescue HTTP::Parser::Error
20
+ if @parser.http_method == 'CONNECT'
21
+ # work-around for CONNECT requests until https://github.com/tmm1/http_parser.rb/pull/15 gets merged
22
+ if @header_data.end_with?("\r\n\r\n")
23
+ restart_with_ssl(@header_data.split("\r\n").first.split(/\s+/)[1])
24
+ end
25
+ else
26
+ close_connection
27
+ end
28
+ end
29
+ end
30
+
31
+ def on_message_begin
32
+ @headers = nil
33
+ @body = ''
34
+ end
35
+
36
+ def on_headers_complete(headers)
37
+ @headers = headers
38
+ end
39
+
40
+ def on_body(chunk)
41
+ @body << chunk
42
+ end
43
+
44
+ def on_message_complete
45
+ if @parser.http_method == 'CONNECT'
46
+ restart_with_ssl(@parser.request_url)
47
+ else
48
+ if @ssl
49
+ @url = "https://#{@ssl}#{@parser.request_url}"
50
+ else
51
+ @url = @parser.request_url
52
+ end
53
+ handle_request
54
+ end
55
+ end
56
+
57
+ protected
58
+
59
+ def restart_with_ssl(url)
60
+ @ssl = url
61
+ @parser = Http::Parser.new(self)
62
+ send_data("HTTP/1.0 200 Connection established\r\nProxy-agent: Puffing-Billy/0.0.0\r\n\r\n")
63
+ start_tls(
64
+ :private_key_file => File.expand_path('../mitm.key', __FILE__),
65
+ :cert_chain_file => File.expand_path('../mitm.crt', __FILE__)
66
+ )
67
+ end
68
+
69
+ def handle_request
70
+ if handler && handler.respond_to?(:call)
71
+ result = handler.call(@parser.http_method, @url, @headers, @body)
72
+ end
73
+ if result
74
+ Billy.log(:info, "STUB #{@parser.http_method} #{@url}")
75
+ response = EM::DelegatedHttpResponse.new(self)
76
+ response.status = result[0]
77
+ response.headers = result[1].merge('Connection' => 'close')
78
+ response.content = result[2]
79
+ response.send_response
80
+ else
81
+ Billy.log(:info, "PROXY #{@parser.http_method} #{@url}")
82
+ proxy_request
83
+ end
84
+ end
85
+
86
+ def proxy_request
87
+ headers = Hash[@headers.map { |k,v| [k.downcase, v] }]
88
+
89
+ req = EventMachine::HttpRequest.new(@url)
90
+ req_opts = {
91
+ :redirects => 0,
92
+ :keepalive => false,
93
+ :head => headers,
94
+ :ssl => { :verify => false }
95
+ }
96
+ req_opts[:body] = @body if @body
97
+
98
+ req = req.send(@parser.http_method.downcase, req_opts)
99
+
100
+ req.errback do
101
+ puts "Request failed: #{url}"
102
+ close_connection
103
+ end
104
+
105
+ req.callback do
106
+ res = EM::DelegatedHttpResponse.new(self)
107
+ res.status = req.response_header.status
108
+ res.headers = req.response_header.merge('Connection' => 'close')
109
+ res.content = req.response
110
+ res.send_response
111
+ end
112
+ end
113
+
114
+ end
115
+ end