Published using Google Docs
fetch() upload streaming
Updated automatically every 5 minutes

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

Background and Motivation

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

Proposal

I propose to introduce streaming body uploading to fetch, where you can use ReadableStream as a POST body.

Design overview

This paragraph describes related existing components and data flow in chromium to fetch and how I update them for streaming.

Initialize

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

Uploading

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.

Component update detail

       auto* form_data = fetch_request_data_->Buffer()->DrainAsFormData(exception_state);
     
if (formData) request.SetHttpBody(form_data);
     
else request.SetChunkedDPG(fetch_request_data_->Buffer()->DrainAdChunkedDPG());

Standard process

Streaming body upload is already standardized in whatwg/fetch.

However there are a couple of open issues to be closed to ship this feature.

Appendix

Design discussions

ReadableBytesConsumer

[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.

PendingRemote<ChunkedDataPipeGetter>

[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

Change Detail (estimated weeks)

(FYI: “Content-Length” in header list is built at HttpNetworkTransaction::BuildRequestHeaders())

Total estimated time: 10 weeks.