Import Upstream version 0.6.1
This commit is contained in:
commit
a803b2684b
16 changed files with 721 additions and 0 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
pkg/*
|
||||||
|
*.gem
|
||||||
|
.bundle
|
19
.travis.yml
Normal file
19
.travis.yml
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
sudo: false
|
||||||
|
cache: bundler
|
||||||
|
language: ruby
|
||||||
|
before_install:
|
||||||
|
- "echo 'gem: --no-ri --no-rdoc' > ~/.gemrc"
|
||||||
|
- gem install bundler
|
||||||
|
- gem update bundler
|
||||||
|
script: bundle exec rake test
|
||||||
|
rvm:
|
||||||
|
- 2.0.0
|
||||||
|
- 2.1.5
|
||||||
|
- 2.2.2
|
||||||
|
- 2.2.3
|
||||||
|
- 2.3.0
|
||||||
|
- 2.3.1
|
||||||
|
env:
|
||||||
|
- RAILS_ENV=test RACK_ENV=test
|
||||||
|
notifications:
|
||||||
|
email: false
|
6
Gemfile
Normal file
6
Gemfile
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
source "http://rubygems.org"
|
||||||
|
|
||||||
|
gem 'rake'
|
||||||
|
|
||||||
|
# Specify your gem's dependencies in rack-proxy.gemspec
|
||||||
|
gemspec
|
28
Gemfile.lock
Normal file
28
Gemfile.lock
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
PATH
|
||||||
|
remote: .
|
||||||
|
specs:
|
||||||
|
rack-proxy (0.6.1)
|
||||||
|
rack
|
||||||
|
|
||||||
|
GEM
|
||||||
|
remote: http://rubygems.org/
|
||||||
|
specs:
|
||||||
|
power_assert (0.2.6)
|
||||||
|
rack (1.2.1)
|
||||||
|
rack-test (0.5.6)
|
||||||
|
rack (>= 1.0)
|
||||||
|
rake (0.9.2.2)
|
||||||
|
test-unit (3.1.5)
|
||||||
|
power_assert
|
||||||
|
|
||||||
|
PLATFORMS
|
||||||
|
ruby
|
||||||
|
|
||||||
|
DEPENDENCIES
|
||||||
|
rack-proxy!
|
||||||
|
rack-test
|
||||||
|
rake
|
||||||
|
test-unit
|
||||||
|
|
||||||
|
BUNDLED WITH
|
||||||
|
1.14.6
|
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2013 Jacek Becela jacek.becela@gmail.com
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
96
README.md
Normal file
96
README.md
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
A request/response rewriting HTTP proxy. A Rack app. Subclass `Rack::Proxy` and provide your `rewrite_env` and `rewrite_response` methods.
|
||||||
|
|
||||||
|
Example
|
||||||
|
-------
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
class Foo < Rack::Proxy
|
||||||
|
|
||||||
|
def rewrite_env(env)
|
||||||
|
env["HTTP_HOST"] = "example.com"
|
||||||
|
|
||||||
|
env
|
||||||
|
end
|
||||||
|
|
||||||
|
def rewrite_response(triplet)
|
||||||
|
status, headers, body = triplet
|
||||||
|
|
||||||
|
headers["X-Foo"] = "Bar"
|
||||||
|
|
||||||
|
triplet
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
### Disable SSL session verification when proxying a server with e.g. self-signed SSL certs
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
class TrustingProxy < Rack::Proxy
|
||||||
|
|
||||||
|
def rewrite_env(env)
|
||||||
|
env["rack.ssl_verify_none"] = true
|
||||||
|
|
||||||
|
env
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
The same can be achieved for *all* requests going through the `Rack::Proxy` instance by using
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
Rack::Proxy.new(ssl_verify_none: true)
|
||||||
|
```
|
||||||
|
|
||||||
|
Using it as a middleware:
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
Example: Proxying only requests that end with ".php" could be done like this:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
require 'rack/proxy'
|
||||||
|
class RackPhpProxy < Rack::Proxy
|
||||||
|
|
||||||
|
def perform_request(env)
|
||||||
|
request = Rack::Request.new(env)
|
||||||
|
if request.path =~ %r{\.php}
|
||||||
|
env["HTTP_HOST"] = "localhost"
|
||||||
|
env["REQUEST_PATH"] = "/php/#{request.fullpath}"
|
||||||
|
super(env)
|
||||||
|
else
|
||||||
|
@app.call(env)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
To use the middleware, please consider the following:
|
||||||
|
|
||||||
|
1) For Rails we could add a configuration in config/application.rb
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
config.middleware.use RackPhpProxy, {ssl_verify_none: true}
|
||||||
|
```
|
||||||
|
|
||||||
|
2) For Sinatra or any Rack-based application:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
class MyAwesomeSinatra < Sinatra::Base
|
||||||
|
use RackPhpProxy, {ssl_verify_none: true}
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
This will allow to run the other requests through the application and only proxy the requests that match the condition from the middleware.
|
||||||
|
|
||||||
|
See tests for more examples.
|
||||||
|
|
||||||
|
WARNING
|
||||||
|
-------
|
||||||
|
|
||||||
|
Doesn't work with fakeweb/webmock. Both libraries monkey-patch net/http code.
|
||||||
|
|
||||||
|
Todos
|
||||||
|
-----
|
||||||
|
|
||||||
|
- 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.
|
14
Rakefile
Normal file
14
Rakefile
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
require 'rubygems'
|
||||||
|
require 'bundler'
|
||||||
|
Bundler::GemHelper.install_tasks
|
||||||
|
|
||||||
|
require "rake/testtask"
|
||||||
|
task :test do
|
||||||
|
Rake::TestTask.new do |t|
|
||||||
|
t.libs << "test"
|
||||||
|
t.test_files = FileList['test/*_test.rb']
|
||||||
|
t.verbose = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
task :default => :test
|
90
lib/net_http_hacked.rb
Normal file
90
lib/net_http_hacked.rb
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
# We are hacking net/http to change semantics of streaming handling
|
||||||
|
# from "block" semantics to regular "return" semnatics.
|
||||||
|
# We need it to construct a streamable rack triplet:
|
||||||
|
#
|
||||||
|
# [status, headers, streamable_body]
|
||||||
|
#
|
||||||
|
# See http://github.com/aniero/rack-streaming-proxy
|
||||||
|
# for alternative that uses additional process.
|
||||||
|
#
|
||||||
|
# BTW I don't like monkey patching either
|
||||||
|
# but this is not real monkey patching.
|
||||||
|
# I just added some methods and named them very uniquely
|
||||||
|
# to avoid eventual conflicts. You're safe. Trust me.
|
||||||
|
#
|
||||||
|
# Also, in Ruby 1.9.2 you could use Fibers to avoid hacking net/http.
|
||||||
|
|
||||||
|
require 'net/https'
|
||||||
|
|
||||||
|
class Net::HTTP
|
||||||
|
# Original #request with block semantics.
|
||||||
|
#
|
||||||
|
# def request(req, body = nil, &block)
|
||||||
|
# unless started?
|
||||||
|
# start {
|
||||||
|
# req['connection'] ||= 'close'
|
||||||
|
# return request(req, body, &block)
|
||||||
|
# }
|
||||||
|
# end
|
||||||
|
# if proxy_user()
|
||||||
|
# unless use_ssl?
|
||||||
|
# req.proxy_basic_auth proxy_user(), proxy_pass()
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# req.set_body_internal body
|
||||||
|
# begin_transport req
|
||||||
|
# req.exec @socket, @curr_http_version, edit_path(req.path)
|
||||||
|
# begin
|
||||||
|
# res = HTTPResponse.read_new(@socket)
|
||||||
|
# end while res.kind_of?(HTTPContinue)
|
||||||
|
# res.reading_body(@socket, req.response_body_permitted?) {
|
||||||
|
# yield res if block_given?
|
||||||
|
# }
|
||||||
|
# end_transport req, res
|
||||||
|
#
|
||||||
|
# res
|
||||||
|
# end
|
||||||
|
|
||||||
|
def begin_request_hacked(req)
|
||||||
|
begin_transport req
|
||||||
|
req.exec @socket, @curr_http_version, edit_path(req.path)
|
||||||
|
begin
|
||||||
|
res = Net::HTTPResponse.read_new(@socket)
|
||||||
|
end while res.kind_of?(Net::HTTPContinue)
|
||||||
|
res.begin_reading_body_hacked(@socket, req.response_body_permitted?)
|
||||||
|
@req_hacked, @res_hacked = req, res
|
||||||
|
@res_hacked
|
||||||
|
end
|
||||||
|
|
||||||
|
def end_request_hacked
|
||||||
|
@res_hacked.end_reading_body_hacked
|
||||||
|
end_transport @req_hacked, @res_hacked
|
||||||
|
@res_hacked
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Net::HTTPResponse
|
||||||
|
# Original #reading_body with block semantics
|
||||||
|
#
|
||||||
|
# def reading_body(sock, reqmethodallowbody) #:nodoc: internal use only
|
||||||
|
# @socket = sock
|
||||||
|
# @body_exist = reqmethodallowbody && self.class.body_permitted?
|
||||||
|
# begin
|
||||||
|
# yield
|
||||||
|
# self.body # ensure to read body
|
||||||
|
# ensure
|
||||||
|
# @socket = nil
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
|
||||||
|
def begin_reading_body_hacked(sock, reqmethodallowbody)
|
||||||
|
@socket = sock
|
||||||
|
@body_exist = reqmethodallowbody && self.class.body_permitted?
|
||||||
|
end
|
||||||
|
|
||||||
|
def end_reading_body_hacked
|
||||||
|
self.body
|
||||||
|
@socket = nil
|
||||||
|
end
|
||||||
|
end
|
1
lib/rack-proxy.rb
Normal file
1
lib/rack-proxy.rb
Normal file
|
@ -0,0 +1 @@
|
||||||
|
require "rack/proxy"
|
77
lib/rack/http_streaming_response.rb
Normal file
77
lib/rack/http_streaming_response.rb
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
require "net_http_hacked"
|
||||||
|
|
||||||
|
module Rack
|
||||||
|
|
||||||
|
# Wraps the hacked net/http in a Rack way.
|
||||||
|
class HttpStreamingResponse
|
||||||
|
attr_accessor :use_ssl
|
||||||
|
attr_accessor :verify_mode
|
||||||
|
attr_accessor :read_timeout
|
||||||
|
attr_accessor :ssl_version
|
||||||
|
|
||||||
|
def initialize(request, host, port = nil)
|
||||||
|
@request, @host, @port = request, host, port
|
||||||
|
end
|
||||||
|
|
||||||
|
def body
|
||||||
|
self
|
||||||
|
end
|
||||||
|
|
||||||
|
def code
|
||||||
|
response.code.to_i
|
||||||
|
end
|
||||||
|
# #status is deprecated
|
||||||
|
alias_method :status, :code
|
||||||
|
|
||||||
|
def headers
|
||||||
|
h = Utils::HeaderHash.new
|
||||||
|
|
||||||
|
response.to_hash.each do |k, v|
|
||||||
|
h[k] = v
|
||||||
|
end
|
||||||
|
|
||||||
|
h
|
||||||
|
end
|
||||||
|
|
||||||
|
# Can be called only once!
|
||||||
|
def each(&block)
|
||||||
|
response.read_body(&block)
|
||||||
|
ensure
|
||||||
|
session.end_request_hacked
|
||||||
|
session.finish
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s
|
||||||
|
@body ||= begin
|
||||||
|
lines = []
|
||||||
|
|
||||||
|
each do |line|
|
||||||
|
lines << line
|
||||||
|
end
|
||||||
|
|
||||||
|
lines.join
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
protected
|
||||||
|
|
||||||
|
# Net::HTTPResponse
|
||||||
|
def response
|
||||||
|
@response ||= session.begin_request_hacked(@request)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Net::HTTP
|
||||||
|
def session
|
||||||
|
@session ||= begin
|
||||||
|
http = Net::HTTP.new @host, @port
|
||||||
|
http.use_ssl = self.use_ssl
|
||||||
|
http.verify_mode = self.verify_mode
|
||||||
|
http.read_timeout = self.read_timeout
|
||||||
|
http.ssl_version = self.ssl_version if self.use_ssl
|
||||||
|
http.start
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
129
lib/rack/proxy.rb
Normal file
129
lib/rack/proxy.rb
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
require "net_http_hacked"
|
||||||
|
require "rack/http_streaming_response"
|
||||||
|
|
||||||
|
module Rack
|
||||||
|
|
||||||
|
# Subclass and bring your own #rewrite_request and #rewrite_response
|
||||||
|
class Proxy
|
||||||
|
VERSION = "0.6.1"
|
||||||
|
|
||||||
|
class << self
|
||||||
|
def extract_http_request_headers(env)
|
||||||
|
headers = env.reject do |k, v|
|
||||||
|
!(/^HTTP_[A-Z0-9_]+$/ === k) || v.nil?
|
||||||
|
end.map do |k, v|
|
||||||
|
[reconstruct_header_name(k), v]
|
||||||
|
end.inject(Utils::HeaderHash.new) do |hash, k_v|
|
||||||
|
k, v = k_v
|
||||||
|
hash[k] = v
|
||||||
|
hash
|
||||||
|
end
|
||||||
|
|
||||||
|
x_forwarded_for = (headers["X-Forwarded-For"].to_s.split(/, +/) << env["REMOTE_ADDR"]).join(", ")
|
||||||
|
|
||||||
|
headers.merge!("X-Forwarded-For" => x_forwarded_for)
|
||||||
|
end
|
||||||
|
|
||||||
|
def normalize_headers(headers)
|
||||||
|
mapped = headers.map do |k, v|
|
||||||
|
[k, if v.is_a? Array then v.join("\n") else v end]
|
||||||
|
end
|
||||||
|
Utils::HeaderHash.new Hash[mapped]
|
||||||
|
end
|
||||||
|
|
||||||
|
protected
|
||||||
|
|
||||||
|
def reconstruct_header_name(name)
|
||||||
|
name.sub(/^HTTP_/, "").gsub("_", "-")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# @option opts [String, URI::HTTP] :backend Backend host to proxy requests to
|
||||||
|
def initialize(app = nil, opts= {})
|
||||||
|
if app.is_a?(Hash)
|
||||||
|
opts = app
|
||||||
|
@app = nil
|
||||||
|
else
|
||||||
|
@app = app
|
||||||
|
end
|
||||||
|
@streaming = opts.fetch(:streaming, true)
|
||||||
|
@ssl_verify_none = opts.fetch(:ssl_verify_none, false)
|
||||||
|
@backend = URI(opts[:backend]) if opts[:backend]
|
||||||
|
@read_timeout = opts.fetch(:read_timeout, 60)
|
||||||
|
@ssl_version = opts[:ssl_version] if opts[:ssl_version]
|
||||||
|
end
|
||||||
|
|
||||||
|
def call(env)
|
||||||
|
rewrite_response(perform_request(rewrite_env(env)))
|
||||||
|
end
|
||||||
|
|
||||||
|
# Return modified env
|
||||||
|
def rewrite_env(env)
|
||||||
|
env
|
||||||
|
end
|
||||||
|
|
||||||
|
# Return a rack triplet [status, headers, body]
|
||||||
|
def rewrite_response(triplet)
|
||||||
|
triplet
|
||||||
|
end
|
||||||
|
|
||||||
|
protected
|
||||||
|
|
||||||
|
def perform_request(env)
|
||||||
|
source_request = Rack::Request.new(env)
|
||||||
|
|
||||||
|
# Initialize request
|
||||||
|
if source_request.fullpath == ""
|
||||||
|
full_path = URI.parse(env['REQUEST_URI']).request_uri
|
||||||
|
else
|
||||||
|
full_path = source_request.fullpath
|
||||||
|
end
|
||||||
|
|
||||||
|
target_request = Net::HTTP.const_get(source_request.request_method.capitalize).new(full_path)
|
||||||
|
|
||||||
|
# Setup headers
|
||||||
|
target_request.initialize_http_header(self.class.extract_http_request_headers(source_request.env))
|
||||||
|
|
||||||
|
# Setup body
|
||||||
|
if target_request.request_body_permitted? && source_request.body
|
||||||
|
target_request.body_stream = source_request.body
|
||||||
|
target_request.content_length = source_request.content_length.to_i
|
||||||
|
target_request.content_type = source_request.content_type if source_request.content_type
|
||||||
|
target_request.body_stream.rewind
|
||||||
|
end
|
||||||
|
|
||||||
|
backend = env.delete('rack.backend') || @backend || source_request
|
||||||
|
use_ssl = backend.scheme == "https"
|
||||||
|
ssl_verify_none = (env.delete('rack.ssl_verify_none') || @ssl_verify_none) == true
|
||||||
|
read_timeout = env.delete('http.read_timeout') || @read_timeout
|
||||||
|
|
||||||
|
# Create the response
|
||||||
|
if @streaming
|
||||||
|
# streaming response (the actual network communication is deferred, a.k.a. streamed)
|
||||||
|
target_response = HttpStreamingResponse.new(target_request, backend.host, backend.port)
|
||||||
|
target_response.use_ssl = use_ssl
|
||||||
|
target_response.read_timeout = read_timeout
|
||||||
|
target_response.verify_mode = OpenSSL::SSL::VERIFY_NONE if use_ssl && ssl_verify_none
|
||||||
|
target_response.ssl_version = @ssl_version if @ssl_version
|
||||||
|
else
|
||||||
|
http = Net::HTTP.new(backend.host, backend.port)
|
||||||
|
http.use_ssl = use_ssl if use_ssl
|
||||||
|
http.read_timeout = read_timeout
|
||||||
|
http.verify_mode = OpenSSL::SSL::VERIFY_NONE if use_ssl && ssl_verify_none
|
||||||
|
http.ssl_version = @ssl_version if @ssl_version
|
||||||
|
|
||||||
|
target_response = http.start do
|
||||||
|
http.request(target_request)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
headers = (target_response.respond_to?(:headers) && target_response.headers) || self.class.normalize_headers(target_response.to_hash)
|
||||||
|
body = target_response.body || [""]
|
||||||
|
body = [body] unless body.respond_to?(:each)
|
||||||
|
|
||||||
|
[target_response.code, headers, body]
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
25
rack-proxy.gemspec
Normal file
25
rack-proxy.gemspec
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
$:.push File.expand_path("../lib", __FILE__)
|
||||||
|
require "rack-proxy"
|
||||||
|
|
||||||
|
Gem::Specification.new do |s|
|
||||||
|
s.name = "rack-proxy"
|
||||||
|
s.version = Rack::Proxy::VERSION
|
||||||
|
s.platform = Gem::Platform::RUBY
|
||||||
|
s.authors = ["Jacek Becela"]
|
||||||
|
s.email = ["jacek.becela@gmail.com"]
|
||||||
|
s.homepage = "http://rubygems.org/gems/rack-proxy"
|
||||||
|
s.summary = %q{A request/response rewriting HTTP proxy. A Rack app.}
|
||||||
|
s.description = %q{A Rack app that provides request/response rewriting proxy capabilities with streaming.}
|
||||||
|
|
||||||
|
s.rubyforge_project = "rack-proxy"
|
||||||
|
|
||||||
|
s.files = `git ls-files`.split("\n")
|
||||||
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
||||||
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
||||||
|
s.require_paths = ["lib"]
|
||||||
|
|
||||||
|
s.add_dependency("rack")
|
||||||
|
s.add_development_dependency("rack-test")
|
||||||
|
s.add_development_dependency("test-unit")
|
||||||
|
end
|
47
test/http_streaming_response_test.rb
Normal file
47
test/http_streaming_response_test.rb
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
require "test_helper"
|
||||||
|
require "rack/http_streaming_response"
|
||||||
|
|
||||||
|
class HttpStreamingResponseTest < Test::Unit::TestCase
|
||||||
|
|
||||||
|
def setup
|
||||||
|
host, req = "www.trix.pl", Net::HTTP::Get.new("/")
|
||||||
|
@response = Rack::HttpStreamingResponse.new(req, host)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_streaming
|
||||||
|
# Response status
|
||||||
|
assert @response.code == 200
|
||||||
|
assert @response.status == 200
|
||||||
|
|
||||||
|
# Headers
|
||||||
|
headers = @response.headers
|
||||||
|
|
||||||
|
assert headers.size > 0
|
||||||
|
|
||||||
|
assert headers["content-type"] == ["text/html;charset=utf-8"]
|
||||||
|
assert headers["CoNtEnT-TyPe"] == headers["content-type"]
|
||||||
|
assert headers["content-length"].first.to_i > 0
|
||||||
|
|
||||||
|
# Body
|
||||||
|
chunks = []
|
||||||
|
@response.body.each do |chunk|
|
||||||
|
chunks << chunk
|
||||||
|
end
|
||||||
|
|
||||||
|
assert chunks.size > 0
|
||||||
|
chunks.each do |chunk|
|
||||||
|
assert chunk.is_a?(String)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_to_s
|
||||||
|
assert_equal @response.headers["Content-Length"].first.to_i, @response.body.to_s.size
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_to_s_called_twice
|
||||||
|
body = @response.body
|
||||||
|
assert_equal body.to_s, body.to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
36
test/net_http_hacked_test.rb
Normal file
36
test/net_http_hacked_test.rb
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
require "test_helper"
|
||||||
|
require "net_http_hacked"
|
||||||
|
|
||||||
|
class NetHttpHackedTest < Test::Unit::TestCase
|
||||||
|
|
||||||
|
def test_net_http_hacked
|
||||||
|
req = Net::HTTP::Get.new("/")
|
||||||
|
http = Net::HTTP.start("www.iana.org", "80")
|
||||||
|
|
||||||
|
# Response code
|
||||||
|
res = http.begin_request_hacked(req)
|
||||||
|
assert res.code == "200"
|
||||||
|
|
||||||
|
# Headers
|
||||||
|
headers = {}
|
||||||
|
res.each_header { |k, v| headers[k] = v }
|
||||||
|
|
||||||
|
assert headers.size > 0
|
||||||
|
assert headers["content-type"] == "text/html; charset=UTF-8"
|
||||||
|
assert !headers["date"].nil?
|
||||||
|
|
||||||
|
# Body
|
||||||
|
chunks = []
|
||||||
|
res.read_body do |chunk|
|
||||||
|
chunks << chunk
|
||||||
|
end
|
||||||
|
|
||||||
|
assert chunks.size > 0
|
||||||
|
chunks.each do |chunk|
|
||||||
|
assert chunk.is_a?(String)
|
||||||
|
end
|
||||||
|
|
||||||
|
http.end_request_hacked
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
118
test/rack_proxy_test.rb
Normal file
118
test/rack_proxy_test.rb
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
require "test_helper"
|
||||||
|
require "rack/proxy"
|
||||||
|
|
||||||
|
class RackProxyTest < Test::Unit::TestCase
|
||||||
|
class HostProxy < Rack::Proxy
|
||||||
|
attr_accessor :host
|
||||||
|
|
||||||
|
def rewrite_env(env)
|
||||||
|
env["HTTP_HOST"] = self.host || 'www.trix.pl'
|
||||||
|
env
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def app(opts = {})
|
||||||
|
return @app ||= HostProxy.new(opts)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_http_streaming
|
||||||
|
get "/"
|
||||||
|
assert last_response.ok?
|
||||||
|
assert_match(/Jacek Becela/, last_response.body)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_http_full_request
|
||||||
|
app(:streaming => false)
|
||||||
|
get "/"
|
||||||
|
assert last_response.ok?
|
||||||
|
assert_match(/Jacek Becela/, last_response.body)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_http_full_request_headers
|
||||||
|
app(:streaming => false)
|
||||||
|
app.host = 'www.google.com'
|
||||||
|
get "/"
|
||||||
|
assert !Array(last_response['Set-Cookie']).empty?, 'Google always sets a cookie, yo. Where my cookies at?!'
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_https_streaming
|
||||||
|
app.host = 'www.apple.com'
|
||||||
|
get 'https://example.com'
|
||||||
|
assert last_response.ok?
|
||||||
|
assert_match(/(itunes|iphone|ipod|mac|ipad)/, last_response.body)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_https_streaming_tls
|
||||||
|
app(:ssl_version => :TLSv1).host = 'www.apple.com'
|
||||||
|
get 'https://example.com'
|
||||||
|
assert last_response.ok?
|
||||||
|
assert_match(/(itunes|iphone|ipod|mac|ipad)/, last_response.body)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_https_full_request
|
||||||
|
app(:streaming => false).host = 'www.apple.com'
|
||||||
|
get 'https://example.com'
|
||||||
|
assert last_response.ok?
|
||||||
|
assert_match(/(itunes|iphone|ipod|mac|ipad)/, last_response.body)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_https_full_request_tls
|
||||||
|
app({:streaming => false, :ssl_version => :TLSv1}).host = 'www.apple.com'
|
||||||
|
get 'https://example.com'
|
||||||
|
assert last_response.ok?
|
||||||
|
assert_match(/(itunes|iphone|ipod|mac|ipad)/, last_response.body)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_normalize_headers
|
||||||
|
proxy_class = Rack::Proxy
|
||||||
|
headers = { 'header_array' => ['first_entry'], 'header_non_array' => :entry }
|
||||||
|
|
||||||
|
normalized_headers = proxy_class.send(:normalize_headers, headers)
|
||||||
|
assert normalized_headers.instance_of?(Rack::Utils::HeaderHash)
|
||||||
|
assert normalized_headers['header_array'] == 'first_entry'
|
||||||
|
assert normalized_headers['header_non_array'] == :entry
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_header_reconstruction
|
||||||
|
proxy_class = Rack::Proxy
|
||||||
|
|
||||||
|
header = proxy_class.send(:reconstruct_header_name, "HTTP_ABC")
|
||||||
|
assert header == "ABC"
|
||||||
|
|
||||||
|
header = proxy_class.send(:reconstruct_header_name, "HTTP_ABC_D")
|
||||||
|
assert header == "ABC-D"
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_extract_http_request_headers
|
||||||
|
proxy_class = Rack::Proxy
|
||||||
|
env = {
|
||||||
|
'NOT-HTTP-HEADER' => 'test-value',
|
||||||
|
'HTTP_ACCEPT' => 'text/html',
|
||||||
|
'HTTP_CONNECTION' => nil,
|
||||||
|
'HTTP_CONTENT_MD5' => 'deadbeef'
|
||||||
|
}
|
||||||
|
|
||||||
|
headers = proxy_class.extract_http_request_headers(env)
|
||||||
|
assert headers.key?('ACCEPT')
|
||||||
|
assert headers.key?('CONTENT-MD5')
|
||||||
|
assert !headers.key?('CONNECTION')
|
||||||
|
assert !headers.key?('NOT-HTTP-HEADER')
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_duplicate_headers
|
||||||
|
proxy_class = Rack::Proxy
|
||||||
|
env = { 'Set-Cookie' => ["cookie1=foo", "cookie2=bar"] }
|
||||||
|
|
||||||
|
headers = proxy_class.normalize_headers(env)
|
||||||
|
assert headers['Set-Cookie'].include?('cookie1=foo'), "Include the first value"
|
||||||
|
assert headers['Set-Cookie'].include?("\n"), "Join multiple cookies with newlines"
|
||||||
|
assert headers['Set-Cookie'].include?('cookie2=bar'), "Include the second value"
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
def test_handles_missing_content_length
|
||||||
|
assert_nothing_thrown do
|
||||||
|
post "/", nil, "CONTENT_LENGTH" => nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
11
test/test_helper.rb
Normal file
11
test/test_helper.rb
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
require "rubygems"
|
||||||
|
require 'bundler/setup'
|
||||||
|
require 'bundler/gem_tasks'
|
||||||
|
require "test/unit"
|
||||||
|
|
||||||
|
require "rack"
|
||||||
|
require "rack/test"
|
||||||
|
|
||||||
|
Test::Unit::TestCase.class_eval do
|
||||||
|
include Rack::Test::Methods
|
||||||
|
end
|
Loading…
Reference in a new issue