Add skeleton

This commit is contained in:
James Hu 2015-06-04 19:59:23 -07:00
parent 5ebb5e1a1d
commit 0dd5aa6a31
5 changed files with 285 additions and 0 deletions

80
Gemfile.lock Normal file
View File

@ -0,0 +1,80 @@
GEM
remote: http://rubygems.org/
specs:
activesupport (4.2.1)
i18n (~> 0.7)
json (~> 1.7, >= 1.7.7)
minitest (~> 5.1)
thread_safe (~> 0.3, >= 0.3.4)
tzinfo (~> 1.1)
addressable (2.3.8)
builder (3.2.2)
descendants_tracker (0.0.4)
thread_safe (~> 0.3, >= 0.3.1)
docile (1.1.5)
faraday (0.9.1)
multipart-post (>= 1.2, < 3)
git (1.2.9.1)
github_api (0.12.3)
addressable (~> 2.3)
descendants_tracker (~> 0.0.4)
faraday (~> 0.8, < 0.10)
hashie (>= 3.3)
multi_json (>= 1.7.5, < 2.0)
nokogiri (~> 1.6.3)
oauth2
hashie (3.4.2)
highline (1.7.2)
i18n (0.7.0)
jeweler (2.0.1)
builder
bundler (>= 1.0)
git (>= 1.2.5)
github_api
highline (>= 1.6.15)
nokogiri (>= 1.5.10)
rake
rdoc
json (1.8.3)
jwt (1.5.0)
mini_portile (0.6.2)
minitest (5.7.0)
multi_json (1.11.0)
multi_xml (0.5.5)
multipart-post (2.0.0)
nokogiri (1.6.6.2)
mini_portile (~> 0.6.0)
oauth2 (1.0.0)
faraday (>= 0.8, < 0.10)
jwt (~> 1.0)
multi_json (~> 1.3)
multi_xml (~> 0.5)
rack (~> 1.2)
rack (1.6.1)
rake (10.4.2)
rdoc (3.12.2)
json (~> 1.4)
shoulda (3.5.0)
shoulda-context (~> 1.0, >= 1.0.1)
shoulda-matchers (>= 1.4.1, < 3.0)
shoulda-context (1.2.1)
shoulda-matchers (2.8.0)
activesupport (>= 3.0.0)
simplecov (0.10.0)
docile (~> 1.1.0)
json (~> 1.8)
simplecov-html (~> 0.10.0)
simplecov-html (0.10.0)
thread_safe (0.3.5)
tzinfo (1.2.2)
thread_safe (~> 0.1)
PLATFORMS
ruby
DEPENDENCIES
bundler (~> 1.0)
jeweler (~> 2.0.1)
rdoc (~> 3.12)
shoulda
simplecov

2
lib/reverse-proxy.rb Normal file
View File

@ -0,0 +1,2 @@
module ReverseProxy
end

143
lib/reverse-proxy/client.rb Normal file
View File

