rack-proxy 0.6.3 → 0.6.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +152 -18
- data/lib/net_http_hacked.rb +6 -6
- data/lib/rack/proxy.rb +1 -1
- data/lib/rack_proxy_examples/example_service_proxy.rb +40 -0
- data/lib/rack_proxy_examples/forward_host.rb +24 -0
- data/lib/rack_proxy_examples/rack_php_proxy.rb +37 -0
- data/lib/rack_proxy_examples/trusting_proxy.rb +24 -0
- data/rack-proxy.gemspec +1 -1
- metadata +8 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ce99adfb07beb9e42b72e91bd39860d2ec1efb7b
|
4
|
+
data.tar.gz: dfb0ede7fc70291ba58a24c7a478691f2fa5558f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 602c219b1b8151a7b77f1e6751d0919cc320cc13c6b0c0ce2f4c2a7c16dca345688ad3b3a4a109480660856a0ee7125ec36d892c97a22fb92a973e8b0a7bc005
|
7
|
+
data.tar.gz: 897c3d1db3ab71a70ce88bb9437b4a7be25cae6db20b2601f20e227b558a4a6b02416997dad68b03a7d38d0d6948bca74d3739fabfd877c0876043ea42a34dc6
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
A request/response rewriting HTTP proxy. A Rack app. Subclass `Rack::Proxy` and provide your `rewrite_env` and `rewrite_response` methods.
|
2
2
|
|
3
3
|
Installation
|
4
|
-
|
4
|
+
----
|
5
5
|
|
6
|
-
Add the following to your Gemfile
|
6
|
+
Add the following to your `Gemfile`:
|
7
7
|
|
8
8
|
```
|
9
|
-
gem 'rack-proxy', '~> 0.6.
|
9
|
+
gem 'rack-proxy', '~> 0.6.4'
|
10
10
|
```
|
11
11
|
|
12
12
|
Or install:
|
@@ -15,22 +15,72 @@ Or install:
|
|
15
15
|
gem install rack-proxy
|
16
16
|
```
|
17
17
|
|
18
|
-
|
19
|
-
|
18
|
+
Use Cases
|
19
|
+
----
|
20
|
+
Below are some examples of real world use cases for Rack-Proxy, done something interesting add the list below and send a PR.
|
21
|
+
|
22
|
+
* Allowing one app to act as central trust authority
|
23
|
+
* handle accepting self-sign certificates for internal apps
|
24
|
+
* authentication / authorization prior to proxying requests to a blindly trusting backend
|
25
|
+
* avoiding CORs complications by proxying from same domain to another backend
|
26
|
+
* subdomain based pass-through to multiple apps
|
27
|
+
* Complex redirect rules
|
28
|
+
* redirect pages with different extensions (ex: `.php`) to another app
|
29
|
+
* useful for handling awkward redirection rules for moved pages
|
30
|
+
* fan Parallel Requests: turning a single API request to [multiple concurrent backend requests](https://github.com/typhoeus/typhoeus#making-parallel-requests) & merging results.
|
31
|
+
* inserting or stripping headers required or problematic for certain clients
|
32
|
+
|
33
|
+
Options
|
34
|
+
----
|
35
|
+
|
36
|
+
Options can be set when initializing the middleware or overriding a method.
|
37
|
+
|
38
|
+
|
39
|
+
* `:streaming` - defaults to `true`, but does not work on all Ruby versions, recommend to set to `false`
|
40
|
+
* `:ssl_verify_none` - tell `Net::HTTP` to not validate certs
|
41
|
+
* `:ssl_version` - tell `Net::HTTP` to set a specific `ssl_version`
|
42
|
+
* `:backend` - the URI parseable format of host and port of the target proxy backend. If not set it will assume the backend target is the same as the source.
|
43
|
+
* `:read_timeout` - set proxy timeout it defaults to 60 seconds
|
44
|
+
|
45
|
+
To pass in options, when you configure your middleware you can pass them in as an optional hash.
|
20
46
|
|
21
47
|
```ruby
|
22
|
-
|
48
|
+
Rails.application.config.middleware.use ExampleServiceProxy, backend: 'http://guides.rubyonrails.org', streaming: false
|
49
|
+
```
|
50
|
+
|
51
|
+
Examples
|
52
|
+
----
|
53
|
+
|
54
|
+
See and run the examples below from `lib/rack_proxy_examples/`. To mount any example into an existing Rails app:
|
55
|
+
|
56
|
+
1. create `config/initializers/proxy.rb`
|
57
|
+
2. modify the file to require the example file
|
58
|
+
```ruby
|
59
|
+
require 'rack_proxy_examples/forward_host'
|
60
|
+
```
|
61
|
+
|
62
|
+
### Forward request to Host and Insert Header
|
63
|
+
|
64
|
+
Test with `require 'rack_proxy_examples/forward_host'`
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
class ForwardHost < Rack::Proxy
|
23
68
|
|
24
69
|
def rewrite_env(env)
|
25
70
|
env["HTTP_HOST"] = "example.com"
|
26
|
-
|
27
71
|
env
|
28
72
|
end
|
29
73
|
|
30
74
|
def rewrite_response(triplet)
|
31
75
|
status, headers, body = triplet
|
32
76
|
|
77
|
+
# example of inserting an additional header
|
33
78
|
headers["X-Foo"] = "Bar"
|
79
|
+
|
80
|
+
# if you rewrite env, it appears that content-length isn't calculated correctly
|
81
|
+
# resulting in only partial responses being sent to users
|
82
|
+
# you can remove it or recalculate it here
|
83
|
+
headers["content-length"] = nil
|
34
84
|
|
35
85
|
triplet
|
36
86
|
end
|
@@ -40,15 +90,30 @@ end
|
|
40
90
|
|
41
91
|
### Disable SSL session verification when proxying a server with e.g. self-signed SSL certs
|
42
92
|
|
93
|
+
Test with `require 'rack_proxy_examples/trusting_proxy'`
|
94
|
+
|
43
95
|
```ruby
|
44
96
|
class TrustingProxy < Rack::Proxy
|
45
97
|
|
46
98
|
def rewrite_env(env)
|
47
|
-
env["
|
99
|
+
env["HTTP_HOST"] = "self-signed.badssl.com"
|
48
100
|
|
101
|
+
# We are going to trust the self-signed SSL
|
102
|
+
env["rack.ssl_verify_none"] = true
|
49
103
|
env
|
50
104
|
end
|
51
105
|
|
106
|
+
def rewrite_response(triplet)
|
107
|
+
status, headers, body = triplet
|
108
|
+
|
109
|
+
# if you rewrite env, it appears that content-length isn't calculated correctly
|
110
|
+
# resulting in only partial responses being sent to users
|
111
|
+
# you can remove it or recalculate it here
|
112
|
+
headers["content-length"] = nil
|
113
|
+
|
114
|
+
triplet
|
115
|
+
end
|
116
|
+
|
52
117
|
end
|
53
118
|
```
|
54
119
|
|
@@ -58,31 +123,98 @@ The same can be achieved for *all* requests going through the `Rack::Proxy` inst
|
|
58
123
|
Rack::Proxy.new(ssl_verify_none: true)
|
59
124
|
```
|
60
125
|
|
61
|
-
|
62
|
-
|
126
|
+
### Rails middleware example
|
127
|
+
|
128
|
+
Test with `require 'rack_proxy_examples/example_service_proxy'`
|
129
|
+
|
130
|
+
```ruby
|
131
|
+
###
|
132
|
+
# This is an example of how to use Rack-Proxy in a Rails application.
|
133
|
+
#
|
134
|
+
# Setup:
|
135
|
+
# 1. rails new test_app
|
136
|
+
# 2. cd test_app
|
137
|
+
# 3. install Rack-Proxy in `Gemfile`
|
138
|
+
# a. `gem 'rack-proxy', '~> 0.6.3'`
|
139
|
+
# 4. install gem: `bundle install`
|
140
|
+
# 5. create `config/initializers/proxy.rb` adding this line `require 'rack_proxy_examples/example_service_proxy'`
|
141
|
+
# 6. run: `SERVICE_URL=http://guides.rubyonrails.org rails server`
|
142
|
+
# 7. open in browser: `http://localhost:3000/example_service`
|
143
|
+
#
|
144
|
+
###
|
145
|
+
ENV['SERVICE_URL'] ||= 'http://guides.rubyonrails.org'
|
146
|
+
|
147
|
+
class ExampleServiceProxy < Rack::Proxy
|
148
|
+
def perform_request(env)
|
149
|
+
request = Rack::Request.new(env)
|
150
|
+
|
151
|
+
# use rack proxy for anything hitting our host app at /example_service
|
152
|
+
if request.path =~ %r{^/example_service}
|
153
|
+
backend = URI(ENV['SERVICE_URL'])
|
154
|
+
# most backends required host set properly, but rack-proxy doesn't set this for you automatically
|
155
|
+
# even when a backend host is passed in via the options
|
156
|
+
env["HTTP_HOST"] = backend.host
|
157
|
+
|
158
|
+
# This is the only path that needs to be set currently on Rails 5 & greater
|
159
|
+
env['PATH_INFO'] = ENV['SERVICE_PATH'] || '/configuring.html'
|
160
|
+
|
161
|
+
# don't send your sites cookies to target service, unless it is a trusted internal service that can parse all your cookies
|
162
|
+
env['HTTP_COOKIE'] = ''
|
163
|
+
super(env)
|
164
|
+
else
|
165
|
+
@app.call(env)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
```
|
170
|
+
|
171
|
+
### Using as middleware to forward only some extensions to another Application
|
172
|
+
|
173
|
+
Test with `require 'rack_proxy_examples/rack_php_proxy'`
|
63
174
|
|
64
175
|
Example: Proxying only requests that end with ".php" could be done like this:
|
65
176
|
|
66
177
|
```ruby
|
67
|
-
|
178
|
+
###
|
179
|
+
# Open http://localhost:3000/test.php to trigger proxy
|
180
|
+
###
|
68
181
|
class RackPhpProxy < Rack::Proxy
|
69
182
|
|
70
183
|
def perform_request(env)
|
71
184
|
request = Rack::Request.new(env)
|
72
185
|
if request.path =~ %r{\.php}
|
73
|
-
env["HTTP_HOST"] = "localhost"
|
74
|
-
|
186
|
+
env["HTTP_HOST"] = ENV["HTTP_HOST"] ? URI(ENV["HTTP_HOST"]).host : "localhost"
|
187
|
+
ENV["PHP_PATH"] ||= '/manual/en/tutorial.firstpage.php'
|
188
|
+
|
189
|
+
# Rails 3 & 4
|
190
|
+
env["REQUEST_PATH"] = ENV["PHP_PATH"] || "/php/#{request.fullpath}"
|
191
|
+
# Rails 5 and above
|
192
|
+
env['PATH_INFO'] = ENV["PHP_PATH"] || "/php/#{request.fullpath}"
|
193
|
+
|
194
|
+
env['content-length'] = nil
|
195
|
+
|
75
196
|
super(env)
|
76
197
|
else
|
77
198
|
@app.call(env)
|
78
199
|
end
|
79
200
|
end
|
201
|
+
|
202
|
+
def rewrite_response(triplet)
|
203
|
+
status, headers, body = triplet
|
204
|
+
|
205
|
+
# if you proxy depending on the backend, it appears that content-length isn't calculated correctly
|
206
|
+
# resulting in only partial responses being sent to users
|
207
|
+
# you can remove it or recalculate it here
|
208
|
+
headers["content-length"] = nil
|
209
|
+
|
210
|
+
triplet
|
211
|
+
end
|
80
212
|
end
|
81
213
|
```
|
82
214
|
|
83
215
|
To use the middleware, please consider the following:
|
84
216
|
|
85
|
-
1) For Rails we could add a configuration in config/application.rb
|
217
|
+
1) For Rails we could add a configuration in `config/application.rb`
|
86
218
|
|
87
219
|
```ruby
|
88
220
|
config.middleware.use RackPhpProxy, {ssl_verify_none: true}
|
@@ -101,11 +233,13 @@ This will allow to run the other requests through the application and only proxy
|
|
101
233
|
See tests for more examples.
|
102
234
|
|
103
235
|
WARNING
|
104
|
-
|
236
|
+
----
|
105
237
|
|
106
|
-
Doesn't work with fakeweb
|
238
|
+
Doesn't work with `fakeweb`/`webmock`. Both libraries monkey-patch net/http code.
|
107
239
|
|
108
240
|
Todos
|
109
|
-
|
241
|
+
----
|
110
242
|
|
111
|
-
|
243
|
+
* Make the docs up to date with the current use case for this code: everything except streaming which involved a rather ugly monkey patch and only worked in 1.8, but does not work now.
|
244
|
+
* Improve and validate requirements for Host and Path rewrite rules
|
245
|
+
* Ability to inject logger and set log level
|
data/lib/net_http_hacked.rb
CHANGED
@@ -4,11 +4,11 @@
|
|
4
4
|
#
|
5
5
|
# [status, headers, streamable_body]
|
6
6
|
#
|
7
|
-
# See http://github.com/aniero/rack-streaming-proxy
|
7
|
+
# See http://github.com/aniero/rack-streaming-proxy
|
8
8
|
# for alternative that uses additional process.
|
9
9
|
#
|
10
10
|
# BTW I don't like monkey patching either
|
11
|
-
# but this is not real monkey patching.
|
11
|
+
# but this is not real monkey patching.
|
12
12
|
# I just added some methods and named them very uniquely
|
13
13
|
# to avoid eventual conflicts. You're safe. Trust me.
|
14
14
|
#
|
@@ -45,7 +45,7 @@ class Net::HTTP
|
|
45
45
|
#
|
46
46
|
# res
|
47
47
|
# end
|
48
|
-
|
48
|
+
|
49
49
|
def begin_request_hacked(req)
|
50
50
|
begin_transport req
|
51
51
|
req.exec @socket, @curr_http_version, edit_path(req.path)
|
@@ -56,7 +56,7 @@ class Net::HTTP
|
|
56
56
|
@req_hacked, @res_hacked = req, res
|
57
57
|
@res_hacked
|
58
58
|
end
|
59
|
-
|
59
|
+
|
60
60
|
def end_request_hacked
|
61
61
|
@res_hacked.end_reading_body_hacked
|
62
62
|
end_transport @req_hacked, @res_hacked
|
@@ -77,12 +77,12 @@ class Net::HTTPResponse
|
|
77
77
|
# @socket = nil
|
78
78
|
# end
|
79
79
|
# end
|
80
|
-
|
80
|
+
|
81
81
|
def begin_reading_body_hacked(sock, reqmethodallowbody)
|
82
82
|
@socket = sock
|
83
83
|
@body_exist = reqmethodallowbody && self.class.body_permitted?
|
84
84
|
end
|
85
|
-
|
85
|
+
|
86
86
|
def end_reading_body_hacked
|
87
87
|
self.body
|
88
88
|
@socket = nil
|
data/lib/rack/proxy.rb
CHANGED
@@ -0,0 +1,40 @@
|
|
1
|
+
###
|
2
|
+
# This is an example of how to use Rack-Proxy in a Rails application.
|
3
|
+
#
|
4
|
+
# Setup:
|
5
|
+
# 1. rails new test_app
|
6
|
+
# 2. cd test_app
|
7
|
+
# 3. install Rack-Proxy in `Gemfile`
|
8
|
+
# a. `gem 'rack-proxy', '~> 0.6.3'`
|
9
|
+
# 4. install gem: `bundle install`
|
10
|
+
# 5. create `config/initializers/proxy.rb` adding this line `require 'rack_proxy_examples/example_service_proxy'`
|
11
|
+
# 6. run: `SERVICE_URL=http://guides.rubyonrails.org rails server`
|
12
|
+
# 7. open in browser: `http://localhost:3000/example_service`
|
13
|
+
#
|
14
|
+
###
|
15
|
+
ENV['SERVICE_URL'] ||= 'http://guides.rubyonrails.org'
|
16
|
+
|
17
|
+
class ExampleServiceProxy < Rack::Proxy
|
18
|
+
def perform_request(env)
|
19
|
+
request = Rack::Request.new(env)
|
20
|
+
|
21
|
+
# use rack proxy for anything hitting our host app at /example_service
|
22
|
+
if request.path =~ %r{^/example_service}
|
23
|
+
backend = URI(ENV['SERVICE_URL'])
|
24
|
+
# most backends required host set properly, but rack-proxy doesn't set this for you automatically
|
25
|
+
# even when a backend host is passed in via the options
|
26
|
+
env["HTTP_HOST"] = backend.host
|
27
|
+
|
28
|
+
# This is the only path that needs to be set currently on Rails 5 & greater
|
29
|
+
env['PATH_INFO'] = ENV['SERVICE_PATH'] || '/configuring.html'
|
30
|
+
|
31
|
+
# don't send your sites cookies to target service, unless it is a trusted internal service that can parse all your cookies
|
32
|
+
env['HTTP_COOKIE'] = ''
|
33
|
+
super(env)
|
34
|
+
else
|
35
|
+
@app.call(env)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
Rails.application.config.middleware.use ExampleServiceProxy, backend: ENV['SERVICE_URL'], streaming: false
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class ForwardHost < Rack::Proxy
|
2
|
+
|
3
|
+
def rewrite_env(env)
|
4
|
+
env["HTTP_HOST"] = "example.com"
|
5
|
+
env
|
6
|
+
end
|
7
|
+
|
8
|
+
def rewrite_response(triplet)
|
9
|
+
status, headers, body = triplet
|
10
|
+
|
11
|
+
# example of inserting an additional header
|
12
|
+
headers["X-Foo"] = "Bar"
|
13
|
+
|
14
|
+
# if you rewrite env, it appears that content-length isn't calculated correctly
|
15
|
+
# resulting in only partial responses being sent to users
|
16
|
+
# you can remove it or recalculate it here
|
17
|
+
headers["content-length"] = nil
|
18
|
+
|
19
|
+
triplet
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
Rails.application.config.middleware.use ForwardHost, backend: 'http://example.com', streaming: false
|
@@ -0,0 +1,37 @@
|
|
1
|
+
###
|
2
|
+
# Open http://localhost:3000/test.php to trigger proxy
|
3
|
+
###
|
4
|
+
class RackPhpProxy < Rack::Proxy
|
5
|
+
|
6
|
+
def perform_request(env)
|
7
|
+
request = Rack::Request.new(env)
|
8
|
+
if request.path =~ %r{\.php}
|
9
|
+
env["HTTP_HOST"] = ENV["HTTP_HOST"] ? URI(ENV["HTTP_HOST"]).host : "localhost"
|
10
|
+
ENV["PHP_PATH"] ||= '/manual/en/tutorial.firstpage.php'
|
11
|
+
|
12
|
+
# Rails 3 & 4
|
13
|
+
env["REQUEST_PATH"] = ENV["PHP_PATH"] || "/php/#{request.fullpath}"
|
14
|
+
# Rails 5 and above
|
15
|
+
env['PATH_INFO'] = ENV["PHP_PATH"] || "/php/#{request.fullpath}"
|
16
|
+
|
17
|
+
env['content-length'] = nil
|
18
|
+
|
19
|
+
super(env)
|
20
|
+
else
|
21
|
+
@app.call(env)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def rewrite_response(triplet)
|
26
|
+
status, headers, body = triplet
|
27
|
+
|
28
|
+
# if you proxy depending on the backend, it appears that content-length isn't calculated correctly
|
29
|
+
# resulting in only partial responses being sent to users
|
30
|
+
# you can remove it or recalculate it here
|
31
|
+
headers["content-length"] = nil
|
32
|
+
|
33
|
+
triplet
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
Rails.application.config.middleware.use RackPhpProxy, backend: ENV["HTTP_HOST"]='http://php.net', streaming: false
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class TrustingProxy < Rack::Proxy
|
2
|
+
|
3
|
+
def rewrite_env(env)
|
4
|
+
env["HTTP_HOST"] = "self-signed.badssl.com"
|
5
|
+
|
6
|
+
# We are going to trust the self-signed SSL
|
7
|
+
env["rack.ssl_verify_none"] = true
|
8
|
+
env
|
9
|
+
end
|
10
|
+
|
11
|
+
def rewrite_response(triplet)
|
12
|
+
status, headers, body = triplet
|
13
|
+
|
14
|
+
# if you rewrite env, it appears that content-length isn't calculated correctly
|
15
|
+
# resulting in only partial responses being sent to users
|
16
|
+
# you can remove it or recalculate it here
|
17
|
+
headers["content-length"] = nil
|
18
|
+
|
19
|
+
triplet
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
Rails.application.config.middleware.use TrustingProxy, backend: 'https://self-signed.badssl.com', streaming: false
|
data/rack-proxy.gemspec
CHANGED
@@ -8,7 +8,7 @@ Gem::Specification.new do |s|
|
|
8
8
|
s.platform = Gem::Platform::RUBY
|
9
9
|
s.authors = ["Jacek Becela"]
|
10
10
|
s.email = ["jacek.becela@gmail.com"]
|
11
|
-
s.homepage = "
|
11
|
+
s.homepage = "https://github.com/ncr/rack-proxy"
|
12
12
|
s.summary = %q{A request/response rewriting HTTP proxy. A Rack app.}
|
13
13
|
s.description = %q{A Rack app that provides request/response rewriting proxy capabilities with streaming.}
|
14
14
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rack-proxy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.6.
|
4
|
+
version: 0.6.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jacek Becela
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-03-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|
@@ -71,12 +71,16 @@ files:
|
|
71
71
|
- lib/rack-proxy.rb
|
72
72
|
- lib/rack/http_streaming_response.rb
|
73
73
|
- lib/rack/proxy.rb
|
74
|
+
- lib/rack_proxy_examples/example_service_proxy.rb
|
75
|
+
- lib/rack_proxy_examples/forward_host.rb
|
76
|
+
- lib/rack_proxy_examples/rack_php_proxy.rb
|
77
|
+
- lib/rack_proxy_examples/trusting_proxy.rb
|
74
78
|
- rack-proxy.gemspec
|
75
79
|
- test/http_streaming_response_test.rb
|
76
80
|
- test/net_http_hacked_test.rb
|
77
81
|
- test/rack_proxy_test.rb
|
78
82
|
- test/test_helper.rb
|
79
|
-
homepage:
|
83
|
+
homepage: https://github.com/ncr/rack-proxy
|
80
84
|
licenses: []
|
81
85
|
metadata: {}
|
82
86
|
post_install_message:
|
@@ -95,7 +99,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
95
99
|
version: '0'
|
96
100
|
requirements: []
|
97
101
|
rubyforge_project: rack-proxy
|
98
|
-
rubygems_version: 2.5.
|
102
|
+
rubygems_version: 2.5.2.2
|
99
103
|
signing_key:
|
100
104
|
specification_version: 4
|
101
105
|
summary: A request/response rewriting HTTP proxy. A Rack app.
|