HTTP/2 for Go - Brad Fitzpatrick - Go Devroom FOSDEM 2015

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
okay I'm good to go I'm Brad Fitzpatrick I work on go I work on a standard library and anyone here using that HTTP okay this is more net HTTP stuff um this talk is I guess maybe one third about HTTP two and what it is I'll go really quickly on that and then we'll jump in and look at lots of code so yeah I work on HTTP and standard library stuff so yeah HTTP - a little history of typing some stuff and a thing and getting a document back on the Internet's I got to start with gopher because it's gopher the protocol kind of looks like this the read was kind of what your browser or whatever you call the thing that was a gopher browser sense and the response was like you know lists of texts and there were columns and the little one and zero basically were the content type whether is you know a text document or another listing or you know they're like seven men - it was like an audio file or something then HTTP 0.9 like 91 you basically just sent one line there were no content types really everything was I guess HTML 100 started adding some headers you could send some key value pairs on your request and get some key value pairs near respond so you have different content types it evolved kind of slowly they added a keepalive so you could opt in to like not tearing down your TCP connection every time and on the response the server would say okay this is a keepalive and then on the same TCP connection you could then issue another one and one one that was implicit so all connections are keep alive unless you opt out a bit and say connection closed now you have to say what hostname you're talking to to make you know solve the ipv4 shortage problem and have make virtual hosting easier that you could have virtual hosts that's you know one IP address for hundreds of customers and you get load balance differently so now we're in like 99 and that's about it that's about where we are so recently this is HTTP to request it looks a little bit different the main thing you'll notice is you know for the last well many many years there's text protocol you can only do one thing at a time only one your browser could only be asking for a single request at a time and if you're hundreds of milliseconds away from your thing that was basically like 600 milliseconds of dead time that that TCP connection was there it is accumulated lots of packs and stuff like special cases for working around browsers and weird servers and stuff over time HTTP - on the other hand is still pretty clean and consistent because there's not many implementations and people are trying pretty hard for interrupt but yeah it's a binary protocol lets you do lots of things at a time and I guess has a good demo close that so here is a live test where's my gopher tiles okay so if this is served over HTTP - as an example if we look at HTTP 1 with 200 milliseconds latency you could see you know you can ajan these are javascript files or CSS but the browser can only keep 6 connections open to serve our time which is like a policy decision it used to be one that it was - and now it's six and that's kind of how browsers have worked around the HTTP one over the time they're like oh no we'll just open more connections but I mean it still kind of sucks especially I like one second late and see if you're at a out in the woods and you have a satellite internet connection this is how you browse the web on the other hand if we look at HTTP - with one second latency you know like it was one second for all of them but we asked for all we asked for all tiles all at once so that is that is the promise of HTTP - but back to slides so yeah there wasn't really any improvement from 99 to 2013 they just kind of up the number of connections there HTTP 1 1 and theory supports pipelining where you could on one TCP connection ask for a whole bunch of things and you have to get them back in orders though but in practice it just doesn't work and all browsers have it turned off and servers don't really support it and people in the middle transparent proxies that try to like cache your stuff and help you out they just choke on it so no one uses it so around 2009 or so probably earlier Google was Google had a browser at this point and you know had some servers and so Google started opting into both sides - like upgrade to a different protocol which is pretty easy over HTTP because nobody in the middle can mess with you because it's all encrypted so what you do at that point you know is cool so they started upgrading people to experimental protocols and measuring lots of things and it worked out really well other people liked it Facebook and Twitter and other people liked it facebook ended up recommending to the ITF that it becomes the basis for HTTP to other people kind of agreed some people disagreed but most people are generally happy with it now HTTP two is basically speedy with a bunch of tweaks so back back to this HTTP request they should be two requests you can think of it like that there's a little nine byte header it starts off with the the frame length so first there's 12 bytes so this is 12 bytes of the payload you always have nine bytes then you have one byte this is what this sort of packet is what this frame is one means headers and then you have a one byte of bits that says better bit specific to this frame so this one says end stream which means so every every basically HTTP request you make over one of these long-lived like could be days TCP connections is a has a unique stream ID and so a headers request starts a new stream and so it has stream ID 1 and the end stream that says they're from my side the client is done sending for instance there's this is the get request there's no post body attached to this and end headers means this is the full extent of my headers and I don't have a follow-up frame coming later that like you know you could in theory have like a gigabyte of HTTP request headers and they would have to be cut into multiple frames in this case it all fits in here and there's the 12 bytes of the whole HTTP request which is compressed with a CH pack which is a whole new compression format that is like an adjunct spec to h-2b - that is just crazy town with lots of different things there's um you know static tables of all the common things there's a have here somewhere it's back yeah so there's there's all these tables of like common HTTP things that you see so these gifts statically assigned numbers or really short variable encoded numbers and there's static Huffman tables for like Google did some analysis of all the traffic as he's on the web and this is the smallest way to encode that the empirical data that's our and so this is baked into the spec and so once you do all this stuff once you do all this stuff that represents like a whole one of those get connection to I browser and this encoding is stateful so if you have this one TCP connection open and you send a cookie that's like you know a page long and you send your user agent which has all that crap and a user agent you send that on the first request and the subsequent request it takes like a couple bits because you just refer back to something you did in the past no no's has nothing to a stream ID but it has to do with the connection so so once your TCP connection dies like all your H PAC state goes away yeah I said all this it's a compression thing so these are the HTTP to frame types that are defined the the ones on the right are kind of optional ones that are hints from the browser kind of and hints from the server but the main ones is data this is stuff going up for stuff going down you know like a post body or a response body a response to a get headers is you know like a new new stream a new HTTP request continuation is kind of a hack in the spec that says whoops it didn't fit in a packet I'm gonna have to add another one on these ones are kind of special in that all the other frames could be interleaved on the connection but whenever there's a header that doesn't have the end headers bits that you have to have a continuation directly adjacent to it on the wire so yeah of course there's little special cases already settings let's the two peers the client and the server negotiate like upgrade to like higher settings and say you know what your flow control rates are and maximum size of packets maximum size of like various other Petters how big you know you can make a HTTP request stuff I got ping just lets the client or server say area are you still there or is this just an idle TCP connection that you know went away go away allows for we'll shut down so in HTTP 1-1 you could have a problem where the you know your apache or whatever has a keepalive timeout of 30 seconds and at 29 and a half seconds the browser is like alright here's a request you know maybe here's the post and the same time you have a TCP packets like lower reset coming this way and the other ones like here's my pretty card details and you don't know if the server got it or not so htv-2 actually addresses this in a clean way where you can like know whether or not the server got it or the client got it and so yeah graceful shutdown on that and window update is for um everything at htv-2 is flow controlled so on every stream so it would between the priority and the flow control you kind of a client that does a whole bunch of requests but then the client knows like to render the HTML maybe I need the JavaScript before the CSS or I really want this file more than that one so the client could tell the server exactly how much it's allowed to send on each one and how many bytes it's allowed so the client can kind of I mean both sides really can control how the other one is sending crap and it's a violation if you don't keep track of all these counters about how many bytes you're allowed to send on the whole connection how many bytes you're allowed to send on a certain stream so yeah both sides have to do lots of accounting during the process as far as how you upgrade from HTTP 1 to HTTP 2 with HTTPS you do it with an extension in TLS it was previous called NPN now it's there's a they moved how it works it's called a LPN but it's basically the same you basically just say in your handshake by the way after we're done with this whole little handshake business these are the protocols that I would like to speak and the other one says that too so just like you negotiate your site for Suites and stuff you can negotiate the protocol that you're gonna speak next and so then right after the handshake is done you know this this works really well for now there's this whole like political thing about whether all that HTTP goodness should also work for HTTP and you know there's people saying oh no we're done you know like we have to encrypt everything on the web now there's other people that say upgrading from HTTP doesn't even work in practice because there's all these transparent proxies that break everything which is true this has been measured people counter argue but we could just tell them to fix it but you know you don't really fix things on the internet so anyway in practice for political reasons as far as I can tell there's support in the spec for upgrading from HTTP but of ie Firefox and Google and chrome and the Google servers nobody's implementing this I'm not planning on implementing this those no servers and no clients are really implementing it but it's in the spec but so will you will ignore that effectively it's HTTPS only maybe with EFS let's encrypt thing more people will have certs by default but um anyway enough talking let's write code so they're gonna yeah so the first thing I did when I started writing this I skipped one little part about um the upgrading connections you can see like this is what you have to do in a TLS thingy to accept a connection you just make a TLS listener and you say the next protocol I'm gonna support is h2 14 which is this is the identifier string for the protocols as HTTP to draft 14 and then after the handshake you look at the connection state and this has a field called negotiated protocol so anyway so in there there's a well I'll get to later so let's read some frames so we want to read something and return something we're probably we return a frame we don't really know exactly what the frame is yet I showed you the frame format so there's nine bytes there's three bytes of length a type a Flags a stream identifier only the lower 31 bits of which are the stream identifier then there's a reserved bit maybe for use in the future so the first thing I did was made a frame header struct and to represent that nine byte header and so there's the type which is defined as just a name type over a byte and then there's the flags which again is just a name for a bite then I have the length which is a UN 32 because go doesn't have a UN 24 but so this thing is the struct is a little bit bigger than 9 bytes but whatever and then the stream ID the frame type like I said was just a byte has proper documentation all that there's a bunch of constants notice I don't use iota here don't use iota when you're actually like implementing a spec that has defined numbers use iota when you just need your own set of numbers that's purely internal so here this is what the spec defines these numbers as I give them you know little names I define string methods on frame type which is you know very useful when you're dealing with a binary protocol and some some crap comes in and you need to like log it it's nice to have the it all stringify out so if it has a name I print out the name if I don't know the name I say unknown frame type and whatever number it is likewise there is a type called flags for thing and you know has little convenience methods on it like has so I could say does this thing have this bit so then the code kind of reads nicer later and I don't have to say like little bit wise operations all over the place and you know it compiles to the same thing then I have all the flags that are defined for each frame so like endstream on data and endstream on this they happen to have the same number but like in this back there they're different so we have different constants here for them then of course I have a big map of which bits are defined for which frame type for you know which flag bits and that ends up string of flying really nicely so when you know I see some debugging I get some string off' ocation out of it that says frame frame headers with these flags set and maybe this bit too isn't known but it's still you know prints out and the other ones are symbolically written and stream one and length 17 so yeah this saves you a lot of time when you actually write good string methods on things ok so we're back we're back to read frame we have an i/o reader and we're gonna return a frame which we still don't know what it is yet but first before we read a frame let's read just the frame header when we do know what a frame header is constant I think you know whatever no one needs to see see this so it's lower case but yeah there's nine headers nine bytes and a frame header and so your first implementation could look like looking like this we have are buff and you have frame header lend by its own 9 bytes and we just we read full it whenever you read a thing it always has to have at least 9 bytes will read full and once we have that thing we could just return the frame header and just unpack it get the length and you know mask off mask off the reserve bit you know we don't really care if that's high or low but the thing you'll notice here is this read full because this is doing an interface call through this the escape analysis really can't tell because it doesn't know what the implementation of reader is doesn't know whether still escape so this will be 9 bytes of garbage every time that this calls so really we want to kind of like reuse the buffer that we read the frame header into so we don't want to function like this we want to like provide it the memory to read into so instead you could write it like this where the caller supplies the buffer to read the frame header into so now now this is being passed in and we just say it has to be at least 9 bytes and you would make that a requirement um so this is actually the real implementation and the code of course there's tests for it always right lots of tests you know um there's actually probably twice as much code and they should it be to package as there are is in like the non test code so I started off with some kind of test like this that I wrote up some manual things and just kind of verified it does approximately the right thing okay so back to read frame we now have read frame header we don't really have a buff yet though to pass to this thingy so rather than just having a read frame function there's probably a method on something else so at this point I make a framer and the framer has a reader and it has a little nine byte buffer so we can just reuse this nine byte buffer every time when we read a little constructor to make a new frame err that's about all the there's there we're gonna use this framer for reading and writing so now we have a read frame method on framer and it just reuses this uh this little nine byte buffer on the thing and then summed up a buck so what is frame you could probably go either way instructor interface I may change it back to a struct at some point for for garbage you reasons but currently it's an interface there's like you know ten different frame types and what they all have in common is they all have a method called header that returns the frame header I'll talk about what invalidate does later we basically want to let the caller we want to reuse the memory for all the internal fields between things so we don't like generate garbage as things are coming in so we have the caller when it's done with the frame call invalidate I'll show how that works anyway so the frame header in addition to like those fields I said before we also added this unexploited valid bit that basically says does the caller own this or not and if they don't you know we will blow up later so that we have methods on the frame header on the pointer to the frame header called invalidate which is part of the interface so nobody else can implement this frame interface it's only the the ten types and the package can implement it because you do that trick of lower case method names on things and then there's this check valid method that just blows up and this is useful for debugging to see if you know someone someone did something on the wrong go routine or somebody like didn't understand the life times so here's an example of one of the ten frame types there's the headers frame I just embed the frame header so I get the invalidate method embedded and I have accessories on it like the headers frame had those twelve bytes in that earlier thing that had the H packing coded stuff so the higher-level code just want to get access to those but it's gonna be using memory this this byte slice points the memory that it doesn't own the framer owns that until the person calls invalidate so this I call check valid on here to see if the caller does indeed still own this frame and if not I blow up otherwise I return them to slice and then I have some convenience methods on the headers frame that have cuter names like headers ended rather than having the call or have to write f flags has flag headers ended so it just it just makes the call sites a lot easier when I'm checking bits okay so now it kind of looks like this we have the framer and we have some optional stuff that's in the spec like the max read size a lot of stuff in HTTP spec I can't say HTTP - it's the tongue twister they start out really low limits like 16 K 64k and various table sizes and the two sides if they agree that they have enough memory your buffer space they can negotiate higher so there's this max read size constant the buffer and then I also have this function here called get read buff so after we provide a frame and we know that there's gonna be like you know 32 K or a Meg or something coming we needed a buffer to read the rest of it into and so I pulled this out into a function so we can do different policies about like how we manage the memory for incoming stuff cuz you get it we don't want to like have every idle connection if you have a server that has like hundreds of thousands of connections you don't want all of them because most of those connections are gonna be idle especially with HTTP - you're gonna lots of idle connections all the time you don't them all holding on to a little buffer just in case I need to read a Meg later you kind of want them to get that lazily when they really really need it and then that you want to like return it back to somebody else that actually needs it so I pull that into a function the the default implementation just appends onto this thing this read buff so you don't really have to think about them unless you want to override the allocator the the default thing is just to put it on there okay so then I have a map of from all the frame type A Frame parser so I have back in the frame header which of the read frame function we read the frame header already we know how big it is we got the memory for it and so now we have make a type called frame parser this is internal detail that given the payload returns the actual final frame interface and so then we have a map from all the all the types and that and we have this function called given a frame type give me a frame parser and if it's a known one so like I don't do I have push during development I only have like three or four of these and you know I would just fall back to the parse unknown frame one which just basically does nothing and so this is good for debugging I probably should change this from not being a map look up here every time and just do an array lookup and fall back to parts unknown frame but so now read frame looks like this we read the frame header and if the length is bigger than we've negotiated is acceptable we blow up and tell them the frame is too large otherwise we get an appropriate buffer to read the payload into read full and now so we now we have both we have the full packet basically the whole frame in memory and now we tell the type frame parser which is you know nose the nose the HT to http two-level details of like how to look at the bytes in there what to do with it and it returns a frame and we also keep track of the last frame we read and at the beginning of a frame we invalidate the previous one if there was one so here's an example of one of the 10 like a Parsa data frame the spec says that if this stream ID is 0 because the data frame must be associated with a stream it's not like some of the frames are like high-level things like negotiating settings but data frames in particular are always associated with a stream so if we see one that's like zero we just drop the connection they violated the spec and so I kind of quote the spec and lots of these places and say what section the spec said this and we have tons of tests for it all one of the bits is flag data padded which is let's the clients obscure the size of their packets by putting in dummy data that isn't actually there and so read all the stuff keep track of what the actual real data is ignoring the padding and then return that frame so there's you know there's ten of these little functions that parse of the different frame types there's another file has the errors have a little error code have all the air codes from the spec and then there's a the HTTP T H - H - I'll call it h - spec defines connection air and stream air connection error you basically kill the whole connection they violated the whole the whole protocol basically and then there's a stream error which means there was some problem specific to a stream but we can recover and only that stream only that HTTP request goes away on the writing side there's a whole bunch of write functions that are specific to the frame type so we have to write writing data settings pings data stuff here's like one that writes settings takes very attic settings thing and we say start write with the flags we don't and however the other one is go over the settings write some fields and then we say and write those things just look like and here settings act this is a really simple right one we started right frame type from settings with that flag and length all right that one is so on the framer there's right and there's the buffer that we write into to like build up that little partial packet start right so the stream ID yeah so the stream of four settings the stream ID is always zero because those aren't specific to settings but we write out the nine bytes here with a pend we leave three bytes they're ready to fill in at the end after we're done writing the thing and then in end right we know how big the buffer is now so we know the proper length and just as a sanity check if we wrote more than we can express in three bytes we blow up and say the caller did something really dumb and then we know the buffer is big enough for this so I kind of just abused append to like quickly put three bytes in there because um I don't know it works and it avoids the bounced checks when you do a pend like this the the compiler emits a check that do you have space for three things rather than saying for every one do you have space for it so it I'm sure it's not even a performance problem but it's it's cute and then we write the thing to whatever the writer is so that's writing and then you know there's these little convenience things for writing a byte or writing a UN 16 or write you in 32 here's the right data frame method we basically say you know we don't have support for padding right now because I'm not sure anyone really uses it but one day I'll add it the framer has support for doing illegal HTTP to illegal things just in case you want to test another person's implementation and send them garbage and so I have actually used my unit test framework against other people's servers and stuff just implementing the right hooks to bring up their servers and stuff so yeah if this is the end stream bool we set that bit and we started right with the right flags put the data in there and so called end right so all these right methods are pretty easy so now writing the server itself I showed it upgrading HTTP to you just say that in connection state and you get a field negotiated protocol and then in the HTTP to package as of like I think go 1 3 or go 1 2 there's this field on the the server called TLS next proto and probably you've never used it before because there's no purpose of using it but this is an existing hook that's been there for like a year so that says given the proto a PN or NPN or a LP an or NPN identifier like HT 214 what should we do with that connection when we receive it and the default if this isn't defined is you know the the go HTTP package it just handles it but if you put something in there and then in this package in the new package there's this configure server function that sets it to you know this is HT 214 or H 2 and then we used to say oh we'll handle the con so basically we hijack the connection from the standard library so if you actually go to use this you don't change any of your code all your HTTP handlers are still the same you still called listen and serve listen sort of TLS or whatever and it just transparently speaks HTTP to when it can umm so inside this package we now have the other HTTP server which has all the configuration that you've set we have the actual connection which is a in practice will be the TLS con and we have the handler that you wanted to run so for every connection that we get we're basically ain't gonna have three go routines or four or five go routines but the three main ones is there's one that I call serve and this one is non-blocking it keeps all the state all this is like a counting a flow control and stuff is in its maps and stuff there and then there's one go routine that's reading and it's just sitting there blocked forever basically recalling read frame header we're just waiting for nine bytes to show up and once it gets nine bytes and I'm gonna reads the rest of the bytes and has a full frame in memory then it sends it over a channel to the servo and says yo deal with this frame and then it sits there bored until it's been handled and then we say okay read another frame for me will we unblock you likewise there's a go routine that's writing to the network and it could block sometimes that has to flush something and but we never want to either block reading or block writing we want this one to all be this would be fast and non blocking and this go routine actually I had this one running all the time but go routines used to be 4k then a K and then in the last release or two okay but still though the six K of memory per idle connection which is kind of a waste if you have you know want hundreds of thousands of connections or something so I actually found it was faster to start up this go routine to write a single packet and just let it be destroyed when it was done then sending a message of her channel writing it and sending a message back that I was done so actually every frame right is a new go routine and it sounds kind of ridiculous you know writing creating so many go routines so quickly but goes really fine with it it doesn't matter at all so your go routine counts and your servers if you look at your debug statistics will be really high but it's fast so I think I can even make this the logic go routine shut down most of the time when there's no packet to be read that 2k will go away and then you'll just for every idle connection you'll have this 2k reader 1 and when a new packet comes in if the the logic girl routine isn't running we'll start it up and it coordinates all the other stuff so whenever a request comes in it decides to make an you know a new HTTP request comes in we we start a new go routine for your HTTP handler which might be you know your default serve MUX or your HTTP handler funk and they communicate with the server one about like okay write some data reads the data and all the pushback of like you know you're not allowed to write you don't have the flow control tokens to write this net are all this pushback happens on channels here so um I'll go through some code that shows some of that stuff so this is a beautiful struct that is created every time you get a connection there's just like crap loads of channels that of things going up and down in sideways between all these little pieces and you know we initialize the flow control with the initial number of tokens you're allowed we set up a CH PAC encoder and decoder with the buffer that we're gonna write to and where we read from what we do when we get a new header field a new key value pair and the headers which basically notifies the server that you know the connection that adds it to a list and we build a framer set the read size based on like whatever has been negotiated in the protocol and then we serve the serving one sets up a bunch of stuff basically the first thing it does is writes a frame to the remote side and says here's my policy on what you're allowed to do like what your max frame size max concurrent streams are like how many how many HTTP requests can you have in flight at once but then there's also all these defers that just win when the serve function returns we tear down the world we tear down all these other girl routines that are running and we make sure to be handlers that are blocked get unblocked because um you know you don't you want a handle or sitting around blocked on a write or something it's trying to write data on HTTP response writer but then the remote sided you know killed their machine or close their connection so you want to unblock those two so all these things kind of just tear down things yeah well right frame does via a write scheduler that I'll show in a second yeah yeah so and then this is the bottom of this first function of serve then basically all it is is this big for select loop that waits for things to happen like a frame frame coming in or a notification from the right go routine that we wrote a frame so then we note ok we're no longer writing a frame now we could schedule another frame right from the frame scheduler because HTTP 2 has all these prioritization things the client could say I want JavaScript before CSS and I want all this and so we may have all this stuff kind of buffer an up ready to send them but we don't know which packet the client wants next so we you know they're all sitting there in memory or some some maximum bounded sizes hitting their memory and we choose what to send them next and then you know there's other things like they didn't send their settings frame within the right amount of time so then we you know we go away this is some shutdown stuff this is the go routine that sits there reading all the time basically it makes a little gate I have on this side I have a gate which is just a fancy name for a channel of empty structs and then I just so I can attach some names to it so I don't have to write G send struck curly curly curly curly curly and I could just say done and wait and so I make this gate I read the frame I send it to the served loop I says hey I got a frame and here's a gate and so when you're basically when you're done with this frame let me know it just sits there and calls wait and expects so this pauses the go routine so it doesn't trample over the memory that somebody else is already dealing with that's the frame the gate nothing fancy there it's the interface value and the channel so I know go routines don't have IDs and we don't ever want to give you IDs because you'll do gross and disgusting things with them but it is possible to get at them and sometimes it's useful um Andrews probably angry at me for even showing the slide but uh so I found so there's these three go routines or four go routines going on you should be handler the serve one the read one the right one and you have all the state all the stuff on the server connection thing and it's easy to document in English what the rules for access is like you know this one can only be done this data is owned by this go routine this data is owned by this mutex are guarded by this mutex but enforcing it the the race detector works very well when you actually trigger a race but it doesn't happen Ted you did wrong but you got lucky so I wanted a little bit more paranoia so I made this type called go routine lock which is just a UN 64 this the size of internally a goroutine ID and I made a little constructor that looks up the current guru teen ID using undisclosed hacks but you can you can look in the code it's really really slow so it's only run during tests and then I say then I have check and I have checked not on and basically this this creates a little number that when you call check on it the number better be the same damn go with routine that when you originally constructed this thing otherwise it just blows up and so during tests I said debugger routines to true and in production these are like no ops but um so then I think I showed that on this page yeah if things like in serve there's like serves each X and so most of the functions and this all start with like what go routine they're locked onto and it's just a check and this I've caught me three or four times but those are three or four times that would have taken like days or weeks to debug so yeah so I have things like right frame handler that has documentation about like what goroutine you have to be on and there's methods here like check that you're not on the surf guru chain to call this one or right frame check that you are on the right on this go routine to call that one so this depends on whether or not it's a handler trying to write something or a frame right that's happening from the like the main logic loop the serve loop and when we do want to send something we basically just add it to the frame scheduler and say hey in queue this maybe not right now and then we schedule a frame right and this the frame writer says if we're already writing a frame and the frame writer always runs up the logic go routine the the serve loop and we say if we're already writing a frame well we can only write one at a time so do nothing well we'll get tickled again later to run and schedule something else whenever that frame is done writing then some stuff like oh if we need to tell the pier to shut down for during graceful shutdown we said that we need to send the settings ACK you know we'll do that otherwise the the main case is we ask the frame scheduler hey what's what's the next best thing to sent and this does the prioritization of various things and once if we got one then we call start frame rate and start frame rate actually does the writing scheduling well okay anyway so then there's um that's the stuff like process right this is why I organize lots of the other ones when a frame comes in you know there's basically just functions for dealing with with each one of the ones when you get like process headers we check all the stuff in this back like is the this what font better verify that the stream ID is the right range the stream ID is always have to be odd in one direction and even in the other direction because either side can initiate streams the server can push priam in I didn't talk about this but an HTTP to the server can preemptively issue a response for an HTTP request that hasn't been made yet so if you get a request for the like slash so your your front page you know that like you know there's some CSS and JavaScript or something on the page so you can send the response for the front page and say here's a hypothetical request for you that you can use if you want and here's the answer to it and so it's then the client can get all that at once and one stream without another round trip and so because both sides can generate IDs one side has to be even one side has to be odd so we on the server we check that it's actually odd we make sure that they're not reusing a stream that was you know old or in the wrong straight but then we create a stream for it with the ID that they said and this or in the certain state and then do the prioritization stuff and then process those 12 bytes remember those 12 bytes are H pack compressed in that first I beliefs ID or it's a continuation which is I told you like the same one that's as headers but they have to appear back-to-back on the wire so if so we process a header block fragment and we write it into the H pack decoder so these can come on any boundaries you can have any arbitrary like user agents you know mozilla 5.0 blah blah that could be split over dozens of frames and there it's not aligned on key value boundaries at all so you just you feed the H back decoder whatever data yeah there's a lot of these to do's where I'm reading the spec and it doesn't say what you're supposed to do so I think I filed six or seven bugs against the spec that just says what error code should I use in this case I don't know I guess I would be a streamer but I haven't implemented that one I just I think that would bubble up and be a connection error any any air that I don't annotate as a connection error stream air becomes a connection error we kill the whole connection but um so yeah call each pack decoder right at the top when I made the H pack new decoder I specified a callback and this is this is a method value so on new method this is the receiver name and this is a method name so the thing by itself is a function and it will go to on new header field that that type there each pack header field is just a struct basically a key value string and then I could check you know whether it's one of these magic ones rather than sending like get all capital in a space and all this stuff the method the path the scheme the authority these are all just magic key value pairs so start with colons and nothing else can all up begin with colons we also check things like cookies or special cased in the spec for weird reasons we check that it's a it's a valid one you can't have any uppercase fields in HTP two so we normalize it and go so you don't see a difference in your HTTP handlers but if on the wire we see a capitalized field you're the spec says you have to hang up on them so tests are fun there's whole bunches of tests in here that do things like so first I made a type called server tester just so I could make the other tests down below easier and these have lots of little convenient methods for doing stuff but then I'll have a test like here's a single test that that a post with no content length and endstream does the right thing so here's an you know HTTP handler and in my little server tester that gets passed into test server requests which little test helper we write the headers with stream ID one and the block fragment with method post and that this bit set that bit set and we verified that what go-sees eventually is a as a post with you know that thing content length can't be zero because we didn't know and so there's just pages of these of all the different little corner cases and you know if you run it you'll see like tons of little tests of all the different cases and there's even one test in there that won't run on my laptop because I don't have a boot to docker but actually yeah oh okay questions there's fun tests we use docker to run integration tests against other implementations yeah the question is that go routine lock making it a public in the standard library I I think actually I would make a case to others that it would be useful but I expect pushback so we I'm not actually writing a little 9 by 8 thing 9 byte in separate packets the frame scheduler has a field those needs flush and once the print schedule has nothing else to say it writes to a buffaio writer and once once there's nothing else to do it spends a sends a special type called flush frame writer that actually flushes it to the wire you could in theory it would be a minor performance improvement it's not worth the complexity yeah I mean there are a couple more copies here but I could do lots of things all right the problem with the bull is like it takes up some space and it also like it seems to it makes the caller think what's magic about true what's magic about false there's nothing magic about either one of those and then also I mean I don't care about what that's free and actually there's fast paths for various things like if you have an empty struct it goes into different schedule or frame blah blah blah so I think I think it's easier to read is the main reason I don't really care about the performance well I could close the channel but I do this multiple times I reuse the same and in the frame writer read rain yeah so I create it once the gate up there and then I just resend the same one every time because I mean channels aren't so small they're like I don't know 64 or 80 bytes or something like that for all they're both keeping so one more question good one is what oh this so the spec they HTTP do spec upstream isn't blessed as official yet and still sometimes changing a little so we don't really want to put it in go until it's done and the quite event finished the client are started the client so it'll probably u16 but it's it's available you know github and you could add it to your thing and like two lines of code I think you'd importand they say configure server so we're using it in camel east or some yeah yeah I mean once it goes into one sec so it'll just be on like you won't have to do anything yeah yeah the the interface that you see is unchanged there'll be some optional things that you could opt into to like do that server push thing but um yeah cool thank you
Info
Channel: The Go Programming Language
Views: 9,157
Rating: undefined out of 5
Keywords: Go (Programming Language), FOSDEM (Conference Series), Brad Fitzpatrick, Software (Industry), http2, http/2
Id: gAfoLwog_MA
Channel Id: undefined
Length: 46min 2sec (2762 seconds)
Published: Wed Mar 04 2015
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.