[{"data":1,"prerenderedAt":2740},["ShallowReactive",2],{"post-\u002Ffr\u002Fposts\u002Fcreate-real-time-nodejs-apps-with-server-sent-events":3},{"id":4,"title":5,"body":6,"contentId":2725,"date":2726,"description":2727,"extension":2728,"meta":2729,"navigation":159,"path":2730,"rawbody":2731,"robots":2732,"schemaOrg":2732,"seo":2733,"sitemap":2734,"stem":2738,"__hash__":2739},"posts\u002Ffr\u002Fposts\u002Fcreate-real-time-nodejs-apps-with-server-sent-events.md","Créer des Applications Node.js en Temps Réel avec les SSE",{"type":7,"value":8,"toc":2715},"minimark",[9,18,27,30,35,38,42,50,63,70,492,501,504,520,524,529,689,692,696,699,706,1008,1033,1037,1040,1043,1356,1365,1371,1609,1613,1616,1619,1626,1629,1632,2137,2147,2153,2162,2183,2193,2197,2202,2209,2212,2651,2660,2666,2672,2675,2681,2685,2688,2691,2705,2708,2711],[10,11,12,13,17],"p",{},"Je suis convaincu que pour comprendre un concept ou une technologie, nous devons tous essayer ",[14,15,16],"strong",{},"de cuisiner quelque chose avec"," en utilisant le plus bas niveau possible. Cela ne concerne pas seulement la programmation, mais tout dans la vie.",[10,19,20,21,26],{},"Dans la suite de l'article ",[22,23,25],"a",{"href":24},".\u002Fsimplify-real-time-with-server-sent-events-over-websockets","Temps réel sans WebSocket",", je pense que construire une petite application client-serveur utilisant des événements sent par le serveur (SSE) est un bon moyen de comprendre comment cela fonctionne et de nous donner la confiance nécessaire pour l'utiliser dans nos projets.",[10,28,29],{},"Cette application client-serveur sera un petit chat où le client enverra un message au serveur et le serveur diffusera le message à tous les clients connectés. Pas de bibliothèque externe, pas de framework, juste Node.js.",[31,32,34],"h2",{"id":33},"exigences","Exigences",[10,36,37],{},"Pour suivre ce tutoriel, nous devons avoir Node.js installé sur notre machine. C'est tout.",[31,39,41],{"id":40},"le-serveur","Le Serveur",[10,43,44,45,49],{},"Créez un fichier nommé ",[46,47,48],"code",{},"server.mjs",". Ce fichier contiendra le code du serveur pour effectuer les actions suivantes :",[51,52,53,57,60],"ul",{},[54,55,56],"li",{},"Servir le code côté client c'est-à-dire un simple fichier HTML",[54,58,59],{},"Recevoir des messages des clients",[54,61,62],{},"Accepter les connexions des événements envoyés par le serveur et diffuser des messages",[10,64,65,66,69],{},"Mais pour l'instant, commençons par servir le code côté client. Notre serveur sera construit en utilisant le module ",[46,67,68],{},"http"," natif de Node.js.",[71,72,77],"pre",{"className":73,"code":74,"language":75,"meta":76,"style":76},"language-js shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","import { readFileSync } from 'node:fs'\nimport { createServer } from 'node:http'\nimport { join } from 'node:path'\n\nconst hostname = '127.0.0.1'\nconst port = 3000\n\nconst server = createServer((req, res) => {\n  \u002F\u002F Servir le fichier index.html\n  res.statusCode = 200\n  res.setHeader('Content-Type', 'text\u002Fhtml')\n\n  const htmlFile = join(process.cwd(), 'index.html')\n  const html = readFileSync(htmlFile, 'utf-8')\n\n  res.write(html)\n  res.end()\n})\n\nserver.listen(port, hostname, () => {\n  console.log(`Serveur en cours d'exécution à http:\u002F\u002F${hostname}:${port}\u002F`)\n})\n","js","",[46,78,79,112,133,154,161,181,195,200,237,244,262,295,300,337,365,370,387,400,408,413,441,485],{"__ignoreMap":76},[80,81,84,88,92,96,99,102,105,109],"span",{"class":82,"line":83},"line",1,[80,85,87],{"class":86},"s7zQu","import",[80,89,91],{"class":90},"sMK4o"," {",[80,93,95],{"class":94},"sTEyZ"," readFileSync",[80,97,98],{"class":90}," }",[80,100,101],{"class":86}," from",[80,103,104],{"class":90}," '",[80,106,108],{"class":107},"sfazB","node:fs",[80,110,111],{"class":90},"'\n",[80,113,115,117,119,122,124,126,128,131],{"class":82,"line":114},2,[80,116,87],{"class":86},[80,118,91],{"class":90},[80,120,121],{"class":94}," createServer",[80,123,98],{"class":90},[80,125,101],{"class":86},[80,127,104],{"class":90},[80,129,130],{"class":107},"node:http",[80,132,111],{"class":90},[80,134,136,138,140,143,145,147,149,152],{"class":82,"line":135},3,[80,137,87],{"class":86},[80,139,91],{"class":90},[80,141,142],{"class":94}," join",[80,144,98],{"class":90},[80,146,101],{"class":86},[80,148,104],{"class":90},[80,150,151],{"class":107},"node:path",[80,153,111],{"class":90},[80,155,157],{"class":82,"line":156},4,[80,158,160],{"emptyLinePlaceholder":159},true,"\n",[80,162,164,168,171,174,176,179],{"class":82,"line":163},5,[80,165,167],{"class":166},"spNyl","const",[80,169,170],{"class":94}," hostname ",[80,172,173],{"class":90},"=",[80,175,104],{"class":90},[80,177,178],{"class":107},"127.0.0.1",[80,180,111],{"class":90},[80,182,184,186,189,191],{"class":82,"line":183},6,[80,185,167],{"class":166},[80,187,188],{"class":94}," port ",[80,190,173],{"class":90},[80,192,194],{"class":193},"sbssI"," 3000\n",[80,196,198],{"class":82,"line":197},7,[80,199,160],{"emptyLinePlaceholder":159},[80,201,203,205,208,210,213,216,218,222,225,228,231,234],{"class":82,"line":202},8,[80,204,167],{"class":166},[80,206,207],{"class":94}," server ",[80,209,173],{"class":90},[80,211,121],{"class":212},"s2Zo4",[80,214,215],{"class":94},"(",[80,217,215],{"class":90},[80,219,221],{"class":220},"sHdIc","req",[80,223,224],{"class":90},",",[80,226,227],{"class":220}," res",[80,229,230],{"class":90},")",[80,232,233],{"class":166}," =>",[80,235,236],{"class":90}," {\n",[80,238,240],{"class":82,"line":239},9,[80,241,243],{"class":242},"sHwdD","  \u002F\u002F Servir le fichier index.html\n",[80,245,247,250,253,256,259],{"class":82,"line":246},10,[80,248,249],{"class":94},"  res",[80,251,252],{"class":90},".",[80,254,255],{"class":94},"statusCode",[80,257,258],{"class":90}," =",[80,260,261],{"class":193}," 200\n",[80,263,265,267,269,272,275,278,281,283,285,287,290,292],{"class":82,"line":264},11,[80,266,249],{"class":94},[80,268,252],{"class":90},[80,270,271],{"class":212},"setHeader",[80,273,215],{"class":274},"swJcz",[80,276,277],{"class":90},"'",[80,279,280],{"class":107},"Content-Type",[80,282,277],{"class":90},[80,284,224],{"class":90},[80,286,104],{"class":90},[80,288,289],{"class":107},"text\u002Fhtml",[80,291,277],{"class":90},[80,293,294],{"class":274},")\n",[80,296,298],{"class":82,"line":297},12,[80,299,160],{"emptyLinePlaceholder":159},[80,301,303,306,309,311,313,315,318,320,323,326,328,330,333,335],{"class":82,"line":302},13,[80,304,305],{"class":166},"  const",[80,307,308],{"class":94}," htmlFile",[80,310,258],{"class":90},[80,312,142],{"class":212},[80,314,215],{"class":274},[80,316,317],{"class":94},"process",[80,319,252],{"class":90},[80,321,322],{"class":212},"cwd",[80,324,325],{"class":274},"()",[80,327,224],{"class":90},[80,329,104],{"class":90},[80,331,332],{"class":107},"index.html",[80,334,277],{"class":90},[80,336,294],{"class":274},[80,338,340,342,345,347,349,351,354,356,358,361,363],{"class":82,"line":339},14,[80,341,305],{"class":166},[80,343,344],{"class":94}," html",[80,346,258],{"class":90},[80,348,95],{"class":212},[80,350,215],{"class":274},[80,352,353],{"class":94},"htmlFile",[80,355,224],{"class":90},[80,357,104],{"class":90},[80,359,360],{"class":107},"utf-8",[80,362,277],{"class":90},[80,364,294],{"class":274},[80,366,368],{"class":82,"line":367},15,[80,369,160],{"emptyLinePlaceholder":159},[80,371,373,375,377,380,382,385],{"class":82,"line":372},16,[80,374,249],{"class":94},[80,376,252],{"class":90},[80,378,379],{"class":212},"write",[80,381,215],{"class":274},[80,383,384],{"class":94},"html",[80,386,294],{"class":274},[80,388,390,392,394,397],{"class":82,"line":389},17,[80,391,249],{"class":94},[80,393,252],{"class":90},[80,395,396],{"class":212},"end",[80,398,399],{"class":274},"()\n",[80,401,403,406],{"class":82,"line":402},18,[80,404,405],{"class":90},"}",[80,407,294],{"class":94},[80,409,411],{"class":82,"line":410},19,[80,412,160],{"emptyLinePlaceholder":159},[80,414,416,419,421,424,427,429,432,434,437,439],{"class":82,"line":415},20,[80,417,418],{"class":94},"server",[80,420,252],{"class":90},[80,422,423],{"class":212},"listen",[80,425,426],{"class":94},"(port",[80,428,224],{"class":90},[80,430,431],{"class":94}," hostname",[80,433,224],{"class":90},[80,435,436],{"class":90}," ()",[80,438,233],{"class":166},[80,440,236],{"class":90},[80,442,444,447,449,452,454,457,460,463,466,468,471,473,476,478,481,483],{"class":82,"line":443},21,[80,445,446],{"class":94},"  console",[80,448,252],{"class":90},[80,450,451],{"class":212},"log",[80,453,215],{"class":274},[80,455,456],{"class":90},"`",[80,458,459],{"class":107},"Serveur en cours d'exécution à http:\u002F\u002F",[80,461,462],{"class":90},"${",[80,464,465],{"class":94},"hostname",[80,467,405],{"class":90},[80,469,470],{"class":107},":",[80,472,462],{"class":90},[80,474,475],{"class":94},"port",[80,477,405],{"class":90},[80,479,480],{"class":107},"\u002F",[80,482,456],{"class":90},[80,484,294],{"class":274},[80,486,488,490],{"class":82,"line":487},22,[80,489,405],{"class":90},[80,491,294],{"class":94},[10,493,494,495,497,498,252],{},"Lorsqu'une requête atteint le serveur, il répondra avec le contenu du fichier ",[46,496,332],{}," en le lisant sur le disque. Le serveur écoute sur le port ",[46,499,500],{},"3000",[10,502,503],{},"Nous pouvons démarrer notre serveur avec la commande suivante :",[71,505,509],{"className":506,"code":507,"language":508,"meta":76,"style":76},"language-sh shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","node server.mjs\n","sh",[46,510,511],{"__ignoreMap":76},[80,512,513,517],{"class":82,"line":83},[80,514,516],{"class":515},"sBMFI","node",[80,518,519],{"class":107}," server.mjs\n",[31,521,523],{"id":522},"le-client","Le Client",[10,525,44,526,528],{},[46,527,332],{}," à la racine du projet. Ce fichier contiendra le code côté client :",[71,530,533],{"className":531,"code":532,"language":384,"meta":76,"style":76},"language-html shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u003C!doctype html>\n\u003Chtml lang=\"en\">\n  \u003Chead>\n    \u003Cmeta charset=\"UTF-8\" \u002F>\n    \u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" \u002F>\n    \u003Ctitle>Node SSE\u003C\u002Ftitle>\n  \u003C\u002Fhead>\n  \u003Cbody>\n  \u003C\u002Fbody>\n\u003C\u002Fhtml>\n",[46,534,535,548,570,580,603,635,655,664,673,681],{"__ignoreMap":76},[80,536,537,540,543,545],{"class":82,"line":83},[80,538,539],{"class":90},"\u003C!",[80,541,542],{"class":274},"doctype",[80,544,344],{"class":166},[80,546,547],{"class":90},">\n",[80,549,550,553,555,558,560,563,566,568],{"class":82,"line":114},[80,551,552],{"class":90},"\u003C",[80,554,384],{"class":274},[80,556,557],{"class":166}," lang",[80,559,173],{"class":90},[80,561,562],{"class":90},"\"",[80,564,565],{"class":107},"en",[80,567,562],{"class":90},[80,569,547],{"class":90},[80,571,572,575,578],{"class":82,"line":135},[80,573,574],{"class":90},"  \u003C",[80,576,577],{"class":274},"head",[80,579,547],{"class":90},[80,581,582,585,588,591,593,595,598,600],{"class":82,"line":156},[80,583,584],{"class":90},"    \u003C",[80,586,587],{"class":274},"meta",[80,589,590],{"class":166}," charset",[80,592,173],{"class":90},[80,594,562],{"class":90},[80,596,597],{"class":107},"UTF-8",[80,599,562],{"class":90},[80,601,602],{"class":90}," \u002F>\n",[80,604,605,607,609,612,614,616,619,621,624,626,628,631,633],{"class":82,"line":163},[80,606,584],{"class":90},[80,608,587],{"class":274},[80,610,611],{"class":166}," name",[80,613,173],{"class":90},[80,615,562],{"class":90},[80,617,618],{"class":107},"viewport",[80,620,562],{"class":90},[80,622,623],{"class":166}," content",[80,625,173],{"class":90},[80,627,562],{"class":90},[80,629,630],{"class":107},"width=device-width, initial-scale=1.0",[80,632,562],{"class":90},[80,634,602],{"class":90},[80,636,637,639,642,645,648,651,653],{"class":82,"line":183},[80,638,584],{"class":90},[80,640,641],{"class":274},"title",[80,643,644],{"class":90},">",[80,646,647],{"class":94},"Node SSE",[80,649,650],{"class":90},"\u003C\u002F",[80,652,641],{"class":274},[80,654,547],{"class":90},[80,656,657,660,662],{"class":82,"line":197},[80,658,659],{"class":90},"  \u003C\u002F",[80,661,577],{"class":274},[80,663,547],{"class":90},[80,665,666,668,671],{"class":82,"line":202},[80,667,574],{"class":90},[80,669,670],{"class":274},"body",[80,672,547],{"class":90},[80,674,675,677,679],{"class":82,"line":239},[80,676,659],{"class":90},[80,678,670],{"class":274},[80,680,547],{"class":90},[80,682,683,685,687],{"class":82,"line":246},[80,684,650],{"class":90},[80,686,384],{"class":274},[80,688,547],{"class":90},[10,690,691],{},"Oui, c'est un fichier HTML très simple, rien de fantaisiste pour l'instant.",[31,693,695],{"id":694},"connexion-des-événements-envoyés-par-le-serveur","Connexion des Événements Envoyés par le Serveur",[10,697,698],{},"Maintenant, ajoutons la connexion des événements envoyés par le serveur au serveur. Pour gérer les SSE, nous devons répondre avec certains en-têtes pour garder la connexion ouverte et envoyer des données. Ainsi, le client demandera une ressource et le serveur répondra avec un flux.",[10,700,701,702,705],{},"Cette ressource sera ",[46,703,704],{},"\u002Fevents",". Mettons à jour le code du serveur :",[71,707,709],{"className":73,"code":708,"language":75,"meta":76,"style":76},"\u002F\u002F ...\n\nconst server = createServer((req, res) => {\n  \u002F\u002F Point de terminaison `\u002Fevents` utilisé pour écouter les messages\n  if (req.url === '\u002Fevents') {\n    res.setHeader('Content-Type', 'text\u002Fevent-stream')\n    res.setHeader('Cache-Control', 'no-cache')\n\n    \u002F\u002F TODO: Gérer la connexion\n  }\n  else {\n    \u002F\u002F Servir le fichier index.html\n    res.statusCode = 200\n    res.setHeader('Content-Type', 'text\u002Fhtml')\n\n    const htmlFile = join(process.cwd(), 'index.html')\n    const html = readFileSync(htmlFile, 'utf-8')\n\n    res.write(html)\n    res.end()\n  }\n})\n\n\u002F\u002F ...\n",[46,710,711,716,720,746,751,781,809,837,841,846,851,858,863,875,901,905,936,960,964,978,988,992,998,1003],{"__ignoreMap":76},[80,712,713],{"class":82,"line":83},[80,714,715],{"class":242},"\u002F\u002F ...\n",[80,717,718],{"class":82,"line":114},[80,719,160],{"emptyLinePlaceholder":159},[80,721,722,724,726,728,730,732,734,736,738,740,742,744],{"class":82,"line":135},[80,723,167],{"class":166},[80,725,207],{"class":94},[80,727,173],{"class":90},[80,729,121],{"class":212},[80,731,215],{"class":94},[80,733,215],{"class":90},[80,735,221],{"class":220},[80,737,224],{"class":90},[80,739,227],{"class":220},[80,741,230],{"class":90},[80,743,233],{"class":166},[80,745,236],{"class":90},[80,747,748],{"class":82,"line":156},[80,749,750],{"class":242},"  \u002F\u002F Point de terminaison `\u002Fevents` utilisé pour écouter les messages\n",[80,752,753,756,759,761,763,766,769,771,773,775,778],{"class":82,"line":163},[80,754,755],{"class":86},"  if",[80,757,758],{"class":274}," (",[80,760,221],{"class":94},[80,762,252],{"class":90},[80,764,765],{"class":94},"url",[80,767,768],{"class":90}," ===",[80,770,104],{"class":90},[80,772,704],{"class":107},[80,774,277],{"class":90},[80,776,777],{"class":274},") ",[80,779,780],{"class":90},"{\n",[80,782,783,786,788,790,792,794,796,798,800,802,805,807],{"class":82,"line":183},[80,784,785],{"class":94},"    res",[80,787,252],{"class":90},[80,789,271],{"class":212},[80,791,215],{"class":274},[80,793,277],{"class":90},[80,795,280],{"class":107},[80,797,277],{"class":90},[80,799,224],{"class":90},[80,801,104],{"class":90},[80,803,804],{"class":107},"text\u002Fevent-stream",[80,806,277],{"class":90},[80,808,294],{"class":274},[80,810,811,813,815,817,819,821,824,826,828,830,833,835],{"class":82,"line":197},[80,812,785],{"class":94},[80,814,252],{"class":90},[80,816,271],{"class":212},[80,818,215],{"class":274},[80,820,277],{"class":90},[80,822,823],{"class":107},"Cache-Control",[80,825,277],{"class":90},[80,827,224],{"class":90},[80,829,104],{"class":90},[80,831,832],{"class":107},"no-cache",[80,834,277],{"class":90},[80,836,294],{"class":274},[80,838,839],{"class":82,"line":202},[80,840,160],{"emptyLinePlaceholder":159},[80,842,843],{"class":82,"line":239},[80,844,845],{"class":242},"    \u002F\u002F TODO: Gérer la connexion\n",[80,847,848],{"class":82,"line":246},[80,849,850],{"class":90},"  }\n",[80,852,853,856],{"class":82,"line":264},[80,854,855],{"class":86},"  else",[80,857,236],{"class":90},[80,859,860],{"class":82,"line":297},[80,861,862],{"class":242},"    \u002F\u002F Servir le fichier index.html\n",[80,864,865,867,869,871,873],{"class":82,"line":302},[80,866,785],{"class":94},[80,868,252],{"class":90},[80,870,255],{"class":94},[80,872,258],{"class":90},[80,874,261],{"class":193},[80,876,877,879,881,883,885,887,889,891,893,895,897,899],{"class":82,"line":339},[80,878,785],{"class":94},[80,880,252],{"class":90},[80,882,271],{"class":212},[80,884,215],{"class":274},[80,886,277],{"class":90},[80,888,280],{"class":107},[80,890,277],{"class":90},[80,892,224],{"class":90},[80,894,104],{"class":90},[80,896,289],{"class":107},[80,898,277],{"class":90},[80,900,294],{"class":274},[80,902,903],{"class":82,"line":367},[80,904,160],{"emptyLinePlaceholder":159},[80,906,907,910,912,914,916,918,920,922,924,926,928,930,932,934],{"class":82,"line":372},[80,908,909],{"class":166},"    const",[80,911,308],{"class":94},[80,913,258],{"class":90},[80,915,142],{"class":212},[80,917,215],{"class":274},[80,919,317],{"class":94},[80,921,252],{"class":90},[80,923,322],{"class":212},[80,925,325],{"class":274},[80,927,224],{"class":90},[80,929,104],{"class":90},[80,931,332],{"class":107},[80,933,277],{"class":90},[80,935,294],{"class":274},[80,937,938,940,942,944,946,948,950,952,954,956,958],{"class":82,"line":389},[80,939,909],{"class":166},[80,941,344],{"class":94},[80,943,258],{"class":90},[80,945,95],{"class":212},[80,947,215],{"class":274},[80,949,353],{"class":94},[80,951,224],{"class":90},[80,953,104],{"class":90},[80,955,360],{"class":107},[80,957,277],{"class":90},[80,959,294],{"class":274},[80,961,962],{"class":82,"line":402},[80,963,160],{"emptyLinePlaceholder":159},[80,965,966,968,970,972,974,976],{"class":82,"line":410},[80,967,785],{"class":94},[80,969,252],{"class":90},[80,971,379],{"class":212},[80,973,215],{"class":274},[80,975,384],{"class":94},[80,977,294],{"class":274},[80,979,980,982,984,986],{"class":82,"line":415},[80,981,785],{"class":94},[80,983,252],{"class":90},[80,985,396],{"class":212},[80,987,399],{"class":274},[80,989,990],{"class":82,"line":443},[80,991,850],{"class":90},[80,993,994,996],{"class":82,"line":487},[80,995,405],{"class":90},[80,997,294],{"class":94},[80,999,1001],{"class":82,"line":1000},23,[80,1002,160],{"emptyLinePlaceholder":159},[80,1004,1006],{"class":82,"line":1005},24,[80,1007,715],{"class":242},[10,1009,1010,1011,1013,1014,1017,1018,1021,1022,1025,1026,1029,1030,1032],{},"Lorsque le client demande la ressource ",[46,1012,704],{},", le serveur répondra avec les en-têtes ",[46,1015,1016],{},"Content-Type: text\u002Fevent-stream"," et ",[46,1019,1020],{},"Cache-Control: no-cache",". Cela informe le client que la connexion est un flux, c'est-à-dire que le serveur enverra des données au client à tout moment. La connexion ne doit pas être mise en cache pour éviter des problèmes avec le flux.\nIl est important de noter que nous n'appelons jamais la fonction ",[46,1023,1024],{},"res.end()",". Cela est dû au fait que ",[14,1027,1028],{},"la connexion doit rester ouverte"," pour permettre au serveur d'envoyer des données au client. La fonction ",[46,1031,1024],{}," ferme la connexion.",[31,1034,1036],{"id":1035},"logique-de-chat","Logique de Chat",[10,1038,1039],{},"C'est très simple et simplifié au maximum, mais la logique sous-jacente reste la même qu'une application plus complexe.",[10,1041,1042],{},"Dans notre serveur, nous aurons une logique pour recevoir des messages des clients.",[71,1044,1046],{"className":73,"code":1045,"language":75,"meta":76,"style":76},"\u002F\u002F ...\n\nconst server = createServer((req, res) => {\n  \u002F\u002F Point de terminaison `\u002Fevents` utilisé pour écouter les messages\n  if (req.url === '\u002Fevents') {\n    \u002F\u002F ...\n\n    \u002F\u002F Point de terminaison `\u002Fmessages` utilisé pour envoyer des messages\n  }\n  else if (req.url === '\u002Fmessage') {\n    let body = ''\n\n    req.on('data', (chunk) => {\n      body += chunk\n    })\n\n    req.on('end', () => {\n      \u002F\u002F TODO: diffuser le message\n\n      \u002F\u002F Rediriger vers le fichier index.html\n      res.statusCode = 302\n      res.setHeader('Location', '\u002F')\n      res.end()\n    })\n  }\n  else {\n    \u002F\u002F Servir le fichier index.html\n    \u002F\u002F ...\n  }\n})\n",[46,1047,1048,1052,1056,1082,1086,1110,1115,1119,1124,1128,1156,1169,1173,1205,1216,1223,1227,1251,1256,1260,1265,1279,1306,1316,1322,1327,1334,1339,1344,1349],{"__ignoreMap":76},[80,1049,1050],{"class":82,"line":83},[80,1051,715],{"class":242},[80,1053,1054],{"class":82,"line":114},[80,1055,160],{"emptyLinePlaceholder":159},[80,1057,1058,1060,1062,1064,1066,1068,1070,1072,1074,1076,1078,1080],{"class":82,"line":135},[80,1059,167],{"class":166},[80,1061,207],{"class":94},[80,1063,173],{"class":90},[80,1065,121],{"class":212},[80,1067,215],{"class":94},[80,1069,215],{"class":90},[80,1071,221],{"class":220},[80,1073,224],{"class":90},[80,1075,227],{"class":220},[80,1077,230],{"class":90},[80,1079,233],{"class":166},[80,1081,236],{"class":90},[80,1083,1084],{"class":82,"line":156},[80,1085,750],{"class":242},[80,1087,1088,1090,1092,1094,1096,1098,1100,1102,1104,1106,1108],{"class":82,"line":163},[80,1089,755],{"class":86},[80,1091,758],{"class":274},[80,1093,221],{"class":94},[80,1095,252],{"class":90},[80,1097,765],{"class":94},[80,1099,768],{"class":90},[80,1101,104],{"class":90},[80,1103,704],{"class":107},[80,1105,277],{"class":90},[80,1107,777],{"class":274},[80,1109,780],{"class":90},[80,1111,1112],{"class":82,"line":183},[80,1113,1114],{"class":242},"    \u002F\u002F ...\n",[80,1116,1117],{"class":82,"line":197},[80,1118,160],{"emptyLinePlaceholder":159},[80,1120,1121],{"class":82,"line":202},[80,1122,1123],{"class":242},"    \u002F\u002F Point de terminaison `\u002Fmessages` utilisé pour envoyer des messages\n",[80,1125,1126],{"class":82,"line":239},[80,1127,850],{"class":90},[80,1129,1130,1132,1135,1137,1139,1141,1143,1145,1147,1150,1152,1154],{"class":82,"line":246},[80,1131,855],{"class":86},[80,1133,1134],{"class":86}," if",[80,1136,758],{"class":274},[80,1138,221],{"class":94},[80,1140,252],{"class":90},[80,1142,765],{"class":94},[80,1144,768],{"class":90},[80,1146,104],{"class":90},[80,1148,1149],{"class":107},"\u002Fmessage",[80,1151,277],{"class":90},[80,1153,777],{"class":274},[80,1155,780],{"class":90},[80,1157,1158,1161,1164,1166],{"class":82,"line":264},[80,1159,1160],{"class":166},"    let",[80,1162,1163],{"class":94}," body",[80,1165,258],{"class":90},[80,1167,1168],{"class":90}," ''\n",[80,1170,1171],{"class":82,"line":297},[80,1172,160],{"emptyLinePlaceholder":159},[80,1174,1175,1178,1180,1183,1185,1187,1190,1192,1194,1196,1199,1201,1203],{"class":82,"line":302},[80,1176,1177],{"class":94},"    req",[80,1179,252],{"class":90},[80,1181,1182],{"class":212},"on",[80,1184,215],{"class":274},[80,1186,277],{"class":90},[80,1188,1189],{"class":107},"data",[80,1191,277],{"class":90},[80,1193,224],{"class":90},[80,1195,758],{"class":90},[80,1197,1198],{"class":220},"chunk",[80,1200,230],{"class":90},[80,1202,233],{"class":166},[80,1204,236],{"class":90},[80,1206,1207,1210,1213],{"class":82,"line":339},[80,1208,1209],{"class":94},"      body",[80,1211,1212],{"class":90}," +=",[80,1214,1215],{"class":94}," chunk\n",[80,1217,1218,1221],{"class":82,"line":367},[80,1219,1220],{"class":90},"    }",[80,1222,294],{"class":274},[80,1224,1225],{"class":82,"line":372},[80,1226,160],{"emptyLinePlaceholder":159},[80,1228,1229,1231,1233,1235,1237,1239,1241,1243,1245,1247,1249],{"class":82,"line":389},[80,1230,1177],{"class":94},[80,1232,252],{"class":90},[80,1234,1182],{"class":212},[80,1236,215],{"class":274},[80,1238,277],{"class":90},[80,1240,396],{"class":107},[80,1242,277],{"class":90},[80,1244,224],{"class":90},[80,1246,436],{"class":90},[80,1248,233],{"class":166},[80,1250,236],{"class":90},[80,1252,1253],{"class":82,"line":402},[80,1254,1255],{"class":242},"      \u002F\u002F TODO: diffuser le message\n",[80,1257,1258],{"class":82,"line":410},[80,1259,160],{"emptyLinePlaceholder":159},[80,1261,1262],{"class":82,"line":415},[80,1263,1264],{"class":242},"      \u002F\u002F Rediriger vers le fichier index.html\n",[80,1266,1267,1270,1272,1274,1276],{"class":82,"line":443},[80,1268,1269],{"class":94},"      res",[80,1271,252],{"class":90},[80,1273,255],{"class":94},[80,1275,258],{"class":90},[80,1277,1278],{"class":193}," 302\n",[80,1280,1281,1283,1285,1287,1289,1291,1294,1296,1298,1300,1302,1304],{"class":82,"line":487},[80,1282,1269],{"class":94},[80,1284,252],{"class":90},[80,1286,271],{"class":212},[80,1288,215],{"class":274},[80,1290,277],{"class":90},[80,1292,1293],{"class":107},"Location",[80,1295,277],{"class":90},[80,1297,224],{"class":90},[80,1299,104],{"class":90},[80,1301,480],{"class":107},[80,1303,277],{"class":90},[80,1305,294],{"class":274},[80,1307,1308,1310,1312,1314],{"class":82,"line":1000},[80,1309,1269],{"class":94},[80,1311,252],{"class":90},[80,1313,396],{"class":212},[80,1315,399],{"class":274},[80,1317,1318,1320],{"class":82,"line":1005},[80,1319,1220],{"class":90},[80,1321,294],{"class":274},[80,1323,1325],{"class":82,"line":1324},25,[80,1326,850],{"class":90},[80,1328,1330,1332],{"class":82,"line":1329},26,[80,1331,855],{"class":86},[80,1333,236],{"class":90},[80,1335,1337],{"class":82,"line":1336},27,[80,1338,862],{"class":242},[80,1340,1342],{"class":82,"line":1341},28,[80,1343,1114],{"class":242},[80,1345,1347],{"class":82,"line":1346},29,[80,1348,850],{"class":90},[80,1350,1352,1354],{"class":82,"line":1351},30,[80,1353,405],{"class":90},[80,1355,294],{"class":94},[10,1357,1358,1359,1361,1362,1364],{},"Lorsque l'URL demandée est ",[46,1360,1149],{},", le serveur écoutera les données envoyées par le client, qui sont le corps de la demande. Lorsque la demande est terminée, le serveur redirige le client vers le fichier ",[46,1363,332],{},". C'est un comportement classique lorsque un formulaire est soumis dans une application rendue par le serveur.",[10,1366,1367,1368,1370],{},"Dans notre fichier ",[46,1369,332],{},", ajoutons un formulaire pour envoyer des messages au serveur :",[71,1372,1374],{"className":531,"code":1373,"language":384,"meta":76,"style":76},"\u003C!doctype html>\n\u003Chtml lang=\"en\">\n  \u003Chead>\n    \u003Cmeta charset=\"UTF-8\" \u002F>\n    \u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" \u002F>\n    \u003Ctitle>Node SSE\u003C\u002Ftitle>\n  \u003C\u002Fhead>\n  \u003Cbody>\n    \u003Cform action=\"\u002Fmessage\" method=\"post\">\n      \u003Cinput type=\"text\" name=\"message\" \u002F>\n      \u003Cbutton type=\"submit\">Envoyer\u003C\u002Fbutton>\n    \u003C\u002Fform>\n  \u003C\u002Fbody>\n\u003C\u002Fhtml>\n",[46,1375,1376,1386,1404,1412,1430,1458,1474,1482,1490,1522,1555,1584,1593,1601],{"__ignoreMap":76},[80,1377,1378,1380,1382,1384],{"class":82,"line":83},[80,1379,539],{"class":90},[80,1381,542],{"class":274},[80,1383,344],{"class":166},[80,1385,547],{"class":90},[80,1387,1388,1390,1392,1394,1396,1398,1400,1402],{"class":82,"line":114},[80,1389,552],{"class":90},[80,1391,384],{"class":274},[80,1393,557],{"class":166},[80,1395,173],{"class":90},[80,1397,562],{"class":90},[80,1399,565],{"class":107},[80,1401,562],{"class":90},[80,1403,547],{"class":90},[80,1405,1406,1408,1410],{"class":82,"line":135},[80,1407,574],{"class":90},[80,1409,577],{"class":274},[80,1411,547],{"class":90},[80,1413,1414,1416,1418,1420,1422,1424,1426,1428],{"class":82,"line":156},[80,1415,584],{"class":90},[80,1417,587],{"class":274},[80,1419,590],{"class":166},[80,1421,173],{"class":90},[80,1423,562],{"class":90},[80,1425,597],{"class":107},[80,1427,562],{"class":90},[80,1429,602],{"class":90},[80,1431,1432,1434,1436,1438,1440,1442,1444,1446,1448,1450,1452,1454,1456],{"class":82,"line":163},[80,1433,584],{"class":90},[80,1435,587],{"class":274},[80,1437,611],{"class":166},[80,1439,173],{"class":90},[80,1441,562],{"class":90},[80,1443,618],{"class":107},[80,1445,562],{"class":90},[80,1447,623],{"class":166},[80,1449,173],{"class":90},[80,1451,562],{"class":90},[80,1453,630],{"class":107},[80,1455,562],{"class":90},[80,1457,602],{"class":90},[80,1459,1460,1462,1464,1466,1468,1470,1472],{"class":82,"line":183},[80,1461,584],{"class":90},[80,1463,641],{"class":274},[80,1465,644],{"class":90},[80,1467,647],{"class":94},[80,1469,650],{"class":90},[80,1471,641],{"class":274},[80,1473,547],{"class":90},[80,1475,1476,1478,1480],{"class":82,"line":197},[80,1477,659],{"class":90},[80,1479,577],{"class":274},[80,1481,547],{"class":90},[80,1483,1484,1486,1488],{"class":82,"line":202},[80,1485,574],{"class":90},[80,1487,670],{"class":274},[80,1489,547],{"class":90},[80,1491,1492,1494,1497,1500,1502,1504,1506,1508,1511,1513,1515,1518,1520],{"class":82,"line":239},[80,1493,584],{"class":90},[80,1495,1496],{"class":274},"form",[80,1498,1499],{"class":166}," action",[80,1501,173],{"class":90},[80,1503,562],{"class":90},[80,1505,1149],{"class":107},[80,1507,562],{"class":90},[80,1509,1510],{"class":166}," method",[80,1512,173],{"class":90},[80,1514,562],{"class":90},[80,1516,1517],{"class":107},"post",[80,1519,562],{"class":90},[80,1521,547],{"class":90},[80,1523,1524,1527,1530,1533,1535,1537,1540,1542,1544,1546,1548,1551,1553],{"class":82,"line":246},[80,1525,1526],{"class":90},"      \u003C",[80,1528,1529],{"class":274},"input",[80,1531,1532],{"class":166}," type",[80,1534,173],{"class":90},[80,1536,562],{"class":90},[80,1538,1539],{"class":107},"text",[80,1541,562],{"class":90},[80,1543,611],{"class":166},[80,1545,173],{"class":90},[80,1547,562],{"class":90},[80,1549,1550],{"class":107},"message",[80,1552,562],{"class":90},[80,1554,602],{"class":90},[80,1556,1557,1559,1562,1564,1566,1568,1571,1573,1575,1578,1580,1582],{"class":82,"line":264},[80,1558,1526],{"class":90},[80,1560,1561],{"class":274},"button",[80,1563,1532],{"class":166},[80,1565,173],{"class":90},[80,1567,562],{"class":90},[80,1569,1570],{"class":107},"submit",[80,1572,562],{"class":90},[80,1574,644],{"class":90},[80,1576,1577],{"class":94},"Envoyer",[80,1579,650],{"class":90},[80,1581,1561],{"class":274},[80,1583,547],{"class":90},[80,1585,1586,1589,1591],{"class":82,"line":297},[80,1587,1588],{"class":90},"    \u003C\u002F",[80,1590,1496],{"class":274},[80,1592,547],{"class":90},[80,1594,1595,1597,1599],{"class":82,"line":302},[80,1596,659],{"class":90},[80,1598,670],{"class":274},[80,1600,547],{"class":90},[80,1602,1603,1605,1607],{"class":82,"line":339},[80,1604,650],{"class":90},[80,1606,384],{"class":274},[80,1608,547],{"class":90},[31,1610,1612],{"id":1611},"diffuser-le-message","Diffuser le Message",[10,1614,1615],{},"La fonctionnalité manquante est la diffusion du message. Comment diffusons-nous le message et qu'est-ce que cela signifie ?",[10,1617,1618],{},"Une diffusion consiste à envoyer un message à tout le monde. Sinon, c'est unicast, lorsque un message est envoyé à une personne spécifique.",[10,1620,1621],{},[1622,1623],"img",{"alt":1624,"src":1625},"Diffusion vs Unicast","\u002Fposts\u002Fcreate-real-time-nodejs-apps-with-server-sent-events\u002Fbroadcast-vs-unicast.png",[10,1627,1628],{},"Dans notre cas, le client A enverra un message au serveur (unicast). Ensuite, le serveur enverra ce message à chaque client (diffusion).",[10,1630,1631],{},"Le unicast a été mis en œuvre dans la section précédente. Pour la diffusion, nous devons garder une trace des clients connectés afin de passer en revue lorsque un message est reçu.",[71,1633,1635],{"className":73,"code":1634,"language":75,"meta":76,"style":76},"\u002F\u002F ...\n\n\u002F\u002F Garder une trace des clients connectés\nconst connection = new Set()\n\nconst server = createServer((req, res) => {\n  \u002F\u002F Point de terminaison `\u002Fevents` utilisé pour écouter les messages\n  if (req.url === '\u002Fevents') {\n    res.setHeader('Content-Type', 'text\u002Fevent-stream')\n    res.setHeader('Cache-Control', 'no-cache')\n\n    connection.add(res)\n\n    req.on('close', () => {\n      connection.delete(res)\n    })\n\n    \u002F\u002F Point de terminaison `\u002Fmessages` utilisé pour envoyer des messages\n  }\n  else if (req.url === '\u002Fmessage') {\n    let body = ''\n\n    req.on('data', (chunk) => {\n      body += chunk\n    })\n\n    req.on('end', () => {\n      \u002F\u002F Diffuser le message à tous les clients connectés\n      for (const client of connection)\n        client.write(`data: ${body}\\n\\n`)\n\n      \u002F\u002F Rediriger vers le fichier index.html\n      res.statusCode = 302\n      res.setHeader('Location', '\u002F')\n      res.end()\n    })\n  }\n  else {\n    \u002F\u002F Servir le fichier index.html\n    \u002F\u002F ...\n  }\n})\n\n\u002F\u002F ...\n",[46,1636,1637,1641,1645,1650,1667,1671,1697,1701,1725,1751,1777,1781,1798,1802,1827,1843,1849,1853,1857,1861,1887,1897,1901,1929,1937,1943,1947,1971,1976,1996,2025,2030,2035,2048,2075,2086,2093,2098,2105,2110,2115,2120,2127,2132],{"__ignoreMap":76},[80,1638,1639],{"class":82,"line":83},[80,1640,715],{"class":242},[80,1642,1643],{"class":82,"line":114},[80,1644,160],{"emptyLinePlaceholder":159},[80,1646,1647],{"class":82,"line":135},[80,1648,1649],{"class":242},"\u002F\u002F Garder une trace des clients connectés\n",[80,1651,1652,1654,1657,1659,1662,1665],{"class":82,"line":156},[80,1653,167],{"class":166},[80,1655,1656],{"class":94}," connection ",[80,1658,173],{"class":90},[80,1660,1661],{"class":90}," new",[80,1663,1664],{"class":212}," Set",[80,1666,399],{"class":94},[80,1668,1669],{"class":82,"line":163},[80,1670,160],{"emptyLinePlaceholder":159},[80,1672,1673,1675,1677,1679,1681,1683,1685,1687,1689,1691,1693,1695],{"class":82,"line":183},[80,1674,167],{"class":166},[80,1676,207],{"class":94},[80,1678,173],{"class":90},[80,1680,121],{"class":212},[80,1682,215],{"class":94},[80,1684,215],{"class":90},[80,1686,221],{"class":220},[80,1688,224],{"class":90},[80,1690,227],{"class":220},[80,1692,230],{"class":90},[80,1694,233],{"class":166},[80,1696,236],{"class":90},[80,1698,1699],{"class":82,"line":197},[80,1700,750],{"class":242},[80,1702,1703,1705,1707,1709,1711,1713,1715,1717,1719,1721,1723],{"class":82,"line":202},[80,1704,755],{"class":86},[80,1706,758],{"class":274},[80,1708,221],{"class":94},[80,1710,252],{"class":90},[80,1712,765],{"class":94},[80,1714,768],{"class":90},[80,1716,104],{"class":90},[80,1718,704],{"class":107},[80,1720,277],{"class":90},[80,1722,777],{"class":274},[80,1724,780],{"class":90},[80,1726,1727,1729,1731,1733,1735,1737,1739,1741,1743,1745,1747,1749],{"class":82,"line":239},[80,1728,785],{"class":94},[80,1730,252],{"class":90},[80,1732,271],{"class":212},[80,1734,215],{"class":274},[80,1736,277],{"class":90},[80,1738,280],{"class":107},[80,1740,277],{"class":90},[80,1742,224],{"class":90},[80,1744,104],{"class":90},[80,1746,804],{"class":107},[80,1748,277],{"class":90},[80,1750,294],{"class":274},[80,1752,1753,1755,1757,1759,1761,1763,1765,1767,1769,1771,1773,1775],{"class":82,"line":246},[80,1754,785],{"class":94},[80,1756,252],{"class":90},[80,1758,271],{"class":212},[80,1760,215],{"class":274},[80,1762,277],{"class":90},[80,1764,823],{"class":107},[80,1766,277],{"class":90},[80,1768,224],{"class":90},[80,1770,104],{"class":90},[80,1772,832],{"class":107},[80,1774,277],{"class":90},[80,1776,294],{"class":274},[80,1778,1779],{"class":82,"line":264},[80,1780,160],{"emptyLinePlaceholder":159},[80,1782,1783,1786,1788,1791,1793,1796],{"class":82,"line":297},[80,1784,1785],{"class":94},"    connection",[80,1787,252],{"class":90},[80,1789,1790],{"class":212},"add",[80,1792,215],{"class":274},[80,1794,1795],{"class":94},"res",[80,1797,294],{"class":274},[80,1799,1800],{"class":82,"line":302},[80,1801,160],{"emptyLinePlaceholder":159},[80,1803,1804,1806,1808,1810,1812,1814,1817,1819,1821,1823,1825],{"class":82,"line":339},[80,1805,1177],{"class":94},[80,1807,252],{"class":90},[80,1809,1182],{"class":212},[80,1811,215],{"class":274},[80,1813,277],{"class":90},[80,1815,1816],{"class":107},"close",[80,1818,277],{"class":90},[80,1820,224],{"class":90},[80,1822,436],{"class":90},[80,1824,233],{"class":166},[80,1826,236],{"class":90},[80,1828,1829,1832,1834,1837,1839,1841],{"class":82,"line":367},[80,1830,1831],{"class":94},"      connection",[80,1833,252],{"class":90},[80,1835,1836],{"class":212},"delete",[80,1838,215],{"class":274},[80,1840,1795],{"class":94},[80,1842,294],{"class":274},[80,1844,1845,1847],{"class":82,"line":372},[80,1846,1220],{"class":90},[80,1848,294],{"class":274},[80,1850,1851],{"class":82,"line":389},[80,1852,160],{"emptyLinePlaceholder":159},[80,1854,1855],{"class":82,"line":402},[80,1856,1123],{"class":242},[80,1858,1859],{"class":82,"line":410},[80,1860,850],{"class":90},[80,1862,1863,1865,1867,1869,1871,1873,1875,1877,1879,1881,1883,1885],{"class":82,"line":415},[80,1864,855],{"class":86},[80,1866,1134],{"class":86},[80,1868,758],{"class":274},[80,1870,221],{"class":94},[80,1872,252],{"class":90},[80,1874,765],{"class":94},[80,1876,768],{"class":90},[80,1878,104],{"class":90},[80,1880,1149],{"class":107},[80,1882,277],{"class":90},[80,1884,777],{"class":274},[80,1886,780],{"class":90},[80,1888,1889,1891,1893,1895],{"class":82,"line":443},[80,1890,1160],{"class":166},[80,1892,1163],{"class":94},[80,1894,258],{"class":90},[80,1896,1168],{"class":90},[80,1898,1899],{"class":82,"line":487},[80,1900,160],{"emptyLinePlaceholder":159},[80,1902,1903,1905,1907,1909,1911,1913,1915,1917,1919,1921,1923,1925,1927],{"class":82,"line":1000},[80,1904,1177],{"class":94},[80,1906,252],{"class":90},[80,1908,1182],{"class":212},[80,1910,215],{"class":274},[80,1912,277],{"class":90},[80,1914,1189],{"class":107},[80,1916,277],{"class":90},[80,1918,224],{"class":90},[80,1920,758],{"class":90},[80,1922,1198],{"class":220},[80,1924,230],{"class":90},[80,1926,233],{"class":166},[80,1928,236],{"class":90},[80,1930,1931,1933,1935],{"class":82,"line":1005},[80,1932,1209],{"class":94},[80,1934,1212],{"class":90},[80,1936,1215],{"class":94},[80,1938,1939,1941],{"class":82,"line":1324},[80,1940,1220],{"class":90},[80,1942,294],{"class":274},[80,1944,1945],{"class":82,"line":1329},[80,1946,160],{"emptyLinePlaceholder":159},[80,1948,1949,1951,1953,1955,1957,1959,1961,1963,1965,1967,1969],{"class":82,"line":1336},[80,1950,1177],{"class":94},[80,1952,252],{"class":90},[80,1954,1182],{"class":212},[80,1956,215],{"class":274},[80,1958,277],{"class":90},[80,1960,396],{"class":107},[80,1962,277],{"class":90},[80,1964,224],{"class":90},[80,1966,436],{"class":90},[80,1968,233],{"class":166},[80,1970,236],{"class":90},[80,1972,1973],{"class":82,"line":1341},[80,1974,1975],{"class":242},"      \u002F\u002F Diffuser le message à tous les clients connectés\n",[80,1977,1978,1981,1983,1985,1988,1991,1994],{"class":82,"line":1346},[80,1979,1980],{"class":86},"      for",[80,1982,758],{"class":274},[80,1984,167],{"class":166},[80,1986,1987],{"class":94}," client",[80,1989,1990],{"class":90}," of",[80,1992,1993],{"class":94}," connection",[80,1995,294],{"class":274},[80,1997,1998,2001,2003,2005,2007,2009,2012,2014,2016,2018,2021,2023],{"class":82,"line":1351},[80,1999,2000],{"class":94},"        client",[80,2002,252],{"class":90},[80,2004,379],{"class":212},[80,2006,215],{"class":274},[80,2008,456],{"class":90},[80,2010,2011],{"class":107},"data: ",[80,2013,462],{"class":90},[80,2015,670],{"class":94},[80,2017,405],{"class":90},[80,2019,2020],{"class":94},"\\n\\n",[80,2022,456],{"class":90},[80,2024,294],{"class":274},[80,2026,2028],{"class":82,"line":2027},31,[80,2029,160],{"emptyLinePlaceholder":159},[80,2031,2033],{"class":82,"line":2032},32,[80,2034,1264],{"class":242},[80,2036,2038,2040,2042,2044,2046],{"class":82,"line":2037},33,[80,2039,1269],{"class":94},[80,2041,252],{"class":90},[80,2043,255],{"class":94},[80,2045,258],{"class":90},[80,2047,1278],{"class":193},[80,2049,2051,2053,2055,2057,2059,2061,2063,2065,2067,2069,2071,2073],{"class":82,"line":2050},34,[80,2052,1269],{"class":94},[80,2054,252],{"class":90},[80,2056,271],{"class":212},[80,2058,215],{"class":274},[80,2060,277],{"class":90},[80,2062,1293],{"class":107},[80,2064,277],{"class":90},[80,2066,224],{"class":90},[80,2068,104],{"class":90},[80,2070,480],{"class":107},[80,2072,277],{"class":90},[80,2074,294],{"class":274},[80,2076,2078,2080,2082,2084],{"class":82,"line":2077},35,[80,2079,1269],{"class":94},[80,2081,252],{"class":90},[80,2083,396],{"class":212},[80,2085,399],{"class":274},[80,2087,2089,2091],{"class":82,"line":2088},36,[80,2090,1220],{"class":90},[80,2092,294],{"class":274},[80,2094,2096],{"class":82,"line":2095},37,[80,2097,850],{"class":90},[80,2099,2101,2103],{"class":82,"line":2100},38,[80,2102,855],{"class":86},[80,2104,236],{"class":90},[80,2106,2108],{"class":82,"line":2107},39,[80,2109,862],{"class":242},[80,2111,2113],{"class":82,"line":2112},40,[80,2114,1114],{"class":242},[80,2116,2118],{"class":82,"line":2117},41,[80,2119,850],{"class":90},[80,2121,2123,2125],{"class":82,"line":2122},42,[80,2124,405],{"class":90},[80,2126,294],{"class":94},[80,2128,2130],{"class":82,"line":2129},43,[80,2131,160],{"emptyLinePlaceholder":159},[80,2133,2135],{"class":82,"line":2134},44,[80,2136,715],{"class":242},[10,2138,2139,2140,2142,2143,2146],{},"Lorsqu'un client se connecte à l'endpoint ",[46,2141,704],{},", le serveur ajoute l'objet de réponse à l'ensemble ",[46,2144,2145],{},"connection",". Lorsque la connexion est fermée, l'objet de réponse est supprimé de l'ensemble. Facile !",[10,2148,2149,2150,2152],{},"Nous gardons cet objet pour pouvoir envoyer des données au client en utilisant la méthode ",[46,2151,379],{},". C'est le cœur du système.",[10,2154,2155,2156,2158,2159,2161],{},"Lorsqu'un message est reçu sur l'endpoint ",[46,2157,1149],{},", le serveur parcourt la ",[46,2160,2145],{}," précédemment sauvegardée pour envoyer le message à chaque client.",[10,2163,2164,2165,2167,2168,2171,2172,2174,2175,2177,2178,2180,2181,252],{},"Nous pouvons observer le mot clé ",[46,2166,1189],{}," dans le message et les doubles ",[46,2169,2170],{},"\\n"," à la fin. C'est le format des événements envoyés par le serveur. Le mot clé ",[46,2173,1189],{}," est utilisé pour envoyer des données au client, et le double ",[46,2176,2170],{}," est utilisé pour indiquer au client que le message est terminé. Le client n'exposera que le contenu après le mot clé ",[46,2179,1189],{}," et avant le double ",[46,2182,2170],{},[10,2184,2185,2186,2189,2190,252],{},"Par exemple, si le serveur envoie ",[46,2187,2188],{},"data: Bonjour\\n\\n",", le client verra ",[46,2191,2192],{},"Bonjour",[31,2194,2196],{"id":2195},"api-eventsource","API EventSource",[10,2198,2199,2200,252],{},"Notre serveur gère la connexion et diffuse des messages, mais le client n'est toujours pas capable de se connecter à l'endpoint SSE, ressource ",[46,2201,704],{},[10,2203,2204,2205,2208],{},"Pour se connecter au serveur, nous utiliserons l'API ",[46,2206,2207],{},"EventSource",". Cette API est une API JavaScript native pour gérer les événements envoyés par le serveur, et elle est très simple.",[10,2210,2211],{},"Au début de notre fichier, nous devons avoir un petit script pour établir la connexion et gérer les messages :",[71,2213,2215],{"className":531,"code":2214,"language":384,"meta":76,"style":76},"\u003C!doctype html>\n\u003Chtml lang=\"en\">\n  \u003Chead>\n    \u003Cmeta charset=\"UTF-8\" \u002F>\n    \u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" \u002F>\n    \u003Ctitle>Node SSE\u003C\u002Ftitle>\n    \u003Cscript>\n      \u002F\u002F Se connecter au serveur\n      const eventSource = new EventSource(\"\u002Fevents\");\n\n      const ul = document.querySelector(\"ul\");\n\n      \u002F\u002F Écouter le message\n      eventSource.onmessage = (event) => {\n        \u002F\u002F Ajouter le message à la liste\n        const li = document.createElement(\"li\");\n        li.innerText = event.data;\n\n        ul.appendChild(li);\n      };\n    \u003C\u002Fscript>\n  \u003C\u002Fhead>\n  \u003Cbody>\n    \u003Cul>\u003C\u002Ful>\n\n    \u003Cform action=\"\u002Fmessage\" method=\"post\">\n      \u003Cinput type=\"text\" name=\"message\" \u002F>\n      \u003Cbutton type=\"submit\">Envoyer\u003C\u002Fbutton>\n    \u003C\u002Fform>\n  \u003C\u002Fbody>\n\u003C\u002Fhtml>\n",[46,2216,2217,2227,2245,2253,2271,2299,2315,2324,2329,2357,2361,2390,2394,2399,2422,2427,2456,2477,2481,2499,2504,2512,2520,2528,2541,2545,2573,2601,2627,2635,2643],{"__ignoreMap":76},[80,2218,2219,2221,2223,2225],{"class":82,"line":83},[80,2220,539],{"class":90},[80,2222,542],{"class":274},[80,2224,344],{"class":166},[80,2226,547],{"class":90},[80,2228,2229,2231,2233,2235,2237,2239,2241,2243],{"class":82,"line":114},[80,2230,552],{"class":90},[80,2232,384],{"class":274},[80,2234,557],{"class":166},[80,2236,173],{"class":90},[80,2238,562],{"class":90},[80,2240,565],{"class":107},[80,2242,562],{"class":90},[80,2244,547],{"class":90},[80,2246,2247,2249,2251],{"class":82,"line":135},[80,2248,574],{"class":90},[80,2250,577],{"class":274},[80,2252,547],{"class":90},[80,2254,2255,2257,2259,2261,2263,2265,2267,2269],{"class":82,"line":156},[80,2256,584],{"class":90},[80,2258,587],{"class":274},[80,2260,590],{"class":166},[80,2262,173],{"class":90},[80,2264,562],{"class":90},[80,2266,597],{"class":107},[80,2268,562],{"class":90},[80,2270,602],{"class":90},[80,2272,2273,2275,2277,2279,2281,2283,2285,2287,2289,2291,2293,2295,2297],{"class":82,"line":163},[80,2274,584],{"class":90},[80,2276,587],{"class":274},[80,2278,611],{"class":166},[80,2280,173],{"class":90},[80,2282,562],{"class":90},[80,2284,618],{"class":107},[80,2286,562],{"class":90},[80,2288,623],{"class":166},[80,2290,173],{"class":90},[80,2292,562],{"class":90},[80,2294,630],{"class":107},[80,2296,562],{"class":90},[80,2298,602],{"class":90},[80,2300,2301,2303,2305,2307,2309,2311,2313],{"class":82,"line":183},[80,2302,584],{"class":90},[80,2304,641],{"class":274},[80,2306,644],{"class":90},[80,2308,647],{"class":94},[80,2310,650],{"class":90},[80,2312,641],{"class":274},[80,2314,547],{"class":90},[80,2316,2317,2319,2322],{"class":82,"line":197},[80,2318,584],{"class":90},[80,2320,2321],{"class":274},"script",[80,2323,547],{"class":90},[80,2325,2326],{"class":82,"line":202},[80,2327,2328],{"class":242},"      \u002F\u002F Se connecter au serveur\n",[80,2330,2331,2334,2337,2339,2341,2344,2346,2348,2350,2352,2354],{"class":82,"line":239},[80,2332,2333],{"class":166},"      const",[80,2335,2336],{"class":94}," eventSource ",[80,2338,173],{"class":90},[80,2340,1661],{"class":90},[80,2342,2343],{"class":212}," EventSource",[80,2345,215],{"class":94},[80,2347,562],{"class":90},[80,2349,704],{"class":107},[80,2351,562],{"class":90},[80,2353,230],{"class":94},[80,2355,2356],{"class":90},";\n",[80,2358,2359],{"class":82,"line":246},[80,2360,160],{"emptyLinePlaceholder":159},[80,2362,2363,2365,2368,2370,2373,2375,2378,2380,2382,2384,2386,2388],{"class":82,"line":264},[80,2364,2333],{"class":166},[80,2366,2367],{"class":94}," ul ",[80,2369,173],{"class":90},[80,2371,2372],{"class":94}," document",[80,2374,252],{"class":90},[80,2376,2377],{"class":212},"querySelector",[80,2379,215],{"class":94},[80,2381,562],{"class":90},[80,2383,51],{"class":107},[80,2385,562],{"class":90},[80,2387,230],{"class":94},[80,2389,2356],{"class":90},[80,2391,2392],{"class":82,"line":297},[80,2393,160],{"emptyLinePlaceholder":159},[80,2395,2396],{"class":82,"line":302},[80,2397,2398],{"class":242},"      \u002F\u002F Écouter le message\n",[80,2400,2401,2404,2406,2409,2411,2413,2416,2418,2420],{"class":82,"line":339},[80,2402,2403],{"class":94},"      eventSource",[80,2405,252],{"class":90},[80,2407,2408],{"class":212},"onmessage",[80,2410,258],{"class":90},[80,2412,758],{"class":90},[80,2414,2415],{"class":220},"event",[80,2417,230],{"class":90},[80,2419,233],{"class":166},[80,2421,236],{"class":90},[80,2423,2424],{"class":82,"line":367},[80,2425,2426],{"class":242},"        \u002F\u002F Ajouter le message à la liste\n",[80,2428,2429,2432,2435,2437,2439,2441,2444,2446,2448,2450,2452,2454],{"class":82,"line":372},[80,2430,2431],{"class":166},"        const",[80,2433,2434],{"class":94}," li",[80,2436,258],{"class":90},[80,2438,2372],{"class":94},[80,2440,252],{"class":90},[80,2442,2443],{"class":212},"createElement",[80,2445,215],{"class":274},[80,2447,562],{"class":90},[80,2449,54],{"class":107},[80,2451,562],{"class":90},[80,2453,230],{"class":274},[80,2455,2356],{"class":90},[80,2457,2458,2461,2463,2466,2468,2471,2473,2475],{"class":82,"line":389},[80,2459,2460],{"class":94},"        li",[80,2462,252],{"class":90},[80,2464,2465],{"class":94},"innerText",[80,2467,258],{"class":90},[80,2469,2470],{"class":94}," event",[80,2472,252],{"class":90},[80,2474,1189],{"class":94},[80,2476,2356],{"class":90},[80,2478,2479],{"class":82,"line":402},[80,2480,160],{"emptyLinePlaceholder":159},[80,2482,2483,2486,2488,2491,2493,2495,2497],{"class":82,"line":410},[80,2484,2485],{"class":94},"        ul",[80,2487,252],{"class":90},[80,2489,2490],{"class":212},"appendChild",[80,2492,215],{"class":274},[80,2494,54],{"class":94},[80,2496,230],{"class":274},[80,2498,2356],{"class":90},[80,2500,2501],{"class":82,"line":415},[80,2502,2503],{"class":90},"      };\n",[80,2505,2506,2508,2510],{"class":82,"line":443},[80,2507,1588],{"class":90},[80,2509,2321],{"class":274},[80,2511,547],{"class":90},[80,2513,2514,2516,2518],{"class":82,"line":487},[80,2515,659],{"class":90},[80,2517,577],{"class":274},[80,2519,547],{"class":90},[80,2521,2522,2524,2526],{"class":82,"line":1000},[80,2523,574],{"class":90},[80,2525,670],{"class":274},[80,2527,547],{"class":90},[80,2529,2530,2532,2534,2537,2539],{"class":82,"line":1005},[80,2531,584],{"class":90},[80,2533,51],{"class":274},[80,2535,2536],{"class":90},">\u003C\u002F",[80,2538,51],{"class":274},[80,2540,547],{"class":90},[80,2542,2543],{"class":82,"line":1324},[80,2544,160],{"emptyLinePlaceholder":159},[80,2546,2547,2549,2551,2553,2555,2557,2559,2561,2563,2565,2567,2569,2571],{"class":82,"line":1329},[80,2548,584],{"class":90},[80,2550,1496],{"class":274},[80,2552,1499],{"class":166},[80,2554,173],{"class":90},[80,2556,562],{"class":90},[80,2558,1149],{"class":107},[80,2560,562],{"class":90},[80,2562,1510],{"class":166},[80,2564,173],{"class":90},[80,2566,562],{"class":90},[80,2568,1517],{"class":107},[80,2570,562],{"class":90},[80,2572,547],{"class":90},[80,2574,2575,2577,2579,2581,2583,2585,2587,2589,2591,2593,2595,2597,2599],{"class":82,"line":1336},[80,2576,1526],{"class":90},[80,2578,1529],{"class":274},[80,2580,1532],{"class":166},[80,2582,173],{"class":90},[80,2584,562],{"class":90},[80,2586,1539],{"class":107},[80,2588,562],{"class":90},[80,2590,611],{"class":166},[80,2592,173],{"class":90},[80,2594,562],{"class":90},[80,2596,1550],{"class":107},[80,2598,562],{"class":90},[80,2600,602],{"class":90},[80,2602,2603,2605,2607,2609,2611,2613,2615,2617,2619,2621,2623,2625],{"class":82,"line":1341},[80,2604,1526],{"class":90},[80,2606,1561],{"class":274},[80,2608,1532],{"class":166},[80,2610,173],{"class":90},[80,2612,562],{"class":90},[80,2614,1570],{"class":107},[80,2616,562],{"class":90},[80,2618,644],{"class":90},[80,2620,1577],{"class":94},[80,2622,650],{"class":90},[80,2624,1561],{"class":274},[80,2626,547],{"class":90},[80,2628,2629,2631,2633],{"class":82,"line":1346},[80,2630,1588],{"class":90},[80,2632,1496],{"class":274},[80,2634,547],{"class":90},[80,2636,2637,2639,2641],{"class":82,"line":1351},[80,2638,659],{"class":90},[80,2640,670],{"class":274},[80,2642,547],{"class":90},[80,2644,2645,2647,2649],{"class":82,"line":2027},[80,2646,650],{"class":90},[80,2648,384],{"class":274},[80,2650,547],{"class":90},[10,2652,2653,2654,2656,2657,2659],{},"Le script commence par demander la ressource ",[46,2655,704],{}," au serveur en utilisant l'API ",[46,2658,2207],{},". Cela gérera automatiquement la reconnexion.",[10,2661,2662,2663,2665],{},"Ensuite, nous enregistrons une méthode ",[46,2664,2408],{}," qui sera déclenchée chaque fois qu'un message est reçu par le client depuis le serveur.",[10,2667,2668,2669,2671],{},"Pour avoir quelque chose de plus semblable à un chat, nous allons ajouter une liste pour afficher les messages. Lorsqu'un message est reçu, nous créerons un élément ",[46,2670,54],{}," et l'ajouterons à la liste.",[10,2673,2674],{},"Maintenant, nous pouvons ouvrir deux onglets dans notre navigateur et envoyer des messages. Ils apparaîtront dans l'autre onglet. :magie:",[10,2676,2677],{},[1622,2678],{"alt":2679,"src":2680},"Chat Node SSE","\u002Fposts\u002Fcreate-real-time-nodejs-apps-with-server-sent-events\u002Fnode-sse-chat.gif",[31,2682,2684],{"id":2683},"derniers-mots","Derniers Mots",[10,2686,2687],{},"Même si c'est un exemple très simple, rappelez-vous que la logique de stockage des clients et de diffusion avec une boucle est la même qu'une application plus complexe.",[10,2689,2690],{},"Il y a deux choses à retenir avec les événements envoyés par le serveur :",[51,2692,2693,2699],{},[54,2694,2695,2696,2698],{},"Utilisez l'API ",[46,2697,2207],{}," pour vous connecter au serveur",[54,2700,2701,2702,2704],{},"Répondez avec ",[46,2703,1016],{}," depuis le serveur",[10,2706,2707],{},"Rien de plus. C'est simple, c'est HTTP, et c'est puissant.",[10,2709,2710],{},"J'espère que cet article vous aidera à comprendre comment fonctionnent les événements envoyés par le serveur et vous donnera la confiance nécessaire pour les utiliser dans vos projets. C'est un outil très puissant pour construire des applications en temps réel sans la complexité des WebSockets.",[2712,2713,2714],"style",{},"html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}",{"title":76,"searchDepth":114,"depth":114,"links":2716},[2717,2718,2719,2720,2721,2722,2723,2724],{"id":33,"depth":114,"text":34},{"id":40,"depth":114,"text":41},{"id":522,"depth":114,"text":523},{"id":694,"depth":114,"text":695},{"id":1035,"depth":114,"text":1036},{"id":1611,"depth":114,"text":1612},{"id":2195,"depth":114,"text":2196},{"id":2683,"depth":114,"text":2684},"4949967d-cd6e-4cf8-b28e-a71db1aaf014","2024-04-03","Créer des applications Node.js en temps réel en utilisant les SSE pour une communication serveur-client efficace sans la complexité des WebSockets.","md",{},"\u002Ffr\u002Fposts\u002Fcreate-real-time-nodejs-apps-with-server-sent-events","---\ncontentId: 4949967d-cd6e-4cf8-b28e-a71db1aaf014\ntitle: Créer des Applications Node.js en Temps Réel avec les SSE\ndescription: Créer des applications Node.js en temps réel en utilisant les SSE pour une communication serveur-client efficace sans la complexité des WebSockets.\ndate: 2024-04-03\n---\n\nJe suis convaincu que pour comprendre un concept ou une technologie, nous devons tous essayer **de cuisiner quelque chose avec** en utilisant le plus bas niveau possible. Cela ne concerne pas seulement la programmation, mais tout dans la vie.\n\nDans la suite de l'article [Temps réel sans WebSocket](.\u002Fsimplify-real-time-with-server-sent-events-over-websockets.md), je pense que construire une petite application client-serveur utilisant des événements sent par le serveur (SSE) est un bon moyen de comprendre comment cela fonctionne et de nous donner la confiance nécessaire pour l'utiliser dans nos projets.\n\nCette application client-serveur sera un petit chat où le client enverra un message au serveur et le serveur diffusera le message à tous les clients connectés. Pas de bibliothèque externe, pas de framework, juste Node.js.\n\n## Exigences\n\nPour suivre ce tutoriel, nous devons avoir Node.js installé sur notre machine. C'est tout.\n\n## Le Serveur\n\nCréez un fichier nommé `server.mjs`. Ce fichier contiendra le code du serveur pour effectuer les actions suivantes :\n\n- Servir le code côté client c'est-à-dire un simple fichier HTML\n- Recevoir des messages des clients\n- Accepter les connexions des événements envoyés par le serveur et diffuser des messages\n\nMais pour l'instant, commençons par servir le code côté client. Notre serveur sera construit en utilisant le module `http` natif de Node.js.\n\n```js\nimport { readFileSync } from 'node:fs'\nimport { createServer } from 'node:http'\nimport { join } from 'node:path'\n\nconst hostname = '127.0.0.1'\nconst port = 3000\n\nconst server = createServer((req, res) => {\n  \u002F\u002F Servir le fichier index.html\n  res.statusCode = 200\n  res.setHeader('Content-Type', 'text\u002Fhtml')\n\n  const htmlFile = join(process.cwd(), 'index.html')\n  const html = readFileSync(htmlFile, 'utf-8')\n\n  res.write(html)\n  res.end()\n})\n\nserver.listen(port, hostname, () => {\n  console.log(`Serveur en cours d'exécution à http:\u002F\u002F${hostname}:${port}\u002F`)\n})\n```\n\nLorsqu'une requête atteint le serveur, il répondra avec le contenu du fichier `index.html` en le lisant sur le disque. Le serveur écoute sur le port `3000`.\n\nNous pouvons démarrer notre serveur avec la commande suivante :\n\n```sh\nnode server.mjs\n```\n\n## Le Client\n\nCréez un fichier nommé `index.html` à la racine du projet. Ce fichier contiendra le code côté client :\n\n```html\n\u003C!doctype html>\n\u003Chtml lang=\"en\">\n  \u003Chead>\n    \u003Cmeta charset=\"UTF-8\" \u002F>\n    \u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" \u002F>\n    \u003Ctitle>Node SSE\u003C\u002Ftitle>\n  \u003C\u002Fhead>\n  \u003Cbody>\n  \u003C\u002Fbody>\n\u003C\u002Fhtml>\n```\n\nOui, c'est un fichier HTML très simple, rien de fantaisiste pour l'instant.\n\n## Connexion des Événements Envoyés par le Serveur\n\nMaintenant, ajoutons la connexion des événements envoyés par le serveur au serveur. Pour gérer les SSE, nous devons répondre avec certains en-têtes pour garder la connexion ouverte et envoyer des données. Ainsi, le client demandera une ressource et le serveur répondra avec un flux.\n\nCette ressource sera `\u002Fevents`. Mettons à jour le code du serveur :\n\n```js\n\u002F\u002F ...\n\nconst server = createServer((req, res) => {\n  \u002F\u002F Point de terminaison `\u002Fevents` utilisé pour écouter les messages\n  if (req.url === '\u002Fevents') {\n    res.setHeader('Content-Type', 'text\u002Fevent-stream')\n    res.setHeader('Cache-Control', 'no-cache')\n\n    \u002F\u002F TODO: Gérer la connexion\n  }\n  else {\n    \u002F\u002F Servir le fichier index.html\n    res.statusCode = 200\n    res.setHeader('Content-Type', 'text\u002Fhtml')\n\n    const htmlFile = join(process.cwd(), 'index.html')\n    const html = readFileSync(htmlFile, 'utf-8')\n\n    res.write(html)\n    res.end()\n  }\n})\n\n\u002F\u002F ...\n```\n\nLorsque le client demande la ressource `\u002Fevents`, le serveur répondra avec les en-têtes `Content-Type: text\u002Fevent-stream` et `Cache-Control: no-cache`. Cela informe le client que la connexion est un flux, c'est-à-dire que le serveur enverra des données au client à tout moment. La connexion ne doit pas être mise en cache pour éviter des problèmes avec le flux.\nIl est important de noter que nous n'appelons jamais la fonction `res.end()`. Cela est dû au fait que **la connexion doit rester ouverte** pour permettre au serveur d'envoyer des données au client. La fonction `res.end()` ferme la connexion.\n\n## Logique de Chat\n\nC'est très simple et simplifié au maximum, mais la logique sous-jacente reste la même qu'une application plus complexe.\n\nDans notre serveur, nous aurons une logique pour recevoir des messages des clients.\n\n```js\n\u002F\u002F ...\n\nconst server = createServer((req, res) => {\n  \u002F\u002F Point de terminaison `\u002Fevents` utilisé pour écouter les messages\n  if (req.url === '\u002Fevents') {\n    \u002F\u002F ...\n\n    \u002F\u002F Point de terminaison `\u002Fmessages` utilisé pour envoyer des messages\n  }\n  else if (req.url === '\u002Fmessage') {\n    let body = ''\n\n    req.on('data', (chunk) => {\n      body += chunk\n    })\n\n    req.on('end', () => {\n      \u002F\u002F TODO: diffuser le message\n\n      \u002F\u002F Rediriger vers le fichier index.html\n      res.statusCode = 302\n      res.setHeader('Location', '\u002F')\n      res.end()\n    })\n  }\n  else {\n    \u002F\u002F Servir le fichier index.html\n    \u002F\u002F ...\n  }\n})\n```\n\nLorsque l'URL demandée est `\u002Fmessage`, le serveur écoutera les données envoyées par le client, qui sont le corps de la demande. Lorsque la demande est terminée, le serveur redirige le client vers le fichier `index.html`. C'est un comportement classique lorsque un formulaire est soumis dans une application rendue par le serveur.\n\nDans notre fichier `index.html`, ajoutons un formulaire pour envoyer des messages au serveur :\n\n```html\n\u003C!doctype html>\n\u003Chtml lang=\"en\">\n  \u003Chead>\n    \u003Cmeta charset=\"UTF-8\" \u002F>\n    \u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" \u002F>\n    \u003Ctitle>Node SSE\u003C\u002Ftitle>\n  \u003C\u002Fhead>\n  \u003Cbody>\n    \u003Cform action=\"\u002Fmessage\" method=\"post\">\n      \u003Cinput type=\"text\" name=\"message\" \u002F>\n      \u003Cbutton type=\"submit\">Envoyer\u003C\u002Fbutton>\n    \u003C\u002Fform>\n  \u003C\u002Fbody>\n\u003C\u002Fhtml>\n```\n\n## Diffuser le Message\n\nLa fonctionnalité manquante est la diffusion du message. Comment diffusons-nous le message et qu'est-ce que cela signifie ?\n\nUne diffusion consiste à envoyer un message à tout le monde. Sinon, c'est unicast, lorsque un message est envoyé à une personne spécifique.\n\n![Diffusion vs Unicast](\u002Fposts\u002Fcreate-real-time-nodejs-apps-with-server-sent-events\u002Fbroadcast-vs-unicast.png)\n\nDans notre cas, le client A enverra un message au serveur (unicast). Ensuite, le serveur enverra ce message à chaque client (diffusion).\n\nLe unicast a été mis en œuvre dans la section précédente. Pour la diffusion, nous devons garder une trace des clients connectés afin de passer en revue lorsque un message est reçu.\n\n```js\n\u002F\u002F ...\n\n\u002F\u002F Garder une trace des clients connectés\nconst connection = new Set()\n\nconst server = createServer((req, res) => {\n  \u002F\u002F Point de terminaison `\u002Fevents` utilisé pour écouter les messages\n  if (req.url === '\u002Fevents') {\n    res.setHeader('Content-Type', 'text\u002Fevent-stream')\n    res.setHeader('Cache-Control', 'no-cache')\n\n    connection.add(res)\n\n    req.on('close', () => {\n      connection.delete(res)\n    })\n\n    \u002F\u002F Point de terminaison `\u002Fmessages` utilisé pour envoyer des messages\n  }\n  else if (req.url === '\u002Fmessage') {\n    let body = ''\n\n    req.on('data', (chunk) => {\n      body += chunk\n    })\n\n    req.on('end', () => {\n      \u002F\u002F Diffuser le message à tous les clients connectés\n      for (const client of connection)\n        client.write(`data: ${body}\\n\\n`)\n\n      \u002F\u002F Rediriger vers le fichier index.html\n      res.statusCode = 302\n      res.setHeader('Location', '\u002F')\n      res.end()\n    })\n  }\n  else {\n    \u002F\u002F Servir le fichier index.html\n    \u002F\u002F ...\n  }\n})\n\n\u002F\u002F ...\n```\n\nLorsqu'un client se connecte à l'endpoint `\u002Fevents`, le serveur ajoute l'objet de réponse à l'ensemble `connection`. Lorsque la connexion est fermée, l'objet de réponse est supprimé de l'ensemble. Facile !\n\nNous gardons cet objet pour pouvoir envoyer des données au client en utilisant la méthode `write`. C'est le cœur du système.\n\nLorsqu'un message est reçu sur l'endpoint `\u002Fmessage`, le serveur parcourt la `connection` précédemment sauvegardée pour envoyer le message à chaque client.\n\nNous pouvons observer le mot clé `data` dans le message et les doubles `\\n` à la fin. C'est le format des événements envoyés par le serveur. Le mot clé `data` est utilisé pour envoyer des données au client, et le double `\\n` est utilisé pour indiquer au client que le message est terminé. Le client n'exposera que le contenu après le mot clé `data` et avant le double `\\n`.\n\nPar exemple, si le serveur envoie `data: Bonjour\\n\\n`, le client verra `Bonjour`.\n\n## API EventSource\n\nNotre serveur gère la connexion et diffuse des messages, mais le client n'est toujours pas capable de se connecter à l'endpoint SSE, ressource `\u002Fevents`.\n\nPour se connecter au serveur, nous utiliserons l'API `EventSource`. Cette API est une API JavaScript native pour gérer les événements envoyés par le serveur, et elle est très simple.\n\nAu début de notre fichier, nous devons avoir un petit script pour établir la connexion et gérer les messages :\n\n```html\n\u003C!doctype html>\n\u003Chtml lang=\"en\">\n  \u003Chead>\n    \u003Cmeta charset=\"UTF-8\" \u002F>\n    \u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" \u002F>\n    \u003Ctitle>Node SSE\u003C\u002Ftitle>\n    \u003Cscript>\n      \u002F\u002F Se connecter au serveur\n      const eventSource = new EventSource(\"\u002Fevents\");\n\n      const ul = document.querySelector(\"ul\");\n\n      \u002F\u002F Écouter le message\n      eventSource.onmessage = (event) => {\n        \u002F\u002F Ajouter le message à la liste\n        const li = document.createElement(\"li\");\n        li.innerText = event.data;\n\n        ul.appendChild(li);\n      };\n    \u003C\u002Fscript>\n  \u003C\u002Fhead>\n  \u003Cbody>\n    \u003Cul>\u003C\u002Ful>\n\n    \u003Cform action=\"\u002Fmessage\" method=\"post\">\n      \u003Cinput type=\"text\" name=\"message\" \u002F>\n      \u003Cbutton type=\"submit\">Envoyer\u003C\u002Fbutton>\n    \u003C\u002Fform>\n  \u003C\u002Fbody>\n\u003C\u002Fhtml>\n```\n\nLe script commence par demander la ressource `\u002Fevents` au serveur en utilisant l'API `EventSource`. Cela gérera automatiquement la reconnexion.\n\nEnsuite, nous enregistrons une méthode `onmessage` qui sera déclenchée chaque fois qu'un message est reçu par le client depuis le serveur.\n\nPour avoir quelque chose de plus semblable à un chat, nous allons ajouter une liste pour afficher les messages. Lorsqu'un message est reçu, nous créerons un élément `li` et l'ajouterons à la liste.\n\nMaintenant, nous pouvons ouvrir deux onglets dans notre navigateur et envoyer des messages. Ils apparaîtront dans l'autre onglet. :magie:\n\n![Chat Node SSE](\u002Fposts\u002Fcreate-real-time-nodejs-apps-with-server-sent-events\u002Fnode-sse-chat.gif)\n\n## Derniers Mots\n\nMême si c'est un exemple très simple, rappelez-vous que la logique de stockage des clients et de diffusion avec une boucle est la même qu'une application plus complexe.\n\nIl y a deux choses à retenir avec les événements envoyés par le serveur :\n\n- Utilisez l'API `EventSource` pour vous connecter au serveur\n- Répondez avec `Content-Type: text\u002Fevent-stream` depuis le serveur\n\nRien de plus. C'est simple, c'est HTTP, et c'est puissant.\n\nJ'espère que cet article vous aidera à comprendre comment fonctionnent les événements envoyés par le serveur et vous donnera la confiance nécessaire pour les utiliser dans vos projets. C'est un outil très puissant pour construire des applications en temps réel sans la complexité des WebSockets.\n",null,{"title":5,"description":2727},{"loc":2730,"images":2735},[2736,2737],{"loc":1625},{"loc":2680},"fr\u002Fposts\u002Fcreate-real-time-nodejs-apps-with-server-sent-events","RTM8CAC98MUlqDbURQuRWlbQQW4RlMmJQeESMOt7aa4",1790086904664]