class Selenium::WebDriver::DevTools::NetworkInterceptor
Wraps the network request/response interception, providing thread-safety guarantees and handling special cases such as browser canceling requests midst interception.
You should not be using this class directly, use Driver#intercept instead. @api private
Constants
- CANNOT_GET_BODY_ON_REDIRECT_ERROR_CODE
-
CDP fails to get body on certain responses (301) and raises: “Can only get response body on requests captured after headers received.”
- INVALID_INTERCEPTION_ID_ERROR_CODE
-
CDP fails to operate with intercepted requests. Typical reason is browser cancelling intercepted requests/responses.
Attributes
Public Class Methods
Source
# File lib/selenium/webdriver/devtools/network_interceptor.rb, line 41 def initialize(devtools) @devtools = devtools @lock = Mutex.new end
Public Instance Methods
Source
# File lib/selenium/webdriver/devtools/network_interceptor.rb, line 46 def intercept(&block) devtools.network.on(:loading_failed) { |params| track_cancelled_request(params) } devtools.fetch.on(:request_paused) { |params| request_paused(params, &block) } devtools.network.set_cache_disabled(cache_disabled: true) devtools.network.enable devtools.fetch.enable(patterns: [{requestStage: 'Request'}, {requestStage: 'Response'}]) end
Private Instance Methods
Source
# File lib/selenium/webdriver/devtools/network_interceptor.rb, line 167 def cancelled?(network_id) lock.synchronize { !!cancelled_requests.delete(network_id) } end
Source
# File lib/selenium/webdriver/devtools/network_interceptor.rb, line 67 def cancelled_requests @cancelled_requests ||= [] end
Ensure usage of cancelled_requests
is thread-safe via synchronization!
Source
# File lib/selenium/webdriver/devtools/network_interceptor.rb, line 127 def continue_request(id) devtools.fetch.continue_request(request_id: id) end
Source
# File lib/selenium/webdriver/devtools/network_interceptor.rb, line 155 def fetch_response_body(id) devtools.fetch.get_response_body(request_id: id).dig('result', 'body') rescue Error::WebDriverError => e raise unless e.message.start_with?(CANNOT_GET_BODY_ON_REDIRECT_ERROR_CODE) end
Source
# File lib/selenium/webdriver/devtools/network_interceptor.rb, line 97 def intercept_request(id, params, &block) original = DevTools::Request.from(id, params) mutable = DevTools::Request.from(id, params) block.call(mutable) do |&continue| pending_response_requests[id] = continue if original == mutable continue_request(original.id) else mutate_request(mutable) end end end
Source
# File lib/selenium/webdriver/devtools/network_interceptor.rb, line 112 def intercept_response(id, params) return continue_response(id) unless block_given? body = fetch_response_body(id) original = DevTools::Response.from(id, body, params) mutable = DevTools::Response.from(id, body, params) yield mutable if original == mutable continue_response(id) else mutate_response(mutable) end end
Source
# File lib/selenium/webdriver/devtools/network_interceptor.rb, line 132 def mutate_request(request) devtools.fetch.continue_request( request_id: request.id, url: request.url, method: request.method, post_data: (Base64.strict_encode64(request.post_data) if request.post_data), headers: request.headers.map do |k, v| {name: k, value: v} end ) end
Source
# File lib/selenium/webdriver/devtools/network_interceptor.rb, line 144 def mutate_response(response) devtools.fetch.fulfill_request( request_id: response.id, body: (Base64.strict_encode64(response.body) if response.body), response_code: response.code, response_headers: response.headers.map do |k, v| {name: k, value: v} end ) end
Source
# File lib/selenium/webdriver/devtools/network_interceptor.rb, line 62 def pending_response_requests @pending_response_requests ||= {} end
We should be thread-safe to use the hash without synchronization because its keys are interception job identifiers and they should be unique within a devtools session.
Source
# File lib/selenium/webdriver/devtools/network_interceptor.rb, line 77 def request_paused(data, &block) id = data['requestId'] network_id = data['networkId'] with_cancellable_request(network_id) do if response?(data) block = pending_response_requests.delete(id) intercept_response(id, data, &block) else intercept_request(id, data, &block) end end end
Source
# File lib/selenium/webdriver/devtools/network_interceptor.rb, line 93 def response?(params) params.key?('responseStatusCode') || params.key?('responseErrorReason') end
The presence of any of these fields indicate we’re at the response stage. @see chromedevtools.github.io/devtools-protocol/tot/Fetch/#event-requestPaused
Source
# File lib/selenium/webdriver/devtools/network_interceptor.rb, line 71 def track_cancelled_request(data) return unless data['canceled'] lock.synchronize { cancelled_requests << data['requestId'] } end
Source
# File lib/selenium/webdriver/devtools/network_interceptor.rb, line 161 def with_cancellable_request(network_id) yield rescue Error::WebDriverError => e raise if e.message.start_with?(INVALID_INTERCEPTION_ID_ERROR_CODE) && !cancelled?(network_id) end