A cross-platform HTTP library for Dart with an emphasis on composition.
This is an alternative to package:http.
package:http has two design choices that led to the creation of this package:
- Inheritance over composition.
- Streaming-responses in
send
.
These design choices make it harder to add application-specific behavior to HTTP clients. A common example is to embed authorization headers in every request:
Code that embeds user credentials in HTTP requests ends up being repeated often
in apps with many small components, such as Flutter apps. Those components often
must work properly regardless of whether a user is signed in. Ideally, we'd be
able to pass each component a Client
object, and the component doesn't have
to know whether the Client
has been configured to send credentials (user
signed in) or not (user signed out).
To accomplish this we can extend package:http
's BaseClient
with a custom
type that holds onto the credentials, and pass that to components. The downside
is that we had to implement an entire client just to modify headers.
Another option is to write a Request
factory that always produces requests
with authentication headers, but then every component needs access to that
factory object as well as the client.
Aside: Another small nitpick is that Client.send
returns a StreamedResponse
,
so apps that specify their own headers are stuck with the streaming API when
they probably don't want it, or at least they may not want every single request
to be streamed. The decision to stream or not stream should be scoped to the
piece of code that reads the Request
body, not the code holding the Client
.
XHTTP solves the above problems by making Client
a concrete type, and
pushing platform and app specific behaviors down into smaller, focused classes.
For example streaming vs. all-at-once responses are handled by the Response
type; Browser vs. VM functionality is implemented by RoundTripper
which sends
requests and manages request resources.
The Client
in package:xhttp
relies on a RoundTripper
to execute each
Request
. RoundTripper
manages the underlying request resources and
execution. Users that need custom client behavior can probably get by using
ModifyingRoundTripper
which transforms requests using a callback. Other users
can subtype or compose different RoundTripper
implementations as needed.
Each Request
body is a ByteStream
. The stream can be read incrementally via
Request.bodyBytes
or all at once using Request.body
.
Future<void> main() async {
final client = Client();
final response = await client.get(Uri.parse(exampleURL));
response.bodyBytes.listen(decodeAndPrint);
client.close();
}
Future<void> main() async {
final client = Client();
final response = await client.get(Uri.parse(exampleURL));
print(await response.body);
client.close();
}
const token = 'secret-auth-token';
Request authenticate(Request request) {
final clone = Request.clone(request);
clone.headers['Authorizaton'] = 'Bearer \$token';
return clone;
}
final transport = ModifyingRoundTripper(authenticate);
final client = Client.withRoundTripper(transport);
client.get('https://myapp.com/protected/resource');