Class: Rack::RequestReplication::Forwarder
- Inherits:
-
Object
- Object
- Rack::RequestReplication::Forwarder
- Defined in:
- lib/rack/request_replication/forwarder.rb
Overview
This class implements forwarding of requests to another host and/or port.
Instance Attribute Summary collapse
-
#app ⇒ Object
readonly
Returns the value of attribute app.
-
#options ⇒ Object
readonly
Returns the value of attribute options.
Instance Method Summary collapse
- #call(env) ⇒ Array(Integer, Hash, #each)
-
#clean_scheme(request) ⇒ Object
Request scheme without the ://.
-
#cookies(request) ⇒ Object
Cookies Hash to use for the forwarded request.
-
#cookies_id(request) ⇒ Object
The key to use when looking up cookie stores in Redis for forwarding requests.
-
#create_delete_request(uri, opts = {}) ⇒ Object
Prepare a DELETE request to the forward app.
-
#create_get_request(uri, opts = {}) ⇒ Object
Prepare a GET request to the forward app.
-
#create_options_request(uri, opts = {}) ⇒ Object
Prepare a OPTIONS request to the forward app.
-
#create_patch_request(uri, opts = {}) ⇒ Object
Prepare a PATCH request to the forward app.
-
#create_post_request(uri, opts = {}) ⇒ Object
Prepare a POST request to the forward app.
-
#create_propfind_request(uri, opts = {}) ⇒ Object
Prepare a PROPFIND request to the forward app.
-
#create_put_request(uri, opts = {}) ⇒ Object
Prepare a PUT request to the forward app.
-
#csrf_token(request) ⇒ Object
The CSRF-token to use.
-
#csrf_token_from(response) ⇒ Object
Pull CSRF token from the HTML document’s header.
-
#forward_host_with_port(request) ⇒ Object
The host to forward to including the port if the port does not match the current scheme.
-
#forward_uri(request) ⇒ Object
Creates a URI based on the request info and the options set.
-
#initialize(app, options = {}) ⇒ Forwarder
constructor
A new instance of Forwarder.
-
#logger ⇒ Object
Logger that logs to STDOUT.
-
#port_matches_scheme?(request) ⇒ Boolean
Checks if the request scheme matches the destination port.
-
#redis ⇒ Object
Persistent Redis connection that is used to store cookies.
-
#replicate(request) ⇒ Object
Replicates the request and passes it on to the request forwarder.
-
#replicate_options_and_data(request) ⇒ Object
Replicates all the options and data that was in the original request and puts them in a Hash.
-
#update_cookies(request, response) ⇒ Object
Update cookies from the forwarded request using the session id from the cookie of the source app as a key.
-
#update_csrf_token(request, response) ⇒ Object
Update CSRF token to bypass XSS errors in Rails.
-
#update_csrf_token_and_cookies(request, response) ⇒ Object
Update CSRF token and cookies.
Constructor Details
#initialize(app, options = {}) ⇒ Forwarder
Returns a new instance of Forwarder.
36 37 38 39 40 41 42 43 44 45 46 47 |
# File 'lib/rack/request_replication/forwarder.rb', line 36 def initialize(app, = {}) @app = app = { host: 'localhost', port: 8080, use_ssl: false, verify_ssl: true, session_key: 'rack.session', root_url: '/', redis: {} }.merge() end |
Instance Attribute Details
#app ⇒ Object (readonly)
Returns the value of attribute app.
17 18 19 |
# File 'lib/rack/request_replication/forwarder.rb', line 17 def app @app end |
#options ⇒ Object (readonly)
Returns the value of attribute options.
18 19 20 |
# File 'lib/rack/request_replication/forwarder.rb', line 18 def end |
Instance Method Details
#call(env) ⇒ Array(Integer, Hash, #each)
54 55 56 57 58 |
# File 'lib/rack/request_replication/forwarder.rb', line 54 def call(env) request = Rack::Request.new(env) replicate(request) app.call(env) end |
#clean_scheme(request) ⇒ Object
Request scheme without the ://
381 382 383 |
# File 'lib/rack/request_replication/forwarder.rb', line 381 def clean_scheme(request) request.scheme.match(/^\w+/)[0] end |
#cookies(request) ⇒ Object
Cookies Hash to use for the forwarded request.
Tries to find the cookies from earlier forwarded requests in the Redis store, otherwise falls back to the cookies from the source app.
173 174 175 176 |
# File 'lib/rack/request_replication/forwarder.rb', line 173 def (request) return (request. || "") unless (request) redis.get((request)) || request. || {} end |
#cookies_id(request) ⇒ Object
The key to use when looking up cookie stores in Redis for forwarding requests. Needed for session persistence over forwarded requests for the same user in the source app.
187 188 189 190 191 192 |
# File 'lib/rack/request_replication/forwarder.rb', line 187 def (request) cs = request. session = cs && cs[[:session_key]] session_id = session && session.split("\n--").last session_id end |
#create_delete_request(uri, opts = {}) ⇒ Object
Prepare a DELETE request to the forward app.
The passed in options hash is ignored.
264 265 266 |
# File 'lib/rack/request_replication/forwarder.rb', line 264 def create_delete_request(uri, opts = {}) Net::HTTP::Delete.new(uri.request_uri) end |
#create_get_request(uri, opts = {}) ⇒ Object
Prepare a GET request to the forward app.
The passed in options hash is ignored.
203 204 205 |
# File 'lib/rack/request_replication/forwarder.rb', line 203 def create_get_request(uri, opts = {}) Net::HTTP::Get.new(uri.request_uri) end |
#create_options_request(uri, opts = {}) ⇒ Object
Prepare a OPTIONS request to the forward app.
The passed in options hash is ignored.
277 278 279 |
# File 'lib/rack/request_replication/forwarder.rb', line 277 def (uri, opts = {}) Net::HTTP::Options.new(uri.request_uri) end |
#create_patch_request(uri, opts = {}) ⇒ Object
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.
249 250 251 252 253 |
# File 'lib/rack/request_replication/forwarder.rb', line 249 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 |
#create_post_request(uri, opts = {}) ⇒ Object
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.
217 218 219 220 221 |
# File 'lib/rack/request_replication/forwarder.rb', line 217 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 |
#create_propfind_request(uri, opts = {}) ⇒ Object
Prepare a PROPFIND request to the forward app.
The passed in options hash is ignored.
290 291 292 |
# File 'lib/rack/request_replication/forwarder.rb', line 290 def create_propfind_request(uri, opts = {}) Net::HTTP::Propfind.new(uri.request_uri) end |
#create_put_request(uri, opts = {}) ⇒ Object
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.
233 234 235 236 237 |
# File 'lib/rack/request_replication/forwarder.rb', line 233 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 |
#csrf_token(request) ⇒ Object
The CSRF-token to use.
110 111 112 113 114 115 |
# File 'lib/rack/request_replication/forwarder.rb', line 110 def csrf_token(request) token = request.params["authenticity_token"] return if token.nil? redis.get("csrf-#{token}") || token end |
#csrf_token_from(response) ⇒ Object
Pull CSRF token from the HTML document’s header.
138 139 140 141 142 143 144 145 146 |
# File 'lib/rack/request_replication/forwarder.rb', line 138 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 |
#forward_host_with_port(request) ⇒ Object
The host to forward to including the port if the port does not match the current scheme.
347 348 349 350 351 |
# File 'lib/rack/request_replication/forwarder.rb', line 347 def forward_host_with_port(request) host = [:host].to_s host = "#{host}:#{options[:port]}" unless port_matches_scheme?(request) host end |
#forward_uri(request) ⇒ Object
Creates a URI based on the request info and the options set.
334 335 336 337 338 |
# File 'lib/rack/request_replication/forwarder.rb', line 334 def forward_uri(request) url = "#{request.scheme}://#{forward_host_with_port(request)}" url << request.fullpath URI(url) end |
#logger ⇒ Object
Logger that logs to STDOUT
390 391 392 |
# File 'lib/rack/request_replication/forwarder.rb', line 390 def logger @logger ||= ::Logger.new(STDOUT) end |
#port_matches_scheme?(request) ⇒ Boolean
Checks if the request scheme matches the destination port.
371 372 373 |
# File 'lib/rack/request_replication/forwarder.rb', line 371 def port_matches_scheme?(request) [:port].to_i == DEFAULT_PORTS[clean_scheme(request)] end |
#redis ⇒ Object
Persistent Redis connection that is used to store cookies.
357 358 359 360 361 362 363 |
# File 'lib/rack/request_replication/forwarder.rb', line 357 def redis @redis ||= Redis.new({ host: 'localhost', port: 6379, db: 'rack-request-replication' }.merge([:redis])) end |
#replicate(request) ⇒ Object
Replicates the request and passes it on to the request forwarder.
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
# File 'lib/rack/request_replication/forwarder.rb', line 66 def replicate(request) opts = (request) uri = forward_uri(request) http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = [:use_ssl] http.verify_mode = OpenSSL::SSL::VERIFY_NONE unless [: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 [:basic_auth] forward_request.basic_auth [:basic_auth][:user], [:basic_auth][:password] end Thread.new do begin forward_request.add_field("Cookie", (request)) (request, http.request(forward_request)) rescue => e logger.debug "Replicating request failed with: #{e.message}" end end end |
#replicate_options_and_data(request) ⇒ Object
Replicates all the options and data that was in the original request and puts them in a Hash.
301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 |
# File 'lib/rack/request_replication/forwarder.rb', line 301 def (request) ||= {} %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) [m] = value unless value.nil? end if [:params]["authenticity_token"] [:params]["authenticity_token"] = csrf_token(request) end end |
#update_cookies(request, response) ⇒ Object
Update cookies from the forwarded request using the session id from the cookie of the source app as a key. The cookie is stored in Redis.
156 157 158 159 160 161 |
# File 'lib/rack/request_replication/forwarder.rb', line 156 def (request, response) return unless (request) = response.to_hash['set-cookie'].collect{ |ea|ea[/^.*?;/] }.join rescue {} = Hash[.split(";").map{ |d|d.split('=') }] rescue {} redis.set((request), ) end |
#update_csrf_token(request, response) ⇒ Object
Update CSRF token to bypass XSS errors in Rails.
122 123 124 125 126 127 128 129 130 |
# File 'lib/rack/request_replication/forwarder.rb', line 122 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 |
#update_csrf_token_and_cookies(request, response) ⇒ Object
Update CSRF token and cookies.
99 100 101 102 |
# File 'lib/rack/request_replication/forwarder.rb', line 99 def (request, response) update_csrf_token(request, response) (request, response) end |