@ -0,0 +1,143 @@
require 'rack'
require 'rack-proxy'
module ReverseProxy
class Client
@@callback_methods = [
:on_response,
:on_set_cookies,
:on_success,
:on_redirect,
:on_missing,
:on_error,
:on_complete
]
# Define callback setters
@@callback_methods.each do |method|
define_method(method) do |&block|
self.callbacks[method] = block
end
end
attr_accessor :url, :callbacks
def initialize(url)
self.url = url
self.callbacks = {}
# Initialize default callbacks with empty Proc
@@callback_methods.each do |method|
self.callbacks[method] = Proc.new {}
end
yield(self) if block_given?
end
def request(env, options = {}, &block)
options.reverse_merge!(
headers: {},
path: nil,
username: nil,
password: nil
)
source_request = Rack::Request.new(env)
# We can pass in a custom path
uri = URI.parse("#{url}#{options[:path] || env['ORIGINAL_FULLPATH']}")
# Initialize request
target_request = Net::HTTP.const_get(source_request.request_method.capitalize).new(uri.request_uri)
# Setup headers
target_request_headers = extract_http_request_headers(source_request.env).merge(options[:headers])
target_request.initialize_http_header(target_request_headers)
# Basic auth
target_request.basic_auth(options[:username], options[:password]) if options[:username] and options[:password]
# Setup body
if target_request.request_body_permitted? \
&& source_request.body
source_request.body.rewind
target_request.body_stream = source_request.body
end
target_request.content_length = source_request.content_length || 0
target_request.content_type = source_request.content_type if source_request.content_type
# Hold the response here
target_response = nil
# Don't encode response/support compression which was
# causing content length not match the actual content
# length of the response which ended up causing issues
# within Varnish (503)
target_request['Accept-Encoding'] = nil
# Make the request
Net::HTTP.start(uri.host, uri.port, use_ssl: (uri.scheme == "https")) do |http|
target_response = http.request(target_request)
end
status_code = target_response.code.to_i
payload = [status_code, target_response]
callbacks[:on_response].call(payload)
if set_cookie_headers = target_response.to_hash['set-cookie']
set_cookies_hash = {}
set_cookie_headers.each do |set_cookie_header|
set_cookie_hash = CookieJar::CookieValidation.parse_set_cookie(set_cookie_header)
set_cookie_hash[:value] = CGI.unescape(set_cookie_hash[:value])
name = set_cookie_hash.delete(:name)
set_cookies_hash[name] = set_cookie_hash
end
callbacks[:on_set_cookies].call(payload | [set_cookies_hash])
end
case status_code
when 200..299
callbacks[:on_success].call(payload)
when 300..399
if redirect_url = target_response['Location']
callbacks[:on_redirect].call(payload | [redirect_url])
end
when 400..499
callbacks[:on_missing].call(payload)
when 500..599
callbacks[:on_error].call(payload)
end
callbacks[:on_complete].call(payload)
payload
end
private
def extract_http_request_headers(env)
headers = env.reject do |k, v|
!(/^HTTP_[A-Z_]+$/ === k) || v.nil?
end.map do |k, v|
[reconstruct_header_name(k), v]
end.inject(Rack::Utils::HeaderHash.new) do |hash, k_v|
k, v = k_v
hash[k] = v
hash
end
headers
end
def reconstruct_header_name(name)
name.sub(/^HTTP_/, "").gsub("_", "-")
end
end
end

View File

@ -0,0 +1,60 @@
module ReverseProxy
module Controller
def reverse_proxy(proxy_url, options = {})
proxy_uri = Addressable::URI.parse(proxy_url)
client = ReverseProxy::Client.new(proxy_url) do |config|
config.on_response do |code, response|
blacklist = [
'Connection', # Always close connection
'Transfer-Encoding', # Let Rails calculate this
'Content-Length' # Let Rails calculate this
]
response.each_capitalized do |key, value|
next if blacklist.include?(key)
headers[key] = value
end
end
config.on_set_cookies do |code, response, set_cookies|
set_cookies.each do |key, attributes|
cookies[key] = attributes
end
end
config.on_redirect do |code, response, redirect_url|
request_uri = Addressable::URI.parse(request.url)
redirect_uri = Addressable::URI.parse(redirect_url)
# Make redirect uri absolute if it's relative by
# joining it with the request url
redirect_uri = request_uri.join(redirect_url) if redirect_uri.host.nil?
unless redirect_uri.port.nil?
# Make sure it's consistent with our request port
redirect_uri.port = request.port if redirect_uri.port == proxy_uri.port
end
redirect_to redirect_uri.to_s, status: code and return
end
config.on_complete do |code, response|
content_type = response['Content-Type']
body = response.body.to_s
if content_type and content_type.match /image/
send_data body, content_type: content_type, disposition: "inline", status: code
else
render text: body, content_type: content_type, status: code
end
end
yield(config) if block_given?
end
client.request(env, options)
end
end
end