Implementation:Teamcapybara Capybara Server Middleware
Overview
Capybara::Server::Middleware is a Rack middleware that wraps the application under test, tracking in-flight HTTP requests and capturing server errors for test isolation. It allows Capybara to wait for pending requests to complete before making test assertions and to detect server-side errors that occur during test execution. The middleware includes a nested Counter class that provides thread-safe request counting.
Source File
| Property | Value |
|---|---|
| File | lib/capybara/server/middleware.rb
|
| Lines | 71 |
| Language | Ruby |
| Module | Capybara::Server::Middleware
|
| Nested Class | Capybara::Server::Middleware::Counter
|
Class: Capybara::Server::Middleware
Attributes
| Attribute | Access | Description |
|---|---|---|
error |
reader | The most recent server error captured during request processing; nil if no error has occurred
|
initialize(app, server_errors, extra_middleware = [])
Creates a new middleware instance wrapping the given Rack application. Accepts a list of exception classes to capture as server errors and an optional array of extra middleware classes to layer on top of the application.
The extra middleware classes are composed using inject, each wrapping the previous application in the chain:
def initialize(app, server_errors, extra_middleware = [])
@app = app
@extended_app = extra_middleware.inject(@app) do |ex_app, klass|
klass.new(ex_app)
end
@counter = Counter.new
@server_errors = server_errors
end
pending_requests
Returns a duplicate array of URI strings for all currently in-flight requests. The array is duplicated to prevent external mutation of the internal state.
def pending_requests @counter.value end
pending_requests?
Returns true if there are any in-flight requests currently being processed by the server.
def pending_requests? @counter.positive? end
clear_error
Resets the captured server error to nil. Typically called between test executions to ensure errors from one test do not affect subsequent tests.
def clear_error @error = nil end
call(env)
The Rack middleware entry point. Handles two cases:
- Identity endpoint -- If the request path is
/__identify__, responds immediately with the application'sobject_idas the body. This is used by Capybara to verify that the middleware is wrapping the correct application instance. - Normal requests -- Increments the pending request counter with the request URI, delegates to the extended application, captures any errors matching
@server_errors, and decrements the counter in theensureblock regardless of success or failure.
def call(env)
if env['PATH_INFO'] == '/__identify__'
[200, {}, [@app.object_id.to_s]]
else
request_uri = env['REQUEST_URI']
@counter.increment(request_uri)
begin
@extended_app.call(env)
rescue *@server_errors => e
@error ||= e
raise e
ensure
@counter.decrement(request_uri)
end
end
end
The error is captured with @error ||= e, meaning only the first error is retained. Subsequent errors are still re-raised but do not overwrite the stored error. The error is also re-raised after capture so that the server's error handling still functions normally.
Nested Class: Counter
The Counter class provides a thread-safe mechanism for tracking in-flight requests by URI. It uses a Mutex to synchronize all operations on the internal array.
Internal State
| Variable | Type | Description |
|---|---|---|
@value |
Array |
List of URI strings for currently active requests |
@mutex |
Mutex |
Synchronization primitive protecting @value
|
initialize
Creates a new counter with an empty array and a new mutex.
def initialize @value = [] @mutex = Mutex.new end
increment(uri)
Adds a URI string to the active requests list within a synchronized block.
def increment(uri)
@mutex.synchronize { @value.push(uri) }
end
decrement(uri)
Removes a single occurrence of the given URI from the active requests list within a synchronized block. Uses delete_at with index to remove only one instance (not all matching entries), defaulting to index -1 if the URI is not found.
def decrement(uri)
@mutex.synchronize { @value.delete_at(@value.index(uri) || -1) }
end
positive?
Returns true if there are any active requests in the list.
def positive?
@mutex.synchronize { @value.length.positive? }
end
value
Returns a duplicate of the internal array, providing a snapshot of active request URIs without exposing the internal state to external modification.
def value
@mutex.synchronize { @value.dup }
end
Key Design Decisions
- Array-based counter -- The counter stores actual URI strings rather than a simple integer, enabling debugging and diagnostics by revealing which specific requests are still pending.
- Thread-safe operations -- All counter operations are synchronized with a
Mutexbecause the Rack server may handle multiple concurrent requests from different threads. - First-error-wins semantics -- Only the first server error is stored (
@error ||= e), but all errors are re-raised. This ensures the original error is available for test assertions while not suppressing subsequent error handling. - Identity endpoint -- The
/__identify__path provides a lightweight mechanism for Capybara to verify server identity by matchingobject_idvalues, enabling multi-server test configurations. - Extra middleware composition -- The
extra_middlewareparameter allows users to inject additional Rack middleware (such as logging or authentication) into the test server stack without modifying the application.
See Also
Capybara::Server-- The server class that uses this middleware to wrap the application under testCapybara::Server::Checker-- Verifies server availability during startup