class Rack::RequestReplication::Forwarder
This class implements forwarding of requests to another host and/or port.
Attributes
Public Class Methods
@param [#call] app @param [Hash{Symbol => Object}] options @option options [String] :host (‘localhost’) @option options [Integer] :port (8080) @option options [String] :session_key (‘rack.session’) @option options [Bool] :use_ssl (false) @option options [Bool] :verify_ssl (true) @option options [Hash{Symbol => Object}] :basic_auth
@option basic_auth [String] :user @option basic_auth [String] :password
@option options [Hash{Symbol => Object}] :redis
@option redis [String] :host ('localhost') @option redis [Integer] :port (6379) @option redis [String] :db ('rack-request-replication')
# File lib/rack/request_replication/forwarder.rb, line 37 def initialize(app, options = {}) @app = app @options = { host: 'localhost', port: 8080, use_ssl: false, verify_ssl: true, session_key: 'rack.session', root_url: '/', redis: {} }.merge(options) end
Public Instance Methods
@param [Hash{String => String}] env @return [Array(Integer, Hash, each)] @see rack.rubyforge.org/doc/SPEC.html
# File lib/rack/request_replication/forwarder.rb, line 55 def call(env) request = Rack::Request.new(env) replicate(request) app.call(env) end
Request scheme without the ://
@param [Rack::Request] request @returns [String]
# File lib/rack/request_replication/forwarder.rb, line 410 def clean_scheme(request) request.scheme.match(/^\w+/)[0] end
Prepare a DELETE request to the forward app.
The passed in options hash is ignored.
@param [URI] uri @param [Hash{Symbol => Object}] opts ({}) @returns [Net:HTTP::Delete]
# File lib/rack/request_replication/forwarder.rb, line 267 def create_delete_request(uri, opts = {}) Net::HTTP::Delete.new(uri.request_uri) end
Prepare a GET request to the forward app.
The passed in options hash is ignored.
@param [URI] uri @param [Hash{Symbol => Object}] opts ({}) @returns [Net:HTTP::Get]
# File lib/rack/request_replication/forwarder.rb, line 206 def create_get_request(uri, opts = {}) Net::HTTP::Get.new(uri.request_uri) end
Prepare a HEAD request to the forward app.
The passed in options hash is ignored.
@param [URI] uri @param [Hash{Symbol => Object}] opts ({}) @returns [Net:HTTP::Head]
# File lib/rack/request_replication/forwarder.rb, line 319 def create_head_request(uri, opts = {}) Net::HTTP::Head.new(uri.request_uri) end
Prepare a OPTIONS request to the forward app.
The passed in options hash is ignored.
@param [URI] uri @param [Hash{Symbol => Object}] opts ({}) @returns [Net:HTTP::Options]
# File lib/rack/request_replication/forwarder.rb, line 280 def create_options_request(uri, opts = {}) Net::HTTP::Options.new(uri.request_uri) end
Prepare a PATCH request to the forward app.
The passed in options hash contains all the data from the request that needs to be forwarded.
@param [URI] uri @param [Hash{Symbol => Object}] opts ({}) @returns [Net:HTTP::Patch]
# File lib/rack/request_replication/forwarder.rb, line 252 def create_patch_request(uri, opts = {}) forward_request = Net::HTTP::Patch.new(uri.request_uri) forward_request.body = opts[:params].to_query forward_request end
Prepare a POST request to the forward app.
The passed in options hash contains all the data from the request that needs to be forwarded.
@param [URI] uri @param [Hash{Symbol => Object}] opts ({}) @returns [Net:HTTP::Post]
# File lib/rack/request_replication/forwarder.rb, line 220 def create_post_request(uri, opts = {}) forward_request = Net::HTTP::Post.new(uri.request_uri) forward_request.body = opts[:params].to_query forward_request end
Prepare a PROPFIND request to the forward app.
The passed in options hash is ignored.
@param [URI] uri @param [Hash{Symbol => Object}] opts ({}) @returns [Net:HTTP::Propfind]
# File lib/rack/request_replication/forwarder.rb, line 293 def create_propfind_request(uri, opts = {}) Net::HTTP::Propfind.new(uri.request_uri) end
Prepare a PUT request to the forward app.
The passed in options hash contains all the data from the request that needs to be forwarded.
@param [URI] uri @param [Hash{Symbol => Object}] opts ({}) @returns [Net:HTTP::Put]
# File lib/rack/request_replication/forwarder.rb, line 236 def create_put_request(uri, opts = {}) forward_request = Net::HTTP::Put.new(uri.request_uri) forward_request.body = opts[:params].to_query forward_request end
Prepare a TRACE request to the forward app.
The passed in options hash is ignored.
@param [URI] uri @param [Hash{Symbol => Object}] opts ({}) @returns [Net:HTTP::Trace]
# File lib/rack/request_replication/forwarder.rb, line 306 def create_trace_request(uri, opts = {}) Net::HTTP::Trace.new(uri.request_uri) end
The CSRF-token to use.
@param [Rack::Request] request @returns [String]
# File lib/rack/request_replication/forwarder.rb, line 113 def csrf_token(request) token = request.params["authenticity_token"] return if token.nil? redis.get("csrf-#{token}") || token end
Pull CSRF token from the HTML document’s header.
@param [Net::HTTP::Response] response @returns [String]
# File lib/rack/request_replication/forwarder.rb, line 141 def csrf_token_from(response) response.split("\n"). select{|l| l.match(/csrf-token/) }. first.split(" "). select{|t| t.match(/^content=/)}.first. match(/content="(.*)"/)[1] rescue nil end
The host to forward to including the port if the port does not match the current scheme.
@param [Rack::Request] request @returns [String]
# File lib/rack/request_replication/forwarder.rb, line 376 def forward_host_with_port(request) host = options[:host].to_s host = "#{host}:#{options[:port]}" unless port_matches_scheme?(request) host end
Creates a URI based on the request info and the options set.
@param [Rack::Request] request @returns [URI]
# File lib/rack/request_replication/forwarder.rb, line 363 def forward_uri(request) url = "#{request.scheme}://#{forward_host_with_port(request)}" url << request.fullpath URI(url) end
Logger that logs to STDOUT
@returns [Logger]
# File lib/rack/request_replication/forwarder.rb, line 419 def logger @logger ||= ::Logger.new(STDOUT) end
Checks if the request scheme matches the destination port.
@param [Rack::Request] request @returns [boolean]
# File lib/rack/request_replication/forwarder.rb, line 400 def port_matches_scheme?(request) options[:port].to_i == DEFAULT_PORTS[clean_scheme(request)] end
Persistent Redis connection that is used to store cookies.
# File lib/rack/request_replication/forwarder.rb, line 386 def redis @redis ||= Redis.new({ host: 'localhost', port: 6379, db: 'rack-request-replication' }.merge(options[:redis])) end
Replicates the request and passes it on to the request forwarder.
@param [Rack::Request] request
# File lib/rack/request_replication/forwarder.rb, line 67 def replicate(request) opts = replicate_options_and_data(request) uri = forward_uri(request) return unless VALID_REQUEST_METHODS.include?(opts[:request_method].downcase) http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = options[:use_ssl] http.verify_mode = OpenSSL::SSL::VERIFY_NONE unless options[:verify_ssl] forward_request = send("create_#{opts[:request_method].downcase}_request", uri, opts) forward_request.add_field("Accept", opts[:accept]) forward_request.add_field("Accept-Encoding", opts[:accept_encoding]) forward_request.add_field("Host", request.host) if options[:basic_auth] forward_request.basic_auth options[:basic_auth][:user], options[:basic_auth][:password] end Thread.new do begin forward_request.add_field("Cookie", cookies(request)) update_csrf_token_and_cookies(request, http.request(forward_request)) rescue => e logger.debug "Replicating request failed with: #{e.message}" end end end
Replicates all the options and data that was in the original request and puts them in a Hash.
@param [Rack::Request] request @returns [Hash]
# File lib/rack/request_replication/forwarder.rb, line 330 def replicate_options_and_data(request) replicated_options ||= {} %w( accept_encoding body request_method content_charset media_type media_type_params params referer request_method user_agent url ).map(&:to_sym).each do |m| value = request.send(m) replicated_options[m] = value unless value.nil? end if replicated_options[:params]["authenticity_token"] replicated_options[:params]["authenticity_token"] = csrf_token(request) end replicated_options end
Update CSRF token to bypass XSS errors in Rails.
@param [Rack::Request] request
# File lib/rack/request_replication/forwarder.rb, line 125 def update_csrf_token(request, response) token = request.params["authenticity_token"] return if token.nil? response_token = csrf_token_from response return token if response_token.nil? redis.set "csrf-#{token}", response_token end