JAKE: Three, two, one. SURMA: Three, two, one. [MUSIC PLAYING] JAKE: Hello, Surma. SURMA: Hello, Jake. JAKE: This is weird, isn't it. This is very different. SURMA: It is. JAKE: I mean-- SURMA: I can't even see you. JAKE: --it's exactly the same
as we did for web.dev Live, so-- SURMA: Yeah. I feel like in web.dev Live, I
could actually see your face. But right now I can't, because
I can see your screen and-- JAKE: Well, do you know what,
the screen is going to be more interesting, because-- yeah. Do you know what, it's
been a while since I spoke about streams. SURMA: It's-- JAKE: And you know how
much I love streams. SURMA: Do you remember which
year was the year of streaming, declared by you? JAKE: [LAUGHS] So for everyone
watching, I, in 2016-- SURMA: 2016. JAKE: --wrote an article called,
"2016 is the Year of Streams." And it was for me. SURMA: [LAUGHS] And
that's all that counts. JAKE: Not sure the rest of
the world was that bothered. But you know, streams
are continuing to happen and get more
exciting, and that's what we're going to
talk about today. This is the fetch API. You've seen it before. This is making a request with
a requestBody post request. The body can be a number
of different formats. So it can be a
string, which means-- SURMA: Yes. JAKE: --it can be
sent as text, right. SURMA: Well, encoded to
UTF-8, more implicitly, and then send those back-- JAKE: Oh, OK, Mr. Pedantic. SURMA: Yeah, we had an
episode on encoding. I'm not going to-- that's what I do now. JAKE: And the nice thing
is it will add the content type for you as well. SURMA: It will. JAKE: And it will add
the UTF-8 part as well. And same with a blob. It'll just send that
as binary, but it'll take the content type
from the blob itself. SURMA: Mm-hmm. JAKE: Uint8Array, it
will send that as just-- SURMA: Application/octet-stream? JAKE: Application/octet. Yes, that one. Well remembered. SURMA: [LAUGHS] JAKE: Form data, that's
a more interesting one, because it's multi-part
encoding, because it could contain everything a form
can contain, including files. And then there's the ye olde
super basic URLSearchParams. SURMA: That one I
didn't know, actually. Cool. JAKE: OK, so one of the nice
things about the form data is the form data can
take a form object in, and it will just represent that
form as multi-part form data. If you're wanting to convert
it down to URLSearchParams, you'll have to do it yourself. It's like two lines of code. It's not much. And of course, you
would have to-- if your form contains a file,
you would have to throw. But this will give you the
standard URL encoding stuff. But with all of these formats,
you need all of the data up front, ready. SURMA: Yeah. You have all the forms
and all the data, and then it turns it basically
into a binary encoding under the hood. And then once that binary
encoding representation is done, that's
what gets sent to-- along with the fetch for
the request, isn't it? JAKE: Exactly. But, ooh, look at this. This is a Chrome 85 with
experimental platform features flag. There's an origin trial as
well, so you can use it on live. You can do this,
which changes stuff. It's a new feature for the web. OK, to describe why, let's
just talk about streams, because I love-- SURMA: It's interesting, because
fetch responses have always been streams, right. Like the body of a response has
always been the stream that you can process as the-- JAKE: Ish. SURMA: Ish? Oh. OK. JAKE: There was a time. We released fetch, and
then sometime later, we put streams in. SURMA: Oh, OK. JAKE: Yeah, they
lagged behind by, I'm going to say a
year, because that's a number that I've heard of. But I can't-- that might
not be 100% correct. But it was some-- SURMA: How did that
work in interop? Like what was body before-- oh, it just didn't
have body, basically? You just had to-- JAKE: Just didn't have body. SURMA: Just had
the methods where you say, text JSON ArrayBuffer. JAKE: Yes. SURMA: Ah, OK. JAKE: Exactly that, which
is what requests are like right now. SURMA: OK. JAKE: So it's the
same thing going on. But yeah, like you said,
the web is a streaming thing by default, right. If you go to a page
and it serves HTML, you'll start seeing
it as it's downloaded, same with images and video. And this is great, right,
because you can do something with just a little
bit of the response. I've actually-- I've spoken
to teams, and they're like, oh, we're pulling like 100
search results down the wire, but we think we're going to
detect if the user's on 2G and instead serve just
10 results so they'll get it quicker. It's like, no,
no, no, just serve a streaming format,
because then, as soon as you get
that first result, you can do something with it. And you don't need to worry
about the user's connection speed, because you'll
just get those things one by one, because that-- SURMA: For me-- JAKE: --that's why
I love streams. SURMA: --the fact that
the web is streaming is the biggest fundamental
difference to all the other platforms, most
notably Android and iOS. Because on Android, you
install your entire app, and everything is there, while
on the web, you have nothing. And you have to press the
most critical resources through this tiny straw
that is the network. And that's what we optimize so
that whatever is needed first arrives first in the
best order possible so that it can be shown and you
only download what you need. I think that's the
biggest difference and why so many assumptions
and patterns from native land don't really carry over
to the web, because we have to stream it. JAKE: Yeah, And that's-- ah. So yeah, on native, you
have to download everything before you can do anything,
which is not true on the web. But that's why-- I'm not going to turn--
this is not going to turn into a framework rant, but I-- SURMA: [LAUGHS] JAKE: It's one of
the things that I get annoyed at is when I
see sites just throw away this huge benefit the web has. SURMA: And it is
a benefit, right. It's not just a constraint or
something that makes it harder. It's a good thing. JAKE: Absolutely. Absolutely. And yes, we've had it on
responses for a few years now in fetch. So here's what it looks like. So you do a fetch,
and you get this. You get the body. You can get a
reader for the body. And then I'm just going
to do a while (true) here. Get some data. When you read data
from a stream, you get these two variables,
so you get value and done. If it's done, that means
you've received the whole-- SURMA: You're done. JAKE: --response. Otherwise-- yeah. Everything's received. Otherwise, you get this value. And when you've
received them all, that means you've
got everything. So these values are
Uint8 arrays of bytes. And the number you get and
the size of those arrays, that depends on the
network conditions, right. Like if you've got
a fast connection, you'll get a few big arrays. If it's a slower
connection, it will drip feed lots of
smaller arrays just to represent the same data. Quite often, you don't want to
be dealing with binary data. Quite often, you're
receiving a text response-- SURMA: Yeah. JAKE: --is one of
the more simple ones. So yes, you can use a
TransformStream, which is-- well, that's only in Chrome
right now still, unfortunately. But you can use a lower level
text encoder, decoder thing. But I really like
the TransformStream, because it's nice. You just do this, and now
all the chunks are text. Yay, that's much more
easy to deal with. Anyway, like you
said, we've had this for a long time, cross-browser,
the whole response streams thing. The new bit that I want to
talk about is request streams. And here is how they work. So I've got a content
type header in there. That's optional, but
it's good practice, just to tell your server
what you're sending. And then yeah, there's
a stream, and that's it. And that just works. So inside the readable
stream, this starts callback. It's called straight away. And from then on,
I can just start pushing stuff into the stream. But this demo is actually going
to fail, because requests, they expect the data
to be Uint8 arrays. SURMA: I was about to ask,
because the body could be a string, but does
it also actually take streams of strings? And you're saying,
no, it doesn't. JAKE: No. No. So problem solved. There you go. We've got these
TransformStreams. SURMA: Yeah. JAKE: Sorts everything out. And so yeah, there you go. That's going to send the UTF-8
bites for hello down the wire. And then five seconds
later, I can send world and close the stream. And that's it. That's how streaming
requests work. The uses cases I can
think of is like you could warm up a connection. So say you've got a chat app. As soon as the user focuses
the text input to start typing, you could start that request
up and get all the headers and stuff out of
the way and then just send a text
once they hit send. Obviously, then on
top of that, there's more genuine streaming formats. You could send audio
data, video data. So you can just start building
up the request body like that. So in this example,
I've created a readable, and I'm giving that to fetch. But sometimes it's
easier to use a writable, and you can do that as well. You can make your
own TransformStreams, like we've seen with the
text encoder, text decoder. But this one here,
this doesn't have an actual transform defined. It's just an empty
TransformStream. And what that does, that
becomes an identity stream. And all that means is, anything
that goes in the writable comes out the readable,
and that's it. But that's useful, because now
I can send the readable part, is the body, to fetch. And now anything I
send to the writable is what's going to actually
be sent over the wire. And you can do some fun
stream composition stuff with this, right. Like I'm going to fetch
something completely different here, and I'm going to pipe
its body to the writable. So now I'm fetching from one
URL and sending it to another. I'm using the user's
machine as a kind of proxy, all in the streaming way. The other user doesn't have
to buffer all the data. It doesn't have to end
up all in their memory. And I can even compress
that data on the fly. So this is using the
compression TransformStream. This is a Chrome only
thing as well right now. It's a web standard, but it's
only implemented by Chrome. But you can see how composable
streams are just plugging them together like this. SURMA: Yeah, I mean, I wrote
an entire library where I was trying to mimic
observables with streams, because they basically
give you the same param. There are some differences,
and I wrote a blog post, blah, blah, blah. But basically, you can
write really complex, or declare really complex
data flow structures with streams, which is
exactly what they're for. And TransformStreams
are the heart of that, and it always breaks my
heart that only a few-- not all browsers have
TransformStreams yet, but once they do, be it's
going to be very interesting. JAKE: Also, it's good that
you mentioned your library, because now we have
to put a link to that in the description. SURMA: Ah. JAKE: I see what you did there. Very smart. Yeah. Oh, well. But then you also mentioned
my blog post in 2016. Although it was to embarrass me,
it's still clicks on my blog. So there we go. SURMA: Well done, us. JAKE: Fair's fair. [LAUGHS] OK, so that
was the good news. There are some
catches, and I find this stuff really fascinating. There are some cases
where these new request streams are going to be
limited, or, I don't know, things might go wrong. HTTP redirects are
an interesting one. SURMA: Oh, god. JAKE: If you make a request and
you hit a redirect other then a 303-- 303s are fine. SURMA: OK. JAKE: But anything
else, it will fail. SURMA: Right. JAKE: And the reason for this
is the other redirect codes. Well, 307 and 308,
they require the body to be re-transmitted
to the new URL. And with streams,
it's gone, right. SURMA: Of course, yes. JAKE: So it doesn't work. 301 and 302 sometimes
don't send the body again but sometimes do. So I think just for
consistency, we've decided-- I mean, in general,
avoid 301 and 302, because they're
weird and legacy. There's is always a better one. You've always got 303 and 307,
which are better equivalents. But yeah, the stream
can't be restarted. Maybe someday we'll find
a mechanism for that, but right now, it doesn't work. One more thing-- I say one more thing. I've got loads
more to talk about. By default, this is only
going to work over HTTP/2. If you want it to
work on HTTP/1, we've made a little
opt-in for it. SURMA: Oh, that's interesting. JAKE: And this is
non-standard as well. This is just during
the experiments. And the reason we're doing
this, this is because of a key difference between
HTTP/1.1 and HTTP/2. SURMA: Oh, 1.1 has
chunking, doesn't it? JAKE: Yes, it does. So here's how you post
something over HTTP. SURMA: Mm-hmm. JAKE: So in this case,
the content length is important, because that's how
the other side knows how much data it's going to receive. It also then knows when
the end of the message is, because it's when you
sent that amount of data. But in a streaming
world, we don't know the length in advance. That's the whole point. So we need a different
way to do it. And like you said, one way of
doing this arrived in HTTP/1.1, and that is chunked
transfer encoding. It's difficult to say. But this is the same
message now in two chunks. SURMA: Yeah. But if I specify
the content length, would it work over
H v1.0 as well? JAKE: So content length
is one of those headers that you're not allowed
to send in fetch. SURMA: Ah. OK, fine. JAKE: Yeah, it's
one of the things that you've not been able to
do it before, so letting you do it now is-- there's that security
question of-- I think if we exposed
a way to do it, it wouldn't be just
by saying the header. It will be by giving us a
number, because then that would at least prevent you saying
content length foo or-- SURMA: Yeah, I guess. JAKE: --which might cause
servers to do something weird. SURMA: Why is that a
question of security? A server must always expect to
get malformed requests, right? JAKE: Well, do you know what,
Surma, not all servers behave. So-- SURMA: True. JAKE: --this is
part of the problem. Like if you send
unexpected data, some servers will
accidentally do things that they weren't meant to do. And the web always plays
it safe in terms of that-- SURMA: Eh, all right. JAKE: --and tries not
to cause problems. But yeah, so in
chunked encoding, you get the length of a chunk,
and that's a hex number, and then the content. And off it goes until
you get a 0 for size, and that's the signal that
that's the end of the response. So that's been around
forever, HTTP/1.1. It's really common in
responses, but this is the first time
browsers have been able to do it with requests. SURMA: I see. OK. JAKE: Going back to that
whole thing about servers not expecting it, we're
worried about compatibility. So we've made it opt-in for now. It's not a problem in HTTP/2. It doesn't have chunked
encoding at all. It has frames, which
are kind of like chunks, and it uses them everywhere,
so there's no compatibility concern at all. So no worries. SURMA: I see. OK. JAKE: So yeah, it's
just a worry of, what are HTTP servers was
going to make of this? Is it going to work? Is it going to break things? But we've put that
in there as a test so developers can try
it out and report back. Another gotcha slash
difference slash restriction, HTTP is bi-directional,
and that's something I always forget about HTTP. You can start
receiving the response before you finish
sending the request. And actually, it
depends on who you-- SURMA: I thought that was
like, many servers do, but it's actually not
necessarily specced that way or something like that. JAKE: Yeah, it
depends who you ask, whether that's what was
intended by the spec or not. But yeah, lots of
servers support it. I know that Node server
supports it really well, probably quite a few others. But yeah, a lot of
implementations don't. A lot of front end
servers don't support it, and neither does Chrome. And I don't think the other
browsers do under the hood either. So that kind of leaks onto this. So in this model, you need to
complete the request before you get any of the response. SURMA: Interesting. JAKE: It'll buffer any response
that you get before that. SURMA: So you can't just pretend
to build your own WebSockets with-- on top of fetch and HTTP/2. JAKE: Well. SURMA: Well? JAKE: You can sort
of hack around it. So you could have two
fetches, one for sending and one for receiving. SURMA: Yeah, OK. JAKE: So you can
create a writable and then send that as
a streamed request, and then make another
request and use that as the streamed response. SURMA: Mm-hmm. JAKE: And now you've got
a readable and a writable for bi-directional
communication. But it's actually
two HTTP requests. But yeah, this is kind of-- SURMA: Which, over H/2, is
still just one connection, so it's kind of fine. So yeah, it is pretty
much equivalent. JAKE: Yeah, exactly. The only thing you need is
something on the server side to tie the two things
together and know that it's part of the same thing. SURMA: Right. But you wanted-- you
probably needed that anyway, even if it had been possible
to do it via one request. You need to do some
bookkeeping there anyway. JAKE: Well, if it's
single request, the server just does
that by default, right. SURMA: Oh, OK. Yeah, yeah, OK, I
see what you mean. JAKE: But I've actually
built a demo of this. I'm glad you asked about
the WebSocket thing, because that's the
one demo I've got. SURMA: [LAUGHS] JAKE: Yeah, and it's just
yeah, a super stupid demo. I just start typing
in this text field, and it starts
appearing in the page. But it's actually making a
round trip to the server-- SURMA: [LAUGHS] JAKE: --to do that. SURMA: [LAUGHS] Well done. JAKE: It's really
pointless, I know. I just wanted to show, in
as little code as possible, how you could recreate the
WebSocket kind of thing. SURMA: Yeah. JAKE: But it shows how you
could do something different. SURMA: I like it. JAKE: But yes, we have
worries around compatibility. So what could go wrong? You need a server that can
handle streaming requests, like Node. Like Node.js-- loads
of servers do, right. SURMA: Mm-hmm. JAKE: It's pretty common. I don't know if PHP does,
but Node, definitely does, and that's what I've
used in this demo. But you often end up
with a chain of servers before you get to Node. Like you'll have a
front end server, something like Apache or
Nginx sitting ahead of it, and then you might have a CDN. And if any of those
decide to buffer-- SURMA: Yeah. JAKE: --the whole
request, then game over. SURMA: For example,
I think I know that most of the
serverless architectures buffer the entire body before
the function is invoked. JAKE: Yes. SURMA: Which is sad. JAKE: Absolutely. Also, if you are running
HTTP/1.1 somewhere in that chain, it might get confused. It might not be expecting
that chunked request. So the only HTTP/1 server I was
using in that demo was Node. Then the front end server was
HTTP/2, and it all worked fine. But yeah, it could go
wrong somewhere else. But this is something
that's under your control. So well, you might not
control the front end server if you're a client
side developer. But it's at least owned by
the company you're working for or something like that. It could get worse, though. It could be problems
on the client side. Now if you're running
HTTPS, you don't have to worry about
intermediate proxies. So that's one way to avoid that. SURMA: Which you
should be doing anyway. JAKE: --the user--
yeah, absolutely. But the user might still
be running a local proxy. SURMA: Yeah. JAKE: A lot of antivirus
software or internet security software, it will
install a certificate so it can sit as an intermediary
between the user and the web, so it can monitor
all the traffic and, I don't know,
filter it or whatever. And again, if that buffers,
it's not going to work. And if it gets confused by
the chunked encoding thing, it might just break. Bad luck. SURMA: This is giving me
anxiety with all the things that could go wrong in the road
from your machine to the server and back. JAKE: Yeah, and that's why
we made HTTP/1.1 opt-in-- SURMA: Yeah. JAKE: --because that's where
it's more likely to go wrong. The buffering thing, there's
not a lot we can do about that. So you really just-- you can sort of feature test it. Just do that WebSocket-y thing,
and send a message on one, and if you get the server pings
back, then yay, it's working. But yeah, so that's what-- it's a new feature. It's an origin trial, so
you can try it on live. It's in Chrome 85 with the
experimental web platform features. It could go wrong. It could be exciting. So we want people
to play with this. We want people to feedback. Like, ah, there was a bug in
Chrome once that it turns out-- we changed somewhere we
made HTTP connections. SURMA: Mm-hmm. JAKE: It made it
all the way to-- I think it made
it to dev or beta until it was actually a VP
in Chrome suddenly went, my internet's not
working anymore, or my Chrome's not working. And it turned-- actually,
it was the whole internet for him wasn't working. And it turns out
one particular brand of router in the US,
a really popular one, this particular way Chrome was
making these connections just caused it to crash
and flip out and not make any more connections. So-- SURMA: Oh, great. JAKE: --when we change-- this is why we're so
cautious about how-- when we change how
requests are made-- SURMA: Yeah. JAKE: --in the browser
that we're all like, ah, we need to do this
very, very carefully. So yeah, we want
people to try it out. We want to find out if there's
a router somewhere that sets on fire when this happens. Please report that. And also, we're sorry if
that happens in advance. We don't expect that to happen,
and I don't think we're liable. I don't know, I'm getting into
very difficult legal territory now. SURMA: Yeah, it's like
let's move on, move along. JAKE: [LAUGHS] But yeah,
that's all I've got. Try it out. Let us know what
you build with it. And hopefully-- well, let
us know if it goes wrong, because we're definitely
interested to hear-- SURMA: Yeah. JAKE: --why and how. But yeah, let us know
what you build with that. SURMA: Definitely. All to your DMs and
Twitter mentions, please. Or even if you don't
build anything with it, just ping Jake. JAKE: Just-- thanks, mate. SURMA: You're welcome. JAKE: So we're going
to make a stream, and we're going to fetch it. SURMA: Mm-hmm. JAKE: So I've got a content
hy-- type header in. Try that again. I've got a contype-- third time's the charm. That's going to be-- it's
going to send UTF-8 bate-- bytes. [BLEEP]