How to use Web-Sockets in Python
CC Attribution - NonCommercial - ShareAlike 3.0 Unported:
You are free to use, adapt and copy, distribute and transmit the work or content in adapted or unchanged form for any legal and non-commercial purpose as long as the work is attributed to the author in the manner specified by the author or licensor and the work or content is shared also in adapted form only under the conditions of this
Caceres, Anton
Modern web-apps require bi-directional communication, reacting not only to user actions but also to server events. This can be achieved elegantly using Web Sockets, a protocol standardized by W3C to be a default tool for full-duplex connections on the Web. Although most web-frameworks do not support Web Socket integration out of the box, Python has multiple options available making it a piece of cake. This talk showcases integrating Web Sockets in Python web-apps, focusing on situations that can make good use of it and presenting implementations with most popular frameworks: Tornado and Django. We will explore a similar style but different underlying technologies of both, and finish with a live demo.
 Thank you very much for coming. It's a pleasure to see all those new and familiar faces here in this room. Have you enjoyed the conference so far? Yeah, I did too. Good. All right, I have a first question to you. You've probably heard of a saying, like, if all you have is a hammer, everything looks like a nail. You certainly did. Well, it's not just a saying. It's actually something formulated as the law of the instrument by the American philosopher Abraham Kaplan. But not the Kaplan, most of the jungle author has nothing to do with it, a different thing. Yeah. Well, anyway, what does it have to do with today's talk? The thing is that I believe that this saying perfectly illustrates the way we work with the web today and the way we are using HTTP. We're a bit childish by using HTTP because it's our hammer. Whatever task we have, HTTP, do it. Do it, please. But there is more than HTTP on the web. So HTTP was not designed to do the tasks that we often forced it to do. The protocol itself, the HTTP, is just centered around the request and response. That's it. There is no progress reporting. There is no multiple requests or responses per request, anything like that. If you need something, you can build an abstraction of top, of course. But at the end, even if you build like the pseudo-acing communication with HTTP, it still does not change the way the underlying HTTP protocol works. It's still synchronous and it's still centered around the request and response cycle. So again, the idea is that simple. In the good old times, there was just a request to the back end. We fetched the page. We displayed it. That's it. Easy. Well, then the web evolved, of course. It became smarter so that we needed the application server. It also became more beautiful, so we needed the CSS, images, fonts, files. And then the picture changed more or less like this. So we still have our browser, the client. It makes requests to the web server to get stuff, more stuff now, and even more stuff, but it still fits in the paradigm. Still fits into the way the HTTP is supposed to work. Just more requests, but that's okay. The first time I noticed, I remember that something goes wrong here, is when I had to deal with icons. Do you remember a good old times of the icon sprites? Like this, it was an obvious problem. Like, hmm, we have too many icons. That means too many requests. HTTP is not handling that well. What can we do? Oh, let's pack all of the icons into one image and just offset it to the left and to the right. Genius. Well, this is something that I mean by the web abuse and using the HTTP, not in the way it is designed to be used. It has nothing to do with web sockets, but the story is going. Then, async requests. So the problem was fixed by introducing Ajax when we thought, okay, we need some pre-order call updates of our content without reloading the whole page. And then we got Ajax. But at the end, it's good. However, Ajax does not change anything for the backend. Ajax is a pure client-side technology. For the backend, it looks totally the same way, and it's again the very same HTTP. Moreover, if you take a look on how exactly the paradigm, how exactly Ajax update works, like, we wanted to get something like a dialogue between the client and the server. What's a dialogue? Well, dialogue is like when two parties exchange their opinions, they're equally involved, they are respecting each other, this is a dialogue. But how our dialogue between the browser and the backend look? It's more like this. Because at the end, we are dictators. The client is a dictator. The servers are not allowed to speak, only when we ask. So it is kind of a funny way of doing dialogue, in my opinion. And so although Ajax solves this problem partially, meaning that we can just periodically ask the backend about the updates, and it's a pseudo-dialogue, it's still ugly. So yeah, as I already explained, we have a couple of problems. We have first problem is that the dialogue is not really a dialogue, it's a pseudo-dialogue. And the second is that you still need a lot of requests, and the more frequent updates you want, the more requests you do. And this obviously does not scale well. Moreover, you are still using HTTP on the background. HTTP has quite a high noise-to-the-signal ratio, meaning that you send all of the headers, all of the cookies, everything, just to get a response from the backend, nothing new happened. That's not cool. And then we come to the long-falling. Well, this is already something. This is already some progress, let's say. We open the connection, we keep the connection alive. Connection is just idling, idling, idling. And whenever something new happens on the backend, we push it through. Well, that's great. HTTP overhead is still in place, but at least it's not periodical as Ajax. It's waiting for stuff, idling. And in my opinion, well, it's of course good because you get things asynchronously, and it's even better because long-falling is kind of romantic. Because if you think of it, it's like looking in someone's eyes when no words are needed. You just wait and wait and wait. And then here she replies something to you. The problem is I don't think that my level of relationship is already there on the web. I don't want it. Enough. Please. Every time you use long-falling, a guy from Boston Dynamics kicks the robot. Now finally, enough of this nonsense. There is just one way to do the real-time bidirectional communication between the client and the server, and that is WebSockets. Nothing else. Forget Socket.io. Forget this library that's built on top of usually long-falling and in the worst case Ajax. They are outdated. There is totally no reason not to use WebSockets these days. As the developers, we can finally benefit from WebSockets because we have no longer a problem of the HTTP overhead. We have no longer a problem of the real, full-duplex connections, meaning both parties can start talking to each other as a normal dialogue. And finally, the interface is really easy from the client as well as from the server. I'll show you in a moment a couple of code pieces and as well as a demo. But first, WebSockets in a glance. So another cool advantage is that WebSockets, they are not HTTP, but they are compatible with HTTP, meaning that they run on ports used by HTTP, 80 and 443. So all of the firewalls will work. All of the proxies will probably just let it through. So basically, you do not need any new setup in your infrastructure. You can just use NGINX or whatever reverse proxy you use. It should just work. Then how exactly we jump from the HTTP to WebSockets protocol? We start normally, as we always did with HTTP. We do the handshake. We establish a connection. And then our client says to the server that, hey, I want to upgrade to the WebSockets protocol. And if the server supports it, it says, yeah, do it. Then they establish this connection, the full-duplex TCP connection. They do not communicate over that connection with HTTP. Now it's pure WebSocket connection, meaning that HTTP headers do not go through it. And this is cool because we're not sending HTTP overhead anymore to the client every time. All right. Let's go for the use cases. So obviously, it's stupid to say just use WebSockets for everything because there are good sites and bad sites. So the good cases for WebSockets are any cases when you need to implement kind of a multiplayer behavior. It's obviously games. It's also tracking system. When you have multiple objects, let's say, moving on the map, and you need to update their locations in the real time, this is typical scenario for WebSockets. Also the collaborative editing, Google Docs. When you have, again, multiple cursors writing in text at the same time, that's exactly the case. However, as I said, there are also bad cases to implement WebSockets. First of all, because you are with losing HTTP overhead, you're also losing the benefits of HTTP. And that is caching. That are the standard HTTP responses like 400, 400, 500, and so on. Most of our JavaScript clients these days see automatically react to the problems, to the error codes of HTTP. Like if you return 500, your web framework will know that it's a server problem, 400, it's a client problem. Well, on WebSockets, we don't have an overhead, meaning we don't have status codes. All that you get is just a message, a pure message. And then it's up to you to handle it. So if you want to say there was an error, you cannot send a status code because there are no status codes. You have to somehow encode it yourself. And then on the client, parse it yourself and say, oh, this was an error. So remember this. Then in HTTP, we know that GET requests are identipotent, meaning that you can repeat it as much as you want. In WebSockets, you cannot say it. Again, it's just sending a message. There are no GETs and posting WebSockets. It's just sending a message. So it's up to you to decide if you want to build it in idempotent fashion on the back end or not. And the client also has nothing to do with it. Now let's see a little example. As I mentioned already, WebSockets are really, really easy. So to prove it, here is the vanilla JS example of what do you need to do to establish the connection with the back end? Can you see it? I can. You can? Oh, that's great. That's already something. I don't have the mouse here, but all right. I'll just comment it like this. So as you see, we're just not using any sort of libraries. It's a pure JavaScript. You open the debugger in Chrome, and that's what you need to type. So you are creating first the WebSocket connection to that host, in this case, local host. And then you define three callbacks. What to do on open when the connection is established? What to do when you get the message? In this case, I'm just depending it to document body. And what to do on error? Obviously, nothing. No care about that. These are 10 lines that you need to write to use WebSockets. So it's really, really easy. Now it would be, of course, boring if I wouldn't come up with a demo. I thought, like, I want to get some feedback about this talk. And the best feedback for a speaker is obviously throwing tomatoes on him. So I really like this shirt, so please don't do it right now. But you can do it on the Web. So if you just go to, oh, this is the wrong URL, you go to, I'll just do it with you. Go to demo.caseras.me. You don't have to do it. But if you have a laptop anyway, you can just do it. Then you learn how to use WebSockets. I hear I have a little code snippet. I'll just zoom in for you. Okay, didn't make it much better. Anyway, you'll get the idea. So if you copy this into the debugger, yeah, I saw that someone already did it. You are throwing tomatoes on the speaker. Well, you might think that this guy is actually not so bad. Like I do not want to throw tomato on him. I better throw a candy. You can do that too. Then instead of tomato over there, you write candy. Oh, somebody throw an egg on me. That's bad. All right. Well, anyway, have fun. Have fun with it. What this demo shows is that when all of you are throwing tomatoes on me, I like tomatoes. That's fine. When you're doing it, one person throws it and everyone sees it everywhere. Because on the background, what you do is you send, oh, my God. Could you please send some candies too or cats? You can send cats, by the way. Oh, my God. Yeah, so you see it's updated in the real time. One person sends, everyone receives it. And if you reload, oh, this is sweet. If you reload the page, then you get it all at once. The WebSocket is pushing through everything that it received by now. And this is just that three lines, four lines over there. And you can see the source code. There are no libraries involved at all. It's just one HTML page with JavaScript inlined. Super easy, super cool. I want to say to you that there is no barrier in not using WebSocket these days. Now back to the presentation. Yeah, you can have fun meanwhile. Let's see at the end. Let's see at the end what's there. I'm really interested now. Okay. Now we still have time to see how much time do we have? 15. Great. So now we obviously want to see, it's a Python conference. How can you use it from the back end side? So we have several frameworks that can do it. And the easiest one is Tornado. Well, in my opinion. Tornado is kind of a flask, but the async way. You all know that flask is super minimalistic, but Tornado is also minimalistic. You can have just one py file and this is actually all that you need, this 26 lines to run the web server with WebSockets. And you have here already the handler implemented. This is literally all you need to do. PIP install Tornado, that you have it running. Now let's just have a few comments on what exactly does it do. It's very, very much similar to what we do on the client side. We define three callbacks on open, on message, and on close. What should we do? And when you receive a message, obviously, or the on message callback is triggered, and in this example, I'm just sending it back. I do sell that right message hello, and this just sends back hello and whatever you received at the beginning. That easy. Of course, it's not so easy if we want to throw tomatoes on the speaker. It's a bit more difficult because you want some place to store all of the thrown objects. And then you want to send it not just to one client, you want to send it to all clients, meaning the broadcasting. But for this, you can just use plain Python tools. Just append everything to the list, and then when the new client connects, it writes on that list and sends it back. So there is no magic. It's just normal Python data structures in this case. I implemented it super simple on purpose. You do not need any kind of readme file for this. Super easy. But if you want more complex setup because it's still minimalistic, you can take a look on Django. Django in channels is very different because Django in the first place is a synchronous web framework. So you cannot run web sockets on Django out of the box because it just does not work by design. What they did, it was a good idea. They created a synchronous server next to the synchronous server. So actually when you send the web socket data in Django, you're not sending it through the main web server. You're sending it on kind of alternative one, which is a synchronous which supports the thing. And then they integrate. So please understand that Django channels has little to do with Django. They tried to write it in the similar way and they succeeded. But it's still two different libraries working in totally different ways. They are somehow integrated and the tool set is really cool and really huge. It's nothing compared to Tornado because it has all of the broadcasting features, the sessions, the users. Tornado doesn't have it. You have to implement it yourself. Like in Django channels, it's relatively easy to say to map the user to the web socket connection if you want. If you want to split your users by group, like only this group will receive this message. In Django channels, it's easy. In Tornado, you have to do it yourself, even though it's also easy, but you have to do it. Okay. Now, let's go to the next one, the bonus one, the web socket package called web sockets. This is how it looks. This is really the most minimalistic thing I have found. So you have just one function, one handler that is handling your web socket connections. And here you can use the asyncio library with asyncio wait syntax if you like. So I think it's quite self-explanatory. I tried to build up this example very minimalistic again. You do the await web socket receive, meaning that you idle in that moment until the data arrives. When it arrives, it's written to the name variable. Then you formulate the string that you want to send back. And then you again do the await web socket.send, sending it synchronously. And that's it. And that's three lines on the bottom are just running the asyncio IOLOOP, quite standard stuff, I would say. And you can integrate those because Tornado runs on, I think, not out of the box, but it supports asyncio IOLOOP. So you can run both. I don't know why would you need this, but people ask these questions if I can integrate this, you can. All right. So this looks already like some dialogue to me. If you use web sockets, I noticed your reaction to the guy kicking the robots from Boston Dynamics. I see that you're also concerned about this topic, so I think at the end of the presentation it's important to show you that there is a petition. Stop robot abuse. And the best thing are the comments there. So feel free to sign it. I think it's outdated, but it's hilarious. I had to edit there. Now the commercial time, sales time, I run a Python agency. If you have projects or you're interested in doing a project with me, please contact me. Let's see how many tomatoes are on me by now. Lots of cats. Well, this is nice. It's like a paradise. Cat paradise. The funny thing is that I was really in a rush for this demo, so I have no way to flush it now. I have to redeploy the whole thing, but I think I will not because it's really cute, isn't it? Okay. Yeah. Then last thing, the credits. There are really cool articles online about web sockets and about using them with Python, also the videos. And that's it. Thank you very much. Okay, anyone have a question? Please stand up and come here. You send that over the HTTP, the Web sockets. You start with it. Yes. Can we have all the, it's a get, I think. It's a get request at first. And can we have all the parameters like a get parameters just to know what to send or what to do with this Web socket? Thanks for the question. So yes, you start with HTTP because you never know if the back end supports the Web socket. You start with, you are connecting as if you are connecting HTTP, but you only do it not to send the data. You only do it to ask if we can upgrade to the Web socket connection. And if you can, there are no get requests. There are no parameters, nothing. So if the server and the client both support Web sockets protocol, as soon as the connection will be established, you totally forget about HTTP. So no, you cannot get parameters and you do not have statuses and you do not have request types like getPostput that doesn't exist. HTTP is really used just as a transport at the first phase to establish the connection and that's it. And it is done that way in order to be compatible with firewalls, reverse proxies and so on. So the protocols are very different. They have not so much similar because the Web socket was developed in order to maximally decrease the overhead of sending messages back and forth. Basically in Web sockets, everything you send, almost everything is a message. There are a few frames at the beginning of the data frame indicating the order so that the back end can then combine this Web socket message pieces into one big message and that's it. That's all the metadata that is sent. So no parameters. Thanks. More questions? How practical Web sockets for Python like other libraries and frameworks there so they could scale big projects, I guess? So how practical are Web sockets in Python? Well, would you have a like a big project like this scale of... Of course....of course....of there so they can have... Well, first of all, most of the things that, as I mentioned in the use cases of collaborative editing, multiplayer games and tracking systems are already on Web sockets like Google Docs. So it's quite a big project, I would say. It's not in Python, unfortunately, but it's basically the only way that you can do the reliable full-duplex communication these days between the client and the server. So it's not like you have much of a choice. You can use Ajax, yes, but if you have imagined like this room right now sending these cats with Ajax, I mean my poor server would just die from that. And since you use in context of Python, I can mention again that it's a special well-playing in Python because you have async await syntax. So it's really cool you don't have to do the callback spaghetti as you have in JavaScript. You can use async.io with async await syntax and it even looks nice and you do not see much of a difference with a normal request handler like you have in Flask or Tornado or Django. You just put await and it's perfect. Thank you. Any more questions? Yes. Oh, that's a, oh, okay. We have the whole queue. Yes. And does this work? Yes. So I am using Web sockets for a trading application and it's, the bandwidth it's using is about, I don't know, maybe one or two megabits per second, not megabytes. And I find that the library I use is consuming up to like 25% CPU just to pass messages. What library do you use? I think it's WebSocket-clients and it's something built on top of it. But do you find that is normal or is it way too much over? I have to be honest, I never used the WebSockets library because that's all you need to write the WebSockets connection. This is ten lines or something. So I know that in the old days when WebSockets have been just introduced, you had a lot of browsers introduced in Internet Explorer that did not support it. Then you had to use something like socket IO that would fall back to long polling and if that doesn't work, it would fall back to Ajax requests. Yes. But these days are gone. That's it. All of the browsers, including IE, I think since even... I have to say it's a common line client in Britain and Python has got nothing to do with the JavaScript world. Python clients. Okay. Which name again? It's WebSocket-clients. I wonder if you could be able to advise efficient CPU-ized WebSocket libraries. I mean that you can just use the standard testing IO with WebSockets library. That's what I was using when I was experimenting in production. I used tornado always for WebSockets because it has other things that the web browser... The web server needs templates, sessions and so on. But if you just need pure WebSockets, then the WebSockets library is okay. I never seen any CPU or memory issues with it, to be honest. It is... Probably just a fluke with the library. It could be that the libraries are outdated, but at the end, if you're using WebSockets with a sync IO, something that would cause you a lot in your resources would be a sync IO and it's optimized quite well now. So I don't think that you will encounter any problem with that. Okay. Thanks. Great. You're welcome. Hi. On your last slide, you had a link to WebSockets versus REST APIs. Can you just talk a little bit about the difference? Yes, sure. REST APIs are using the HTTP. So with that, you automatically, when you're writing the REST clients, you can first of all rely on the status codes, as I said, that if you do any request and it returns you any number in the status that begins with four, you can understand that you are doing something wrong with your requests. It would be probably parameters. You forgot something in your parameters. Or you didn't authenticate. That's another status code, actually, but REST APIs are not always implemented. So like just having default responses can already give you some hints on what you're doing wrong and what you're doing right. Like if you get response to 100, status means everything is perfect. On WebSockets, you don't have it. So let's say you receive a WebSocket message, okay. That's probably good. But the fact is that you do not even need to receive any message, because when you send the WebSockets message to the backend, there is no response by default. Only if the server wants to send you something back, you will know if it even worked or not. Like here, you just send cats to me and the only way you know that it's there is by seeing it on the page, meaning that the whole loop worked. In the RESTful APIs, you would see in the shell over there on the right, red signs. You know, when something on the HTTP goes wrong, the debugger tells you automatically like on this line, in this file, or in this connection, something is bad. In WebSockets, you don't have it. You have to debug everything by yourself. You have to understand everything by yourself. The browser knows nothing about your communication. And that's the first and I think the biggest difference and difficulties in building APIs in using WebSockets and using RESTful approach. And secondly, it's just hard to compare it, because there's two totally different partings. I'm just comparing now from the technical side, like exactly the protocol and implementation. But in general, like you can also think on the WebSockets, when you reconnected to this page, like did the page reload, what I have to do here is send one message for each object over there. In the RESTful API, you would usually send them all at once, like one big thing. And the big difference is that you could cache it with RESTful APIs. Here you can't. So it's kind of different approaches. The WebSockets are really tailored to be one by one communication. So I'm sending everything to everyone. I cannot cache it. That's another thing. Yeah, I mean, we can discuss it for a really long time, because RESTful and WebSockets are two different sites at this point. But I think that these are two main differences as a technical protocol perspective. Welcome. One more question. Does WebSocket can be based on the path of the server? It is. Yes. So WebSocket, this is the path that I'm using right now. WS is normal WebSockets. WSS is the secure WebSockets, kind of the HTTPS for WebSockets. And yes, you can have different paths here. And this would map in your application server to different handlers or endpoints that will be handling that. What's the behavior of WebSockets when the client is roaming in a mobile, for example, and changing from one, losing the connection for some reason, but reconnecting later? You don't have it. So again, in HTTP requests, if you didn't get requests and something is wrong with connection, this get is always adempotent, the browser knows that it can just repeat it, can do it again and again until it gets something. In WebSockets, you don't. And there is no success message or failure message. So you sent it, network, gone, you don't know if it was delivered or not. It's TCP, so on the protocol level, you do know. But you do not know if it was complete, if it was successful and so on. So this is, yeah, it's a big difference. You don't have this. You have to take care of it by yourself. So the usual thing is that in the client, the connection is lost, then the client connects again. Yes, yes. But you have to implement it. You have to implement it yourself. Okay, one more. Hi. So can you do query parameters for WebSockets request? No, you can't. Let's take a quick one. Yeah. What's the point of using async if you don't get a response at all? So where's the async actually mattering? Well, thank you. Yeah, but thanks. Yeah, well, there are many cases of using WebSockets. Like, first of all, if you ask me about the async, I'd first of all think on the back end. On the back end side, it's fully async. You do not have an overhead of keeping the connection open as you have in WebSockets, in long polling. The protocol is tailored that way that you have minimal overhead of multiple connections. So with just minimal AWS machine, you can easily solve the 10K connection problem. It costs back and nothing. And you can use a single-threaded server like Tornado or Node.js to handle them, and you can use a fancy async await syntax on Python. So even if you are not using it in a request response fashion, which you actually shouldn't do because it's totally up to you, you can just receive messages. You do not have to send something back. If it's, let's say, a tracking application, you just want to get the positions of your items somewhere. You're just collecting them. You're not sending anything back. But the way you're collecting them are asynchronous in a sense that the connection is open. You do not do anything with that connection. The data comes through, and only when it comes through, your code is triggered just as you have on the HTTP connections as well if you're using the async or your similar thing, but without the HTTP overhead. Okay. I think it's time to off. Good. Okay. Coffee break. Okay. You're talking about it. Yeah. Ask me on the coffee break if you have more questions. Thank you very much for that. Okay. Thank you. Okay. I'll get it. Okay. I'll get it. Okay. Okay. Okay. Okay. Okay. Okay. Okay. Okay. Okay. Okay. Okay. Okay. Okay. Okay. Okay. Okay. Okay. Okay.
 Thank you very much for coming.  It's a pleasure to see all those new and familiar faces here in this room.  Have you enjoyed the conference so far?  Yeah, I did too.  Good.  All right, I have a first question to you.  You've probably heard of a saying, like, if all you have is a hammer, everything looks  like a nail.  You certainly did.  Well, it's not just a saying.  It's actually something formulated as the law of the instrument by the American philosopher  Abraham Kaplan.  But not the Kaplan, most of the jungle author has nothing to do with it, a different thing.  Yeah.  Well, anyway, what does it have to do with today's talk?  The thing is that I believe that this saying perfectly illustrates the way we work with  the web today and the way we are using HTTP.  We're a bit childish by using HTTP because it's our hammer.  Whatever task we have, HTTP, do it.  Do it, please.  But there is more than HTTP on the web.  So HTTP was not designed to do the tasks that we often forced it to do.  The protocol itself, the HTTP, is just centered around the request and response.  That's it.  There is no progress reporting.  There is no multiple requests or responses per request, anything like that.  If you need something, you can build an abstraction of top, of course.  But at the end, even if you build like the pseudo-acing communication with HTTP, it still  does not change the way the underlying HTTP protocol works.  It's still synchronous and it's still centered around the request and response cycle.  So again, the idea is that simple.  In the good old times, there was just a request to the back end.  We fetched the page.  We displayed it.  That's it.  Easy.  Well, then the web evolved, of course.  It became smarter so that we needed the application server.  It also became more beautiful, so we needed the CSS, images, fonts, files.  And then the picture changed more or less like this.  So we still have our browser, the client.  It makes requests to the web server to get stuff, more stuff now, and even more stuff,  but it still fits in the paradigm.  Still fits into the way the HTTP is supposed to work.  Just more requests, but that's okay.  The first time I noticed, I remember that something goes wrong here, is when I had to  deal with icons.  Do you remember a good old times of the icon sprites?  Like this, it was an obvious problem.  Like, hmm, we have too many icons.  That means too many requests.  HTTP is not handling that well.  What can we do?  Oh, let's pack all of the icons into one image and just offset it to the left and to the  right.  Genius.  Well, this is something that I mean by the web abuse and using the HTTP, not in the way  it is designed to be used.  It has nothing to do with web sockets, but the story is going.  Then, async requests.  So the problem was fixed by introducing Ajax when we thought, okay, we need some pre-order  call updates of our content without reloading the whole page.  And then we got Ajax.  But at the end, it's good.  However, Ajax does not change anything for the backend.  Ajax is a pure client-side technology.  For the backend, it looks totally the same way, and it's again the very same HTTP.  Moreover, if you take a look on how exactly the paradigm, how exactly Ajax update works,  like, we wanted to get something like a dialogue between the client and the server.  What's a dialogue?  Well, dialogue is like when two parties exchange their opinions, they're equally involved,  they are respecting each other, this is a dialogue.  But how our dialogue between the browser and the backend look?  It's more like this.  Because at the end, we are dictators.  The client is a dictator.  The servers are not allowed to speak, only when we ask.  So it is kind of a funny way of doing dialogue, in my opinion.  And so although Ajax solves this problem partially, meaning that we can just periodically ask the  backend about the updates, and it's a pseudo-dialogue, it's still ugly.  So yeah, as I already explained, we have a couple of problems.  We have first problem is that the dialogue is not really a dialogue, it's a pseudo-dialogue.  And the second is that you still need a lot of requests, and the more frequent updates  you want, the more requests you do.  And this obviously does not scale well.  Moreover, you are still using HTTP on the background.  HTTP has quite a high noise-to-the-signal ratio, meaning that you send all of the headers,  all of the cookies, everything, just to get a response from the backend, nothing new happened.  That's not cool.  And then we come to the long-falling.  Well, this is already something.  This is already some progress, let's say.  We open the connection, we keep the connection alive.  Connection is just idling, idling, idling.  And whenever something new happens on the backend, we push it through.  Well, that's great.  HTTP overhead is still in place, but at least it's not periodical as Ajax.  It's waiting for stuff, idling.  And in my opinion, well, it's of course good because you get things asynchronously, and  it's even better because long-falling is kind of romantic.  Because if you think of it, it's like looking in someone's eyes when no words are needed.  You just wait and wait and wait.  And then here she replies something to you.  The problem is I don't think that my level of relationship is already there on the web.  I don't want it.  Enough.  Please.  Every time you use long-falling, a guy from Boston Dynamics kicks the robot.  Now finally, enough of this nonsense.  There is just one way to do the real-time bidirectional communication between the client and the server,  and that is WebSockets.  Nothing else.  Forget Socket.io.  Forget this library that's built on top of usually long-falling and in the worst case  Ajax.  They are outdated.  There is totally no reason not to use WebSockets these days.  As the developers, we can finally benefit from WebSockets because we have no longer  a problem of the HTTP overhead.  We have no longer a problem of the real, full-duplex connections, meaning both parties can start  talking to each other as a normal dialogue.  And finally, the interface is really easy from the client as well as from the server.  I'll show you in a moment a couple of code pieces and as well as a demo.  But first, WebSockets in a glance.  So another cool advantage is that WebSockets, they are not HTTP, but they are compatible  with HTTP, meaning that they run on ports used by HTTP, 80 and 443.  So all of the firewalls will work.  All of the proxies will probably just let it through.  So basically, you do not need any new setup in your infrastructure.  You can just use NGINX or whatever reverse proxy you use.  It should just work.  Then how exactly we jump from the HTTP to WebSockets protocol?  We start normally, as we always did with HTTP.  We do the handshake.  We establish a connection.  And then our client says to the server that, hey, I want to upgrade to the WebSockets protocol.  And if the server supports it, it says, yeah, do it.  Then they establish this connection, the full-duplex TCP connection.  They do not communicate over that connection with HTTP.  Now it's pure WebSocket connection, meaning that HTTP headers do not go through it.  And this is cool because we're not sending HTTP overhead anymore to the client every time.  All right.  Let's go for the use cases.  So obviously, it's stupid to say just use WebSockets for everything because there are  good sites and bad sites.  So the good cases for WebSockets are any cases when you need to implement kind of a multiplayer  behavior.  It's obviously games.  It's also tracking system.  When you have multiple objects, let's say, moving on the map, and you need to update  their locations in the real time, this is typical scenario for WebSockets.  Also the collaborative editing, Google Docs.  When you have, again, multiple cursors writing in text at the same time, that's exactly the  case.  However, as I said, there are also bad cases to implement WebSockets.  First of all, because you are with losing HTTP overhead, you're also losing the benefits  of HTTP.  And that is caching.  That are the standard HTTP responses like 400, 400, 500, and so on.  Most of our JavaScript clients these days see automatically react to the problems, to  the error codes of HTTP.  Like if you return 500, your web framework will know that it's a server problem, 400,  it's a client problem.  Well, on WebSockets, we don't have an overhead, meaning we don't have status codes.  All that you get is just a message, a pure message.  And then it's up to you to handle it.  So if you want to say there was an error, you cannot send a status code because there  are no status codes.  You have to somehow encode it yourself.  And then on the client, parse it yourself and say, oh, this was an error.  So remember this.  Then in HTTP, we know that GET requests are identipotent, meaning that you can repeat  it as much as you want.  In WebSockets, you cannot say it.  Again, it's just sending a message.  There are no GETs and posting WebSockets.  It's just sending a message.  So it's up to you to decide if you want to build it in idempotent fashion on the back  end or not.  And the client also has nothing to do with it.  Now let's see a little example.  As I mentioned already, WebSockets are really, really easy.  So to prove it, here is the vanilla JS example of what do you need to do to establish the  connection with the back end?  Can you see it?  I can.  You can?  Oh, that's great.  That's already something.  I don't have the mouse here, but all right.  I'll just comment it like this.  So as you see, we're just not using any sort of libraries.  It's a pure JavaScript.  You open the debugger in Chrome, and that's what you need to type.  So you are creating first the WebSocket connection to that host, in this case, local host.  And then you define three callbacks.  What to do on open when the connection is established?  What to do when you get the message?  In this case, I'm just depending it to document body.  And what to do on error?  Obviously, nothing.  No care about that.  These are 10 lines that you need to write to use WebSockets.  So it's really, really easy.  Now it would be, of course, boring if I wouldn't come up with a demo.  I thought, like, I want to get some feedback about this talk.  And the best feedback for a speaker is obviously throwing tomatoes on him.  So I really like this shirt, so please don't do it right now.  But you can do it on the Web.  So if you just go to, oh, this is the wrong URL, you go to, I'll just do it with you.  Go to demo.caseras.me.  You don't have to do it.  But if you have a laptop anyway, you can just do it.  Then you learn how to use WebSockets.  I hear I have a little code snippet.  I'll just zoom in for you.  Okay, didn't make it much better.  Anyway, you'll get the idea.  So if you copy this into the debugger, yeah, I saw that someone already did it.  You are throwing tomatoes on the speaker.  Well, you might think that this guy is actually not so bad.  Like I do not want to throw tomato on him.  I better throw a candy.  You can do that too.  Then instead of tomato over there, you write candy.  Oh, somebody throw an egg on me.  That's bad.  All right.  Well, anyway, have fun.  Have fun with it.  What this demo shows is that when all of you are throwing tomatoes on me, I like tomatoes.  That's fine.  When you're doing it, one person throws it and everyone sees it everywhere.  Because on the background, what you do is you send, oh, my God.  Could you please send some candies too or cats?  You can send cats, by the way.  Oh, my God.  Yeah, so you see it's updated in the real time.  One person sends, everyone receives it.  And if you reload, oh, this is sweet.  If you reload the page, then you get it all at once.  The WebSocket is pushing through everything that it received by now.  And this is just that three lines, four lines over there.  And you can see the source code.  There are no libraries involved at all.  It's just one HTML page with JavaScript inlined.  Super easy, super cool.  I want to say to you that there is no barrier in not using WebSocket these days.  Now back to the presentation.  Yeah, you can have fun meanwhile.  Let's see at the end.  Let's see at the end what's there.  I'm really interested now.  Okay.  Now we still have time to see how much time do we have?  15.  Great.  So now we obviously want to see, it's a Python conference.  How can you use it from the back end side?  So we have several frameworks that can do it.  And the easiest one is Tornado.  Well, in my opinion.  Tornado is kind of a flask, but the async way.  You all know that flask is super minimalistic, but Tornado is also minimalistic.  You can have just one py file and this is actually all that you need, this 26 lines  to run the web server with WebSockets.  And you have here already the handler implemented.  This is literally all you need to do.  PIP install Tornado, that you have it running.  Now let's just have a few comments on what exactly does it do.  It's very, very much similar to what we do on the client side.  We define three callbacks on open, on message, and on close.  What should we do?  And when you receive a message, obviously, or the on message callback is triggered, and  in this example, I'm just sending it back.  I do sell that right message hello, and this just sends back hello and whatever you received  at the beginning.  That easy.  Of course, it's not so easy if we want to throw tomatoes on the speaker.  It's a bit more difficult because you want some place to store all of the thrown objects.  And then you want to send it not just to one client, you want to send it to all clients,  meaning the broadcasting.  But for this, you can just use plain Python tools.  Just append everything to the list, and then when the new client connects, it writes on  that list and sends it back.  So there is no magic.  It's just normal Python data structures in this case.  I implemented it super simple on purpose.  You do not need any kind of readme file for this.  Super easy.  But if you want more complex setup because it's still minimalistic, you can take a look  on Django.  Django in channels is very different because Django in the first place is a synchronous  web framework.  So you cannot run web sockets on Django out of the box because it just does not work by  design.  What they did, it was a good idea.  They created a synchronous server next to the synchronous server.  So actually when you send the web socket data in Django, you're not sending it through the  main web server.  You're sending it on kind of alternative one, which is a synchronous which supports the  thing.  And then they integrate.  So please understand that Django channels has little to do with Django.  They tried to write it in the similar way and they succeeded.  But it's still two different libraries working in totally different ways.  They are somehow integrated and the tool set is really cool and really huge.  It's nothing compared to Tornado because it has all of the broadcasting features, the  sessions, the users.  Tornado doesn't have it.  You have to implement it yourself.  Like in Django channels, it's relatively easy to say to map the user to the web socket  connection if you want.  If you want to split your users by group, like only this group will receive this message.  In Django channels, it's easy.  In Tornado, you have to do it yourself, even though it's also easy, but you have to do  it.  Okay.  Now, let's go to the next one, the bonus one, the web socket package called web sockets.  This is how it looks.  This is really the most minimalistic thing I have found.  So you have just one function, one handler that is handling your web socket connections.  And here you can use the asyncio library with asyncio wait syntax if you like.  So I think it's quite self-explanatory.  I tried to build up this example very minimalistic again.  You do the await web socket receive, meaning that you idle in that moment until the data  arrives.  When it arrives, it's written to the name variable.  Then you formulate the string that you want to send back.  And then you again do the await web socket.send, sending it synchronously.  And that's it.  And that's three lines on the bottom are just running the asyncio IOLOOP, quite standard  stuff, I would say.  And you can integrate those because Tornado runs on, I think, not out of the box, but  it supports asyncio IOLOOP.  So you can run both.  I don't know why would you need this, but people ask these questions if I can integrate  this, you can.  All right.  So this looks already like some dialogue to me.  If you use web sockets, I noticed your reaction to the guy kicking the robots from Boston Dynamics.  I see that you're also concerned about this topic, so I think at the end of the presentation  it's important to show you that there is a petition.  Stop robot abuse.  And the best thing are the comments there.  So feel free to sign it.  I think it's outdated, but it's hilarious.  I had to edit there.  Now the commercial time, sales time, I run a Python agency.  If you have projects or you're interested in doing a project with me, please contact  me.  Let's see how many tomatoes are on me by now.  Lots of cats.  Well, this is nice.  It's like a paradise.  Cat paradise.  The funny thing is that I was really in a rush for this demo, so I have no way to flush  it now.  I have to redeploy the whole thing, but I think I will not because it's really cute,  isn't it?  Okay.  Yeah.  Then last thing, the credits.  There are really cool articles online about web sockets and about using them with Python,  also the videos.  And that's it.  Thank you very much.  Okay, anyone have a question?  Please stand up and come here.  You send that over the HTTP, the Web sockets.  You start with it.  Yes.  Can we have all the, it's a get, I think.  It's a get request at first.  And can we have all the parameters like a get parameters just to know what to send or  what to do with this Web socket?  Thanks for the question.  So yes, you start with HTTP because you never know if the back end supports the Web  socket.  You start with, you are connecting as if you are connecting HTTP, but you only do it not  to send the data.  You only do it to ask if we can upgrade to the Web socket connection.  And if you can, there are no get requests.  There are no parameters, nothing.  So if the server and the client both support Web sockets protocol, as soon as the connection  will be established, you totally forget about HTTP.  So no, you cannot get parameters and you do not have statuses and you do not have request  types like getPostput that doesn't exist.  HTTP is really used just as a transport at the first phase to establish the connection  and that's it.  And it is done that way in order to be compatible with firewalls, reverse proxies and so on.  So the protocols are very different.  They have not so much similar because the Web socket was developed in order to maximally  decrease the overhead of sending messages back and forth.  Basically in Web sockets, everything you send, almost everything is a message.  There are a few frames at the beginning of the data frame indicating the order so that  the back end can then combine this Web socket message pieces into one big message and that's  it.  That's all the metadata that is sent.  So no parameters.  Thanks.  More questions?  How practical Web sockets for Python like other libraries and frameworks there so they  could scale big projects, I guess?  So how practical are Web sockets in Python?  Well, would you have a like a big project like this scale of...  Of course. ...of course. ...of there so they can have...  Well, first of all, most of the things that, as I mentioned in the use cases of collaborative  editing, multiplayer games and tracking systems are already on Web sockets like Google Docs.  So it's quite a big project, I would say.  It's not in Python, unfortunately, but it's basically the only way that you can do the  reliable full-duplex communication these days between the client and the server.  So it's not like you have much of a choice.  You can use Ajax, yes, but if you have imagined like this room right now sending these cats  with Ajax, I mean my poor server would just die from that.  And since you use in context of Python, I can mention again that it's a special well-playing  in Python because you have async await syntax.  So it's really cool you don't have to do the callback spaghetti as you have in JavaScript.  You can use async.io with async await syntax and it even looks nice and you do not see  much of a difference with a normal request handler like you have in Flask or Tornado  or Django.  You just put await and it's perfect.  Thank you.  Any more questions?  Yes.  Oh, that's a, oh, okay.  We have the whole queue.  Yes.  And does this work?  Yes.  So I am using Web sockets for a trading application and it's, the bandwidth it's using is about,  I don't know, maybe one or two megabits per second, not megabytes.  And I find that the library I use is consuming up to like 25% CPU just to pass messages.  What library do you use?  I think it's WebSocket-clients and it's something built on top of it.  But do you find that is normal or is it way too much over?  I have to be honest, I never used the WebSockets library because that's all you need to write  the WebSockets connection.  This is ten lines or something.  So I know that in the old days when WebSockets have been just introduced, you had a lot of  browsers introduced in Internet Explorer that did not support it.  Then you had to use something like socket IO that would fall back to long polling and  if that doesn't work, it would fall back to Ajax requests.  Yes.  But these days are gone.  That's it.  All of the browsers, including IE, I think since even...  I have to say it's a common line client in Britain and Python has got nothing to do with  the JavaScript world.  Python clients.  Okay.  Which name again?  It's WebSocket-clients.  I wonder if you could be able to advise efficient CPU-ized WebSocket libraries.  I mean that you can just use the standard testing IO with WebSockets library.  That's what I was using when I was experimenting in production.  I used tornado always for WebSockets because it has other things that the web browser...  The web server needs templates, sessions and so on.  But if you just need pure WebSockets, then the WebSockets library is okay.  I never seen any CPU or memory issues with it, to be honest.  It is...  Probably just a fluke with the library.  It could be that the libraries are outdated, but at the end, if you're using WebSockets  with a sync IO, something that would cause you a lot in your resources would be a sync  IO and it's optimized quite well now.  So I don't think that you will encounter any problem with that.  Okay.  Thanks.  Great.  You're welcome.  Hi.  On your last slide, you had a link to WebSockets versus REST APIs.  Can you just talk a little bit about the difference?  Yes, sure.  REST APIs are using the HTTP.  So with that, you automatically, when you're writing the REST clients, you can first of  all rely on the status codes, as I said, that if you do any request and it returns you any  number in the status that begins with four, you can understand that you are doing something  wrong with your requests.  It would be probably parameters.  You forgot something in your parameters.  Or you didn't authenticate.  That's another status code, actually, but REST APIs are not always implemented.  So like just having default responses can already give you some hints on what you're  doing wrong and what you're doing right.  Like if you get response to 100, status means everything is perfect.  On WebSockets, you don't have it.  So let's say you receive a WebSocket message, okay.  That's probably good.  But the fact is that you do not even need to receive any message, because when you send  the WebSockets message to the backend, there is no response by default.  Only if the server wants to send you something back, you will know if it even worked or not.  Like here, you just send cats to me and the only way you know that it's there is by seeing  it on the page, meaning that the whole loop worked.  In the RESTful APIs, you would see in the shell over there on the right, red signs.  You know, when something on the HTTP goes wrong, the debugger tells you automatically  like on this line, in this file, or in this connection, something is bad.  In WebSockets, you don't have it.  You have to debug everything by yourself.  You have to understand everything by yourself.  The browser knows nothing about your communication.  And that's the first and I think the biggest difference and difficulties in building APIs  in using WebSockets and using RESTful approach.  And secondly, it's just hard to compare it, because there's two totally different partings.  I'm just comparing now from the technical side, like exactly the protocol and implementation.  But in general, like you can also think on the WebSockets, when you reconnected to this  page, like did the page reload, what I have to do here is send one message for each object  over there.  In the RESTful API, you would usually send them all at once, like one big thing.  And the big difference is that you could cache it with RESTful APIs.  Here you can't.  So it's kind of different approaches.  The WebSockets are really tailored to be one by one communication.  So I'm sending everything to everyone.  I cannot cache it.  That's another thing.  Yeah, I mean, we can discuss it for a really long time, because RESTful and WebSockets are  two different sites at this point.  But I think that these are two main differences as a technical protocol perspective.  Welcome.  One more question.  Does WebSocket can be based on the path of the server?  It is.  Yes.  So WebSocket, this is the path that I'm using right now.  WS is normal WebSockets.  WSS is the secure WebSockets, kind of the HTTPS for WebSockets.  And yes, you can have different paths here.  And this would map in your application server to different handlers or endpoints that will  be handling that.  What's the behavior of WebSockets when the client is roaming in a mobile, for example,  and changing from one, losing the connection for some reason, but reconnecting later?  You don't have it.  So again, in HTTP requests, if you didn't get requests and something is wrong with connection,  this get is always adempotent, the browser knows that it can just repeat it, can do it  again and again until it gets something.  In WebSockets, you don't.  And there is no success message or failure message.  So you sent it, network, gone, you don't know if it was delivered or not.  It's TCP, so on the protocol level, you do know.  But you do not know if it was complete, if it was successful and so on.  So this is, yeah, it's a big difference.  You don't have this.  You have to take care of it by yourself.  So the usual thing is that in the client, the connection is lost, then the client connects  again.  Yes, yes.  But you have to implement it.  You have to implement it yourself.  Okay, one more.  Hi.  So can you do query parameters for WebSockets request?  No, you can't.  Let's take a quick one.  Yeah.  What's the point of using async if you don't get a response at all?  So where's the async actually mattering?  Well, thank you.  Yeah, but thanks.  Yeah, well, there are many cases of using WebSockets.  Like, first of all, if you ask me about the async, I'd first of all think on the back  end.  On the back end side, it's fully async.  You do not have an overhead of keeping the connection open as you have in WebSockets,  in long polling.  The protocol is tailored that way that you have minimal overhead of multiple connections.  So with just minimal AWS machine, you can easily solve the 10K connection problem.  It costs back and nothing.  And you can use a single-threaded server like Tornado or Node.js to handle them, and you  can use a fancy async await syntax on Python.  So even if you are not using it in a request response fashion, which you actually shouldn't  do because it's totally up to you, you can just receive messages.  You do not have to send something back.  If it's, let's say, a tracking application, you just want to get the positions of your  items somewhere.  You're just collecting them.  You're not sending anything back.  But the way you're collecting them are asynchronous in a sense that the connection is open.  You do not do anything with that connection.  The data comes through, and only when it comes through, your code is triggered just as you  have on the HTTP connections as well if you're using the async or your similar thing, but  without the HTTP overhead.  Okay.  I think it's time to off.  Good.  Okay.  Coffee break.  Okay.  You're talking about it.  Yeah.  Ask me on the coffee break if you have more questions.  Thank you very much for that.  Okay.  Thank you.  Okay.  I'll get it.  Okay.  I'll get it.  Okay.  Okay.  Okay.  Okay.  Okay.  Okay.  Okay.  Okay.  Okay.  Okay.  Okay.  Okay.  Okay.  Okay.  Okay.  Okay.  Okay.  Okay.
