Create a gist now

Instantly share code, notes, and snippets.

A simple HTTP Proxy
# You are free to copy and use this code in any way you see fit
# with absolutely no requirements, but no warranty is provided.
require 'rubygems'
require 'sinatra/base'
require 'net/http'
require 'logger'
module PassThroughProxy
class Api < Sinatra::Base
attr_accessor :logger
#Most header fields from rfc 2616 section 14 -- There is a larger list at: http://www.iana.org/assignments/message-headers/message-headers.xhtml, which I have not included
HTTP_HEADER_FIELDS = [ "Accept", "Accept-Charset", "Accept-Encoding", "Accept-Language", "Accept-Ranges", "Age", "Allow", "Authorization", "Cache-Control", "Connection", "Content-Encoding", "Content-Language", "Content-Length", "Content-Location", "Content-MD5", "Content-Range", "Content-Type", "Date", "ETag", "Expect", "Expires", "From", "Host", "If-Match", "If-Modified-Since", " If-None-Match", "If-Range", "If-Unmodified-Since", "Last-Modified", "Location", "Max-Forwards", "Pragma", "Proxy-Authenticate", "Proxy-Authorization", "Range", "Referer", "Retry-After", "Server", "TE", "User-Agent", "Vary", "Via", "Warning", "WWW-Authenticate" ]
def initialize()
super
logFileName = "/tmp/passthroughproxy.log"
begin
logTarget = File.open(logFileName, "a")
logTarget.sync = true
self.logger = Logger.new(logTarget)
self.logger.level = Logger::DEBUG
rescue Exception => e
self.logger = Logger.new(STDOUT)
self.logger.level = Logger::DEBUG
logger.warn("Error initializing logger with logTargetg file: #{logFileName}: #{e}")
end
logger.debug("Initializing passthrough proxy")
begin
rescue Exception => e
logger.debug("Error parsing server settings, #{e}")
end
logger.debug("Finished Initializing passthrough proxy")
end
# If you want to catch any specific route and so something special with it, you should add the route here
# before the catchall routes.
get '*' do
logger.debug("Processing get * route")
handleRequest "get"
end
delete '*' do
logger.info("Processing delete * route")
handleRequest "delete"
end
post '*' do
logger.info("Processing post * route")
handleRequest "post"
end
put '*' do
logger.info("Processing put * route")
handleRequest "put"
end
head '*' do
#TODO if useful
logger.info("Processing HEAD * route")
end
delete '*' do
#TODO if useful
logger.info("Processing DELETE * route")
end
options '*' do
#TODO if useful
logger.info("Processing OPTIONS * route")
end
not_found do
logger.error( "Requested a unhandled route, #{request.request_method} #{request.host} #{request.path_info}" )
end
def handleRequest(req_type)
begin
destination = request.host
port = request.port
route=params[:splat].first
logger.debug("Route is: #{route}")
logger.debug("Destination: #{destination}")
if request.scheme == "https"
logger.error("HTTPS proxy not currently supported")
status 500
return
end
uri = getURIForRequest( destination, request.path_info, request.query_string, port )
logger.info("Request being forwarded as follows: #{uri.to_s}")
http = Net::HTTP.new(uri.host,uri.port)
headers = getHeadersFromRequest
case req_type
when "put"
return resultFromPutRequest(uri,headers,http)
when "post"
return resultFromPostRequest(uri,headers,http)
when "delete"
return resultFromDeleteRequest(uri,headers,http)
when "get"
return resultFromGetRequest(uri,headers,http)
end
rescue Exception => e
logger.error("Error occurred processing catchall route: #{e}")
status 500
return
end
end
def resultFromPutRequest(uri,headers,http)
putrequest = Net::HTTP::Put.new(uri.request_uri, headers)
putrequest.body = request.body.read
putresponse = http.request(putrequest)
return createRackResponse(putresponse.body, putresponse.code, putresponse.header)
end
def resultFromPostRequest(uri,headers,http)
postrequest = Net::HTTP::Post.new(uri.request_uri, headers)
postrequest.body = request.body.read
postresponse = http.request(postrequest)
return createRackResponse(postresponse.body, postresponse.code, postresponse.header)
end
def resultFromDeleteRequest(uri,headers,http)
deleterequest = Net::HTTP::Delete.new(uri.request_uri, headers)
deleterequest.body = request.body.read
deleteresponse = http.request(deleterequest)
return createRackResponse(deleteresponse.body, deleteresponse.code, deleteresponse.header)
end
def resultFromGetRequest(uri,headers,http)
get_request = Net::HTTP::Get.new(uri.request_uri, headers)
get_request.body = request.body.read
getresponse = http.request(get_request)
return createRackResponse(getresponse.body, getresponse.code, getresponse.header)
end
def createRackResponse(body,code,headers)
respbody=""
responseheaders = Hash.new()
if( body != nil )
respbody=body
end
if( headers != nil )
headers.each {|key,value| responseheaders[key]=value }
end
return [ code.to_i(), responseheaders, respbody ]
end
def getURIForRequest( host,path_info,query_string,port )
if( query_string == nil or query_string.empty? )
uri = URI::HTTP.build(
:host => host,
:path => path_info,
:port => port
)
else
uri = URI::HTTP.build(
:host => host,
:path => path_info,
:query => query_string,
:port => port
)
end
return uri
end
def getHeadersFromRequest()
result = Hash.new()
request.env.each do |e|
header = e[0].gsub("_","-")
header = header.sub("HTTP-","")
if isForwardableHTTPHeader?(header) or header.start_with?("X-")
result["#{header}"]=e[1]
logger.debug("#{header} = #{e[1]}")
end
end
return result
end
def isForwardableHTTPHeader?( val )
if HTTP_HEADER_FIELDS.any?{ |s| s.casecmp(val)==0 }
return true
else
return false
end
end
end
end
if __FILE__ == $0
PassThroughProxy::Api.run!
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment