Class: Dommy::Rack::Resources
- Inherits:
-
Object
- Object
- Dommy::Rack::Resources
- Defined in:
- lib/dommy/rack/resources.rb
Overview
Dommy::Resources adapter backed by a Rack session: serves same-origin
requests from the real app (sharing the session's cookie jar). Cross-origin
requests are declined (return nil, so callers fall through to stubs) UNLESS
the session's subresource allowlist permits the host — letting an embedding
browser opt a host in (after prompting) so a SPA's cross-origin bundle can
load. Declined cross-origin hosts are recorded on the session for that UI.
This is the Rack arm of the single resources interface used for both
<script src> loads and fetch / XHR; NetworkBridge.install wires it as
the window's fetch handler.
Constant Summary collapse
- PREFETCH_CONCURRENCY =
Number of bundles prefetched at once: enough to saturate the link without spawning a thread per script on a huge page.
8
Instance Method Summary collapse
- #get(url, headers: {}) ⇒ Object
-
#initialize(session) ⇒ Resources
constructor
A new instance of Resources.
-
#prefetch(urls) ⇒ Object
Warm the GET cache for
urlsby downloading them CONCURRENTLY, so the synchronous script boot that follows reads them instantly instead of fetching a dozen big bundles one after another (the dominant cost of a heavy SPA's first paint). - #request(method:, url:, headers: {}, body: nil) ⇒ Object
-
#request_job(method:, url:, headers: {}, body: nil) ⇒ Object
The async-network counterpart of #request: makes the same page-thread serve/decline decision (origin gate, blocked-host recording), then — for a served URL — returns a worker-safe thunk that runs the request off the page thread and yields a Resources::Response.
Constructor Details
#initialize(session) ⇒ Resources
Returns a new instance of Resources.
17 18 19 20 |
# File 'lib/dommy/rack/resources.rb', line 17 def initialize(session) @session = session @prefetched = {} # absolute GET url => Resources::Response, warmed by #prefetch end |
Instance Method Details
#get(url, headers: {}) ⇒ Object
22 |
# File 'lib/dommy/rack/resources.rb', line 22 def get(url, headers: {}) = request(method: "GET", url: url, headers: headers) |
#prefetch(urls) ⇒ Object
Warm the GET cache for urls by downloading them CONCURRENTLY, so the
synchronous script boot that follows reads them instantly instead of
fetching a dozen big bundles one after another (the dominant cost of a
heavy SPA's first paint). Gated on browser mode (open cross-origin) so
plain test / Rails sessions stay sequential and deterministic. Uses its
own bounded thread set (independent of any fetch/XHR executor) and blocks
until warmed (the wait is the slowest single download, not their sum). The
worker-safe jobs touch only thread-safe state (cookie jar, frozen config).
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
# File 'lib/dommy/rack/resources.rb', line 54 def prefetch(urls) return unless @session.open_subresources? jobs = warmable_jobs(urls) return if jobs.empty? pending = Thread::Queue.new jobs.each { |job| pending << job } done = Thread::Queue.new threads = Array.new([PREFETCH_CONCURRENCY, jobs.size].min) do Thread.new do while (item = (pending.pop(true) rescue nil)) target, job = item done << [target, (job.call rescue nil)] end end end jobs.size.times do target, rack_response = done.pop @prefetched[target] = to_resources_response(rack_response) if rack_response end threads.each(&:join) end |
#request(method:, url:, headers: {}, body: nil) ⇒ Object
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
# File 'lib/dommy/rack/resources.rb', line 24 def request(method:, url:, headers: {}, body: nil) target = served_target(method: method, url: url, headers: headers, body: body) return nil unless target # A warmed GET (the page's <script src> bundles, fetched concurrently # before the synchronous boot) is served from the cache with no network. if method.to_s.upcase == "GET" && @prefetched.key?(target) return @prefetched[target] end to_resources_response(@session.fetch( target, method: method.to_s.upcase, headers: headers.is_a?(Hash) ? headers : {}, body: body&.to_s )) end |
#request_job(method:, url:, headers: {}, body: nil) ⇒ Object
The async-network counterpart of #request: makes the same page-thread serve/decline decision (origin gate, blocked-host recording), then — for a served URL — returns a worker-safe thunk that runs the request off the page thread and yields a Resources::Response. Returns nil for a URL we do not serve, exactly like #request, so the fetch handler falls through to stubs. The fetch handler submits the thunk to the network executor.
84 85 86 87 88 89 90 91 92 93 94 95 |
# File 'lib/dommy/rack/resources.rb', line 84 def request_job(method:, url:, headers: {}, body: nil) target = served_target(method: method, url: url, headers: headers, body: body) return nil unless target job = @session.build_subresource_fetch_job( target, method: method.to_s.upcase, headers: headers.is_a?(Hash) ? headers : {}, body: body&.to_s ) -> { to_resources_response(job.call) } end |