You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

287 lines
10 KiB

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

---
gitea: none
include_toc: true
---
[TOC]
# Architecture client/serveur
Un site Web est généralement constitué d'au moins 2 parties :
- *le côté client* : qui est l'ensemble des fichiers (HTML/CSS/JS/WASM) interprétés par le navigateur et exécuté sur le poste de travail de l'utilisateur.
- *le côté serveur* : qui va s'exécuter sur le serveur et qui répondra aux requêtes du navigateur. C'est lui qui va, entre autres, donner au navigateur les fichiers HTML/CSS/JS/WASM nécessaires à l'affichage du site.
La communication entre le client et le serveur se fait généralement via le protocole HTTP(S). Le client envoie une requête HTTP au serveur qui retourne une réponse HTTP.
<img title="" src="./HTTP%20queries.png" alt="" data-align="center">
L'affichage d'une page Web se déroule usuellement de la sorte :
```html
<!DOCTYPE>
<html>
<head>
<script src='...' defer></script>
<link rel='stylesheet' href='...'></link>
</head>
<body>
</body>
</html>
```
1. Le navigateur demande au serveur le fichier HTML correspondant à la page Web à afficher.
2. Le navigateur commence à lire et à interpréter le fichier HTML reçu.
3. Le navigateur lit la balise `<script>` et commence à télécharger le fichier correspondant. Comme la balise a l'attribut `defer`, le navigateur continue de lire et interpréter le fichier HTML.
4. Le navigateur lit la balise `<link>` et commence à télécharger le fichier CSS correspondant.
5. Le navigateur commence à construire l'arbre DOM à partir du contenu de `<body>`.
6. Une fois l'arbre DOM construit, il execute le script qui était `defer`.
7. Une fois l'exécution du script fini, le navigateur dessine la page Web pour la première fois.
## Optimisations
L'un des objectifs des développeur Web est de dessiner la page Web le plus tôt possible. Pour cela, il va user de diverses techniques :
- *Compresser les fichiers* avant de les transmettre, réduisant le temps de téléchargement des ressources nécessaires.
- *Réduire la taille des fichiers* en
- supprimant le code inutilisé
- le minimifiant (supprime les espaces et retours à la ligne)
- en l'uglifiant (réduit la taille du code au prix de sa lisibilité, e.g. en renommant les variables).
- pour les images, en réduisant sa résolution/qualité, ou en utilisant des formats vectoriels (SVG).
- pour les images, afficher un *placeholder* ou version en basse résolution puis progressivement [la remplacer une version de plus haute résolution](https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Tutorials/js13kGames/Loading#images).
- *Télécharger ou exécuter plus tard* les ressources non-nécessaires à l'affichage, via :
- du *lazy load*, e.g. ne télécharger les images que lorsqu'elles apparaissent à l'écran.
- de la *pagination*, e.g. sur un tableau ne télécharger/afficher que les X premières entrées et proposer des boutons pour afficher les entrées suivantes.
- en reportant l'execution de certains bouts de codes à plus tard.
- *Réduire le nombre de fichiers téléchargés* (on vise généralement 3-4 fichiers par page) :
- remplacer l'URL d'une image par [une chaîne de charactère en base64](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URLs).
- fusionner les fichiers CSS en un seul.
- fusionner les fichiers JS en un seul.
- intégrer certaines ressources (e.g. JSON) dans le fichier JS.
- intégrer le JS et/ou le CSS dans le fichier HTML.
- *Utiliser des caches* pour éviter de re-télécharger à chaque fois certaines ressources :
- un cache basé le protocole HTTP (cf [Cache-Control](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching)).
- un cache local basé sur `localStorage`, `sessionStorage`, ou `indexDB`.
- un cache côté serveur en enregistrant des données dans la RAM afin de pouvoir répondre plus rapidement aux requêtes qui s'il fallait aller les lire directement sur le disque.
- *Pré-télécharger les ressources*, en commençant à les télécharger le plus tôt possible :
- dans les balises `<script>`, l'attribut `defer`.
- pour d'autres ressources utilisées plus tard, cf [<link rel=preload href='...' as='...'/>](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel/preload).
- Pour les site Web massivement visités :
- *distribuer la charge* (load balancing) entre plusieurs serveurs (⚠ engendre des problématiques de synchronisations).
- *rapprocher le serveur du client*, *edge computing* (contenu dynamique), *content delivery network* (contenu statique).
- découper son site Web en plusieurs modules/services indépendants hébergés sur plusieurs serveurs (⚠ si les services doivent fréquemment communiquer entre eux cela peut s'avérer contre-productif).
⚠ L'optimisation prématurée est diabolique. Vous n'avez, à votre niveau, pas besoin d'optimiser vos sites Web. Vous n'avez pas non plus à implémenter vous-mêmes ces optimisations, de nombreux outils le font déjà pour vous (e.g. Webpack).
🚩 [TODO] : outils navigateur pour network / load performances (lighthouse)
🚩 [TODO] : archi projet dev vs prod.
# Le serveur Web
Nginx/Apache2/Live Server vs Deno => pourquoi séparer (statique vs dynamique ! CORS !) ?
Command line
BDD API/FileAPI
! Jamais faire confiance client
=> Routes (chemin)
=> body / url  
=> Serveur Python/JS/TS
=> Requête HTTP
## API REST
Une API REST est une manière de concevoir les échanges entre le client et le serveur de sorte à les uniformaliser, les rendre plus compréhensibles, et faciliter les opérations de tests et de déboguages.
Une requête REST est composée de 3 éléments :
- *une URI* (ou route) qui désigne une ressource ;
- la *méthode HTTP* qui indique le type d'opération à effectuer ;
- le *corps de la requête* contenant des données envoyées au serveur.
Une API REST supporte 5 méthodes HTTP :
- *GET :* lire la ressource.
- POST : ajouter une ressource à une collection.
- *PUT :* modifier entièrement une ressource ou la créer si elle n'existe pas.
- *PATCH :* modifier partiellement une ressource.
- *DELETE :* supprimer une ressource.
L'URI/routes des ressources doivent suivre le même format. On appelle *collection* une ressource qui est elle-même un ensemble de ressources. La modification d'une ressource est effectuée de la sorte :
- `GET /$COLLECTION/` : obtenir la liste des ressources de la collection.
- `POST /$COLLECTION/` : créer une nouvelle ressource dans la collection.
- `GET /$COLLECTION/$ID` : obtenir la ressource d'identifiant `$ID` appartenant à la collection.
- `PUT /$COLLECTION/$ID` : modifier ou créer la ressource d'identifiant `$ID` appartenant à la collection.
- `PATCH /$COLLECTION/$ID` : modifier la ressource d'identifiant `$ID` appartenant à la collection.
- `DELETE /$COLLECTION/$ID` : supprimer la ressource d'identifiant `$ID` appartenant à la collection.
Le corps de la requête et de la réponse REST sont usuellement au format JSON, mais peuvent utiliser n'importe quel format (potentiellement déterminé par la requête REST) :
- URLSearchParams ;
- texte brut ;
- données binaires ;
- XML ;
- etc.
Les API REST sont *sans état*, c'est à dire que le serveur n'enrigistre pas l'état de la connexion/session HTTP. L'état est stocké côté client et les données nécessaires sont inclues dans la requête REST.
Le fait de ne pas stocker d'état côté serveur permet d'éviter certaines attaques DoS de clients malicieux qui ouvriraient des connexions/sessions HTTP afin de surcharger le serveur. Cela permet aussi de mettre en cache la réponse à certaines requêtes REST, ainsi que de plus facilement répartir l'API REST sur plusieurs serveur en limitant les problématiques de synchronisations.
Pour les opérations de tests et de débogues, le fait qu'il n'y a pas d'état stocké signifie qu'une même requête REST produira le même effet, quel que soit l'état de la connexion/session. Cela permet aussi de mieux comprendre les échanges entre le client et le serveur.
## Websocket
🚩 [TODO]
## Server Send Event
Contrairement aux WebSockets, les *Server Send Events* ne permettent qu'une communication unidirectionnelle du serveur vers le client. Il est utilisé lorsque le serveur doit régulièrement envoyer des informations au client, sans attendre de réponses, e.g. envoyer des logs en temps réel.
Le principe est très simple, il s'agit d'une requête et d'une réponse HTTP normales, à l'exception que la réponse HTTP est maintenue en vie (`keep-alive`) et est écrite petit à petit (`text/event-stream`). Le corps de la réponse suit le format suivant :
```
event: $EVENT_NAME
data: $DATA
event: $EVENT_NAME
data: $DATA
event: $EVENT_NAME
data: $DATA
```
Côté client, l'utilisation est très simple, il suffit d'écouter des événements d'un `EventSource`.
```javascript
// [JS] JavaScript
// Client
const servEvent = new EventSource($URL);
servEvent.addEventListener($EVENT_NAME, function(ev) {
console.log(ev.data)
});
servEvent.close(); // termine la communication.
// Serveur (helper)
class SSE {
constructor(res) {
this.res = res;
this.#setHeaders
}
#setHeaders() {
this.res.set({
'Cache-Control': 'no-cache',
'Content-Type' : 'text/event-stream',
'Connection' : 'keep-alive'
});
this.res.flushHeaders();
}
dispatchEvent(name, data) {
this.res.write(`event: ${name}\n${data}\n\n`);
}
}
// Serveur
app.get($URL, async function(req, res) {
const sse = new SSE(res);
sse.dispatchEvent($EVENT_NAME, $DATA); // Envoyer un event.
});
```
```python
# [🐍] Python
# Client
def handler(ev):
console.log(ev.data)
const servEvent = EventSource.new($URL);
servEvent.addEventListener($EVENT_NAME, handler);
servEvent.close(); # termine la communication.
# Serveur
# pip3 install aiohttp_sse
from aiohttp_sse import sse_response
async def GET(request):
req = sse_response(request)
req.send($DATA, event=$EVENT_NAME) # Envoyer un event.
```