class Proxy
A HTTP middleware for proxying requests to a given set of hosts. Typically used for implementing virtual servers.
Nested
Definitions
HOP_HEADERS = [...]
HTTP hop headers which should not be passed through the proxy.
Implementation
HOP_HEADERS = [
"connection",
"keep-alive",
"public",
"proxy-authenticate",
"transfer-encoding",
"upgrade",
]
def initialize(app, hosts)
Initialize the proxy middleware.
Signature
-
parameter
app
Protocol::HTTP::Middleware
The middleware to use if a request can't be proxied.
-
parameter
hosts
Hash(String, Service::Proxy)
The host applications to proxy to.
Implementation
def initialize(app, hosts)
super(app)
@server_context = nil
@hosts = hosts
@clients = {}
@count = 0
end
attr :count
The number of requests that have been proxied.
Signature
-
attribute
Integer
def close
Close all the connections to the upstream hosts.
Implementation
def close
@clients.each_value(&:close)
super
end
def connect(endpoint)
Establish a connection to the specified upstream endpoint.
Signature
-
parameter
endpoint
Async::HTTP::Endpoint
Implementation
def connect(endpoint)
@clients[endpoint] ||= Async::HTTP::Client.new(endpoint)
end
def lookup(request)
Lookup the appropriate host for the given request.
Signature
-
parameter
request
Protocol::HTTP::Request
-
returns
Service::Proxy
Implementation
def lookup(request)
# Trailing dot and port is ignored/normalized.
if authority = request.authority&.sub(/(\.)?(:\d+)?$/, "")
return @hosts[authority]
end
end
def prepare_headers(headers)
Prepare the headers to be sent to an upstream host. In particular, we delete all connection and hop headers.
Implementation
def prepare_headers(headers)
if connection = headers[CONNECTION]
headers.extract(connection)
end
headers.extract(HOP_HEADERS)
end
def prepare_request(request, host)
Prepare the request to be proxied to the specified host.
In particular, we set appropriate VIA = "via"
, FORWARDED = "forwarded"
, X_FORWARDED_FOR = "x-forwarded-for"
and X_FORWARDED_PROTO = "x-forwarded-proto"
headers.
Implementation
def prepare_request(request, host)
forwarded = []
Console.debug(self) do |buffer|
buffer.puts "Request authority: #{request.authority}"
buffer.puts "Host authority: #{host.authority}"
buffer.puts "Request: #{request.method} #{request.path} #{request.version}"
buffer.puts "Request headers: #{request.headers.inspect}"
end
# The authority of the request must match the authority of the endpoint we are proxying to, otherwise SNI and other things won't work correctly.
request.authority = host.authority
if address = request.remote_address
request.headers.add(X_FORWARDED_FOR, address.ip_address)
forwarded << "for=#{address.ip_address}"
end
if scheme = request.scheme
request.headers.add(X_FORWARDED_PROTO, scheme)
forwarded << "proto=#{scheme}"
end
unless forwarded.empty?
request.headers.add(FORWARDED, forwarded.join(";"))
end
request.headers.add(VIA, "#{request.version} #{self.class}")
self.prepare_headers(request.headers)
return request
end
def call(request)
Proxy the request if the authority matches a specific host.
Signature
-
parameter
request
Protocol::HTTP::Request
Implementation
def call(request)
if host = lookup(request)
@count += 1
request = self.prepare_request(request, host)
client = connect(host.endpoint)
client.call(request)
else
super
end
rescue => error
Console::Event::Failure.for(error).emit(self)
return Protocol::HTTP::Response[502, {"content-type" => "text/plain"}, [error.class.name]]
end