ruby-rack-proxy/lib/rack/proxy.rb

130 lines
4.1 KiB
Ruby

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