reqwest streams for Rust
Library provides HTTP response streaming support for reqwest:
- JSON array stream format
- JSON lines stream format
- CSV stream
- Protobuf len-prefixed stream format
- Arrow IPC stream format
This type of responses are useful when you are reading huge stream of objects from some source (such as database, file, etc) and want to avoid huge memory allocation.
Quick start
Cargo.toml:
[]
= { = "0.17", =["json", "csv", "protobuf", "arrow"] }
Example code:
use *;
use BoxStream;
use ;
async
All examples available in examples directory.
To run example use:
# cargo run --example json-stream
Observing errors
An error that happens mid-stream is yielded as an item, so a consumer that stops at the first
one — try_collect(), or ? inside a loop — silently ends up with a truncated result and no
indication of why.
Use on_error to observe them. It is called for every error, both transport errors and
decoding errors produced by the format itself:
response.
Alternatively, enable the tracing feature to have the library log them for you:
= { = "0.17", = ["json", "tracing"] }
Errors are then logged at the ERROR level on the reqwest_streams target, so they can be
filtered with the usual RUST_LOG=reqwest_streams=off. Both the log event and your on_error
callback fire when the feature is enabled and a callback is set.
Note that an error is not necessarily the end of the stream. The JSON Lines and CSV formats produce their decoding errors from a successfully framed line, so reading resumes with the next one; a malformed frame, by contrast, does end the stream.
Observing progress
A response is read lazily, long after the call that created it returned, so nothing at the call
site can tell you how much of it actually arrived. Enable the tracing feature to have the
library report that for you:
= { = "0.17", = ["json", "tracing"] }
At INFO every stream reports its totals once, when it ends:
INFO reqwest_streams::response_stream{format="json_array" status=200 buf_capacity=8192 max_obj_len=65536 items=300 bytes=8891 errors=0 elapsed_ms=3396 outcome="completed"}: Finished streaming an HTTP body items=300 bytes=8891 errors=0 elapsed_ms=3396 outcome="completed"
The outcome tells apart the three ways a stream can end: completed, aborted (the consumer
stopped reading early, which is otherwise invisible), and failed, which reports at ERROR
instead, alongside the errors themselves. A stream that was built but never polled reports
nothing at all.
Raise it to RUST_LOG=reqwest_streams=debug and long-running streams additionally report
progress about once a second:
DEBUG reqwest_streams::response_stream{format="json_array" status=200}: Streaming an HTTP body items=45 bytes=1295 elapsed_ms=510
DEBUG reqwest_streams::response_stream{format="json_array" status=200}: Streaming an HTTP body items=90 bytes=2600 elapsed_ms=1020
INFO reqwest_streams::response_stream{format="json_array" status=200 items=300 bytes=8891 errors=0 elapsed_ms=3396 outcome="completed"}: Finished streaming an HTTP body ...
Everything is recorded on a reqwest_streams::response_stream span, created while your own
span is still current, so collectors nest it under your request and read items, bytes,
errors, elapsed_ms and outcome as span attributes rather than as log text. The span also
carries the response status and, when the server sent one, its content_length — which is
what lets you turn bytes into a completion percentage. Use reqwest_streams=trace to
additionally get an event per body chunk.
Reporting is time-based by default, so the number of lines is bound by how long a stream runs and not by how much it carries. Both triggers are configurable, and progress is also reported whenever the item count crosses a step if you ask for one:
response.
The same accounting is available without tracing, for metrics:
response.
items counts the objects successfully decoded, so an item that failed to decode is not
counted, and an item is whatever the format produces: for the Arrow format that is a
RecordBatch, not a row. bytes counts what reqwest handed over, which is after transfer-
and content-decoding — with reqwest's gzip or brotli features enabled that is the
decompressed size, not the size on the wire.
Note that ReqwestStreamOptions::new() does not limit object size: unlike the
positional-argument methods, which make you choose, max_obj_len defaults to usize::MAX. Set
it explicitly when reading from a source you do not control.
Nothing is counted at all unless something is listening: with no on_error, no on_progress
and no subscriber interested in reqwest_streams, the stream pipeline is left untouched.
Need server support?
There is the same functionality:
Licence
Apache Software License (ASL)
Author
Abdulla Abdurakhmanov