-
Notifications
You must be signed in to change notification settings - Fork 331
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uploading a Request made from a ReadableStream body #425
Conversation
079a0be
to
82d0b86
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally this looks okay, but I'm a little concerned that the first step of https://fetch.spec.whatwg.org/#concept-http-network-or-cache-fetch is not modified. It seems this should directly affect that, no?
@@ -589,6 +589,9 @@ user-agent-defined <a for=header>value</a> for the | |||
|
|||
<li><p>A <dfn export for=body id=concept-body-total-bytes>total bytes</dfn> (an | |||
integer), initially 0. | |||
|
|||
<li><p>A <dfn export for=body id=concept-body-replayable>replayable flag</dfn> (a boolean), | |||
initially false. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know this is annoying, and at some point we're going to burn all the flags, but for now we should keep consistency and only set and unset flags and don't say they're booleans.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, I think we want to call this cloneable maybe? That is what the operation we want to fail is called.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replaced boolean/true/false with (none)/set/unset.
We actually clone a body regardless of the flag. So calling it cloneable is confusing, I think.
@@ -3025,6 +3028,11 @@ in addition to <a>HTTP fetch</a> above. | |||
<a lt="include credential">includes credentials</a>, then return a | |||
<a>network error</a>. | |||
|
|||
<li><p>If <var>request</var>'s <a for=request>method</a> is not <code>303</code>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you mean to check response's status here, no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, fixed.
typedef (Blob or BufferSource or FormData or URLSearchParams or USVString) BodyInit; | ||
|
||
typedef (BodyInit or ReadableStream) ResponseBodyInit;</pre> | ||
typedef (Blob or BufferSource or FormData or URLSearchParams or USVString or ReadableStream) BodyInit;</pre> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should continue to put USVString last.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
Okay, so you are saying that |
@@ -3025,6 +3027,11 @@ in addition to <a>HTTP fetch</a> above. | |||
<a lt="include credential">includes credentials</a>, then return a | |||
<a>network error</a>. | |||
|
|||
<li><p>If <var>actualResponse</var>'s <a for=response>status</a> is not <code>303</code>, | |||
<var>request</var>'s <a for=request>body</a> is not null, and <var>request</var>'s |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
non-null
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
@@ -3366,6 +3373,10 @@ steps: | |||
<li class=XXX><p>Needs testing: multiple `<code>WWW-Authenticate</code>` headers, missing, | |||
parsing issues. | |||
|
|||
<li><p>If <var>request</var>'s <a for=request>body</a> is not null and <var>request</var>'s |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
non-null
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
As concept body has the body data as a ReadableStream, we need to clone it to replay uploading regardless of the source data type (String, ArrayBuffer, ReadableStream, ...). There are some options I could think of to handle this issue:
I chose the last option as it looked the easiest. |
I think we should way more carefully explain that setup then, but I don't like it as much as just being explicit about it as we have done thus far. Maybe 1 is the better solution if that is what implementations actually do. |
Hmm, 2ab2fce introduces the original object (source) instead of replayable flag. Does that look better to you? With the change we don't need teeing stream for body in http-network-or-cache-fetch, but apparently cloning a request is needed for headers according to the comment. Should I create clone-request-without-teeing-body? |
Yeah, I think that might be needed. There used to be some handwavy language along the lines of "clone except for body", but maybe that's gone now due to other changes. |
@@ -4268,6 +4301,8 @@ typedef (BodyInit or ReadableStream) ResponseBodyInit;</pre> | |||
<p>If <var>object</var>'s {{Blob/type}} | |||
attribute is not the empty byte sequence, set <var>Content-Type</var> to its value. | |||
|
|||
<p>Set <var>source</var> to object. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
<var>object</var>
here and below, no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
then return a <a>network error</a>. | ||
|
||
<li> | ||
<p> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This <p>
seems like a typo.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
Removed teeing from http-network-or-cache-fetch. PTAL again. |
@@ -3093,16 +3093,36 @@ steps: | |||
<i>authentication-fetch flag</i>. | |||
|
|||
<ol> | |||
<li><p>Let <var>httpRequest</var> be a new <a for=/>request</a>. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems you should just let it be null, no? Since every substep overrides what it is.
"<code>error</code>", and the result of <a lt=clone for=request>cloning</a> | ||
<var>request</var> otherwise. | ||
<p>Otherwise, if <var>request</var>'s <a for=request>body</a> is null, then set | ||
<var>httpRequest</var> to a copy of <var>request</var> except for its <a for=request>body</a>. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems you can combine this step with the next step. And only if request's body is non-null do the dance of moving the pointer across.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought the current structure was easier to read because it saved one nesting level. Don't you think so?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure. The fact that the note needs to be duplicated makes me think it's not.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
Apologies for the delay, I've been traveling/vacationing. Another thing I forgot to ask for is tests. We're trying to get to a place where all specification changes are accompanied by tests in w3c/web-platform-tests. |
nullity has already been checked. | ||
|
||
<p class="note no-backref">The <a lt=extract for=BodyInit>extracting</a> operation cannot throw | ||
as it was called for the same <a for=body>source</a> before. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you combine these two notes into one?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
|
||
<li><p>Set <var>request</var>'s <a for=request>body</a> to a new <a for=/>body</a> whose | ||
<a for=body>stream</a> is null and whose <a for=body>source</a> is <var>httpRequest</var>'s | ||
<a for=request>body</a>'s <a for=body>source</a>. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's worth pointing out here why the request isn't simply cloned.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
I'll write tests soon... |
<p class="note no-backref">Here we do not <a for=request>clone</a> <var>request</var> in order | ||
to reduce memory consumption. <var>request</var> can be reused with redirects, authentication, | ||
and proxy authentication. | ||
</ol> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This still seems wrong. Shouldn't we be setting httpRequest's body rather than that of request? Also, what happens to request's body?
The note should probably also move to after the </ol>
closing tag. We copy and move body around to preserve memory consumption. And we copy in the first place since it needs to be possible to add headers without affecting request, as request is reused with redirects et al. It seems you dropped part of that note, but we should keep that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, sorry. Fixed. The intention is, passing the ReadableStream to httpRequests for sending the payload and keeping the source information in request in order to restore the payload if necessary.
@@ -577,7 +577,7 @@ user-agent-defined <a for=header>value</a> for the | |||
<ul> | |||
<li> | |||
<p>A <dfn export for=body id=concept-body-stream>stream</dfn> (a | |||
{{ReadableStream}} object). | |||
{{ReadableStream}} object or null). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: this should be "null or a ReadableStream object". Maybe now is also a good time to remove the XXX paragraph that follows this? It's been long resolved per yutakahirano/fetch-with-streams#46.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
Does |
@guest271314 I appreciate you're interested in learning how this all works, but a better place to ask questions is on IRC (#whatwg, Freenode). Pull requests are meant for reviewing changes to a standard. |
Can you take a look again? |
I didn't realize you were waiting for me, apologies. Let me clean up the PR a bit and evaluate where we stand. |
Basic test: web-platform-tests/wpt#4362. More tests are expected to be written as part of the implementation effort. Further work: #441. Fixes #88.
|
||
<p class="note no-backref">Here we do not <a for=request>clone</a> <var>request</var> in order | ||
to reduce memory consumption. <var>request</var> can be reused with redirects, authentication, | ||
and proxy authentication. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This note is still problematic I think compared to the original. I think it should say something like the following:
A request is copied as httpRequest here as we need to be able to add headers to httpRequest and read its body without affecting request. Namely, request can be reused with redirects, authentication, and proxy authentication. We copy rather than clone in order to reduce memory consumption. In case request's body's source is a ReadableStream object, redirects and authentication will end up failing the fetch.
Does that make sense to you?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd say "request's body's source is null" or "request's body is made from ReadableStream" insetad of "request's body's source is a ReadableStream object".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, yes, that makes sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fetch post-call Always return 415 ??????????WHY
Aside from that note, the other thing I'd like to understand is what the plan is surrounding the contents of these streams. When should we fail upon non-Uint8Array object usage? Is that going to be fixed as part of #441 for both requests and responses? |
Sorry for the long silence. We, Chrome, did try to prove that this feature is useful, with gRPC/web. But gRPC/web lost its interest to this feature, given we now have WebTransport. As a result, I'm not confident that I can persuade Chromium API owners to ship this feature, given the spec/impl complexity needed for this feature. FWIW gRPC/web was the only web developer who wanted to use this feature with HTTP/1.1, so I believe we all are now happy to drop the support of this feature on HTTP/1.1. We have two paths forward:
Thoughts? |
I had no idea this was available on HTTP/1.1... we've been needing this for streaming media for a very long time, and yet previous discussions stated that it was locked to HTTP/2. Existing servers, such as Icecast and FFmpeg, do not support HTTP/2 and have no need to. They also do not necessarily need to support HTTPS. I would strongly urge reconsidering. Please keep HTTP/1.1 streaming request bodies available, and tell us how to use them in Chromium. It does not seem to be available with current builds. If there is some sort of flag we need, what is it? And then, we should go back to all of the threads where this was discussed and inform folks that it is available:
|
This feature has never been part of a Chrome release. The specs is here, but implementations are missing. Chrome devs told they would implement it, but since it's too complex for Chrome's code base, could the specs please be altered to make it HTTP2+ and then only it may be shipped inside Chrome. |
@drzraf Ok, thank you for that clarification. |
If it's helpful, https://github.com/socketry/async-http supports full bi-directional HTTP/1.1 and HTTP/2 client and server. So you can test with real world code. By the way, maybe this is chicken and egg problem. Because no browser supports it, and not many people know it's possible, we reach for different tools like WebSocket etc. But frankly as an implementor of HTTP/1 and 2, this is a great disappointment since HTTP itself does support full bi-directional communication and it still remains under-utilized. WebSocket and other solutions are harder to use and harder to implement, and make things more complicated. I'm not a fan of WebSocket despite implementing both client and server - it's a messy spec and it makes HTTP implementation messy. "We won't implement it because no one is using it" is a self-deprecating argument so I think it's invalid. Not sure what the solution here is, but ultimately I think we should lead with a good specification - I'd definitely hope teams like Node.js are more willing to entertain such a proposal (in terms of a direct implementation of |
I'm ready to add support for SignalR in .NET, I'd love to chrome and edge to support this. It would be enough to justify doing adding this transport. |
FTR Chrome ran an origin trial for this feature. Web developers were able to use the feature at that time. |
@yutakahirano can you please open a new issue to track this? I suppose part of it is covered by #966 but deciding on the fate of this feature seems worthy of its own issue. |
Sure! |
Here it is: #1438 |
This change enables developers to create a request from a ReadableStream body. fetch() reads data from the body and sends it to the server.
Fixing yutakahirano/fetch-with-streams#66.