 Okay, this seems to work. I hope at least we are back from the lunch break and we have with us already waiting our first speaker for this block in the Brian room. I am going to invite, let's see, work. Hey, I'm going to invite Philip Jones from my night. Philip, where are you calling us from? London today, in the UK. That's nice. And have you been working from home all the time? Yeah. Like, like almost everybody. You're involved with a lot of asynchronous projects like quad, hypercone, and where else do people know you from? Probably that, maybe from helping maintain the pallets libraries as well, but probably those if people know me. Yeah, and today you're going to show us how to do our own asynchronous GI server from scratch, right? Yep. So we're really looking forward to that. So please start your screen share and show us how we can do this at home. Okay, so hopefully you can see if it's correct. Yep. I presume you can see everything. I can't hear you if you can, but I'm hoping if it's good. So yes, so I'm going to talk today about building an ASCII server from scratch, quite what that means and everything about it I'll explain in a minute. You can probably find the slides in quite a number of places at the moment, but you can also find them on my site, which is PGJones.dev. Excuse me. And you can find the slides on the talks page. I've also got some code to go along with this which you can find on GitHub. Under my name PGJones, if you look for ASCII server from scratch. So all the code I've got in these slides is also available on GitHub to help make things a bit easier. Before I get into the talk itself, I'll just tell you a bit about myself. So I think you already know about the bit of the open source work I do, and you can find me here. You can find me as PGJones on GitHub and GitLab. And on Twitter, if you wanted to tweet me, you need to remember this extra D. I was too slow to get the PGJones, so there's an extra D there. And as I just said, at the start of this year, I co-founded Moneyd, which at the time seemed like a great time for it. Since then it's proved a little harder. What Moneyd is, is it's a financial tool for people in the UK. So what it provides you is financial planning tools and guidance to effectively become your own financial advisor. So if you're in the UK and you're interested in your finances, please have a look at Moneyd. Okay, on to the talk. So our aim today is to build this green box in the middle. So we have a client talking to us and we can talk to some ASCII app. And ASCII, which I'll come to in a minute, is basically the definition of the interface between our server and any app. But of course we need to be able to understand what the client does. And whilst ASCII can do more than HTTP, it can do web sockets and stuff like that. For time constraints, I'm just going to focus on HTTP. So sorry about that. Okay, kind of like introduce why you'd want to do this and what you need to consider. I think I just need to say a little bit about ASINCO and the ASINCO wake keywords. So the joy, if you're in my eyes of ASINCO and awake is they're very explicit. It's very clear what's going on and what parts of code could yield control back to the event loop and what couldn't. The downside of this, of course, is that a coroutine function and async def can't be run itself. If you just call it, it's just going to return you a coroutine and do nothing. What you need to have is an event loop, run it for you, for example, like this. Of course, the bonus of that is the event loop can then interleave the IO as the whole point and know when to yield. And in our case, what we're going to be interested in is creating a server and listening. So this is the start server command. So, brief introduction there to kind of introduce ASGI. I'm going to start by talking very briefly about WGSGI, which is the web server gateway interface. So if you're using pretty much any kind of framework in Python at the moment, particularly Django or Flask, you're almost certainly using WSGI whiskey. And what it is in its simplest form is a definition of the interface between the server and the app. So between any server and your Django or Flask app. And what it says is the server will call a callable that takes two arguments, an environment and a start response. So your app at the very least has to be this callable. And the environment is a dictionary that fully describes the request and start response is a callable that the your app uses to tell the server when it's ready to start responding. And this callable takes two arguments, the status and then a list of headers. And then this callable should yield or return the body. So in a way of HPP, the server figures out what the request is, calls this callable, and then your app will start the response and return the body. So this is all very good. It's actually worked really well in the Python community but you can see it's got no async. So it doesn't work with a sink await. So your app can't be asynchronous. So this is where ASGI comes in, which I always pronounce as ASCII, but I'm sure I was doing differently, which is the asynchronous server gateway interface and it's heavily influenced by WSGI, as you can tell by the name. And to kind of show the comparison with that with the whiskey example I showed there, the interface is that you have a callable. This time it's asynchronous that takes a scope and two more callables are receiving a send. Now the scope is very similar to the environment. It's a dictionary that fully describes the request, but it doesn't say anything about the body this time. Now the application has to get the body by receiving it so it can call this receive. And then when it's done, it can send messages back to the server using the send, as you can see here so we send we say the response is ready to start with a status code and some headers, and then we send the body. So this is pretty much the same as the WSGI version, except for ASGI. So this is our interface effectively this and the definition of these messages that go between the server and the app is what we need to build on that side. Okay, so just to hopefully clarify what I was talking about there. So we're building the server. So STI is the interface between the server and the app. And the real advantage I forgot to say really is that you can switch out any ASGI server for any other ASGI server and not have to change your app. She's good at the moment there's like Daphne you've a corn hyper corn, you can just choose between and hopefully your own at the end of this talk. Of course, we need to do this part. So I'm going to focus on this part now the connection between the server and the client. So this is actually turns out first talk mostly a synchro bit here and what we need to do is open a socket on the server, listen for incoming connections, and then do something with that data. And this example I'm going to start with is just going to send every byte it receives back to the client. So if we start in the main, we use the start server command I mentioned earlier to create a server. And this server is going to listen on the host and port, and every connection is going to call echo server callable. So I'm going to get to in a minute. And then the server itself, if we just await its server ever methods, and it will just serve for as long as you don't interrupt it. So very simple and we can run this main in the asynchro run or some other way, which I spoke about earlier. So it's a server bit that we're really interested in so every connection is a new task is going to be created running this core team function. And this is going to take a reader and a writer and it's exactly as you'd expect the reader reads messages from the client and the writer writes them back. So a simple echo server simply needs to listen to everything the reader sends until it indicates it's done, which is the at end of file. So it reads up to 100 bytes, and then writes it back. And then for a bit of flow control, it waits for the client to have received that data. I'm not going to talk more in this talk for time reasons about flow control, but if you do go down the route right in your own server, this is really important, very easy to get wrong. It's actually quite pain. But anyway, so we read the data, write it back out. Then when the client said it's done, we close. So this is entirely controlled by the client. The server doesn't actually decide when to can close the connection. So as long as the client plays nice, it should be good. So let's test it. So if I run it this way, I can connect using a tool called town that. And then I can type in hello, and the server's going to send it back to me. I can type in goodbye, so it's going to send it back and then I can close the connection and the server's going to close it. So it's done everything we wanted to do. She's great. Again, this codes in the GitHub repo if you want to play around yourself. So what we've done now is figured out how to do this connection here. So we can send bytes back and forth between the client and server, which is great. But what these bytes are actually going to be formatted in or the protocol is going to be HP. So we really need to understand that. So let's move on to that. So I think if you're present in the last talk, you saw a snippet of this as well. I'm going to focus on HP one. I'll talk a bit about HP two and HP three later on, but let's start with HP one because it's much easier. So HP one requests looks like this. And we have a request line first, which has a method path and a version. Then we have the headers, which consists of a name colon and a value and possibly more of them. And then we have some where we have a blank line and then some body. So this is what it is. So our parser needs to be able to understand this. So this is a very simple parser I've written for this talk. And you can see we feed it lines to the key thing here is your kind of implicit, but there's line breaks between each one of these. So you know, it's the next thing. So we feed it the lines and it starts off in the request. So the first line should be the method path and the version separate by spaces. And as soon as we got that we can look at the headers. Now the headers, it could be one or many. So as long as we don't see a blank line, we think it's still the headers. But as soon as we see that blank line, we know that now we've moved on to the body. Everything that comes after is going to be the body. So not really any more parsing to do. It's just all going to go in the body. I'm going to ignore training headers for simplicity. So if we're in the headers part, this is probably the most complex bit. We split on the colon into the name and value. And then we append it to the list of headers. And we're just going to keep as well. If we find the content length, we're going to keep hold of that. So that'll tell us how long the body is, so when to stop reading the body when we're done. And we're also ignoring other ways of like, like truncating coding or anything like that. We're just going to do content length. So this is a very simple parser, which will allow us to pull out the important parts of the HP request, notably the method, the path and the body and the headers. Okay, so now we have this, we can combine it with the server we had previously and start to understand the requests coming in from the client. So this is it. So this is, if you just imagine I've switched out the echo server to this HP parser server, still gets the reader and the writer. And I create the parser and byte array to store the body in. Once again, I'm going to keep listening until the reader, the client says it's done. And I'm just going to feed the lines that I get to the parser until it says everything else should be body. And then when I am in the body states, I just keep reading until I've read as much as I expect to read. And at the end, I could print out the methods, the path, the headers and the body, which is what we want. And then I'll just write a simple response so the client doesn't get upset. I'll just tell it that everything was okay, but I'm not going to send it any data. I'll come to response later. Again, a bit of flow control and then close. So if I send a HP one request to this, I should print out all the important parts, send a valid response back, and then close. So we can test this. So we run it this way. And this time I'm going to use curl rather than telnet because curl will do the whole request for me. And just using the V flag so we can see it all, you see it's going to send this request line, then these headers, then the five bytes, which is going to be hello. And then it will get a response back and you can see when we test this, it sends all this and gets a good response back and the connection closes. So as far as curls concerned, our server did everything nicely. So as far as we're concerned, the server did as well because it's printed out the method here and the path, and then all the headers that passed in and the right body. So that's pretty good. So we now have a server, very basic server that can listen on a port and excuse me, and understand the HP request being sent to it. So now we have this, we need to effectively convert the HPB into ASCII messages. And to an extent this is what an ASCII server is. It just changes from the HP protocol to the ASCII protocol and back. So you can imagine we get HPN, we change it to an ASCII message, send it to the app. The app is going to decide to do something. So if you can imagine if you're using Django, it will match the path, send it to the right handler. The handler will do some logic and then send a response. And that response is going to be a new ASCII message, which goes back to the server. And our server is going to have to turn that ASCII message into HP and send it back to the client. So that's what we're up to. So let's focus a bit on the app. Sorry, I'll come to the app in a minute. Let's focus first on changing HP into ASCII. So I mentioned earlier on that the scope pretty much fully defines the request other than the body. And to fully define the request, and it's a bit more than this actually, but in this simple case for this presentation, we need the method, the path, the headers. And you see the ASCII scope looks like this. You tell it, it's HPV, has a method, has the path information, and then the headers. So the code to do this looks like this. So if we use our parser from earlier, a scope, which is the scope dictionary, is simply created this way. So that's great. So now we can tell the app about the scope, about the request. We also want to be able to tell the app about any body. And that obviously isn't going to happen for a GET request, but for a POST request with some content, the client's going to send us a body, which is some bytes. Now the scope is created pretty much the same way as before, exactly the same. But now we can send extra messages to the client. And these extra messages are going to be type HP requests. They're going to include some body. And for as long as the server thinks there's more body coming, we should set more body to true. But as soon as we know there's no more body coming, the content length is five and we see five bytes, we say more body false, which tells the app that it's got everything it's going to get about the request, it can then move on to response. So the code, again, is quite simple. It's just a simple function that takes the body bytes and a little flag to see whether there's more bytes coming or not. And we just create a message. And we can send this message to the app. And I'll show you how in a minute. Okay, now coming back so the app has got everything it needs to know about the request. Now it needs to tell us how it wants to respond. So the app does this by sending a message to us, to us as being the server, and that type is going to be the response start and the status code. And it usually 200, but it could be anything. And then whatever headers it wants to send. So this is the first message, the, the app will send back to the server when it's ready to respond. And the second message will be if it has any body. So if it doesn't have anybody, this should be blank and more body would be false. But if it does, you can see it has hello and and us as a good server should probably check that the content length matches the body it sent us. And the right today doesn't, but you can imagine that's a nice extension, just to make sure nothing strange is going on. So just to summarize, when we get a HP request we create a scope, and we pass that to the app, we call the app with the scope, we then pass asking messages, defining any body to the app. And then we can decide what to do with that request, and then send a response and that response is going to be a start response message, and then any extra response messages. And so that's the app process. So now I can actually tell you about the app itself. This is a hopefully quite simple app, which is very similar I hope to the echo server we said earlier, it's basically going to echo out any body that's been sent to us but now it's running over the HP protocol. So you can see, in this case we don't actually care about the scope. All we want to do is receive the messages from the server and get the body, basically so every message we get every event, we get the body out and extend our body copy. And as soon as we're told there's no more body, we break out of this loop. So the server tells us about all the, all the crests all the body, and then we do something to some logic. This could be like I said just like matching the path, deciding which route to call but in our echo app it's all we know it's going to do is send the body back. So we start by saying, everything's good, and we're going to send you the link for the body bites back. So this is it basically. So this is a very simple app that will just listen to all the messages from the server build up a body and then send that back to the server expecting the server to then echo that back to the client. So now we have a test app to use we can actually build our server and our server will call the app so just to qualify here what we just spoke about here was an ASCII app you can use any ASCII app for it but this one's kind of simple enough to show an example. So what we're trying to build is the server. What I'm going to focus on first is this flow so I spoke through this is kind of the messages come from the client HP, our server turns them into ASCII and sends them to the app. So I'm going to focus on that first. And this is the code for it. So this is a little bit more. This is a little bit more code so I'll take a while to talk through it. Again, it's another server. So very similar to the two we've spoken about before it just takes a reader and a writer. We use our parser familiar. And this time we create a queue to put the messages we're going to send to the app. So this is, this is what this is for so two app we're going to send to the app. We're also going to keep account of how much, how many bytes we've read, just to know when to finish, basically. So, again, we're going to keep reading as long as the client hasn't told us it's done or there's a disconnection. We feed the lines into the parser as before. This time, if we find after the passes in the body states that there is no body expected. I maybe it's a get request or something like that. We simply put a message to the client that says, I'm done. And we can break out of this loop. If not, we're going to start reading in the body. And we're going to send a message to the client that has that these body bites in and it tells us tells the client whether there's anything left. So we're keeping track of how many we've read. I just noticed what's making me hesitate is this should say read here rather than length body otherwise it would break. So sorry about that typo. But hopefully you can see what's going on here. We're reading the body from the client and that's coming in chunks. Very likely we're sending each chunk to the app and we keep in track of how many we've read so that when we've read as many as we expect we can break out of this loop. So we've hopefully here past the entire request from the client. And now we can create a scope as well. So with the combination of the scope and this queue of messages that we're going to send to the app, we have everything the app needs to understand the request. I'll just note as well here that you can kind of see how you may be thinking that we've read and done everything before we've called the app. And you're right, this is to make it simpler to show on the slide, but a real ASCII server could have actually created the scope before it even started to read the body. And the app could have started to understand and process the request whilst the body was still being still arriving. That's one of the advantages of it. But it's a little hard to write that on a slide. Okay. So we've now got the information coming in, turned into ASCII messages to send to the app. Now we need to go back as well. So the app's going to send us some ASCII messages, which we need to understand, turn into HP messages and send back to the client. So this is the code for that. So now we have another queue coming from the app. And now I'll just note that we actually do call the app here. So this is the bit where the app's going to do everything. It's going to read from the two app queue and fill up the from app queue. So as a server, we just need to get the messages from the app queue. The first one we expect is the response starts. And when that happens, we just write out the status code. And we write out the headers and a blank line. Then the next messages, we expect our body. So as long as the, there is a body in it, we write it out. And then as soon as the clients, the app, sorry, has told us there's no more body, we can break this loop as well. So that is it pretty much the combination of this code to send messages back to the client. And this code to read messages from the client gives us an ASCII server for HPP. So let's test it out. Again, this code is in the repo. So we'll use curl again. So this time I'm going to do the same thing as earlier. I'm just going to send hello to it. It's going to send all these headers. But what we're expecting our app to do is just echo back hello to us. I tested it with a lowercase h. So sorry about that typo as well. But that's what it does. And it's working as we expect. So as long as clients do the very limited HPP one that we support, this will work fine. And yeah, it works nicely as an ASCII server. So if you follow along, you now have your own ASCII server. So what's next? Well, whilst the simple HPP parser I showed you there does work and it gives really nice benchmarking scores. In fact, it is not stable enough for production and it doesn't cover any near enough edge cases. So I'd recommend you use something like H11 for this, which is a sans IO HPP parser. And you can then build on from there and there's an example in the repo for this talk if you want to look at that. Going any further, I think you should take a look at hypercorn, which is a project I maintain. So hypercorn is an ASCII server that supports asyncio and Tria workers sports HP one using H11 HP to use in H2 and HP free using a quick. It also supports WebSuckets using WS Proto. And of course, didn't talk about WebSuckets at all now, but real advantage of an ASCII a synchronous server is WebSuckets fit very naturally and you don't have to kind of like elbow them in like you do with whiskey servers. So that's it. Hopefully you've got a good idea of what makes up a very basic ASCII server. And yeah, I look forward to your questions. Oh yeah, thank you. Thank you very much for the talk. I think you overwhelmed almost everybody because we just have one question. What's the benefit of writing this by hand compared to using a library like Flask. Flask is is just the the app side. So you'd need a server for that as well. Now for Flask you probably use something like to unicorn or you whiskey which are your WS GI servers. So Flask itself isn't yet ASCII compatible. So you wouldn't use Flask. But if you were using say ASCII one like Starlet or Sonic or any of the others, then you could use a server like this you've written yourself. There is no advantage. I would actually say you probably don't want to write it yourself because there's a lot you can get wrong. However, I think it's quite interesting to know how this works and to have a good understanding of the code you're using. So hopefully you've gained that from my talk. Yeah, thank you very much. That was really interesting. So let's have some virtual applause and everybody at home please clap along. Thanks. And just a reminder to everybody who wants to learn more about this. There is a separate chat room for this talk called talk ASGI server so if you press controls K and ASGI you will find it on the score. So if you have any further questions you will probably be able to go there but I see there's a last minute question coming in and we should ask that it's why Django and would Django and tornado be considered WS GI. Yes, they are both WS GI. However, I think they're both. I'm not so sure about tornado but Django certainly is gaining ASGI capabilities as well so Django free, I think, or 3.1 which is going to be released this this summer is is going to be an ASGI framework as well. So you could use ASGI servers like hypercorn or Daphne or others to serve your Django apps. And so, for example, from the previous talk if you wanted to play around HP free, you could start hypercorn HP free survey Django app and play around have fun. And we have one final fast question from Javier do you recommend to use hypercorn with fast API. It depends really on your use case and what you'd like to do, but yeah, it's certainly I mean I would personally because I work on hypercorns, but yeah, you certainly can it's up to you. Okay, thank you very much again. That's all that we have time for now. So, we have to get ready for next talk so see you at the conference and in the chat rooms. Bye.