fetch() upload streaming
yoichio@chromium
Published: http://bit.ly/3asqra2
Commenting for @chromium: http://bit.ly/2tSWsIH
Issue tracker: http://crbug.com/688906
Last update: 2021-2-26
The Fetch API provides an interface for fetching resources (including across the network). It will seem familiar to anyone who has used XMLHttpRequest, but the new API provides a more powerful and flexible feature set.
However current fetch doesn’t have a function whereby web authors can
I propose to introduce streaming body uploading to fetch, where you can use ReadableStream as a POST body.
This paragraph describes related existing components and data flow in chromium to fetch and how I update them for streaming.
When |window.fetch(.. {body:ReadableStream})| is called, Blink creates ReadableStreamBytesConsumer to read bytes from the stream and BytesLoader inheriting mojo::ChunkedDataPipeGetter to send the byte via mojo::DataPipe.
Then we bring PendingRemote<ChunkedDataPipeGetter> from renderer to network service.
Finally HTTPStreamParser has ChunkedDataPipeUploadDataStream, which has the DataPipe consumer from ReadbleStreamUpstreamLoader.
Pros
Cons
When HTTPStreamParser::DoSendBody() is called, it has ChunkedDataPipeUploadDataStream read data from ReadableStream via mojo::DataPipe. HTTPStreamParser has a buffer to receive the data and if the buffer is written, HTTPStreamParser calls Socket::Write() to upload the data until reading all data from ReadableStream.
Uploading via ChunkedDataPipeGetter in network service is already implemented and not needed to be updated.
auto* form_data = fetch_request_data_->Buffer()->DrainAsFormData(exception_state); |
Streaming body upload is already standardized in whatwg/fetch.
However there are a couple of open issues to be closed to ship this feature.
[yhirano] We don't have |consumer_| when |made_from_readable_stream_| is true.
[yoichio] We do that on this design. See the first bullet line.
[yhirano] Then we need to rewrite all the code touching |readable_stream_| because |readable_stream_| is locked when passed to ReadableStreamBytesConsumer (see the class comment).
I think it's better to create a ReadableStreamBytesConsumer when DrainAsChunkedDataPipeGetter is called.
[yoichio] I see. Should we have additional Member<ReadableStreamBytesConsumer> or can reuse |consumer_|?
[yhirano] You don't need to create a member. You don't have to use |consumer_| as well. You just need to create a ReadableStreamBytesConsumer and pass it to a (also newly created) BytesUploader.
[yoichio] We only have BodyStreamBuffer retain BytesUploader instance?
[yhirano] Yes.
[yoichio] Done. Also updated the images.
[yhirano] How do you call this function?
[yoichio] Call at FetchManager::Loader::PerformHTTPFetch() below.
[yhirano] Thanks. But PerformHttpFetch doesn't know whether the BytesConsumer is ReadableStreamBytesConsumer.
[yoichio] At request.cc::ExtractBody() first, we let BodyStreamBuffer know it is initialized with ReadableStreamBytesConsumer.
[yhirano] I think that's fragile. How about defining DrainAsChunkedDataPipeGetter in BytesConsumer?
[yoichio] Done.
Design #1(declined): Have blink::FormDataElement holding ChunkedDataPipeGetter
Add FomDataElement scoped_refptr<ChunkedDataPipeGetter> member and
new enum kTypeChunkedDataPipe.
Pros
Cons
(FYI: “Content-Length” in header list is built at HttpNetworkTransaction::BuildRequestHeaders())