class Falcon::Middleware::Proxy

A HTTP middleware for proxying requests to a given set of hosts. Typically used for implementing virtual servers.

Constants

CONNECTION
FORWARDED
HOP_HEADERS

HTTP hop headers which should not be passed through the proxy.

VIA
X_FORWARDED_FOR
X_FORWARDED_PROTO

Attributes

count[R]

The number of requests that have been proxied. @attribute [Integer]

Public Class Methods

new(app, hosts) click to toggle source

Initialize the proxy middleware. @parameter app [Protocol::HTTP::Middleware] The middleware to use if a request can't be proxied. @parameter hosts [Array(Service::Proxy)] The host applications to proxy to.

Calls superclass method
# File lib/falcon/middleware/proxy.rb, line 62
def initialize(app, hosts)
        super(app)
        
        @server_context = nil
        
        @hosts = hosts
        @clients = {}
        
        @count = 0
end

Public Instance Methods

call(request) click to toggle source

Proxy the request if the authority matches a specific host. @parameter request [Protocol::HTTP::Request]

Calls superclass method
# File lib/falcon/middleware/proxy.rb, line 148
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
        Console.logger.error(self) {$!}
        return Protocol::HTTP::Response[502, {'content-type' => 'text/plain'}, ["#{$!.inspect}: #{$!.backtrace.join("\n")}"]]
end
close() click to toggle source

Close all the connections to the upstream hosts.

Calls superclass method
# File lib/falcon/middleware/proxy.rb, line 78
def close
        @clients.each_value(&:close)
        
        super
end
connect(endpoint) click to toggle source

Establish a connection to the specified upstream endpoint. @parameter endpoint [Async::HTTP::Endpoint]

# File lib/falcon/middleware/proxy.rb, line 86
def connect(endpoint)
        @clients[endpoint] ||= Async::HTTP::Client.new(endpoint)
end
lookup(request) click to toggle source

Lookup the appropriate host for the given request. @parameter request [Protocol::HTTP::Request] @returns [Service::Proxy]

# File lib/falcon/middleware/proxy.rb, line 93
def lookup(request)
        # Trailing dot and port is ignored/normalized.
        if authority = request.authority&.sub(/(\.)?(:\d+)?$/, '')
                return @hosts[authority]
        end
end
prepare_headers(headers) click to toggle source

Prepare the headers to be sent to an upstream host. In particular, we delete all connection and hop headers.

# File lib/falcon/middleware/proxy.rb, line 102
def prepare_headers(headers)
        if connection = headers[CONNECTION]
                headers.extract(connection)
        end
        
        headers.extract(HOP_HEADERS)
end
prepare_request(request, host) click to toggle source

Prepare the request to be proxied to the specified host. In particular, we set appropriate {VIA}, {FORWARDED}, {X_FORWARDED_FOR} and {X_FORWARDED_PROTO} headers.

# File lib/falcon/middleware/proxy.rb, line 112
def prepare_request(request, host)
        forwarded = []
        
        Console.logger.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