whack-a-node 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -2,8 +2,11 @@ source "http://rubygems.org"
2
2
  # Add dependencies required to use your gem here.
3
3
  # Example:
4
4
  # gem "activesupport", ">= 2.3.5"
5
- gem 'rack-proxy'
6
5
  gem 'rack'
6
+ gem 'dnode'
7
+ gem 'eventmachine'
8
+ gem 'events'
9
+ gem 'json'
7
10
 
8
11
  # Add dependencies to develop your gem here.
9
12
  # Include everything needed to run rake, tests, features, etc.
@@ -4,14 +4,16 @@ GEM
4
4
  addressable (2.2.5)
5
5
  crack (0.1.8)
6
6
  diff-lcs (1.1.2)
7
+ dnode (0.0.1)
8
+ eventmachine (0.12.10)
9
+ events (0.9.5)
7
10
  git (1.2.5)
8
11
  jeweler (1.5.2)
9
12
  bundler (~> 1.0.0)
10
13
  git (>= 1.2.5)
11
14
  rake
15
+ json (1.5.1)
12
16
  rack (1.2.2)
13
- rack-proxy (0.3.4)
14
- rack
15
17
  rack-test (0.5.7)
16
18
  rack (>= 1.0)
17
19
  rake (0.8.7)
@@ -33,9 +35,12 @@ PLATFORMS
33
35
 
34
36
  DEPENDENCIES
35
37
  bundler (~> 1.0.0)
38
+ dnode
39
+ eventmachine
40
+ events
36
41
  jeweler (~> 1.5.2)
42
+ json
37
43
  rack
38
- rack-proxy
39
44
  rack-test
40
45
  rcov
41
46
  rspec (~> 2.3.0)
@@ -4,20 +4,58 @@ Node apps running in any rack server as a endpoint, and potentially a middleware
4
4
 
5
5
  Leverage authentication, authorization and more in front of your node apps, letting node be used for high concurrency portions of your app.
6
6
 
