Uniform::HTTP
Uniform::HTTP provides small, framework-neutral HTTP objects for Perl.
It gives different HTTP libraries a common way to represent:
- requests
- responses
- headers
- buffered bodies
- HTTP authentication
It does not open sockets, run an event loop, parse HTTP from the network, or send requests. Those jobs stay with the HTTP client, server, or framework using it.
The useful mental model is:
HTTP client / server / framework
|
Uniform::HTTP
|
request / response data
This makes it possible for unrelated HTTP implementations to exchange the same kind of request and response objects without depending on each other's object model.
Installation
From CPAN:
cpanm Uniform::HTTP
Uniform::HTTP requires Perl 5.16 or newer.
Start here
Most application code will use Uniform::HTTP::Request and
Uniform::HTTP::Response.
Create a request:
use Uniform::HTTP::Request;
my $request = Uniform::HTTP::Request->new(
method => 'GET',
target => '/users?id=42',
scheme => 'https',
authority => 'example.com',
headers => [
[ 'Accept', 'application/json' ],
],
);
say $request->method; # GET
say $request->target; # /users?id=42
say $request->header('Accept'); # application/json
Create a response:
use Uniform::HTTP::Response;
my $response = Uniform::HTTP::Response->new(
status => 200,
headers => [
[ 'Content-Type', 'text/plain' ],
],
body => "hello\n",
);
say $response->status; # 200
say $response->body; # hello
These are plain detached HTTP message objects. Creating one does not perform network I/O.
Headers
Headers are stored as an ordered list instead of a hash.
That matters because HTTP can contain repeated fields:
my $response = Uniform::HTTP::Response->new(
status => 200,
headers => [
[ 'Set-Cookie', 'a=1' ],
[ 'Set-Cookie', 'b=2' ],
],
);
my $first = $response->header('Set-Cookie');
my $all = $response->header_values('Set-Cookie');
# [ 'a=1', 'b=2' ]
Uniform::HTTP preserves:
- duplicate header fields
- header order
- original field-name spelling
Header lookup is case-insensitive.
Bodies
body() represents a body that is already buffered in memory.
my $body = $response->body;
Uniform::HTTP never reads a socket, filehandle, callback, or streaming body
source just because body() was called.
Use:
$response->has_buffered_body;
to tell whether a complete buffered body is available.
Streaming belongs to the HTTP implementation around the Uniform object.
Changing a message
Canonical Uniform objects are mutable by default:
$request->header('Accept', 'text/html');
$response->status(404);
They can be frozen when no more message values should change:
$response->freeze;
After freeze(), setters throw an exception.
is_mutable() reports whether the current representation can still be
changed.
is_complete() reports whether the whole message is known to be complete.
Adapters may return undef when their framework cannot know yet.
HTTP/2 and HTTP/3
Uniform::HTTP does not implement HTTP/2 or HTTP/3. It only represents the HTTP message semantics those protocols carry.
Requests have separate scheme(), authority(), and target() values so
HTTP/1, HTTP/2, and HTTP/3 implementations can map their native request data
without losing meaning.
For ordinary HTTP/2 or HTTP/3 CONNECT, the exact :authority value is used as
the authority-form request target.
Protocol-specific validation and wire framing remain the job of the HTTP implementation.
Authentication
Uniform::HTTP::Auth prepares Basic, Bearer, and Digest authentication values.
A normal username/password example:
use Uniform::HTTP::Auth;
my $auth = Uniform::HTTP::Auth->new(
origin => 'https://example.com:443',
credentials => {
username => 'user',
password => 'secret',
},
);
my $result = $auth->prepare_authentication(
challenge_headers => [
'Digest realm="Members", nonce="abc", qop="auth", algorithm=SHA-256',
],
method => 'GET',
request_target => '/private',
);
my $value = $result->{value};
$value is the complete authentication field value. The surrounding HTTP
implementation decides whether to put it in Authorization or
Proxy-Authorization, and whether to retry the request.
Authentication performs no network I/O.
Supported schemes are:
- Basic
- Bearer
- Digest
Most applications should use Uniform::HTTP::Auth directly. The
Basic, Bearer, and Digest submodules are also available for code that
only wants the lower-level calculations.
What Uniform::HTTP does not do
Uniform::HTTP deliberately does not own:
- sockets or TLS
- connections
- HTTP parsing or serialization
- HTTP/1 framing
- HTTP/2 or HTTP/3 streams
- event loops
- request retries
- redirects
- streaming I/O
- framework response lifecycle
This is what keeps the objects usable across different HTTP implementations.
Modules
The distribution contains:
Uniform::HTTP::Message- shared message behaviorUniform::HTTP::Request- HTTP requestsUniform::HTTP::Response- HTTP responsesUniform::HTTP::Auth- HTTP authenticationUniform::HTTP::Auth::BasicUniform::HTTP::Auth::BearerUniform::HTTP::Auth::Digest
Adapters
A framework can expose its native request or response through the Uniform HTTP contract without subclassing the canonical classes.
Adapters should be separate distributions. Uniform::HTTP itself does not depend on Mojolicious, PSGI, PAGI, Linux::Event, or another HTTP stack.
Most users do not need to know the adapter rules. They are documented for HTTP library authors in:
docs/MESSAGE-SPEC.mddocs/ADAPTERS.mddocs/AUTH-SPEC.md
Migration from Uniform-HTTP-Auth
Uniform::HTTP::Auth was originally released in the
Uniform-HTTP-Auth distribution.
Beginning with Uniform-HTTP 0.02, the same module is part of Uniform-HTTP.
Existing code using:
use Uniform::HTTP::Auth;
does not need to change.
License
MIT License.