puffing-billy 0.1.0

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.
@@ -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