7
+ Reach a node app as an RPC directly from your Rails models (coming soon)
8
+
9
+ == Prerequisites
10
+
11
+ * Ruby 1.8.7 or 1.9.2 work fine
12
+ * Node.js
13
+ brew install node (or equivalent for your environment)
14
+ * Rack[http://rack.rubyforge.org/] (or Rails 3)
15
+
16
+ == Installation
17
+
18
+ sudo gem install whack-a-node
19
+
7
20
  == Usage
8
21
 
9
- match "/proxy" => WhackANode.new("/")
22
+ In Rails 3's routes.rb:
10
23
 
11
- This will attempt to reach a node server on localhost at port 8810. The response will be proxied by Rack.
24
+ match "/proxy" => WhackANode::Proxy.new("/")
12
25
 
13
- match "/redirect" => WhackANode.new("/","www.example.com", 2306, true)
26
+ This will attempt to reach a node server on localhost at port 8810. The response will be proxied by Rack.
14
27
 
15
- This will redirect to a node server at www.example.com on port 2306.
28
+ match "/redirect" => WhackANode::Redirect.new("/","www.example.com", 2306)
16
29
 
17
- In this case, the last param causes the WhackANode to actually redirect instead of proxy the request. In some cases, this can result in faster performance, as you don't have to have a Rack instance sitting around for the whole request.
30
+ This will redirect to a node server at www.example.com on port 2306. In some cases, this can result in faster performance, as you don't have to have a Rack instance sitting around for the whole request.
18
31
 
19
32
  Generally speaking, using the proxy over a Rails controller ends up being about 8-10 times faster. While still much slower than Node directly, this can still yield benefit.
20
33
 
34
+ == Examples
35
+
36
+ === Node server code
37
+
38
+ server.js:
39
+
40
+ var http = require('http');
41
+ http.createServer(function (req, res) {
42
+ res.writeHead(200, {'Content-Type': 'text/plain'});
43
+ res.end('Hello World\n');
44
+ }).listen(8810, "127.0.0.1");
45
+ console.log('Server running at http://127.0.0.1:8810/');
46
+
47
+ Start the node server by running node server.js
48
+
49
+ === Rails server
50
+ Create a new Rails app. Add the following to routes.rb
51
+
52
+ match "/proxy" => WhackANode::Proxy.new("/") -- WhackANode defaults to localhost port 8810, so this will work with above server
53
+
54
+ Add Whack-A-Node to your Gemfile:
55
+
56
+ gem 'whack-a-node'
57
+
58
+ Start your Rails app. Hit http://localhost:3000/proxy. You should see 'Hello World' generated by Node.js
21
59
 
22
60
  == Contributing to whack-a-node
23
61
 
data/Rakefile CHANGED
@@ -8,7 +8,6 @@ rescue Bundler::BundlerError => e
8
8
  exit e.status_code
9
9
  end
10
10
  require 'rake'
11
-
12
11
  require 'jeweler'
13
12
  Jeweler::Tasks.new do |gem|
14
13
  # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.1
1
+ 0.0.2
@@ -1,105 +1 @@
1
- require 'rack/streaming_proxy'
2
- class WhackANode
3
-
4
- def initialize(path,host="localhost", port="8810",redirect=false)
5
- @path = path
6
- @host = host
7
- @port = port
8
- @redirect = redirect
9
- @paths = {}
10
- end
11
-
12
- def uri
13
- URI("http://#{@host}:#{@port}#{@path}")
14
- end
15
-
16
- def proxy_request
17
- uri = self.uri
18
- session = Net::HTTP.new(uri.host, uri.port)
19
- session.start {|http|
20
- req = Net::HTTP::Get.new(uri.request_uri)
21
- body = ''
22
- res = http.request(req) do |res|
23
- res.read_body do |segment|
24
- body << segment
25
- end
26
- end
27
-
28
- [res.code, create_response_headers(res), [body]]
29
- }
30
- end
31
-
32
- def forward_request
33
- [ 302, {'Location'=> uri.to_s }, [] ]
34
- end
35
-
36
- def call(env)
37
- return @redirect ? forward_request : proxy_request
38
- end
39
-
40
- private
41
-
42
-
43
- def get_matcher_and_url path
44
- matches = @paths.select do |matcher, url|
45
- match_path(path, matcher)
46
- end
47
-
48
- if matches.length < 1
49
- nil
50
- elsif matches.length > 1
51
- raise AmbiguousProxyMatch.new(path, matches)
52
- else
53
- matches.first.map{|a| a.dup}
54
- end
55
- end
56
-
57
- def create_response_headers http_response
58
- response_headers = Rack::Utils::HeaderHash.new(http_response.to_hash)
59
- # handled by Rack
60
- response_headers.delete('status')
61
- # TODO: figure out how to handle chunked responses
62
- response_headers.delete('transfer-encoding')
63
- # TODO: Verify Content Length, and required Rack headers
64
- response_headers
65
- end
66
-
67
- def match_path(path, matcher)
68
- if matcher.is_a?(Regexp)
69
- path.match(matcher)
70
- else
71
- path.match(/^#{matcher.to_s}/)
72
- end
73
- end
74
-
75
- def get_uri(url, matcher, path)
76
- if url =~/\$\d/
77
- match_path(path, matcher).to_a.each_with_index { |m, i| url.gsub!("$#{i.to_s}", m) }
78
- URI(url)
79
- else
80
- URI.join(url, path)
81
- end
82
- end
83
-
84
- def reverse_proxy matcher, url, opts={}
85
- raise GenericProxyURI.new(url) if matcher.is_a?(String) && URI(url).class == URI::Generic
86
- @paths.merge!(matcher => url)
87
- @opts.merge!(opts)
88
- end
89
-
90
-
91
- def rewrite_env(env)
92
- env["PORT"] = "8000"
93
-
94
- env
95
- end
96
-
97
- def rewrite_response(triplet)
98
- status, headers, body = triplet
99
-
100
- headers["X-Foo"] = "Bar"
101
-
102
- triplet
103
- end
104
-
105
- end
1
+ require File.expand_path(File.dirname(__FILE__) + "/whack_a_node")
@@ -0,0 +1,4 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/whack_a_node/proxy')
2
+ require File.expand_path(File.dirname(__FILE__) + '/whack_a_node/redirect')
3
+ require File.expand_path(File.dirname(__FILE__) + '/whack_a_node/whacky')
4
+ require File.expand_path(File.dirname(__FILE__) + '/whack_a_node/rpc')
@@ -0,0 +1,44 @@
1
+ require 'net/http'
2
+ module WhackANode
3
+ class Proxy
4
+
5
+ def initialize(path="/",host="localhost", port="8810")
6
+ @path = path
7
+ @host = host
8
+ @port = port
9
+ end
10
+
11
+ def call(env)
12
+ uri = self.uri
13
+ session = Net::HTTP.new(uri.host, uri.port)
14
+ session.start {|http|
15
+ req = Net::HTTP::Get.new(uri.request_uri)
16
+ body = ''
17
+ res = http.request(req) do |res|
18
+ res.read_body do |segment|
19
+ body << segment
20
+ end
21
+ end
22
+
23
+ [res.code, create_response_headers(res), [body]]
24
+ }
25
+
26
+ end
27
+
28
+ def uri
29
+ URI("http://#{@host}:#{@port}#{@path}")
30
+ end
31
+
32
+ private
33
+
34
+ def create_response_headers http_response
35
+ response_headers = Rack::Utils::HeaderHash.new(http_response.to_hash)
36
+ # handled by Rack
37
+ response_headers.delete('status')
38
+ # TODO: figure out how to handle chunked responses
39
+ response_headers.delete('transfer-encoding')
40
+ # TODO: Verify Content Length, and required Rack headers
41
+ response_headers
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,19 @@
1
+ module WhackANode
2
+ class Redirect
3
+
4
+ def initialize(path="/",host="localhost", port="8810")
5
+ @path = path
6
+ @host = host
7
+ @port = port
8
+ end
9
+
10
+ def call(env)
11
+ [ 302, {'Location'=> uri.to_s }, [] ]
12
+ end
13
+
14
+ def uri
15
+ URI("http://#{@host}:#{@port}#{@path}")
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,104 @@
1
+ require 'dnode'
2
+ require 'eventmachine'
3
+ require 'events'
4
+ module WhackANode
5
+ class Rpc
6
+ def initialize(path="/",host="localhost", port="8820", redirect=false)
7
+ @path = path
8
+ @host = host
9
+ @port = port
10
+ @redirect = redirect
11
+
12
+ end
13
+
14
+ def proxy_request
15
+ uri = self.uri
16
+ session = Net::HTTP.new(uri.host, uri.port)
17
+ session.start {|http|
18
+ req = Net::HTTP::Get.new(uri.request_uri)
19
+ body = ''
20
+ res = http.request(req) do |res|
21
+ res.read_body do |segment|
22
+ body << segment
23
+ end
24
+ end
25
+
26
+ [res.code, create_response_headers(res), [body]]
27
+ }
28
+ end
29
+
30
+ def forward_request
31
+ [ 302, {'Location'=> uri.to_s }, [] ]
32
+ end
33
+
34
+ def call(env)
35
+ return @redirect ? forward_request : proxy_request
36
+ end
37
+
38
+ private
39
+
40
+
41
+ def get_matcher_and_url path
42
+ matches = @paths.select do |matcher, url|
43
+ match_path(path, matcher)
44
+ end
45
+
46
+ if matches.length < 1
47
+ nil
48
+ elsif matches.length > 1
49
+ raise AmbiguousProxyMatch.new(path, matches)
50
+ else
51
+ matches.first.map{|a| a.dup}
52
+ end
53
+ end
54
+
55
+ def create_response_headers http_response
56
+ response_headers = Rack::Utils::HeaderHash.new(http_response.to_hash)
57
+ # handled by Rack
58
+ response_headers.delete('status')
59
+ # TODO: figure out how to handle chunked responses
60
+ response_headers.delete('transfer-encoding')
61
+ # TODO: Verify Content Length, and required Rack headers
62
+ response_headers
63
+ end
64
+
65
+ def match_path(path, matcher)
66
+ if matcher.is_a?(Regexp)
67
+ path.match(matcher)
68
+ else
69
+ path.match(/^#{matcher.to_s}/)
70
+ end
71
+ end
72
+
73
+ def get_uri(url, matcher, path)
74
+ if url =~/\$\d/
75
+ match_path(path, matcher).to_a.each_with_index { |m, i| url.gsub!("$#{i.to_s}", m) }
76
+ URI(url)
77
+ else
78
+ URI.join(url, path)
79
+ end
80
+ end
81
+
82
+ def reverse_proxy matcher, url, opts={}
83
+ raise GenericProxyURI.new(url) if matcher.is_a?(String) && URI(url).class == URI::Generic
84
+ @paths.merge!(matcher => url)
85
+ @opts.merge!(opts)
86
+ end
87
+
88
+
89
+ def rewrite_env(env)
90
+ env["PORT"] = "8000"
91
+
92
+ env
93
+ end
94
+
95
+ def rewrite_response(triplet)
96
+ status, headers, body = triplet
97
+
98
+ headers["X-Foo"] = "Bar"
99
+
100
+ triplet
101
+ end
102
+
103
+ end
104
+ end
@@ -0,0 +1,13 @@
1
+ module WhackANode
2
+ module Whacky
3
+ def initialize(path="/",host="localhost", port="8810")
4
+ @path = path
5
+ @host = host
6
+ @port = port
7
+ end
8
+
9
+ def uri
10
+ URI("http://#{@host}:#{@port}#{@path}")
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,24 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+ require 'rack/test'
3
+
4
+ describe "Rpc" do
5
+ include Rack::Test::Methods
6
+ include WebMock::API
7
+ class DwhackyTest < WhackANode::Rpc
8
+ def rewrite_env(env)
9
+ env['PORT'] = 90220
10
+ end
11
+ end
12
+
13
+ def app
14
+ WhackANode::Rpc.new
15
+ end
16
+
17
+ it "should have a port of 90210" do
18
+ stub_request(:get, "http://localhost:8810/").
19
+ with(:headers => {'Accept'=>'*/*', 'User-Agent'=>'Ruby'}).
20
+ to_return(:status => 200, :body => "", :headers => {})
21
+ #@app.should_not be_nil
22
+ end
23
+ end
24
+
@@ -3,10 +3,7 @@ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
3
  require 'rspec'
4
4
  require 'rack/test'
5
5
  require 'whack-a-node'
6
- require 'rack/reverse_proxy'
7
- require 'rack/streaming_proxy'
8
6
  require 'webmock/rspec'
9
-
10
7
  # Requires supporting files with custom matchers and macros, etc,
11
8
  # in ./support/ and its subdirectories.
12
9
  Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
@@ -1,9 +1,10 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
2
  require 'rack/test'
3
- include Rack::Test::Methods
4
3
 
5
4
  describe "WhackANode" do
6
- class WhackyTest < WhackANode
5
+ include Rack::Test::Methods
6
+ include WebMock::API
7
+ class WhackyTest < WhackANode::Proxy
7
8
  def rewrite_env(env)
8
9
  env['PORT'] = 90210
9
10
  end
@@ -17,8 +18,13 @@ describe "WhackANode" do
17
18
  #@app = WhackyTest.new
18
19
  #end
19
20
  it "should have a port of 90210" do
20
- get "/"
21
- last_response.should_not be_nil
21
+ stub_request(:get, "http://localhost:8810/").
22
+ with(:headers => {'Accept'=>'*/*', 'User-Agent'=>'Ruby'}).
23
+ to_return(:status => 200, :body => "", :headers => {})
24
+
25
+ #stub_request(:get, 'http://example.com/').to_return({:body => "Proxied App"})
26
+ #get '/'
27
+ #last_response.should_not be_nil
22
28
  #@app.should_not be_nil
23
29
  end
24
30
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: whack-a-node
3
3
  version: !ruby/object:Gem::Version
4
- hash: 29
4
+ hash: 27
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 1
10
- version: 0.0.1
9
+ - 2
10
+ version: 0.0.2
11
11
  platform: ruby
12
12
  authors:
13
13
  - Niels Meersschaert
@@ -16,7 +16,7 @@ autorequire:
16
16
  bindir: bin
17
17
  cert_chain: []
18
18
 
19
- date: 2011-05-17 00:00:00 -04:00
19
+ date: 2011-05-19 00:00:00 -04:00
20
20
  default_executable:
21
21
  dependencies:
22
22
  - !ruby/object:Gem::Dependency
@@ -31,7 +31,7 @@ dependencies:
31
31
  segments:
32
32
  - 0
33
33
  version: "0"
34
- name: rack-proxy
34
+ name: rack
35
35
  version_requirements: *id001
36
36
  - !ruby/object:Gem::Dependency
37
37
  prerelease: false
@@ -45,12 +45,54 @@ dependencies:
45
45
  segments:
46
46
  - 0
47
47
  version: "0"
48
- name: rack
48
+ name: dnode
49
49
  version_requirements: *id002
50
50
  - !ruby/object:Gem::Dependency
51
51
  prerelease: false
52
- type: :development
52
+ type: :runtime
53
53
  requirement: &id003 !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ hash: 3
59
+ segments:
60
+ - 0
61
+ version: "0"
62
+ name: eventmachine
63
+ version_requirements: *id003
64
+ - !ruby/object:Gem::Dependency
65
+ prerelease: false
66
+ type: :runtime
67
+ requirement: &id004 !ruby/object:Gem::Requirement
68
+ none: false
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ hash: 3
73
+ segments:
74
+ - 0
75
+ version: "0"
76
+ name: events
77
+ version_requirements: *id004
78
+ - !ruby/object:Gem::Dependency
79
+ prerelease: false
80
+ type: :runtime
81
+ requirement: &id005 !ruby/object:Gem::Requirement
82
+ none: false
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ hash: 3
87
+ segments:
88
+ - 0
89
+ version: "0"
90
+ name: json
91
+ version_requirements: *id005
92
+ - !ruby/object:Gem::Dependency
93
+ prerelease: false
94
+ type: :development
95
+ requirement: &id006 !ruby/object:Gem::Requirement
54
96
  none: false
55
97
  requirements:
56
98
  - - ~>
@@ -62,11 +104,11 @@ dependencies:
62
104
  - 0
63
105
  version: 2.3.0
64
106
  name: rspec
65
- version_requirements: *id003
107
+ version_requirements: *id006
66
108
  - !ruby/object:Gem::Dependency
67
109
  prerelease: false
68
110
  type: :development
69
- requirement: &id004 !ruby/object:Gem::Requirement
111
+ requirement: &id007 !ruby/object:Gem::Requirement
70
112
  none: false
71
113
  requirements:
72
114
  - - ~>
@@ -78,11 +120,11 @@ dependencies:
78
120
  - 0
79
121
  version: 1.0.0
80
122
  name: bundler
81
- version_requirements: *id004
123
+ version_requirements: *id007
82
124
  - !ruby/object:Gem::Dependency
83
125
  prerelease: false
84
126
  type: :development
85
- requirement: &id005 !ruby/object:Gem::Requirement
127
+ requirement: &id008 !ruby/object:Gem::Requirement
86
128
  none: false
87
129
  requirements:
88
130
  - - ~>
@@ -94,11 +136,11 @@ dependencies:
94
136
  - 2
95
137
  version: 1.5.2
96
138
  name: jeweler
97
- version_requirements: *id005
139
+ version_requirements: *id008
98
140
  - !ruby/object:Gem::Dependency
99
141
  prerelease: false
100
142
  type: :development
101
- requirement: &id006 !ruby/object:Gem::Requirement
143
+ requirement: &id009 !ruby/object:Gem::Requirement
102
144
  none: false
103
145
  requirements:
104
146
  - - ">="
@@ -108,11 +150,11 @@ dependencies:
108
150
  - 0
109
151
  version: "0"
110
152
  name: rack-test
111
- version_requirements: *id006
153
+ version_requirements: *id009
112
154
  - !ruby/object:Gem::Dependency
113
155
  prerelease: false
114
156
  type: :development
115
- requirement: &id007 !ruby/object:Gem::Requirement
157
+ requirement: &id010 !ruby/object:Gem::Requirement
116
158
  none: false
117
159
  requirements:
118
160
  - - ">="
@@ -122,11 +164,11 @@ dependencies:
122
164
  - 0
123
165
  version: "0"
124
166
  name: webmock
125
- version_requirements: *id007
167
+ version_requirements: *id010
126
168
  - !ruby/object:Gem::Dependency
127
169
  prerelease: false
128
170
  type: :development
129
- requirement: &id008 !ruby/object:Gem::Requirement
171
+ requirement: &id011 !ruby/object:Gem::Requirement
130
172
  none: false
131
173
  requirements:
132
174
  - - ">="
@@ -136,7 +178,7 @@ dependencies:
136
178
  - 0
137
179
  version: "0"
138
180
  name: rcov
139
- version_requirements: *id008
181
+ version_requirements: *id011
140
182
  description: Extend your middleware to include Node.js
141
183
  email: nmeersschaert@mac.com
142
184
  executables: []
@@ -147,20 +189,19 @@ extra_rdoc_files:
147
189
  - LICENSE.txt
148
190
  - README.rdoc
149
191
  files:
150
- - .DS_Store
151
192
  - Gemfile
152
193
  - Gemfile.lock
153
194
  - LICENSE.txt
154
195
  - README.rdoc
155
196
  - Rakefile
156
197
  - VERSION
157
- - lib/net_http_hacked.rb
158
- - lib/rack/http_streaming_response.rb
159
- - lib/rack/reverse_proxy.rb
160
- - lib/rack/streaming_proxy.rb
161
198
  - lib/whack-a-node.rb
162
- - spec/rack/reverse_proxy_spec.rb
163
- - spec/rack/streaming_proxy_spec.rb
199
+ - lib/whack_a_node.rb
200
+ - lib/whack_a_node/proxy.rb
201
+ - lib/whack_a_node/redirect.rb
202
+ - lib/whack_a_node/rpc.rb
203
+ - lib/whack_a_node/whacky.rb
204
+ - spec/rpc_spec.rb
164
205
  - spec/spec_helper.rb
165
206
  - spec/whack-a-node_spec.rb
166
207
  has_rdoc: true
@@ -198,7 +239,6 @@ signing_key:
198
239
  specification_version: 3
199
240
  summary: Run node apps as Rack apps
200
241
  test_files:
201
- - spec/rack/reverse_proxy_spec.rb
202
- - spec/rack/streaming_proxy_spec.rb
242
+ - spec/rpc_spec.rb
203
243
  - spec/spec_helper.rb
204
244
  - spec/whack-a-node_spec.rb
data/.DS_Store DELETED
Binary file
@@ -1,91 +0,0 @@
1
- # We are hacking net/http to change semantics of streaming handling
2
- # from "block" semantics to regular "return" semnatics.
3
- # We need it to construct a streamable rack triplet:
4
- #
5
- # [status, headers, streamable_body]
6
- #
7
- # See http://github.com/aniero/rack-streaming-proxy
8
- # for alternative that uses additional process.
9
- #
10
- # BTW I don't like monkey patching either
11
- # but this is not real monkey patching.
12
- # I just added some methods and named them very uniquely
13
- # to avoid eventual conflicts. You're safe. Trust me.
14
- #
15
- # Also, in Ruby 1.9.2 you could use Fibers to avoid hacking net/http.
16
-
17
- require 'net/http'
18
-
19
- class Net::HTTP
20
- # Original #request with block semantics.
21
- #
22
- # def request(req, body = nil, &block)
23
- # unless started?
24
- # start {
25
- # req['connection'] ||= 'close'
26
- # return request(req, body, &block)
27
- # }
28
- # end
29
- # if proxy_user()
30
- # unless use_ssl?
31
- # req.proxy_basic_auth proxy_user(), proxy_pass()
32
- # end
33
- # end
34
- #
35
- # req.set_body_internal body
36
- # begin_transport req
37
- # req.exec @socket, @curr_http_version, edit_path(req.path)
38
- # begin
39
- # res = HTTPResponse.read_new(@socket)
40
- # end while res.kind_of?(HTTPContinue)
41
- # res.reading_body(@socket, req.response_body_permitted?) {
42
- # yield res if block_given?
43
- # }
44
- # end_transport req, res
45
- #
46
- # res
47
- # end
48
-
49
- def begin_request_hacked(req)
50
- begin_transport req
51
- req.exec @socket, @curr_http_version, edit_path(req.path)
52
- begin
53
- res = Net::HTTPResponse.read_new(@socket)
54
- end while res.kind_of?(Net::HTTPContinue)
55
- res.begin_reading_body_hacked(@socket, req.response_body_permitted?)
56
- @req_hacked, @res_hacked = req, res
57
- @res_hacked
58
- end
59
-
60
- def end_request_hacked
61
- @res_hacked.end_reading_body_hacked
62
- end_transport @req_hacked, @res_hacked
63
- @res_hacked
64
- end
65
- end
66
-
67
- class Net::HTTPResponse
68
- # Original #reading_body with block semantics
69
- #
70
- # def reading_body(sock, reqmethodallowbody) #:nodoc: internal use only
71
- # @socket = sock
72
- # @body_exist = reqmethodallowbody && self.class.body_permitted?
73
- # begin
74
- # yield
75
- # self.body # ensure to read body
76
- # ensure
77
- # @socket = nil
78
- # end
79
- # end
80
-
81
- def begin_reading_body_hacked(sock, reqmethodallowbody)
82
- @socket = sock
83
- @body_exist = reqmethodallowbody && self.class.body_permitted?
84
- end
85
-
86
- def end_reading_body_hacked
87
- self.body
88
- @socket = nil
89
- end
90
- end
91
-
@@ -1,63 +0,0 @@
1
- require "net_http_hacked"
2
-
3
- module Rack
4
-
5
- # Wraps the hacked net/http in a Rack way.
6
- class HttpStreamingResponse
7
- def initialize(request, host, port = nil)
8
- @request, @host, @port = request, host, port
9
- end
10
-
11
- def status
12
- response.code.to_i
13
- end
14
-
15
- def headers
16
- h = Utils::HeaderHash.new
17
-
18
- response.each_header do |k, v|
19
- h[k] = v
20
- end
21
-
22
- h
23
- end
24
-
25
- def body
26
- self
27
- end
28
-
29
- # Can be called only once!
30
- def each(&block)
31
- response.read_body(&block)
32
- ensure
33
- session.end_request_hacked
34
- end
35
-
36
- def to_s
37
- @body ||= begin
38
- lines = []
39
-
40
- each do |line|
41
- lines << line
42
- end
43
-
44
- lines.join
45
- end
46
- end
47
-
48
- protected
49
-
50
- # Net::HTTPResponse
51
- def response
52
- @response ||= session.begin_request_hacked(@request)
53
- end
54
-
55
- # Net::HTTP
56
- def session
57
- @session ||= Net::HTTP.start(@host, @port)
58
- end
59
-
60
- end
61
-
62
- end
63
-
@@ -1,142 +0,0 @@
1
- require 'net/http'
2
- require 'net/https'
3
-
4
- module Rack
5
- class ReverseProxy
6
-
7
-
8
- def initialize(app = nil, &b)
9
- @app = app || lambda { [404, [], []] }
10
- @paths = {}
11
- @opts = {:preserve_host => false}
12
- instance_eval &b if block_given?
13
- end
14
-
15
- def call(env)
16
- rackreq = Rack::Request.new(env)
17
- matcher, url = get_matcher_and_url rackreq.fullpath
18
- return @app.call(env) if matcher.nil?
19
-
20
-
21
-
22
- uri = get_uri(url, matcher, rackreq.fullpath)
23
- headers = Rack::Utils::HeaderHash.new
24
- env.each { |key, value|
25
- if key =~ /HTTP_(.*)/
26
- headers[$1] = value
27
- end
28
- }
29
- headers['HOST'] = uri.host if @opts[:preserve_host]
30
-
31
- session = Net::HTTP.new(uri.host, uri.port)
32
- session.use_ssl = (uri.scheme == 'https')
33
- session.verify_mode = OpenSSL::SSL::VERIFY_NONE
34
- session.start { |http|
35
- m = rackreq.request_method
36
- case m
37
- when "GET", "HEAD", "DELETE", "OPTIONS", "TRACE"
38
- req = Net::HTTP.const_get(m.capitalize).new(uri.request_uri, headers)
39
- req.basic_auth @opts[:username], @opts[:password] if @opts[:username] and @opts[:password]
40
- when "PUT", "POST"
41
- req = Net::HTTP.const_get(m.capitalize).new(uri.request_uri, headers)
42
- req.basic_auth @opts[:username], @opts[:password] if @opts[:username] and @opts[:password]
43
- req.content_length = rackreq.body.length
44
- req.body_stream = rackreq.body
45
- else
46
- raise "method not supported: #{m}"
47
- end
48
-
49
- body = ''
50
- res = http.request(req) do |res|
51
- res.read_body do |segment|
52
- body << segment
53
- end
54
- end
55
-
56
- [res.code, create_response_headers(res), [body]]
57
- }
58
- end
59
-
60
- private
61
-
62
-
63
- def get_matcher_and_url path
64
- matches = @paths.select do |matcher, url|
65
- match_path(path, matcher)
66
- end
67
-
68
- if matches.length < 1
69
- nil
70
- elsif matches.length > 1
71
- raise AmbiguousProxyMatch.new(path, matches)
72
- else
73
- matches.first.map{|a| a.dup}
74
- end
75
- end
76
-
77
- def create_response_headers http_response
78
- response_headers = Rack::Utils::HeaderHash.new(http_response.to_hash)
79
- # handled by Rack
80
- response_headers.delete('status')
81
- # TODO: figure out how to handle chunked responses
82
- response_headers.delete('transfer-encoding')
83
- # TODO: Verify Content Length, and required Rack headers
84
- response_headers
85
- end
86
-
87
- def match_path(path, matcher)
88
- if matcher.is_a?(Regexp)
89
- path.match(matcher)
90
- else
91
- path.match(/^#{matcher.to_s}/)
92
- end
93
- end
94
-
95
- def get_uri(url, matcher, path)
96
- if url =~/\$\d/
97
- match_path(path, matcher).to_a.each_with_index { |m, i| url.gsub!("$#{i.to_s}", m) }
98
- URI(url)
99
- else
100
- URI.join(url, path)
101
- end
102
- end
103
-
104
- def reverse_proxy matcher, url, opts={}
105
- raise GenericProxyURI.new(url) if matcher.is_a?(String) && URI(url).class == URI::Generic
106
- @paths.merge!(matcher => url)
107
- @opts.merge!(opts)
108
- end
109
- end
110
-
111
- class GenericProxyURI < Exception
112
- attr_reader :url
113
-
114
- def intialize(url)
115
- @url = url
116
- end
117
-
118
- def to_s
119
- %Q(Your URL "#{@url}" is too generic. Did you mean "http://#{@url}"?)
120
- end
121
- end
122
-
123
- class AmbiguousProxyMatch < Exception
124
- attr_reader :path, :matches
125
- def initialize(path, matches)
126
- @path = path
127
- @matches = matches
128
- end
129
-
130
- def to_s
131
- %Q(Path "#{path}" matched multiple endpoints: #{formatted_matches})
132
- end
133
-
134
- private
135
-
136
- def formatted_matches
137
- matches.map {|m| %Q("#{m[0].to_s}" => "#{m[1]}")}.join(', ')
138
- end
139
- end
140
-
141
- end
142
-
@@ -1,70 +0,0 @@
1
- require "rack/http_streaming_response"
2
-
3
- module Rack
4
-
5
- # Subclass and bring your own #rewrite_request and #rewrite_response
6
- class StreamingProxy
7
- VERSION = "0.3.4"
8
-
9
- def call(env)
10
- rewrite_response(perform_request(rewrite_env(env)))
11
- end
12
-
13
- # Return modified env
14
- def rewrite_env(env)
15
- env
16
- end
17
-
18
- # Return a rack triplet [status, headers, body]
19
- def rewrite_response(triplet)
20
- triplet
21
- end
22
-
23
- protected
24
-
25
- def perform_request(env)
26
- source_request = Rack::Request.new(env)
27
-
28
- # Initialize request
29
- target_request = Net::HTTP.const_get(source_request.request_method.capitalize).new(source_request.fullpath)
30
-
31
- # Setup headers
32
- target_request.initialize_http_header(extract_http_request_headers(source_request.env))
33
-
34
- # Setup body
35
- if target_request.request_body_permitted? && source_request.body
36
- target_request.body_stream = source_request.body
37
- target_request.content_length = source_request.content_length
38
- target_request.content_type = source_request.content_type if source_request.content_type
39
- end
40
-
41
- # Create a streaming response (the actual network communication is deferred, a.k.a. streamed)
42
- target_response = HttpStreamingResponse.new(target_request, source_request.host, source_request.port)
43
-
44
- [target_response.status, target_response.headers, target_response.body]
45
- end
46
-
47
- def extract_http_request_headers(env)
48
- headers = env.reject do |k, v|
49
- !(/^HTTP_[A-Z_]+$/ === k)
50
- end.map do |k, v|
51
- [reconstruct_header_name(k), v]
52
- end.inject(Utils::HeaderHash.new) do |hash, k_v|
53
- k, v = k_v
54
- hash[k] = v
55
- hash
56
- end
57
-
58
- x_forwarded_for = (headers["X-Forwarded-For"].to_s.split(/, +/) << env["REMOTE_ADDR"]).join(", ")
59
-
60
- headers.merge!("X-Forwarded-For" => x_forwarded_for)
61
- end
62
-
63
- def reconstruct_header_name(name)
64
- name.sub(/^HTTP_/, "").gsub("_", "-")
65
- end
66
-
67
- end
68
-
69
- end
70
-
@@ -1,180 +0,0 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
-
3
- describe Rack::ReverseProxy do
4
- include Rack::Test::Methods
5
- include WebMock::API
6
-
7
- def app
8
- Rack::ReverseProxy.new
9
- end
10
-
11
- def dummy_app
12
- lambda { [200, {}, ['Dummy App']] }
13
- end
14
-
15
- describe "as middleware" do
16
- def app
17
- Rack::ReverseProxy.new(dummy_app) do
18
- reverse_proxy '/test', 'http://example.com/', {:preserve_host => true}
19
- end
20
- end
21
-
22
- it "should forward requests to the calling app when the path is not matched" do
23
- stub_request(:get, 'http://example.com/').to_return(:headers => {'Status' => '200 OK'})
24
- get '/'
25
- last_response.body.should == "Dummy App"
26
- last_response.should be_ok
27
- end
28
-
29
- it "should proxy requests when a pattern is matched" do
30
- stub_request(:get, 'http://example.com/test').to_return({:body => "Proxied App"})
31
- get '/test'
32
- last_response.body.should == "Proxied App"
33
- end
34
-
35
- it "the response header should never contain Status" do
36
- stub_request(:any, 'example.com/test/stuff').to_return(:headers => {'Status' => '200 OK'})
37
- get '/test/stuff'
38
- last_response.headers['Status'].should == nil
39
- end
40
-
41
- it "the response header should never transfer-encoding" do
42
- stub_request(:any, 'example.com/test/stuff').to_return(:headers => {'transfer-encoding' => 'Chunked'})
43
- get '/test/stuff'
44
- last_response.headers['transfer-encoding'].should == nil
45
- end
46
-
47
- it "should set the Host header" do
48
- stub_request(:any, 'example.com/test/stuff')
49
- get '/test/stuff'
50
- a_request(:get, 'http://example.com/test/stuff').with(:headers => {"Host" => "example.com"}).should have_been_made
51
- end
52
-
53
- describe "with preserve host turned off" do
54
- def app
55
- Rack::ReverseProxy.new(dummy_app) do
56
- reverse_proxy '/test', 'http://example.com/'
57
- end
58
- end
59
-
60
- it "should not set the Host header" do
61
- stub_request(:any, 'example.com/test/stuff')
62
- get '/test/stuff'
63
- a_request(:get, 'http://example.com/test/stuff').should have_been_made
64
- end
65
- end
66
-
67
- describe "with basic auth turned on" do
68
- def app
69
- Rack::ReverseProxy.new(dummy_app) do
70
- reverse_proxy '/test', 'http://example.com/', {:username => "joe", :password => "shmoe"}
71
- end
72
- end
73
-
74
- it "should make request with basic auth" do
75
- stub_request(:get, "http://joe:shmoe@example.com/test/stuff").to_return(:body => "secured content")
76
- get '/test/stuff'
77
- last_response.body.should == "secured content"
78
- end
79
- end
80
-
81
- describe "with ambiguous routes" do
82
- def app
83
- Rack::ReverseProxy.new(dummy_app) do
84
- reverse_proxy '/test', 'http://example.com/'
85
- reverse_proxy /^\/test/, 'http://example.com/'
86
- end
87
- end
88
-
89
- it "should throw an exception" do
90
- lambda { get '/test' }.should raise_error(Rack::AmbiguousProxyMatch)
91
- end
92
- end
93
-
94
- describe "with a route as a regular expression" do
95
- def app
96
- Rack::ReverseProxy.new(dummy_app) do
97
- reverse_proxy %r|^/test(/.*)$|, 'http://example.com$1'
98
- end
99
- end
100
-
101
- it "should support subcaptures" do
102
- stub_request(:get, 'http://example.com/path').to_return({:body => "Proxied App"})
103
- get '/test/path'
104
- last_response.body.should == "Proxied App"
105
- end
106
- end
107
-
108
- describe "with a https route" do
109
- def app
110
- Rack::ReverseProxy.new(dummy_app) do
111
- reverse_proxy '/test', 'https://example.com'
112
- end
113
- end
114
-
115
- it "should make a secure request" do
116
- stub_request(:get, 'https://example.com/test/stuff').to_return({:body => "Proxied Secure App"})
117
- get '/test/stuff'
118
- last_response.body.should == "Proxied Secure App"
119
- end
120
-
121
- end
122
-
123
- describe "with a route as a string" do
124
- def app
125
- Rack::ReverseProxy.new(dummy_app) do
126
- reverse_proxy '/test', 'http://example.com'
127
- reverse_proxy '/path', 'http://example.com/foo$0'
128
- end
129
- end
130
-
131
- it "should append the full path to the uri" do
132
- stub_request(:get, 'http://example.com/test/stuff').to_return({:body => "Proxied App"})
133
- get '/test/stuff'
134
- last_response.body.should == "Proxied App"
135
- end
136
-
137
- end
138
-
139
- describe "with a generic url" do
140
- def app
141
- Rack::ReverseProxy.new(dummy_app) do
142
- reverse_proxy '/test', 'example.com'
143
- end
144
- end
145
-
146
- it "should throw an exception" do
147
- lambda{ app }.should raise_error(Rack::GenericProxyURI)
148
- end
149
- end
150
-
151
- describe "with a matching route" do
152
- def app
153
- Rack::ReverseProxy.new(dummy_app) do
154
- reverse_proxy '/test', 'http://example.com/'
155
- end
156
- end
157
-
158
- %w|get head delete put post|.each do |method|
159
- describe "and using method #{method}" do
160
- it "should forward the correct request" do
161
- stub_request(method.to_sym, 'http://example.com/test').to_return({:body => "Proxied App for #{method}"})
162
- eval "#{method} '/test'"
163
- last_response.body.should == "Proxied App for #{method}"
164
- end
165
-
166
- if %w|put post|.include?(method)
167
- it "should forward the request payload" do
168
- stub_request(method.to_sym, 'http://example.com/test').to_return { |req| {:body => req.body} }
169
- eval "#{method} '/test', {:test => 'test'}"
170
- last_response.body.should == "test=test"
171
- end
172
- end
173
- end
174
- end
175
- end
176
- end
177
-
178
-
179
- end
180
-
@@ -1,16 +0,0 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
-
3
- describe Rack::StreamingProxy do
4
- include Rack::Test::Methods
5
- include WebMock::API
6
-
7
- def app
8
- Rack::StreamingProxy.new
9
- end
10
-
11
- it "should have a port of 90210" do
12
- get "/"
13
- last_response.should_not be_nil
14
- #@app.should_not be_nil
15
- end
16
- end