{"id":3810,"date":"2023-03-14T14:24:23","date_gmt":"2023-03-14T13:24:23","guid":{"rendered":"https:\/\/blog.alwaysdata.com\/?p=3810"},"modified":"2023-03-14T14:24:24","modified_gmt":"2023-03-14T13:24:24","slug":"accrochez-vous-a-vos-chaussettes-hebergement-de-donnees-a-grande-vitesse-avec-websockets","status":"publish","type":"post","link":"https:\/\/blog.alwaysdata.com\/fr\/2023\/03\/14\/accrochez-vous-a-vos-chaussettes-hebergement-de-donnees-a-grande-vitesse-avec-websockets\/","title":{"rendered":"Accrochez-vous \u00e0&nbsp;vos chaussettes&nbsp;: h\u00e9bergement de donn\u00e9es \u00e0&nbsp;grande vitesse avec WebSockets"},"content":{"rendered":"\n<p>Cr\u00e9\u00e9 par <a href=\"https:\/\/websockets.spec.whatwg.org\/\">WHATWG<\/a> en 2011 et normalis\u00e9 par l\u2019<a href=\"https:\/\/datatracker.ietf.org\/doc\/html\/rfc6455\">IETF<\/a> la m\u00eame ann\u00e9e, le protocole <a href=\"https:\/\/fr.wikipedia.org\/wiki\/WebSocket\">WebSocket<\/a> offre depuis plus de 10 ans la possibilit\u00e9 aux d\u00e9veloppeurs d\u2019applications Web de s\u2019affranchir du mod\u00e8le Client\/Server TCP traditionnel pour le transfert de donn\u00e9es en temps&nbsp;r\u00e9el.<\/p>\n\n\n\n<p>Dans ce mod\u00e8le initial, le client requ\u00eate aupr\u00e8s du serveur une ressource, ce dernier la lui retourne, fin de la communication. Il n\u2019est notamment pas possible pour le serveur de \u00ab&nbsp;pousser&nbsp;\u00bb facilement des messages au client (comme des notifications).<\/p>\n\n\n\n<p>WebSocket apporte le support d\u2019une communication bidirectionnelle (dite <em>full-duplex<\/em>) dans laquelle le serveur peut d\u00e9sormais spontan\u00e9ment \u00ab&nbsp;pousser&nbsp;\u00bb de la donn\u00e9e vers le client&nbsp;; et o\u00f9 le client peut souscrire aux messages qui l\u2019int\u00e9ressent pour y&nbsp;r\u00e9agir ensuite. L\u2019interactivit\u00e9 applicative, directement dans le  navigateur&nbsp;!<\/p>\n\n\n\n<p>En 2023, h\u00e9berger une application serveur WebSocket, comment \u00e7a marche&nbsp;?<\/p>\n\n\n<figure class=\"embed-media__giphy\" style=\"width:65%; padding-bottom:calc(65% * (360 + 12) \/ (480 + 12))\">\n    <video id=\"giphy-${token}\" autoplay loop muted playsinline>\n        <source src=\"https:\/\/media.giphy.com\/media\/3orifaveUBlsnIAdPO\/giphy.mp4\" type=\"video\/mp4\">\n        <img decoding=\"async\" src=\"https:\/\/media.giphy.com\/media\/3orifaveUBlsnIAdPO\/giphy.gif\" alt=\"speaking bart simpson GIF @Giphy\">\n    <\/video>\n<\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Attention ch\u00e9rie, \u00e7a va trancher<\/h2>\n\n\n\n<p>Revenons aux fondamentaux&nbsp;: WebSocket est un protocole r\u00e9seau dans la couche application (dans le fameux <a href=\"https:\/\/fr.wikipedia.org\/wiki\/Mod%C3%A8le_OSI\">mod\u00e8le OSI<\/a>). Pas besoin d\u2019\u00eatre expert en r\u00e9seau pour l\u2019utiliser, donc&nbsp;: c\u2019est au niveau du code que tout va se passer. Il exploite une connexion TCP traditionnelle entre le client et le serveur, et l\u2019architecture HTTP.<\/p>\n\n\n\n<p>Pour faire simple&nbsp;:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>le client va charger aupr\u00e8s du serveur une premi\u00e8re ressource applicative (HTML +&nbsp;JS)&nbsp;;<\/li>\n\n\n\n<li>cette ressource va \u00eatre en charge d\u2019\u00e9tablir une communication aupr\u00e8s du serveur WebSocket pour en recevoir les notifications&nbsp;;<\/li>\n\n\n\n<li>le serveur WebSocket va enregistrer le client dans ses destinataires et lui poussera la data concern\u00e9e quand ce sera n\u00e9cessaire&nbsp;;<\/li>\n\n\n\n<li>le client, jusqu\u2019alors en attente, recevra les flux de data en provenance du serveur et pourra traiter cette donn\u00e9e.<\/li>\n<\/ol>\n\n\n\n<p>On notera que dans WebSocket, il y&nbsp;a <em>Web<\/em>&nbsp;: l\u2019architecture n\u2019est pas diff\u00e9rente des applications Web traditionnelles. On exploite toujours HTTP\/TCP, on utilise juste un autre protocole applicatif. Ce qui signifie que l\u2019on va devoir utiliser des librairies d\u00e9di\u00e9es.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Un service qui fait \u00ab&nbsp;pong&nbsp;\u00bb<\/h2>\n\n\n\n<p>Il existe plein de biblioth\u00e8ques capables de vous proposer le support WebSocket, dans \u00e0&nbsp;peu pr\u00e8s tous les langages Web disponibles. Pour les besoins de la d\u00e9monstration<span class=\"footnote_referrer\"><a role=\"button\" tabindex=\"0\" onclick=\"footnote_moveToReference_3810_1('footnote_plugin_reference_3810_1_1');\" onkeypress=\"footnote_moveToReference_3810_1('footnote_plugin_reference_3810_1_1');\"><sup id=\"footnote_plugin_tooltip_3810_1_1\" class=\"footnote_plugin_tooltip_text\">1)<\/sup><\/a><span id=\"footnote_plugin_tooltip_text_3810_1_1\" class=\"footnote_tooltip\"><\/span><\/span>, nous allons impl\u00e9menter un petit serveur WebSocket en Node.js, puis en Python. Libre \u00e0&nbsp;vous de le r\u00e9aliser en PHP, en Ruby, en Java, en&nbsp;Perl\u2026<\/p>\n\n\n\n<p>Notre exemple est tout simple&nbsp;: une fois connect\u00e9, notre client pourra envoyer le message <code>ping<\/code> au serveur, qui renverra alors <code>pong<\/code> \u00e0&nbsp;tous les clients connect\u00e9s, une seconde plus tard. Ce simple exemple d\u00e9montre l\u2019interactivit\u00e9 entre tous les \u00e9l\u00e9ments connect\u00e9s (c\u2019est \u00e0&nbsp;dire du <em>broadcasting<\/em>) de notre application.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">En Node.js\u2026<\/h3>\n\n\n\n<p>\u00c0 l\u2019heure actuelle la librairie la plus r\u00e9put\u00e9e pour r\u00e9aliser une application serveur WebSocket en Node.js est <a href=\"https:\/\/github.com\/websockets\/ws\">websockets\/ws<\/a>. Ajoutez le paquet <code>ws<\/code> \u00e0&nbsp;votre projet via <code>npm<\/code><span class=\"footnote_referrer\"><a role=\"button\" tabindex=\"0\" onclick=\"footnote_moveToReference_3810_1('footnote_plugin_reference_3810_1_2');\" onkeypress=\"footnote_moveToReference_3810_1('footnote_plugin_reference_3810_1_2');\"><sup id=\"footnote_plugin_tooltip_3810_1_2\" class=\"footnote_plugin_tooltip_text\">2)<\/sup><\/a><span id=\"footnote_plugin_tooltip_text_3810_1_2\" class=\"footnote_tooltip\"><\/span><\/span>, et cr\u00e9ez un fichier <code>wss.js<\/code> pour votre serveur WebSocket&nbsp;:<\/p>\n\n\n\n<pre class=\"wp-block-code lang:js\"><code>import WebSocket, { WebSocketServer } from 'ws';\n\nconst wss = new WebSocketServer({\n  host: process.env.IP || '',\n  port: process.env.PORT || 8080\n});<\/code><\/pre>\n\n\n\n<p>Nous attachons le serveur WebSocket au couple <code>IP<\/code>\/<code>PORT<\/code> expos\u00e9 dans les variables d\u2019environnement pour plus de flexibilit\u00e9, avec un fallback vers le port <code>8080<\/code> pour faciliter le d\u00e9veloppement.<\/p>\n\n\n\n<p>Notre serveur est pr\u00eat, reste \u00e0&nbsp;le doter de ses fonctionnalit\u00e9s. Tout d\u2019abord, il doit pouvoir recevoir les connexions des clients&nbsp;:<\/p>\n\n\n\n<pre class=\"wp-block-code lang:js\"><code>wss.on('connection', (ws) =&gt; {\n  \/\/ log as an error any exception\n  ws.on('error', console.error);\n});<\/code><\/pre>\n\n\n\n<p>Ensuite, lorsqu\u2019un client enverra le message <code>ping<\/code>, il devra renvoyer <code>pong<\/code> \u00e0&nbsp;tous les clients actifs&nbsp;:<\/p>\n\n\n\n<pre class=\"wp-block-code lang:js\"><code>wss.on('connection', (ws) =&gt; {\n  \/* ... *\/\n  ws.on('message', (data) =&gt; {\n    \/\/ only react to `ping` message\n    if (data != 'ping') { return }\n    wss.clients.forEach<span class=\"footnote_referrer\"><a role=\"button\" tabindex=\"0\" onclick=\"footnote_moveToReference_3810_1('footnote_plugin_reference_3810_1_3');\" onkeypress=\"footnote_moveToReference_3810_1('footnote_plugin_reference_3810_1_3');\"><sup id=\"footnote_plugin_tooltip_3810_1_3\" class=\"footnote_plugin_tooltip_text\">3)<\/sup><\/a><span id=\"footnote_plugin_tooltip_text_3810_1_3\" class=\"footnote_tooltip\"><\/span><\/span><\/code><\/pre>\n\n\n\n<p>D\u00e9finissons maintenant nos fonctionnalit\u00e9s&nbsp;: enregistrement des clients et diffusion du message lors d\u2019un <code>ping<\/code>. Enrichissez la m\u00e9thode <code>handler<\/code> qui contient la logique de notre serveur&nbsp;:<\/p>\n\n\n\n<pre class=\"wp-block-code lang:python\"><code>connected = set()\n\n\nasync def handler(websocket):\n    if websocket not in connected:\n        connected.add(websocket)\n    async for message in websocket:\n        if message == 'ping':\n            await asyncio.sleep(1)\n            websockets.broadcast(connected, 'pong')<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Le client Web<\/h3>\n\n\n\n<p>Notre client va rester simple&nbsp;: une page web embarquant directement le JavaScript \u00e0&nbsp;ex\u00e9cuter pour se connecter au serveur WebSocket, et envoyer un <code>ping<\/code> lors de la connexion. Il disposera aussi d\u2019une m\u00e9thode pour renvoyer un nouveau <code>ping<\/code> manuellement.<\/p>\n\n\n\n<p>Cr\u00e9ez un fichier <code>index.html<\/code> pour votre client&nbsp;:<\/p>\n\n\n\n<pre class=\"wp-block-code lang:html\"><code>&lt;!DOCTYPE html&gt;\n&lt;script&gt;\n\tWS_SERVER = 'localhost:8080'\n\tdateFormatter = new Intl.DateTimeFormat('en-US', {\n\t\thour: \"numeric\",\n\t\tminute: \"numeric\",\n\t\tsecond: \"numeric\"\n\t})\n\n\tconst websocket = new WebSocket(`ws:\/\/${WS_SERVER}\/`)\n\n\tconst ping = (msg) =&gt; {\n\t\tmsg = msg || 'ping'\n\t\tconsole.log(\"Send message\", msg)\n\t\twebsocket.send(msg)\n\t}\n\n\twebsocket.addEventListener('message', ({data}) =&gt; {\n\t\tconsole.log(\"Recv message\", data, dateFormatter.format(Date.now()))\n\t})\n\n\twebsocket.addEventListener('open', () =&gt; ping())\n&lt;\/script&gt;\n&lt;p&gt;Open the developer console and run the &lt;code&gt;ping()&lt;\/code&gt; function&lt;\/p&gt;<\/code><\/pre>\n\n\n\n<p>Lancez votre serveur WebSocket (Python ou Node.js) et ouvrez cette page HTML avec les devtools ouverts. Vous devriez voir appara\u00eetre les messages de <code>ping<\/code>\/<code>pong<\/code> dans la console. Essayez en ex\u00e9cutant manuellement la fonction <code>ping()<\/code> dans les devtools.<\/p>\n\n\n\n<p>Ouvrez maintenant une seconde fois cette page HTML dans un autre onglet, devtools ouverts. Ex\u00e9cutez la fonction <code>ping()<\/code> indiff\u00e9remment dans l\u2019un des deux onglets. Les deux vont recevoir le <code>pong<\/code> depuis le serveur.<\/p>\n\n\n\n<p>F\u00e9licitations, vous avez un premier niveau de communication broadcast bi-directionnelle client\/serveur en <em>full-duplex<\/em> via WebSocket&nbsp;!<\/p>\n\n\n<figure class=\"embed-media__giphy\" style=\"width:65%; padding-bottom:calc(65% * (360 + 12) \/ (476 + 12))\">\n    <video id=\"giphy-${token}\" autoplay loop muted playsinline>\n        <source src=\"https:\/\/media.giphy.com\/media\/psv1zrhPZM6WI\/giphy.mp4\" type=\"video\/mp4\">\n        <img decoding=\"async\" src=\"https:\/\/media.giphy.com\/media\/psv1zrhPZM6WI\/giphy.gif\" alt=\"Noot Noot GIF @Giphy\">\n    <\/video>\n<\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Noot Noot&nbsp;: WebSite ou WebService&nbsp;?<\/h2>\n\n\n\n<p>Il nous reste \u00e0&nbsp;d\u00e9ployer ce serveur WebSocket et le client dans un environnement de production.<\/p>\n\n\n\n<p>Chez <strong>alwaysdata<\/strong> nous proposons plusieurs solutions pour d\u00e9ployer des outils devant \u00eatre ex\u00e9cut\u00e9s en temps long, c\u2019est \u00e0&nbsp;dire de mani\u00e8re joignable dans le futur par un client&nbsp;: les <em>Sites<\/em>, et les <em>Services<\/em>.<\/p>\n\n\n\n<p>Pour les <em>Sites<\/em>, pas besoin de faire un dessin&nbsp;: il s\u2019agit de d\u00e9ployer un serveur Web (Apache, WSGI, Node, etc.) qui sera requ\u00eat\u00e9 ult\u00e9rieurement par un client pour obtenir diff\u00e9rentes ressources via HTTP. Le Web historique traditionnel.<\/p>\n\n\n\n<p>Les <em>Services<\/em>, eux, sont destin\u00e9s \u00e0&nbsp;ex\u00e9cuter des processus longs, potentiellement joignables autrement que par HTTP, dans l\u2019environnement de votre compte (un serveur de d\u00e9p\u00f4ts Git over SSH avec <a href=\"https:\/\/github.com\/charmbracelet\/soft-serve\">soft-serve<\/a> ou un syst\u00e8me de surveillance \/ filtrage de messagerie, par exemple).<\/p>\n\n\n\n<p>Alors, pour un serveur WebSocket, <em>Sites<\/em>, ou <em>Services<\/em>&nbsp;?<\/p>\n\n\n\n<p>Si on pourrait imaginer que les services sont le bon endroit\u200a\u2014\u200a<em>un processus long potentiellement joignable de l\u2019ext\u00e9rieur<\/em>\u200a\u2014\u200aje le r\u00e9p\u00e8te ici&nbsp;: dans WebSocket, il y&nbsp;a <em>Web<\/em>&nbsp;! Le protocole WebSocket est logiciel et exploite HTTP\/TCP, comme n\u2019importe quel <em>Site<\/em>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Un plan sans accroc<\/h3>\n\n\n\n<p>Votre application se compose de deux briques&nbsp;: un client, et un serveur WebSocket. Le serveur WebSocket ne peut pas servir de ressource comme un serveur Web traditionnel (ce n\u2019est pas son r\u00f4le), il va donc vous falloir deux&nbsp;sites&nbsp;:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>un site de type <em>Fichiers statiques<\/em> qui va servir votre client <code>index.html<\/code>&nbsp;;<\/li>\n\n\n\n<li>un second site adapt\u00e9 au langage de votre serveur WebSocket pour ex\u00e9cuter ce dernier.<\/li>\n<\/ol>\n\n\n\n<p>Pour le serveur WebSocket, utilisez une adresse du type&nbsp;: <code>[mon-compte].alwaysdata.net\/wss<\/code>. Votre client WebSocket devra se connecter \u00e0&nbsp;cette adresse. Puisque celui-ci se trouve derri\u00e8re un <code>pathUrl<\/code> (et non \u00e0&nbsp;la racine), pensez \u00e0&nbsp;cocher la case <em>extraire le chemin<\/em>. Il vous faut aussi passer la valeur du <em>temps d\u2019inactivit\u00e9<\/em> \u00e0&nbsp;<code>0<\/code> pour garantir que celui-ci ne sera jamais arr\u00eat\u00e9 par le syst\u00e8me, et maintenir actives les  connexions vers les clients, m\u00eame en cas d\u2019absence d\u2019activit\u00e9 prolong\u00e9e.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large is-resized\"><a href=\"https:\/\/blog.alwaysdata.com\/wp-content\/uploads\/2023\/03\/wss-server-site-1_fr.png\"><img decoding=\"async\" src=\"https:\/\/blog.alwaysdata.com\/wp-content\/uploads\/2023\/03\/wss-server-site-1_fr-961x1024.png\" alt class=\"wp-image-3821\" width=\"725\" srcset=\"https:\/\/blog.alwaysdata.com\/wp-content\/uploads\/2023\/03\/wss-server-site-1_fr-961x1024.png 961w, https:\/\/blog.alwaysdata.com\/wp-content\/uploads\/2023\/03\/wss-server-site-1_fr-282x300.png 282w, https:\/\/blog.alwaysdata.com\/wp-content\/uploads\/2023\/03\/wss-server-site-1_fr-768x818.png 768w, https:\/\/blog.alwaysdata.com\/wp-content\/uploads\/2023\/03\/wss-server-site-1_fr.png 1215w\" sizes=\"(max-width: 961px) 100vw, 961px\"><\/a><figcaption class=\"wp-element-caption\">Site WebSocket (python)<\/figcaption><\/figure><\/div>\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large is-resized\"><a href=\"https:\/\/blog.alwaysdata.com\/wp-content\/uploads\/2023\/03\/wss-server-site-2_fr.png\"><img decoding=\"async\" src=\"https:\/\/blog.alwaysdata.com\/wp-content\/uploads\/2023\/03\/wss-server-site-2_fr-1024x269.png\" alt class=\"wp-image-3823\" width=\"725\" srcset=\"https:\/\/blog.alwaysdata.com\/wp-content\/uploads\/2023\/03\/wss-server-site-2_fr-1024x269.png 1024w, https:\/\/blog.alwaysdata.com\/wp-content\/uploads\/2023\/03\/wss-server-site-2_fr-300x79.png 300w, https:\/\/blog.alwaysdata.com\/wp-content\/uploads\/2023\/03\/wss-server-site-2_fr-768x202.png 768w, https:\/\/blog.alwaysdata.com\/wp-content\/uploads\/2023\/03\/wss-server-site-2_fr.png 1215w\" sizes=\"(max-width: 1024px) 100vw, 1024px\"><\/a><figcaption class=\"wp-element-caption\">Site WebSocket, r\u00e9glages avanc\u00e9s&nbsp;: temps d\u2019inactivit\u00e9 \u00e0&nbsp;0, et exclure le chemin<\/figcaption><\/figure><\/div>\n\n\n<p>Modifiez ensuite le fichier <code>index.html<\/code> pour renseigner l\u2019url du serveur WebSocket dans la variable <code>WS_SERVER<\/code>. Cr\u00e9ez ensuite un site <em>Fichiers statiques<\/em> avec l\u2019adresse <code>[mon-compte].alwaysdata.net<\/code> pour servir ce fichier.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large is-resized\"><a href=\"https:\/\/blog.alwaysdata.com\/wp-content\/uploads\/2023\/03\/wss-static-site_fr.png\"><img decoding=\"async\" src=\"https:\/\/blog.alwaysdata.com\/wp-content\/uploads\/2023\/03\/wss-static-site_fr-1024x885.png\" alt class=\"wp-image-3819\" width=\"725\" srcset=\"https:\/\/blog.alwaysdata.com\/wp-content\/uploads\/2023\/03\/wss-static-site_fr-1024x885.png 1024w, https:\/\/blog.alwaysdata.com\/wp-content\/uploads\/2023\/03\/wss-static-site_fr-300x259.png 300w, https:\/\/blog.alwaysdata.com\/wp-content\/uploads\/2023\/03\/wss-static-site_fr-768x664.png 768w, https:\/\/blog.alwaysdata.com\/wp-content\/uploads\/2023\/03\/wss-static-site_fr.png 1215w\" sizes=\"(max-width: 1024px) 100vw, 1024px\"><\/a><figcaption class=\"wp-element-caption\">Site client statique<\/figcaption><\/figure><\/div>\n\n\n<p>Rendez-vous maintenant sur cette adresse&nbsp;: votre communication WebSocket est fonctionnelle&nbsp;!<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\">\n\n\n<figure class=\"embed-media__giphy\" style=\"width:65%; padding-bottom:calc(65% * (360 + 12) \/ (480 + 12))\">\n    <video id=\"giphy-${token}\" autoplay loop muted playsinline>\n        <source src=\"https:\/\/media.giphy.com\/media\/3ohryvw2Kca5ggPAAg\/giphy.mp4\" type=\"video\/mp4\">\n        <img decoding=\"async\" src=\"https:\/\/media.giphy.com\/media\/3ohryvw2Kca5ggPAAg\/giphy.gif\" alt=\"A Team GIF @Giphy\">\n    <\/video>\n<\/figure>\n\n\n\n<p>Cet article n\u2019est, \u00e9videmment, qu\u2019une introduction aux concepts de WebSocket, mais il pointe plusieurs \u00e9l\u00e9ments fondamentaux&nbsp;:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>WebSocket permet une communication bi-directionnelle et multi-cliente simultan\u00e9e.<\/li>\n\n\n\n<li>Un serveur WebSocket tourne en parall\u00e8le du serveur Web de l\u2019application. Ce dernier se charge de distribuer l\u2019application au navigateur, qui va la d\u00e9marrer&nbsp;; mais c\u2019est le premier qui est responsable du transit des flux de donn\u00e9es m\u00e9tier.<\/li>\n\n\n\n<li>M\u00eame s\u2019il semble \u00eatre un protocole diff\u00e9rent, WebSocket exploite les fondamentaux du Web, ses briques sous-jacentes, et ses protocoles robustes.<\/li>\n<\/ol>\n\n\n\n<p>Vous n\u2019avez pas besoin d\u2019\u00eatre un expert r\u00e9seau pour d\u00e9velopper avec WebSocket. Vous utiliserez ce que vous connaissez d\u00e9j\u00e0 bien du <em>Web<\/em>, avec son mod\u00e8le d\u2019\u00e9v\u00e8nements.<\/p>\n\n\n\n<p>Libre \u00e0&nbsp;vous de trouver maintenant les bons usages, adapt\u00e9s \u00e0&nbsp;vos besoins&nbsp;; d\u2019ajouter du traitement de donn\u00e9es&nbsp;; faire transiter des formats JSON ou binaires&nbsp;; de supporter une authentification\u2026 Tout ce que vous savez d\u00e9j\u00e0 faire en Web est applicable.<\/p>\n\n\n\n<p>\u00c0 vos claviers, et bon&nbsp;dev&nbsp;!<\/p>\n<div class=\"speaker-mute footnotes_reference_container\"> <div class=\"footnote_container_prepare\"><p><span role=\"button\" tabindex=\"0\" class=\"footnote_reference_container_label pointer\" onclick=\"footnote_expand_collapse_reference_container_3810_1();\">Notes<\/span><span role=\"button\" tabindex=\"0\" class=\"footnote_reference_container_collapse_button\" style=\"display: none;\" onclick=\"footnote_expand_collapse_reference_container_3810_1();\">[<a id=\"footnote_reference_container_collapse_button_3810_1\">+<\/a>]<\/span><\/p><\/div> <div id=\"footnote_references_container_3810_1\" style><table class=\"footnotes_table footnote-reference-container\"><caption class=\"accessibility\">Notes<\/caption> <tbody> \n\n<tr class=\"footnotes_plugin_reference_row\"> <th scope=\"row\" class=\"footnote_plugin_index_combi pointer\" onclick=\"footnote_moveToAnchor_3810_1('footnote_plugin_tooltip_3810_1_1');\"><a id=\"footnote_plugin_reference_3810_1_1\" class=\"footnote_backlink\"><span class=\"footnote_index_arrow\">\u2191<\/span>1<\/a><\/th> <td class=\"footnote_plugin_text\">et pour ne pas toujours refaire la m\u00eame&nbsp;chose<\/td><\/tr>\n\n<tr class=\"footnotes_plugin_reference_row\"> <th scope=\"row\" class=\"footnote_plugin_index_combi pointer\" onclick=\"footnote_moveToAnchor_3810_1('footnote_plugin_tooltip_3810_1_2');\"><a id=\"footnote_plugin_reference_3810_1_2\" class=\"footnote_backlink\"><span class=\"footnote_index_arrow\">\u2191<\/span>2<\/a><\/th> <td class=\"footnote_plugin_text\">ou <code>pnpm<\/code>, ou <code>yarn<\/code><\/td><\/tr>\n\n<tr class=\"footnotes_plugin_reference_row\"> <th scope=\"row\" class=\"footnote_plugin_index_combi pointer\" onclick=\"footnote_moveToAnchor_3810_1('footnote_plugin_tooltip_3810_1_3');\"><a id=\"footnote_plugin_reference_3810_1_3\" class=\"footnote_backlink\"><span class=\"footnote_index_arrow\">\u2191<\/span>3<\/a><\/th> <td class=\"footnote_plugin_text\">client) =&gt; {\n      \/\/ send to active clients only\n      if (client.readyState != WebSocket.OPEN) { return }\n      setTimeout(() =&gt; client.send(\u201cpong\u201d), 1000);&nbsp;});&nbsp;});&nbsp;});\n\n\n\n<p>Rien de plus. Pour lancer le serveur WebSocket, ex\u00e9cutez simplement le fichier <code>wss.js<\/code> avec Node.js&nbsp;:<\/p>\n\n\n\n<pre class=\"wp-block-code lang:sh\"><code>$ node wss.js<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">\u2026 ou en Python&nbsp;!<\/h3>\n\n\n\n<p>Pour changer un peu des exemples habituels, r\u00e9alisons le m\u00eame serveur WebSocket en Python, \u00e0&nbsp;l\u2019aide d\u2019<em>asyncio<\/em> et de <a href=\"https:\/\/pypi.org\/project\/websockets\/\">websockets<\/a>. Commencez par installer le paquet <code>websockets<\/code> \u00e0&nbsp;l\u2019aide de <code>pip<\/code> dans votre <em>venv<\/em>, puis cr\u00e9ez un fichier <code>wss.py<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-code lang:python\"><code>#!\/usr\/bin\/env python\n\nimport os\n\nimport asyncio\nimport websockets\n\n\nasync def handler(websocket): pass\n\n\nasync def main():\n    async with websockets.serve(\n        handler,\n        os.environ.get('IP', ''),\n        os.environ.get('PORT', 8080)\n    ):\n        # Run forever\n        await asyncio.Future()\n\n\nif name == \"__main__\":\n    asyncio.run(main(<\/code><\/pre><\/td><\/tr>\n\n <\/tbody> <\/table> <\/div><\/div><script type=\"text\/javascript\"> function footnote_expand_reference_container_3810_1() { jQuery('#footnote_references_container_3810_1').show(); jQuery('#footnote_reference_container_collapse_button_3810_1').text('\u2212'); } function footnote_collapse_reference_container_3810_1() { jQuery('#footnote_references_container_3810_1').hide(); jQuery('#footnote_reference_container_collapse_button_3810_1').text('+'); } function footnote_expand_collapse_reference_container_3810_1() { if (jQuery('#footnote_references_container_3810_1').is(':hidden')) { footnote_expand_reference_container_3810_1(); } else { footnote_collapse_reference_container_3810_1(); } } function footnote_moveToReference_3810_1(p_str_TargetID) { footnote_expand_reference_container_3810_1(); var l_obj_Target = jQuery('#' + p_str_TargetID); if (l_obj_Target.length) { jQuery( 'html, body' ).delay( 0 ); jQuery('html, body').animate({ scrollTop: l_obj_Target.offset().top - window.innerHeight * 0.2 }, 380); } } function footnote_moveToAnchor_3810_1(p_str_TargetID) { footnote_expand_reference_container_3810_1(); var l_obj_Target = jQuery('#' + p_str_TargetID); if (l_obj_Target.length) { jQuery( 'html, body' ).delay( 0 ); jQuery('html, body').animate({ scrollTop: l_obj_Target.offset().top - window.innerHeight * 0.2 }, 380); } }<\/script>","protected":false},"excerpt":{"rendered":"<p>H\u00e9berger un serveur WebSocket, ce n\u2019est rien de plus que faire du bon vieux Web. WebSocket sans prise de t\u00eate, c\u2019est par&nbsp;l\u00e0&nbsp;!<\/p>\n","protected":false},"author":12,"featured_media":3827,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"wp_typography_post_enhancements_disabled":false,"footnotes":""},"categories":[],"tags":[169,266,182,189,274],"class_list":["post-3810","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","tag-devops-fr","tag-frontend-fr","tag-node-js-fr","tag-python-fr","tag-websocket-fr"],"acf":[],"_links":{"self":[{"href":"https:\/\/blog.alwaysdata.com\/fr\/wp-json\/wp\/v2\/posts\/3810","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.alwaysdata.com\/fr\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.alwaysdata.com\/fr\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.alwaysdata.com\/fr\/wp-json\/wp\/v2\/users\/12"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.alwaysdata.com\/fr\/wp-json\/wp\/v2\/comments?post=3810"}],"version-history":[{"count":0,"href":"https:\/\/blog.alwaysdata.com\/fr\/wp-json\/wp\/v2\/posts\/3810\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.alwaysdata.com\/fr\/wp-json\/wp\/v2\/media\/3827"}],"wp:attachment":[{"href":"https:\/\/blog.alwaysdata.com\/fr\/wp-json\/wp\/v2\/media?parent=3810"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.alwaysdata.com\/fr\/wp-json\/wp\/v2\/categories?post=3810"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.alwaysdata.com\/fr\/wp-json\/wp\/v2\/tags?post=3810"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}