Anton_Caceres_-_Python_and_Web_Sockets_6oQuQ5coSQ0_0_647.png Anton_Caceres_-_Python_and_Web_Sockets_6oQuQ5coSQ0_648_1199.png Anton_Caceres_-_Python_and_Web_Sockets_6oQuQ5coSQ0_1200_1451.png Anton_Caceres_-_Python_and_Web_Sockets_6oQuQ5coSQ0_1452_2267.png Anton_Caceres_-_Python_and_Web_Sockets_6oQuQ5coSQ0_2268_4091.png Anton_Caceres_-_Python_and_Web_Sockets_6oQuQ5coSQ0_4092_4379.png Anton_Caceres_-_Python_and_Web_Sockets_6oQuQ5coSQ0_4380_4979.png Anton_Caceres_-_Python_and_Web_Sockets_6oQuQ5coSQ0_4980_5315.png Anton_Caceres_-_Python_and_Web_Sockets_6oQuQ5coSQ0_5316_5975.png Anton_Caceres_-_Python_and_Web_Sockets_6oQuQ5coSQ0_5976_7763.png Anton_Caceres_-_Python_and_Web_Sockets_6oQuQ5coSQ0_7764_8255.png Anton_Caceres_-_Python_and_Web_Sockets_6oQuQ5coSQ0_8256_8351.png Anton_Caceres_-_Python_and_Web_Sockets_6oQuQ5coSQ0_8352_8411.png Anton_Caceres_-_Python_and_Web_Sockets_6oQuQ5coSQ0_8412_11423.png Anton_Caceres_-_Python_and_Web_Sockets_6oQuQ5coSQ0_11424_11651.png Anton_Caceres_-_Python_and_Web_Sockets_6oQuQ5coSQ0_11652_12275.png Anton_Caceres_-_Python_and_Web_Sockets_6oQuQ5coSQ0_12276_14171.png Anton_Caceres_-_Python_and_Web_Sockets_6oQuQ5coSQ0_14172_14351.png Anton_Caceres_-_Python_and_Web_Sockets_6oQuQ5coSQ0_14352_15623.png Anton_Caceres_-_Python_and_Web_Sockets_6oQuQ5coSQ0_15624_16199.png Anton_Caceres_-_Python_and_Web_Sockets_6oQuQ5coSQ0_16200_16319.png Anton_Caceres_-_Python_and_Web_Sockets_6oQuQ5coSQ0_16320_16367.png Anton_Caceres_-_Python_and_Web_Sockets_6oQuQ5coSQ0_16368_16499.png Anton_Caceres_-_Python_and_Web_Sockets_6oQuQ5coSQ0_16500_16631.png Anton_Caceres_-_Python_and_Web_Sockets_6oQuQ5coSQ0_16632_17459.png Anton_Caceres_-_Python_and_Web_Sockets_6oQuQ5coSQ0_17460_19079.png Anton_Caceres_-_Python_and_Web_Sockets_6oQuQ5coSQ0_19080_20027.png Anton_Caceres_-_Python_and_Web_Sockets_6oQuQ5coSQ0_20028_20075.png Anton_Caceres_-_Python_and_Web_Sockets_6oQuQ5coSQ0_20076_20435.png Anton_Caceres_-_Python_and_Web_Sockets_6oQuQ5coSQ0_20436_20603.png Anton_Caceres_-_Python_and_Web_Sockets_6oQuQ5coSQ0_20604_22007.png Anton_Caceres_-_Python_and_Web_Sockets_6oQuQ5coSQ0_22008_22919.png Anton_Caceres_-_Python_and_Web_Sockets_6oQuQ5coSQ0_22920_23807.png Anton_Caceres_-_Python_and_Web_Sockets_6oQuQ5coSQ0_23808_24827.png Anton_Caceres_-_Python_and_Web_Sockets_6oQuQ5coSQ0_24828_24971.png Anton_Caceres_-_Python_and_Web_Sockets_6oQuQ5coSQ0_24972_26471.png Anton_Caceres_-_Python_and_Web_Sockets_6oQuQ5coSQ0_26472_26843.png Anton_Caceres_-_Python_and_Web_Sockets_6oQuQ5coSQ0_26844_27035.png Anton_Caceres_-_Python_and_Web_Sockets_6oQuQ5coSQ0_27036_27263.png Anton_Caceres_-_Python_and_Web_Sockets_6oQuQ5coSQ0_27264_27491.png Anton_Caceres_-_Python_and_Web_Sockets_6oQuQ5coSQ0_27492_27551.png Anton_Caceres_-_Python_and_Web_Sockets_6oQuQ5coSQ0_27552_28127.png Anton_Caceres_-_Python_and_Web_Sockets_6oQuQ5coSQ0_28128_28331.png Anton_Caceres_-_Python_and_Web_Sockets_6oQuQ5coSQ0_28332_28799.png Anton_Caceres_-_Python_and_Web_Sockets_6oQuQ5coSQ0_28800_37679.png Anton_Caceres_-_Python_and_Web_Sockets_6oQuQ5coSQ0_37680_37931.png Anton_Caceres_-_Python_and_Web_Sockets_6oQuQ5coSQ0_37932_41027.png Anton_Caceres_-_Python_and_Web_Sockets_6oQuQ5coSQ0_41028_47867.png Anton_Caceres_-_Python_and_Web_Sockets_6oQuQ5coSQ0_47868_47939.png Anton_Caceres_-_Python_and_Web_Sockets_6oQuQ5coSQ0_47940_48052.png