module ServerContext
ServerContext provides a test fixture for HTTP server and client testing. It sets up an HTTP server with a bound endpoint and provides a corresponding client for making requests. This context handles the lifecycle of both server and client, ensuring proper setup and teardown.
The context automatically:
- Binds to an available port
- Starts an HTTP server with configurable middleware
- Creates a client configured to connect to the server
- Handles cleanup of resources after tests
Definitions
def protocol
The HTTP protocol version to use for the server.
Signature
-
returns
Class
The protocol class (defaults to HTTP/1.1).
Implementation
def protocol
::Async::HTTP::Protocol::HTTP1
end
def url
The base URL for the HTTP server.
Signature
-
returns
String
The URL string with host and port (defaults to localhost:0 for auto-assigned port).
Implementation
def url
"http://localhost:0"
end
def bound_urls
Get all bound URLs for the server endpoints.
Signature
-
returns
Array(String)
Array of URLs the server is bound to, sorted alphabetically.
Implementation
def bound_urls
urls = []
@client_endpoint.each do |address_endpoint|
address = address_endpoint.address
host = address.ip_address
if address.ipv6?
host = "[#{host}]"
end
port = address.ip_port
urls << "#{endpoint.scheme}://#{host}:#{port}"
end
urls.sort!
return urls
end
def bound_url
Get the first bound URL for the server.
Signature
-
returns
String
The first bound URL, typically used for single-endpoint testing.
Implementation
def bound_url
bound_urls.first
end
def endpoint_options
Options for configuring the HTTP endpoint.
Signature
-
returns
Hash
Hash of endpoint options including port reuse and protocol settings.
Implementation
def endpoint_options
{reuse_port: true, protocol: protocol}
end
def endpoint
The HTTP endpoint configuration.
Signature
-
returns
Async::HTTP::Endpoint
Parsed endpoint with configured options.
Implementation
def endpoint
::Async::HTTP::Endpoint.parse(url, **endpoint_options)
end
def retries
Number of retries for client requests.
Signature
-
returns
Integer
Number of retry attempts (defaults to 1).
Implementation
def retries
1
end
def app
The HTTP application/middleware to serve.
Signature
-
returns
Object
The application object that handles HTTP requests (defaults to HelloWorld middleware).
Implementation
def app
::Protocol::HTTP::Middleware::HelloWorld
end
def middleware
The middleware stack for the HTTP server.
Signature
-
returns
Object
The middleware configuration (defaults to the app).
Implementation
def middleware
app
end
def make_server_endpoint(bound_endpoint)
Create the server endpoint from a bound endpoint.
Signature
-
parameter
bound_endpoint
Async::HTTP::Endpoint
The bound endpoint.
-
returns
Async::HTTP::Endpoint
The server endpoint configuration.
Implementation
def make_server_endpoint(bound_endpoint)
bound_endpoint
end
def make_server(endpoint)
Create an HTTP server instance.
Signature
-
parameter
endpoint
Async::HTTP::Endpoint
The endpoint to bind the server to.
-
returns
Async::HTTP::Server
The configured HTTP server.
Implementation
def make_server(endpoint)
::Async::HTTP::Server.new(middleware, endpoint)
end
def server
The HTTP server instance.
Signature
-
returns
Async::HTTP::Server
The running HTTP server.
Implementation
def server
@server
end
def client
The HTTP client instance.
Signature
-
returns
Async::HTTP::Client
The HTTP client configured to connect to the server.
Implementation
def client
@client
end
def client_endpoint
The client endpoint configuration.
Signature
-
returns
Async::HTTP::Endpoint
The endpoint the client uses to connect to the server.
Implementation
def client_endpoint
@client_endpoint
end
def make_client_endpoint(bound_endpoint)
Create a client endpoint from a bound endpoint.
Signature
-
parameter
bound_endpoint
Async::HTTP::Endpoint
The bound server endpoint.
-
returns
Async::HTTP::Endpoint
The client endpoint with local address and timeout.
Implementation
def make_client_endpoint(bound_endpoint)
# Pass through the timeout:
bound_endpoint.local_address_endpoint(timeout: endpoint.timeout)
end
def make_client(endpoint, **options)
Create an HTTP client instance.
Signature
-
parameter
endpoint
Async::HTTP::Endpoint
The endpoint to connect to.
-
parameter
options
Hash
Additional client options.
-
returns
Async::HTTP::Client
The configured HTTP client.
Implementation
def make_client(endpoint, **options)
options[:retries] = retries unless options.key?(:retries)
::Async::HTTP::Client.new(endpoint, **options)
end
def before
Set up the server and client before running tests. This method binds the endpoint, starts the server, and creates a client.
Implementation
def before
super
# We bind the endpoint before running the server so that we know incoming connections will be accepted:
@bound_endpoint = endpoint.bound
@server_endpoint = make_server_endpoint(@bound_endpoint)
mock(@server_endpoint) do |wrapper|
wrapper.replace(:protocol) {endpoint.protocol}
wrapper.replace(:scheme) {endpoint.scheme}
wrapper.replace(:authority) {endpoint.authority}
wrapper.replace(:path) {endpoint.path}
end
@server = make_server(@server_endpoint)
@server_task = @server.run
@client_endpoint = make_client_endpoint(@bound_endpoint)
mock(@client_endpoint) do |wrapper|
wrapper.replace(:protocol) {endpoint.protocol}
wrapper.replace(:scheme) {endpoint.scheme}
wrapper.replace(:authority) {endpoint.authority}
wrapper.replace(:path) {endpoint.path}
end
@client = make_client(@client_endpoint)
end
def after(error = nil)
Clean up resources after running tests. This method closes the client, stops the server, and closes the bound endpoint.
Signature
-
parameter
error
Exception | Nil
Any error that occurred during the test.
Implementation
def after(error = nil)
# We add a timeout here, to avoid hanging in `@client.close`:
::Async::Task.current.with_timeout(1) do
@client&.close
@server_task&.stop
@server_task&.wait
@bound_endpoint&.close
end
super
end