\documentclass{slides} \addbibresource{slides.bib} \title{Systèmes à objets répartis} \subtitle{M1 Informatique, S8} \date{Janvier 2026} \titlegraphic{\flushright\includegraphics[height=1.3cm]{img/logos.png}\vspace{.5cm}} \author{ \texorpdfstring{ \begin{table}[] \begin{flushleft} \begin{tabular}{ll} & Vincent Lannurien~\inst{1} \\ & \url{vincent.lannurien@univ-brest.fr} \\ \end{tabular} \end{flushleft} \end{table} } { Vincent Lannurien } } \institute{ \texorpdfstring{ \begin{table}[] \centering \begin{flushleft} \begin{tabular}{L} \inst{1}~Lab-STICC, CNRS UMR 6285, Université de Bretagne Occidentale, Brest \end{tabular} \end{flushleft} \end{table} } { Lab-STICC, CNRS UMR 6285, Université de Bretagne Occidentale, Brest } } \begin{document} \maketitle \begin{frame}{Introduction} \begin{center} \url{http://info-demo-sor.univ-brest.fr:3000} \end{center} \end{frame} \begin{frame}[t]{Plan du cours} \begin{columns} \column{0.6\textwidth} \tableofcontents[hideallsubsections] \column{0.4\textwidth} \begin{center} \includegraphics[width=\columnwidth]{img/screenshot.png} \end{center} \end{columns} \end{frame} \section{Organisation de l'UE} \begin{frame}{Organisation de l'UE -- Contenu} \begin{itemize} \item Objectifs : \begin{itemize} \item Développer une application suivant l'\textbf{architecture trois tiers}, s'appuyant sur des communications via \textbf{HTTP} et \textbf{WebSockets} ; \item Comprendre les mécanismes de l'\textbf{authentification} (avec ou sans état) d'un client auprès d'un serveur ; \item S'initier au \textbf{déploiement} d'une application répartie à l'aide d'un \textit{reverse proxy}. \end{itemize} \item Prérequis : \begin{itemize} \item Côté client : HTML/CSS \end{itemize} \item Méthode : \begin{itemize} \item Un peu de cours... \item Beaucoup de pratique ! \end{itemize} \end{itemize} \end{frame} \begin{frame}{Organisation de l'UE -- Logistique} \begin{itemize} \item Volume horaire : \begin{itemize} \item CM : 3 séances de 2h (total : 6h) \item TP : 8 séances de 2h (total : 16h) \end{itemize} \item Contrôle des connaissances : \begin{itemize} \item TP noté (2h sur cette partie du cours) \item Examen final (2h en tout) \end{itemize} \end{itemize} \end{frame} \section{Introduction} \begin{frame}{Technologies utilisées} \begin{itemize} \item Côté serveur : \begin{itemize} \item Runtime (Deno) \item Langage (TypeScript) \item Framework (Oak) \item Base de données (SQLite) % \item Cache (Redis) \end{itemize} \item Côté client : \begin{itemize} \item Bundler (Vite) \item Langage (TypeScript) \item Framework (React) \end{itemize} \item Communications : \begin{itemize} \item Requêtes HTTP \item WebSockets \end{itemize} \end{itemize} \end{frame} \section{Cas d'étude applicatif} \begin{frame}{Présentation de l'application} \begin{block}{Description} L'application est une \textbf{plateforme de sondages en ligne}. Elle permet à des utilisateurs de \textbf{créer des sondages} et d'ajouter des \textbf{options de réponse}. Les participants peuvent \textbf{voter} pour une ou plusieurs options selon des règles définies par le créateur du sondage. L'application gère également l'\textbf{authentification} des utilisateurs et assure la \textbf{persistance} des données. \end{block} \end{frame} \begin{frame}{Présentation de l'application} \begin{alertblock}{Acteurs} Les acteurs de l'application sont les suivants : \begin{itemize} \item Utilisateur authentifié : peut créer des sondages, voter, et consulter les résultats ; \item Utilisateur invité : peut voter (si autorisé) et consulter les résultats (si autorisé) ; \item Administrateur : peut gérer les sondages et les utilisateurs. \end{itemize} \end{alertblock} \end{frame} \begin{frame}{Présentation de l'application} \begin{alertblock}{Fonctionnalités} Les principales fonctionnalités de l'application peuvent être résumées ainsi : \begin{itemize} \item Création de sondages avec : titre, description, date de création, date d'expiration, statut (actif/inactif) ; \item Ajout d'options à un sondage : texte descriptif ; \item Vote pour une option de sondage ; \item Consultation des résultats (nombre de votes par option) ; \item Gestion des utilisateurs (inscription, authentification). \end{itemize} \end{alertblock} \end{frame} \section{Architecture : modèle client/serveur} \begin{frame}{Vue d'ensemble} \centering \includegraphics[width=.9\textwidth]{img/architecture.png} \end{frame} \begin{frame}{Monolithes ou microservices} \centering \includegraphics[width=.8\textwidth]{img/scaling-monolith.png} \addtocounter{footnote}{1} \footnotetext{\fullcite{Lannurien2023}} \addtocounter{footnote}{1} \footnotetext{\fullcite{fowlerMicroservices}} \end{frame} \begin{frame}{Monolithes ou microservices} \centering \includegraphics[width=.8\textwidth]{img/scaling-uservices.png} \addtocounter{footnote}{-1} \footnotetext{\fullcite{Lannurien2023}} \addtocounter{footnote}{1} \footnotetext{\fullcite{fowlerMicroservices}} \end{frame} \section{JavaScript, TypeScript et \textit{runtime}} \begin{frame}{JavaScript} \centering \includegraphics[width=.5\textwidth]{img/javascript-alert.png} \end{frame} \begin{frame}{JavaScript} \begin{columns} \column{.8\textwidth} \begin{itemize} \item 1995 : Brendan Eich développe en 10 jours un langage simple, interprété par le navigateur, pour dynamiser les pages web \item 1997 : première standardisation par ECMA (\textit{European Computer Manufacturers Association}) sous le nom ECMAScript \item 2005 : AJAX popularise les applications web dynamiques \item 2008 : Google publie la machine virtuelle V8 pour l'exécution du code JavaScript dans Chrome \item 2009 : Node.js étend l'utilisation de JavaScript au côté serveur \item 2010 -- 2014 : émergence des frameworks et d'un outillage moderne pour JavaScript (jQuery, AngularJS, etc.) \item 2015 : ECMAScript Edition 6 (ES6/ES2015) transforme le langage (modules, classes, \textit{arrow functions}, \textit{Promises}) \end{itemize} \column{.2\textwidth} \includegraphics[width=\columnwidth]{img/js.png} \includegraphics[width=\columnwidth]{img/netscape.png} \includegraphics[width=\columnwidth]{img/nodejs.png} \end{columns} \end{frame} \begin{frame} \begin{columns} \column{.6\textwidth} \centering \includegraphics[width=.9\columnwidth]{img/jetbrains-ecosystem-2025-evolution.png} \column{.4\textwidth} \begin{itemize} \item \textit{"Among the most dramatic rises in real-world usage over the last five years is TypeScript."} \end{itemize} \end{columns} \end{frame} \begin{frame} \centering \includegraphics[width=.7\columnwidth]{img/jetbrains-ecosystem-2025-platforms.png} \addtocounter{footnote}{1} \footnotetext{\url{https://devecosystem-2025.jetbrains.com/tools-and-trends}} \end{frame} \begin{frame}{TypeScript} \begin{itemize} \item Conçu en 2012 par Anders Hejlsberg \item Open source, maintenu par Microsoft \item Ni interprété, ni compilé : langage \textbf{transpilé} vers JavaScript, \textit{superset} de JS \item Exécutable dans n'importe quel \textit{runtime} JS (navigateur, Node.js, etc.) \item Multi-paradigme : \textbf{fonctionnel}, générique, impératif, orienté objet \end{itemize} \end{frame} \begin{frame}{TypeScript} \begin{columns} \column{.8\textwidth} \begin{itemize} \item \textbf{Typage statique} : détecter les erreurs et incohérences à la compilation (donc avant l'exécution) \item \textbf{Interopérabilité} : typage \textit{progressif} (tout fichier JavaScript valide est un fichier TypeScript valide) \end{itemize} \column{.2\textwidth} \includegraphics[width=\columnwidth]{img/ts.png} \end{columns} Adoption par l'industrie : \begin{itemize} \item Slack~\footnote[frame]{\url{https://slack.engineering/typescript-at-slack/}} : migration du frontend React vers TS pour sécuriser les appels d'API et réduire les erreurs au runtime \item Airbnb~\footnote[frame]{\url{https://www.youtube.com/watch?v=P-J9Eg7hJwE}} : typage progressif du code JS pour réduire les coûts de maintenance \item Microsoft~\footnote[frame]{\url{https://www.git-tower.com/blog/developing-for-the-desktop-vscode}} : migration vers TS pour faciliter l'évolution de VS Code ($>$ 1M SLoC) \item Shopify~\footnote[frame]{\url{https://shopify.engineering/migrating-large-typescript-codebases-project-references}} : migration frontend et backend pour fiabiliser les fonctions critiques \end{itemize} \end{frame} \begin{frame}{TypeScript} \begin{itemize} \item Ressources : \begin{itemize} \item \textit{The TypeScript Handbook}~\footnote{\fullcite{HandbookTypeScriptHandbook}} \item \textit{The Concise TypeScript Book}~\footnote{\fullcite{poggialiConciseTypeScriptBook2026}} \item TypeScript TV~\footnote[frame]{\url{https://typescript.tv/}} (liste des codes d'erreur) \item TS Playground~\footnote[frame]{\url{https://www.typescriptlang.org/play/}} (environnement d'exécution en ligne) \end{itemize} \end{itemize} \end{frame} \subsection{Typage statique et dynamique} \begin{frame}{TypeScript : typage du code JavaScript} Types de base disponibles dans TypeScript~\footnote{\url{https://www.typescriptlang.org/docs/handbook/2/everyday-types.html}} : \begin{itemize} \item \texttt{boolean} \item \texttt{number} \item \texttt{string} \item Tableaux (dynamiques) -- exemple : \texttt{string[]} ou \texttt{Array} \item Tuples (taille fixe) -- exemple : \texttt{[string, number]} \item \texttt{enum} \item \texttt{unknown} (type à préciser) \item \texttt{any} (à éviter : toute valeur autorisée) \item \texttt{void} (absence de valeur) \item \texttt{undefined} (valeur non définie) \item \texttt{null} (valeur vide) \item \texttt{never} (valeur impossible) \end{itemize} \end{frame} \begin{frame}[fragile]{Exercice 1 : les pièges du typage dynamique} \begin{block}{Fonctions et paramètres} \begin{itemize} \item Écrire une fonction \texttt{addition(a, b)} qui additionne deux nombres. \item Tester la fonction avec les entrées suivantes : \end{itemize} \end{block} \begin{minted}{js} addition(2, 3); // attendu : 5 addition("2", 3); // attendu : ? addition(true, 3); // attendu : ? \end{minted} \begin{alertblock}{Questions} \begin{enumerate} \item Que se passe-t-il pour les deux derniers cas ? \item Quels problèmes cela pose-t-il poser dans une application réelle ? \item Proposer une version TypeScript pour éviter ces erreurs \textit{à la compilation}. \end{enumerate} \end{alertblock} \end{frame} \begin{frame}[fragile]{Exercice 1 : les pièges du typage dynamique} \begin{minted}{ts} function addition2(a: number, b: number): number { return a + b; } console.log(addition2(2, 3)); // L'argument de type 'string' n'est pas attribuable au paramètre // de type 'number'.ts(2345) console.log(addition2("2", 3)); // L'argument de type 'boolean' n'est pas attribuable au paramètre // de type 'number'.ts(2345) console.log(addition2(true, 3)); \end{minted} \end{frame} \begin{frame}[fragile]{JavaScript : POJO (\textit{Plain Old Java Object})} \begin{minted}{js} const o = { "foo": "bar", "value": 42, other: true, } console.log(o.foo); console.log(o["value"]); console.log(o.other); \end{minted} \end{frame} \begin{frame}[fragile]{POJO et listes : \textit{Spread operator}} \begin{minted}{ts} const o1 = { "abc": 123 }; // Ajout d'une nouvelle valeur dans `o1` o1["def"] = 456; // Nouvel objet avec copie des valeurs de `o1` const o2 = { ...o1, "ghi": 789 }; \end{minted} \vspace{.5cm} \begin{minted}{ts} const l1 = [123]; // Ajout d'une nouvelle valeur à la fin de `l1` l1.append(456); // Nouvelle liste avec copies des valeurs de `l1` const l2 = [...l1, 789]; \end{minted} \end{frame} \begin{frame}[fragile]{TypeScript : typer les objets avec \texttt{Record}} \begin{minted}{js} const o: Record = { "foo": "bar", "value": 42, other: true, } console.log(o.foo); console.log(o["value"]); console.log(o.other); console.log(o.blabla); // undefined \end{minted} \end{frame} \begin{frame}[fragile]{Typage, interfaces et classes} \begin{columns} \column{.5\textwidth} \begin{minted}{ts} // Définition interface Bidule { truc: string; machin: number; toto?: boolean; } // Instanciation const b: Bidule = { truc: "bidule", machin: 42, }; \end{minted} \column{.5\textwidth} \begin{minted}{ts} // Définition class Bidule { constructor( public truc: string, public machin: number, public toto?: boolean, ) {} } // Instanciation const b: Bidule = new Bidule( "bidule", 42 ); \end{minted} \end{columns} \end{frame} \begin{frame}[fragile]{Exercice 2 : interfaces et contrats} \begin{block}{Typage des objets} \begin{itemize} \item Écrire une fonction \texttt{afficherUtilisateur(u)} qui prend un objet représentant un utilisateur et affiche son nom et son âge. \item Tester la fonction avec les entrées suivantes : \end{itemize} \end{block} \begin{minted}{js} afficherUtilisateur({ nom: "Alice", age: 25 }); afficherUtilisateur({ nom: "Bob" }); \end{minted} \begin{alertblock}{Questions} \begin{enumerate} \item Que se passe-t-il pour le second cas ? \item Comment TypeScript peut-il garantir la présence des propriétés attendues ? \item Proposer une solution avec TypeScript. \end{enumerate} \end{alertblock} \end{frame} \begin{frame}[fragile]{Exercice 2 : interfaces et contrats} \begin{minted}{ts} interface Utilisateur { nom: string; age: number; } function afficherUtilisateur(u: Utilisateur): void { console.log(`${u.nom} a ${u.age} ans`); } afficherUtilisateur({ nom: "Alice", age: 25 }); // L'argument de type '{ nom: string; }' n'est pas attribuable // au paramètre de type 'Utilisateur'. // La propriété 'age' est absente du type '{ nom: string; }' // mais obligatoire dans le type 'Utilisateur'.ts(2345) afficherUtilisateur({ nom: "Bob" }); \end{minted} \end{frame} \begin{frame}{Typage, interfaces et classes} \begin{itemize} \item Types \textbf{effacés} à la compilation \begin{itemize} \item Pas d'impact sur les performances (compromis : temps de compilation vs temps d'exécution) \item Une erreur de typage n'empêche pas la compilation vers JavaScript \end{itemize} \item Corollaire : il n'est pas possible de vérifier les types TypeScript \textbf{lors de l'exécution} du programme... \item ... sauf pour les types primitifs de JavaScript ! \end{itemize} \end{frame} \begin{frame}[fragile]{Typage, interfaces et classes} \begin{minted}{ts} // type union : soit number, soit string const fn = (x: number | string) => { // typeof type guard if (typeof x === 'number') { return x + 1; } return -1; }; \end{minted} \begin{minted}{ts} // type union : soit string, soit null const toUpperCase = (name: string | null) => { // truthiness narrowing if (name) { return name.toUpperCase(); } else { return null; } }; \end{minted} \end{frame} \begin{frame}[fragile]{Exercice 3 : types au \textit{runtime}} \begin{minted}[fontsize=\footnotesize]{ts} interface Animal { name: string; } interface Dog extends Animal { bark: () => void; } interface Cat extends Animal { meow: () => void; } const makeNoise = (animal: Animal) => { if (animal instanceof Dog) { // 'Dog' fait référence à un type mais s'utilise en tant que valeur ici.ts(2693) animal.bark(); } else { animal.meow(); } }; \end{minted} \begin{alertblock}{Questions} \begin{enumerate} \item Que signifie l'erreur levée par le compilateur ? \item Comment peut-on vérifier la nature de l'objet \texttt{animal} au \textit{runtime} ? \end{enumerate} \end{alertblock} \end{frame} \begin{frame}[fragile]{Exercice 3 : types au \textit{runtime}} \begin{minted}{ts} interface Dog { kind: "dog"; // Union discriminée bark: () => void; } interface Cat { kind: "cat"; // Union discriminée meow: () => void; } type Animal = Dog | Cat; // Type union const makeNoise = (animal: Animal) => { if (animal.kind === "dog") animal.bark(); else animal.meow(); }; \end{minted} \end{frame} \begin{frame}[fragile]{Exercice 3 : types au \textit{runtime}} \begin{minted}{ts} class Dog { constructor(public name: string, public bark: () => void) {} } class Cat { constructor(public name: string, public meow: () => void) {} } type Animal = Dog | Cat; // Union type const makeNoise = (animal: Animal) => { if (animal instanceof Dog) animal.bark(); else animal.meow(); }; \end{minted} \end{frame} \begin{frame}[fragile]{Exercice 3 : types au \textit{runtime}} \begin{minted}[fontsize=\footnotesize]{ts} interface Dog { kind: "dog"; // Union discriminée bark: () => void; } interface Cat { kind: "cat"; // Union discriminée meow: () => void; } type Animal = Dog | Cat; const makeNoise = (animal: Animal) => { if (animal.kind === "dog") animal.bark(); else animal.meow(); }; const dog: Dog = { kind: "dog", bark: () => console.log("bark") }; makeNoise(dog); \end{minted} \end{frame} \begin{frame}[fragile]{Type Guards : \textit{compile time} et \textit{run time}} \begin{itemize} \item \textit{Type narrowing} : \textit{compile time} \item \textit{Type assertion} : \textit{run time} \end{itemize} \vspace{.5cm} \begin{minted}{ts} interface Something { id: string }; export function isSomething(obj: unknown): obj is Something { return !!obj && typeof obj === "object" && "id" in obj && typeof obj.id === "string"; } const bla = { id: "blabla" }; \end{minted} \end{frame} \subsection{Généricité} \begin{frame}[fragile]{Généricité} \begin{itemize} \item \texttt{SomeContainer} \begin{itemize} \item \texttt{T} est un type paramètre contraint à hériter ou implémenter \texttt{SomeData} \item Permet de réutiliser le même conteneur pour différents types de données \end{itemize} \item Objectifs : \begin{itemize} \item Définir des structures et fonctions abstraites sur un type variable \item Maintenir les contraintes de type \end{itemize} \end{itemize} \begin{columns} \column{.6\textwidth} \begin{minted}[fontsize=\footnotesize]{ts} interface SomeData { id: string; } interface SpecializedData extends SomeData { name: string; value: number; } interface SomeContainer { data: T; } \end{minted} \column{.4\textwidth} \begin{minted}[fontsize=\footnotesize]{ts} const c: SomeContainer< SpecializedData > = { data: { id: "foo", name: "Foo Bar", value: 42, }, }; \end{minted} \end{columns} \end{frame} \subsection{Gestion des erreurs} \begin{frame}[fragile]{Gestion des erreurs : exceptions} \begin{minted}{ts} const randInt = Math.floor(Math.random() * 3) + 1; // [1, 3] const error = randInt === 1 ? new TypeError() : randInt === 2 ? new RangeError() : new EvalError(); try { throw error; } catch { // handle error } \end{minted} \end{frame} \begin{frame}[fragile]{Gestion des erreurs : exceptions} \begin{minted}{ts} const randInt = Math.floor(Math.random() * 3) + 1; // [1, 3] const error = randInt === 1 ? new TypeError() : randInt === 2 ? new RangeError() : new EvalError(); try { throw error; } catch (e) { if (e instanceof TypeError) { // handle TypeError } else if (e instanceof RangeError) { // handle RangeError } else if (e instanceof EvalError) { // handle EvalError } } \end{minted} \end{frame} \begin{frame}[fragile]{Gestion des erreurs : exceptions} \begin{minted}{ts} try { const file = await Deno.open("example.txt", { read: true }); for await (const chunk of Deno.iter(file)) { console.log(new TextDecoder().decode(chunk)); } } catch (e) { console.error("Error reading file:", e); } finally { // Toujours fermer le fichier if (file) { file.close(); } } \end{minted} \end{frame} \subsection{Asynchronisme et parallélisme} \begin{frame}{Programmation asynchrone} Comment manipuler des données... \begin{itemize} \item que l'on n'a pas encore ? \item que l'on n'aura peut-être jamais ? \item qui l'on obtient dans un temps variable ? \end{itemize} Par exemple... \begin{itemize} \item le résultat d'une requête à une API ? \item le contenu d'un fichier volumineux sur le disque ? \item le résultat d'une requête à une base de données ? \end{itemize} \end{frame} \begin{frame}{Programmation asynchrone} \centering \includegraphics[width=.9\textwidth]{img/async.png} \end{frame} \begin{frame}{Programmation asynchrone} \begin{itemize} \item API : \textbf{Promises} \begin{itemize} \item Traiter des opérations \textbf{asynchrones} \item Une Promise \textit{encapsule un résultat futur} \item Elle est associée à des \textit{callbacks} qui seront exécutés à l'obtention de ce résultat \item Elle peut être \textit{résolue} (réussite) ou \textit{rejetée} (échec) \end{itemize} \end{itemize} \centering \includegraphics[width=.8\textwidth]{img/promises.png} \addtocounter{footnote}{1} \footnotetext{\url{https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise}} \end{frame} \begin{frame}[fragile]{Programmation asynchrone} \begin{minted}[fontsize=\footnotesize]{ts} function repeatMaybe(repeat: string): Promise { return new Promise((resolve, reject) => { const maybe: number = randomIntegerBetween(0, 1); if (!maybe) { reject("Nope"); return; } setTimeout(() => { resolve(repeat); }, 1000); }); } const blabla = repeatMaybe("bla").then((result: string) => { console.log(result); }).catch((error: string) => { console.log(error) }); \end{minted} \end{frame} \begin{frame}[fragile]{Programmation asynchrone} \begin{columns} \column{.5\textwidth} \begin{itemize} \item API : \texttt{async}/\texttt{await} \begin{itemize} \item Sucre syntaxique pour "aplatir" du code asynchrone \item \texttt{async} définit une fonction asynchrone \item \texttt{await} met l'exécution du programme en pause dans l'attente d'un résultat \end{itemize} \end{itemize} \column{.5\textwidth} \centering \includegraphics[width=.7\columnwidth]{img/async-joke.jpg} \end{columns} \vspace{1cm} \begin{columns} \column{.5\textwidth} \begin{minted}[fontsize=\footnotesize]{ts} asyncFunction().then((res) => { console.log(res); }).catch((err) => { console.log(err); }); \end{minted} \column{.5\textwidth} \begin{minted}[fontsize=\footnotesize]{ts} try { const res = await asyncFunction(); console.log(res); // "bla" } catch (err) { console.log(err); // "Nope" } \end{minted} \end{columns} \end{frame} \begin{frame}[fragile]{Programmation asynchrone} \begin{minted}[fontsize=\footnotesize]{ts} function repeatMaybe(repeat: string): Promise { return new Promise((resolve, reject) => { const maybe: number = randomIntegerBetween(0, 1); if (!maybe) { reject("Nope"); return; } setTimeout(() => { resolve(repeat); }, 1000); }); } try { const blabla = await repeatMaybe("bla"); console.log(blabla); // "bla" } catch (error) { console.log(error); // "Nope" } \end{minted} \end{frame} \begin{frame}{Programmation parallèle} \begin{itemize} \item \texttt{Promise.all} \begin{itemize} \item exécute toutes les Promises en \textbf{parallèle} \item résout quand toutes les Promises ont \textbf{réussi} \item rejette immédiatement si \textbf{une seule échoue} \item retourne un tableau des résultats dans l'ordre initial des Promises \end{itemize} \item \texttt{Promise.allSettled} \item \begin{itemize} \item exécute toutes les Promises en \textbf{parallèle} \item résout quand toutes les Promises sont terminées, \textbf{succès ou échec} \item ne rejette \textbf{jamais} \item retourne un tableau d'objets de type : \begin{itemize} \item \texttt{{status: 'fulfilled' | 'rejected', value | reason}} \end{itemize} \end{itemize} \end{itemize} \end{frame} \begin{frame}[fragile]{Exercice 4 : programmation asynchrone} \begin{block}{Manipuler l'API Promise} \begin{itemize} \item Écrire une fonction \texttt{readTextFileIfExists(path)} qui lit un fichier s'il existe et retourne son contenu si le fichier n'est pas vide : \end{itemize} \end{block} \begin{minted}{js} readTextFileIfExists("data.txt") .then(text => console.log("File contents:", text)) .catch(err => console.error("I/O error:", err.message)); \end{minted} \begin{alertblock}{Questions} \begin{enumerate} \item Quelle est la signature de cette fonction en TypeScript ? \item Comment utiliser cette fonction avec \texttt{await} ? \item Comment utiliser cette fonction sur une liste de fichiers ? \item Comment paralléliser la lecture des fichiers de la liste ? \end{enumerate} \end{alertblock} \end{frame} \begin{frame}[fragile]{Exercice 4 : programmation asynchrone} \begin{minted}[fontsize=\footnotesize]{js} function readTextFileIfExists(path) { return new Promise((resolve, reject) => { if (!path) { reject(new Error("No file path provided")); return; } readFile(path, "utf8") .then(data => { if (data.length === 0) { reject(new Error("File is empty")); // explicit failure } else { resolve(data); // successful read } }) .catch(err => reject(err)); // I/O error }); } \end{minted} \end{frame} \begin{frame}[fragile]{Exercice 4 : programmation asynchrone} \begin{minted}{js} try { const text = await readTextFileIfExists("data.txt"); console.log("File contents:", text); } catch (err) { console.error("I/O error:", err.message); } \end{minted} \end{frame} \begin{frame}[fragile]{Exercice 4 : programmation asynchrone} \begin{minted}{js} const files = ["file1.txt", "file2.txt", "file3.txt"]; for (const file of files) { try { const text = await readTextFileIfExists(file); console.log("File contents:", text); } catch (err) { console.error("I/O error:", err.message); continue; } } \end{minted} \vspace{.5cm} \begin{block}{\emoji{thinking}} Temps d'exécution total = ? \end{block} \end{frame} \begin{frame}[fragile]{Exercice 4 : programmation asynchrone} \begin{minted}{js} const files = ["does_not_exist.txt", "file2.txt", "file3.txt"]; const contents = await Promise.all( files.map(readTextFileIfExists) ); try { contents.forEach((text, i) => { console.log("File contents:", text); }); } catch (err) { console.error("I/O error:", err.message); } \end{minted} \vspace{.5cm} \begin{block}{\emoji{thinking}} Temps d'exécution total = ? \end{block} \end{frame} \begin{frame}[fragile]{Exercice 4 : programmation asynchrone} \begin{minted}{js} const files = ["does_not_exist.txt", "file2.txt", "file3.txt"]; const contents = await Promise.allSettled( files.map(readTextFileIfExists) ); contents.forEach((result, i) => { if (result.status === "fulfilled") { console.log("File contents:", result.value); } else if (result.status === "rejected") { console.error("I/O error:", result.reason); } }); \end{minted} \vspace{.5cm} \begin{block}{\emoji{thinking}} Temps d'exécution total = ? \end{block} \end{frame} \subsection{Environnement d'exécution} \begin{frame}{Exécution : machine virtuelle} \begin{itemize} \item JavaScript : langage interprété dans une VM \item Langage permet d'exprimer des instructions \textbf{asynchrones} \item Runtime \textbf{synchrone} : boucle d'événements (pas de coroutines, pas de threads~\footnote{Sauf \textit{workers}, voir~\url{https://developer.mozilla.org/en-US/docs/Glossary/Thread}}) \end{itemize} \centering \includegraphics[width=\textwidth]{img/js-event-loop.png} \end{frame} \begin{frame}{Runtime : Deno} \begin{columns} \column{.8\textwidth} \begin{itemize} \item Runtime pour exécuter du code JavaScript en-dehors du navigateur \item Basé sur le moteur V8 (comme Node.js) \item Créé en 2018 par Ryan Dahl, développeur de Node.js~\footnote[frame]{\url{https://www.youtube.com/watch?v=M3BM9TB-8yA}} \item Points forts : \begin{itemize} \item \textit{Sandbox} : permissions explicites (lecture/écriture, accès au réseau, etc.) \item Exécute du code TypeScript sans outillage supplémentaire \item Inclut un formateur, un \textit{linter}, une plateforme de test, un profileur \end{itemize} \end{itemize} \column{.2\textwidth} \includegraphics[width=\columnwidth]{img/deno.png} \end{columns} \end{frame} \begin{frame}{Sécurité, permissions} \begin{itemize} \item \textit{Supply-chain attack} : introduction de vulnérabilités dans un logiciel \textit{via} compromission d'une ou plusieurs de ses dépendances \item Exemple : \textit{Shai Hulud}, novembre 2025~\footnote[frame]{\url{https://www.cert.ssi.gouv.fr/actualite/CERTFR-2025-ACT-051/}}, 700 paquets compromis sur NPM \begin{itemize} \item récupération de différents secrets présents sur la machine compromise [...] ; \item exfiltration des secrets ; \item réplication par l'infection de paquets NPM ; \item suppression de données utilisateurs ; \item mise en place de persistance. \end{itemize} \end{itemize} \end{frame} \begin{frame}{Gestion des dépendances} \begin{columns} \column{.8\textwidth} Outils et registres : \begin{itemize} \item \texttt{npmjs.org} : registre historique des paquets JavaScript \begin{itemize} \item Gestionnaires de paquets : \textbf{npm / yarn} \end{itemize} \item \texttt{jsr.org} : "nouveau" registre, orienté TypeScript \begin{itemize} \item Gestionnaire de paquets : \textbf{jsr}, intégré dans Deno \end{itemize} \end{itemize} \vspace{.5cm} Modules et imports : \begin{itemize} \item \textbf{CommonJS (CJS)} : compatibilité historique \begin{itemize} \item Import : \texttt{const fs = require('fs');} \item Export : \texttt{module.exports = \{ readFile \};} \item Chargement synchrone \end{itemize} \item \textbf{ECMAScript Modules (ESM)} : standard moderne \begin{itemize} \item Import : \texttt{import \{ readFile \} from 'fs/promises';} \item Export : \texttt{export const myFunc = () => \{...\};} \item Chargement statique ou dynamique (asynchrone) \end{itemize} \end{itemize} \column{.2\textwidth} \includegraphics[width=\columnwidth]{img/npm.png} \includegraphics[width=\columnwidth]{img/jsr.png} \end{columns} \end{frame} \section{Serveur web} \begin{frame}{\textit{It's a series of tubes...}} \centering \includegraphics[width=.6\textwidth]{img/series-of-tubes.png} \end{frame} \begin{frame}{Résumé de la méthode en TP} \begin{enumerate} \item La base de données retourne des \textbf{enregistrements} : ce sont des données arbitraires \begin{itemize} \item Élément unique : \texttt{Record | undefined} \begin{itemize} \item Peut être \texttt{undefined} si introuvable ! \end{itemize} \item Liste : \texttt{Record[]} \begin{itemize} \item Peut être vide ! \end{itemize} \end{itemize} \item On doit \textit{vérifier le typage des données} pour que l'objet se conforme à son \textit{interface base de données} (\texttt{PollRow}, ...) \item On peut alors \textit{convertir l'enregistrement} en objet de notre \textit{API} (\texttt{Poll}, ...) \item L'objet peut être retourné au client de manière \textit{sûre} \begin{itemize} \item \textit{Compile time} : typage statique des données dans l'application client \item \textit{Run time} : pas d'erreur à l'exécution pour l'utilisateur \end{itemize} \end{enumerate} \end{frame} \begin{frame} \centering \includegraphics[width=\textwidth]{img/type-guard-1.png} \end{frame} \begin{frame}[noframenumbering] \centering \includegraphics[width=\textwidth]{img/type-guard-2.png} \end{frame} \begin{frame}[noframenumbering] \centering \includegraphics[width=\textwidth]{img/type-guard-3.png} \end{frame} \begin{frame}[noframenumbering] \centering \includegraphics[width=\textwidth]{img/type-guard-4.png} \end{frame} \begin{frame}[noframenumbering] \centering \includegraphics[width=\textwidth]{img/type-guard-5.png} \end{frame} \begin{frame}[fragile]{Résumé de la méthode en TP} \begin{columns} \column{.5\textwidth} \begin{minted}{ts} export interface PollRow { id: string; title: string; description: string | null; user_id: string | null; created_at: string; expires_at: string | null; is_active: number; [key: string]: SQLOutputValue; } \end{minted} \column{.5\textwidth} \begin{minted}{ts} export interface Poll { id: string; title: string; description?: string; options: PollOption[]; userId?: string; createdAt: string; expiresAt?: string; isActive: boolean; } \end{minted} \end{columns} \end{frame} \begin{frame}[fragile]{Résumé de la méthode en TP} \begin{minted}[fontsize=\small]{ts} export function isPollRow( obj: Record ): obj is PollRow { return !!obj && typeof obj === "object" && "id" in obj && typeof obj.id === "string" && "title" in obj && typeof obj.title === "string" && "description" in obj && (typeof obj.description === "string" || obj.description === null) && "user_id" in obj && (typeof obj.user_id === "string" || obj.user_id === null) && "created_at" in obj && typeof obj.created_at === "string" && "expires_at" in obj && (typeof obj.expires_at === "string" || obj.expires_at === null) && "is_active" in obj && typeof obj.is_active === "number"; } \end{minted} \end{frame} \begin{frame}[fragile]{Résumé de la méthode en TP} \begin{minted}{ts} export function pollRowToApi( row: PollRow, optionRows: PollOptionRow[], ): Poll { return { id: row.id, title: row.title, description: row.description ?? undefined, createdAt: row.created_at, expiresAt: row.expires_at ?? undefined, isActive: row.is_active === 1, options: optionRows.map(pollOptionRowToApi), }; } \end{minted} \end{frame} \begin{frame}[fragile]{Résumé de la méthode en TP} \begin{minted}{ts} export function pollApiToRow( poll: Poll ): [PollRow, PollOptionRow[]] { return [ { id: poll.id, title: poll.title, description: poll.description ?? null, user_id: poll.userId ?? null, created_at: poll.createdAt, expires_at: poll.expiresAt ?? null, is_active: Number(poll.isActive), }, poll.options.map((pollOption) => pollOptionApiToRow( poll.id, pollOption )), ]; } \end{minted} \end{frame} \subsection{Protocole HTTP} \begin{frame}{HTTP} \begin{columns} \column{.6\textwidth} \begin{itemize} \item \textit{Hypertext Transfer Protocol} : à l'origine, sert à partager des pages web reliées entre elles par des liens \item Inventé au CERN~\footnote[frame]{Initialement \textit{Conseil européen pour la recherche nucléaire}, plus grand centre de recherche au monde en physique des particules} par un chercheur britannique, Tim Berners-Lee, entre 1989 et 1991 \item 1997 : standardisation de HTTP/1.1 : \begin{itemize} \item Apparition de l'en-tête \texttt{Host}, permettant d'héberger plusieurs domaines sur la même adresse IP, donc la colocation de serveurs web \end{itemize} \end{itemize} \begin{center} \includegraphics[width=.4\columnwidth]{img/tbl.jpg} \end{center} \column{.4\textwidth} \includegraphics[width=\columnwidth]{img/osi-model.png} \end{columns} \end{frame} \begin{frame}{HTTP : requêtes et réponses} \centering \includegraphics[width=\textwidth]{img/http-message.png} \end{frame} \begin{frame}{HTTP : méthodes} Méthodes principales pour les requêtes HTTP~\footnote[frame]{\url{https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Methods}}, aussi appelées \textit{verbes} : \begin{itemize} \item \texttt{GET} : demande d'une ressource \item \texttt{POST} : envoi de données du \textit{corps} de la requête vers une ressource \item \texttt{PUT} : remplacement d'une ressource par le \textit{corps} de la requête \item \texttt{DELETE} : suppression d'une ressource \end{itemize} \end{frame} \begin{frame}{HTTP : codes de réponse} \begin{columns} \column{.5\textwidth} \begin{itemize} \item \texttt{100} à \texttt{199} : réponses informatives \begin{itemize} \item \texttt{101 Switching Protocols} \end{itemize} \item \texttt{200} à \texttt{299} : réponses de succès \begin{itemize} \item \texttt{200 OK} \end{itemize} \item \texttt{300} à \texttt{399} : réponses de redirection \begin{itemize} \item \texttt{301 Moved Permanently} \end{itemize} \item \texttt{400} à \texttt{499} : erreurs du client \begin{itemize} \item \texttt{401 Unauthorized} \end{itemize} \item \texttt{500} à \texttt{599} : erreurs du serveur \begin{itemize} \item \texttt{500 Internal Server Error} \end{itemize} \end{itemize} \column{.5\textwidth} \includegraphics[width=\columnwidth]{img/http-cat.png} ... et bien d'autres : \url{https://http.cat/} \end{columns} \end{frame} \begin{frame}{HTTP : architecture REST} \begin{columns} \column{.7\textwidth} \begin{itemize} \item \textit{REpresentational State Transfer}~\cite{fielding2000architectural} : défini par Roy Fielding en 2000, conçu pendant son doctorat (1996 - 1999) \item Modèle d'architecture logicielle pour applications réseau \item Manipuler des ressources distantes via des représentations textuelles \item Opérations uniformes, prédéfinies, et \textbf{sans état} \end{itemize} \column{.3\textwidth} \includegraphics[width=\columnwidth]{img/roy_fielding_OSCON_2008.jpg} \end{columns} \end{frame} \begin{frame} \begin{center} \includegraphics[width=.8\textwidth]{img/rest-wikipedia.png} \end{center} \end{frame} \begin{frame}[fragile]{Exercice 5 : un serveur HTTP minimal avec TypeScript} \begin{block}{Lire et écrire sur un socket TCP} Ci-dessous, la boucle principale pour ouvrir et écouter sur un socket TCP avec Deno. Dès la connexion d'un \textbf{client}, on passe le socket à la fonction \texttt{handleConn}. \end{block} \begin{minted}{ts} // TCP socket const listener = Deno.listen({ port: 8080 }); // Strings <-> Bytes const decoder = new TextDecoder(); const encoder = new TextEncoder(); // Listen loop for await (const conn of listener) { handleConn(conn); } \end{minted} \end{frame} \begin{frame}[fragile]{Exercice 5 : un serveur HTTP minimal avec TypeScript} \begin{block}{Lire et écrire sur un socket TCP} Ci-dessous, la fonction \texttt{handleConn} par laquelle chaque \textbf{requête} en provenance d'un \textbf{client} est traitée. Le serveur reçoit un \textbf{tableau d'octets} ; on le convertit en une chaîne de caractères dans \texttt{rawRequest}. \end{block} \begin{minted}{ts} async function handleConn(conn: Deno.Conn) { const buf = new Uint8Array(1024); const bytes = await conn.read(buf); if (bytes === null) { conn.close(); return; } const rawRequest = decoder.decode(buf.subarray(0, bytes)); } \end{minted} \end{frame} \begin{frame}[fragile]{Exercice 5 : un serveur HTTP minimal avec TypeScript} \begin{minted}{sh} curl -X POST http://localhost:8080 \ -H "Content-Type: text/plain" \ -d "hello world" \end{minted} \vspace{1cm} \begin{columns} \column{.4\textwidth} \begin{minted}{text} POST / HTTP/1.1 Host: localhost:8080 User-Agent: curl/8.14.1 Accept: */* Content-Type: text/plain Content-Length: 11 hello world \end{minted} \column{.6\textwidth} \begin{alertblock}{Questions} \begin{enumerate} \item Quels sont les champs à inclure dans la réponse que l'on va retourner au client ? \item Compléter la fonction \texttt{handleConn} pour signifier une réponse OK comportant du texte au format JSON. \item Comment va-t-on gérer l'horodatage ? \end{enumerate} \end{alertblock} \end{columns} \end{frame} \subsection{Mise en œuvre} \begin{frame}{Framework pour serveur HTTP : Oak} \begin{columns} \column{.8\textwidth} \begin{itemize} \item Inspiré par Express.js, initialement développé pour Deno \item Framework \textit{middleware} : un logiciel qui s'intercale entre la requête d'un client et la réponse d'un serveur \item \textbf{Contexte} $=$ \textbf{Requête} $+$ \textbf{Réponse} \item Mécanisme principal : le \textbf{routage} \begin{itemize} \item Pour un point d'entrée donné, appliquer telle fonction, retourner telle valeur \item Requête : \texttt{GET http://blabla.com/item/123} \item Méthode : \texttt{GET} \item Route : \texttt{/item/:itemId} \item Fonction : \texttt{getItem(id)} \item Réponse : \texttt{\{ id: 123, value: "..." \}} \end{itemize} \end{itemize} \column{.2\textwidth} \includegraphics[width=\columnwidth]{img/oak.png} \end{columns} \end{frame} \begin{frame}[fragile]{Framework pour serveur HTTP : Oak} \begin{minted}[fontsize=\footnotesize]{ts} // Configuration const BASE_URL = "http://localhost:8000"; // Initialisation const app = new Application(); const router = new Router(); // Middlewares app.use(router.routes()); // Encore faut-il définir des routes ! ... app.use(router.allowedMethods()); // Événements app.addEventListener( "listen", () => console.log(`Server listening on ${BASE_URL}`), ); app.addEventListener( "error", (e) => console.log(`Uncaught error: ${e.message}`), ); // Boucle principale if (import.meta.main) { await app.listen({ hostname: "localhost", port: 8000 }); } // Export export { app }; \end{minted} \end{frame} \begin{frame}[fragile]{Routage avec Oak : paramètres de la requête} \begin{itemize} \item L'utilisateur envoie une requête \texttt{GET} \item Adresse : \texttt{http://localhost:8000/polls/abcd} \end{itemize} \vspace{.5cm} \begin{minted}{ts} router.get("/:pollId", (ctx) => { const pollId = ctx.params.pollId; // abcd }); \end{minted} \end{frame} \begin{frame}[fragile]{Routage avec Oak : paramètres de la requête} \begin{itemize} \item L'utilisateur envoie une requête \texttt{POST} \item Corps de la requête : un objet JSON \end{itemize} \vspace{.5cm} \begin{minted}{json} { "foo": "bar" } \end{minted} \begin{itemize} \item Adresse : \texttt{http://localhost:8000/polls} \end{itemize} \vspace{.5cm} \begin{minted}{ts} router.post("/polls", async (ctx) => { const createPollRequest = await ctx.request.body.json(); // { "foo": "bar" } }); \end{minted} \end{frame} \begin{frame}[fragile]{Routage avec Oak : réponse à la requête} \begin{minted}{ts} export interface APISuccess { success: true; data: T; error?: never; } export interface APIFailure { success: false; data?: never; error: APIError; } \end{minted} \end{frame} \begin{frame}[fragile]{Routage avec Oak : réponse à la requête} \begin{minted}{ts} export enum APIErrorCode { NOT_FOUND = "NOT_FOUND", SERVER_ERROR = "SERVER_ERROR", TIMEOUT = "TIMEOUT", UNAUTHORIZED = "UNAUTHORIZED", VALIDATION_ERROR = "VALIDATION_ERROR", } export interface APIError { code: APIErrorCode; message: string; } \end{minted} \end{frame} \begin{frame}[fragile]{Routage avec Oak : réponse à la requête} \begin{itemize} \item Et finalement, le type de \textbf{toute} réponse de l'API... \emoji{drum}\emoji{drum}\emoji{drum} \end{itemize} \vspace{1.5cm} \pause \begin{minted}{ts} export type APIResponse = APISuccess | APIFailure; \end{minted} \end{frame} \begin{frame}[fragile]{Routage avec Oak : réponse à la requête} \begin{itemize} \item Si l'entité demandée est trouvée : \end{itemize} \vspace{.5cm} \begin{minted}{ts} const responseBody: APIResponse = { success: true, data: poll, }; // Oak passe implicitement le code HTTP OK dans la réponse ctx.response.body = responseBody; \end{minted} \end{frame} \begin{frame}[fragile]{Routage avec Oak : réponse à la requête} \begin{itemize} \item Si l'entité demandée est introuvable : \end{itemize} \vspace{.5cm} \begin{minted}{ts} const responseBody: APIResponse = { success: false, error: { code: APIErrorCode.NOT_FOUND, message: "Poll not found", }, }; ctx.response.status = 404; ctx.response.body = responseBody; \end{minted} \end{frame} \begin{frame}[fragile]{Routage avec Oak : réponse à la requête} \begin{itemize} \item Le serveur rencontre une erreur : \end{itemize} \vspace{.5cm} \begin{minted}{ts} const responseBody: APIResponse = { success: false, error: { code: APIErrorCode.SERVER_ERROR, message: "Failed to fetch poll", }, }; ctx.response.status = 500; ctx.response.body = responseBody; \end{minted} \end{frame} \begin{frame}{Routage avec Oak : contexte} Quelques propriétés importantes de l'objet \texttt{ctx: Context}~\footnote{\url{https://jsr.io/@oak/oak/doc/context/~/Context}} : \vspace{.5cm} \begin{itemize} \item \texttt{request: Request} -- An object which contains information about the current request. \item \texttt{response: Response} -- An object which contains information about the response that will be sent when the middleware finishes processing. \item \texttt{state: S} -- The object to pass state to front-end views. This can be typed by supplying the generic state argument when creating a new app. \begin{itemize} \item \textit{On each request/response cycle, the context's state is cloned from the application state. This means changes to the context's .state will be dropped when the request drops, but "defaults" can be applied to the application's state. Changes to the application's state though won't be reflected until the next request in the context's state.} \end{itemize} \end{itemize} \end{frame} \begin{frame}{Middleware} \begin{itemize} \item Code applicatif qui se positionne \textbf{entre la requête et la réponse} \item \textbf{Intercepte} les données pour leur appliquer des modifications ou effectuer des effets de bord \item En pratique : \begin{itemize} \item Une \textbf{fonction} qui prend en paramètre le \textbf{contexte} (\texttt{ctx}) et la \textbf{prochaine fonction} (\texttt{next}) \item Invoquée dans une \textbf{chaîne} de traitement, ordonnée, pour chaque requête \item Capable d'observer, modifier, court-circuiter ou ignorer le cycle de vie requête-réponse \item Appelée par le framework (Oak) lors de la réception d'une requête \end{itemize} \end{itemize} \end{frame} \begin{frame}[fragile]{Middleware} \begin{minted}[fontsize=\small]{ts} interface HelloWorldState { hello?: string; world?: string; } async function hello(ctx: Context, next: Next) { ctx.state.hello = "Hello"; await next(); } async function world(ctx: Context, next: Next) { ctx.state.world = "world"; await next(); } const router = new Router(); router.get("/hello", hello, world, (ctx) => { ctx.response.body = `${ctx.state.hello}, ${ctx.state.world}!`; }); const app = new Application(); \end{minted} \end{frame} \begin{frame}[fragile]{Exercice 6 : gestion des erreurs du serveur} \begin{block}{Un unique point de sortie pour les réponses d'erreur} On veut éviter de dupliquer du code pour retourner des erreurs au client dans les fonctions de nos routes. On va donc utiliser un \textbf{middleware} qui sera chargé de remplir la réponse en cas d'erreur. \end{block} \begin{minted}{ts} export async function errorMiddleware(ctx: Context, next: Next) { } \end{minted} \begin{alertblock}{Questions} \begin{enumerate} \item Sur quel mécanisme s'appuyer pour intercepter les erreurs levées dans les routes de l'application ? \item Comment différencier une erreur \textit{prévue} d'une erreur \textit{inattendue} ? \end{enumerate} \end{alertblock} \end{frame} \subsection{Gestion de l'état} \begin{frame}[fragile]{Base de données} \begin{columns} \column{.8\textwidth} \begin{itemize} \item 2000 : création de SQLite par D. Richard Hipp \item Objectifs initiaux : base de données relationnelle \textbf{légère} et \textbf{sans serveur} pour être \textbf{embarquée} dans les applications \item Caractéristiques : \begin{itemize} \item \textit{In-memory} : les opérations sont réalisées dans la RAM (pas sur le disque) \item \textit{Single-file} : pas de processus serveur, pas de runtime, pas de dépendances, etc. \end{itemize} \item Simple à déployer, simple à maintenir \item Compétitive en matière de fonctionnalités (transactions, concurrence des accès en lecture/écriture, extensible, etc.) \end{itemize} \vspace{.5cm} \begin{minted}{sh} sqlite3 database.db < schema.sql \end{minted} \column{.2\textwidth} \includegraphics[width=\columnwidth]{img/sqlite.png} \end{columns} \end{frame} \begin{frame}[fragile]{Base de données : rappels sur SQL} \begin{minted}{sql} CREATE TABLE things ( id TEXT PRIMARY KEY, name TEXT NOT NULL, maybe TEXT, active INTEGER NOT NULL ); CREATE TABLE thing_options ( id TEXT PRIMARY KEY, thing_id TEXT NOT NULL, value TEXT NOT NULL, FOREIGN KEY (thing_id) REFERENCES things(id) ON DELETE CASCADE ); \end{minted} \end{frame} \begin{frame}[fragile]{Base de données : transactions} \begin{itemize} \item \textbf{Interdépendance} (sémantique et/ou fonctionnelle) entre deux requêtes \textbf{distinctes} \item Les deux doivent réussir ou échouer \textbf{ensemble} pour éviter une \textbf{incohérence dans l'état} de l'application \end{itemize} \vspace{.5cm} \begin{minted}[fontsize=\footnotesize]{ts} // Enregistrer le vote const voteId = crypto.randomUUID(); const now = new Date().toISOString(); db.prepare( "INSERT INTO votes " + "(id, poll_id, option_id, user_id, created_at) " + "VALUES (?, ?, ?, ?, ?);", ).run(voteId, pollId, optionId, userId || null, now); // Incrémenter le compteur db.prepare( "UPDATE poll_options SET vote_count = vote_count + 1 WHERE id = ?;", ).run(optionId); \end{minted} \end{frame} \begin{frame}[fragile]{Base de données : transactions} \begin{itemize} \item Mécanisme : \textbf{transactions} \item Unités de travail qui garantissent l'\textbf{atomicité} des opérations \end{itemize} \vspace{.5cm} \begin{minted}{ts} try { db.prepare("BEGIN").run(); // Début de transaction db.prepare("...").run(); // Requête 1 db.prepare("...").run(); // Requête 2 db.prepare("COMMIT").run(); // Transaction validée } catch { db.prepare("ROLLBACK").run(); // Transaction annulée } \end{minted} \end{frame} \begin{frame}{Exercice 7 : gestion des erreurs du serveur} \begin{alertblock}{Besoin d'atomicité} \begin{enumerate} \item Quelles opérations doivent être \textbf{atomiques} dans notre application de sondage ? \item Pourquoi ? C'est-à-dire : quels problèmes peuvent survenir si les sous-opérations, disjointes, échouent ? \end{enumerate} \end{alertblock} \end{frame} \subsection{Architecture} \begin{frame}{Organisation du dépôt} \begin{center} \includegraphics[width=.5\textwidth]{img/point.png} \end{center} \end{frame} \begin{frame}[fragile]{Exercice 8 : architecture du serveur} \begin{block}{Organisation des fichiers de l'application} Oak ne privilégie pas de paradigme précis, la responsabilité incombe aux développeurs d'organiser le code de leur application. À ce stade, les composants principaux sont : \begin{itemize} \item Routes \item Middleware \item Interfaces \item Base de données \end{itemize} \end{block} \begin{alertblock}{Questions} \begin{enumerate} \item Doit-on privilégier une organisation type \texttt{module/module.routes.ts} ? \texttt{routes/module.ts} ? Donner des avantages et des inconvénients. \item Pour les deux cas, montrer les directives \texttt{export} et \texttt{import} associées. \end{enumerate} \end{alertblock} \end{frame} \subsection{Fiabilité et tests} \begin{frame}[fragile]{Tests} \begin{minted}{ts} import { assertEquals, assert } from "@std/assert"; const BASE_URL = `http://localhost:8000`; Deno.test({ name: "polls management API", async fn() { const listRes = await fetch(`${BASE_URL}/polls`); assertEquals(listRes.status, 200); const listBody = await listRes.json(); assert(listBody.success); assert(listBody.data.length >= 1); }, }); \end{minted} \end{frame} \begin{frame}{Exercice 9 : un premier jeu de tests} \begin{block}{Création d'un jeu de tests unitaires} On souhaite éviter toute \textbf{régression} dans le comportement du serveur. Il faut donc tester \textbf{unitairement} les fonctionnalités de base de l'API. \end{block} \begin{alertblock}{Questions} \begin{enumerate} \item Écrire un test pour vérifier le fonctionnement nominal de : \begin{itemize} \item la fonction de vérification de l'interface d'un enregistrement issu de la base de données (\texttt{PollRow}) ; \item la fonction de conversion de cet enregistrement en objet de l'API (\texttt{Poll}) ; \end{itemize} \item Écrire un test pour vérifier le fonctionnement nominal de la route \texttt{/polls}. \end{enumerate} \end{alertblock} \end{frame} \section{Client web} \subsection{Requêtes HTTP} \begin{frame}{HTTP, encore} \begin{itemize} \item Avant 2014 : API \texttt{XMLHttpRequest} \begin{itemize} \item Basée sur l'utilisation de \textit{callbacks} \end{itemize} \item Depuis 2014 : API \texttt{fetch} \begin{itemize} \item Basée sur l'utilisation de \textit{Promises} \item 2014 : \textit{Web Hypertext Application Technology Working Group (WHATWG) Fetch Standard}~\footnote{\url{https://fetch.spec.whatwg.org/}} \item 2015 : implantation dans Chrome et Firefox \end{itemize} \end{itemize} \begin{block}{Standard Fetch} \textit{The Fetch standard defines requests, responses, and the process that binds them: fetching.} \end{block} \end{frame} \begin{frame}[fragile]{Requêtes HTTP : \texttt{XMLHttpRequest}} \begin{minted}{js} const xhr = new XMLHttpRequest(); xhr.open("GET", "https://api.example.com/data"); xhr.addEventListener("load", () => { try { if (xhr.status >= 200 && xhr.status < 300) { const data = JSON.parse(xhr.responseText); console.log("Response:", data); } else { throw new Error(`HTTP ${xhr.status}`); } } catch (error) { console.error("Error:", error); } }); xhr.addEventListener("error", () => { console.error("Network Error"); }); xhr.send(); \end{minted} \end{frame} \begin{frame}[fragile]{Requêtes HTTP : \texttt{fetch}} \begin{minted}{js} fetch("https://api.example.com/data") .then(response => { if (!response.ok) throw new Error(`HTTP ${response.status}`); return response.json(); }) .then(data => console.log("Response:", data)) .catch(error => console.error("Error:", error)); \end{minted} \end{frame} \begin{frame}[fragile]{Requêtes HTTP : \texttt{fetch} (avec \texttt{await})} \begin{minted}{js} try { const response = await fetch("https://api.example.com/data"); if (!response.ok) throw new Error(`HTTP ${response.status}`); const data = await response.json(); console.log("Response:", data); } catch (error) { console.error("Error:", error); } \end{minted} \end{frame} \subsection{IHM et DOM} \begin{frame}[fragile]{Un client web minimal avec JavaScript} \begin{minted}{html} Title
\end{minted} \end{frame} \begin{frame}[fragile]{Un client web minimal avec JavaScript} \begin{minted}{js} // script.js document.addEventListener("DOMContentLoaded", () => { fetch("http://localhost:8000") .then(response => { if (!response.ok) { throw new Error(`HTTP error ${response.status}`); } return response.text(); }) .then(data => { const main = document.querySelector("main.content"); main.innerHTML = data; }) .catch(error => { console.error("Fetch failed:", error); }); }); \end{minted} \end{frame} \begin{frame}{Qu'est-ce qu'une page web ?} \begin{columns} \column{.6\textwidth} \begin{itemize} \item \textit{Document Object Model} : représentation de la structure d'un document (une page web) en mémoire \item Forme : arbre logique (les branches relient des nœuds, les nœuds contiennent des objets) \item Les nœuds peuvent être associés à des gestionnaires d'événements (\textit{Event Listeners}) qui s'exécutent lors d'un changement dans le DOM \end{itemize} \column{.4\textwidth} \includegraphics[width=\columnwidth]{img/dom-model.png} \end{columns} \addtocounter{footnote}{1} \footnotetext{\url{https://dom.spec.whatwg.org/}} \addtocounter{footnote}{1} \footnotetext{\url{https://fr.wikipedia.org/wiki/Document_Object_Model}} \end{frame} \begin{frame}{IHM : paradigme} \begin{itemize} \item Paradigme impératif : "s'il se passe quelque chose, modifier l'état local de telle manière" (commander des changements) \begin{itemize} \item L'état est \textbf{mutable}, le flot de contrôle décide des changements dans l'interface \item Un événement déclenche une modification de l'interface \end{itemize} \item Paradigme réactif : "dès qu'il se passe quelque chose, recalculer l'état pour redessiner la page" (décrire un résultat) \begin{itemize} \item L'état est \textbf{immuable}, l'interface est une fonction de l'état \item $\text{UI} = f(\text{state})$ \item Un événement définit une transition d'état \begin{itemize} \item Le prochain DOM est calculé à partir de l'événement \item React le compare (\textit{diff}) avec l'arbre courant \item React applique le delta des modifications au DOM \end{itemize} \end{itemize} \end{itemize} \end{frame} \subsection{React} \begin{frame}{Framework : React} \begin{columns} \column{.7\textwidth} \begin{itemize} \item Initialement \textit{ReactJS}, créé par Jordan Walke (Facebook) en 2013 \item Besoin : un framework efficace pour développer des IHM interactives pour des applications intensives en données (Facebook Ads) \item Fondamentaux : \begin{itemize} \item Architecture à base de composants : "conteneurs" réutilisables, chacun maintient son propre état local \item Déclaratif plutôt qu'impératif : les développeurs définissent l'apparence de l'interface pour chaque état possible, et React gère la mise à jour du DOM automatiquement \item Document abstrait : le \textit{DOM virtuel} permet de représenter les mises à jour efficacement en mémoire \item Flot de données unidirectionnel : les données circulent du parent vers les composants enfants via les \textit{props} \end{itemize} \end{itemize} \column{.3\textwidth} \includegraphics[width=\columnwidth]{img/react.png} \end{columns} \end{frame} \begin{frame}{React : \textit{workflow}} \begin{columns} \column{.8\textwidth} \begin{itemize} \item De nombreux outils sont utilisés pour produire une application client : \begin{itemize} \item \textbf{Vite} : outils de développement \begin{itemize} \item inclut le compilateur TypeScript (\texttt{tsc} et \texttt{esbuild}) \item fournit un serveur de développement avec rechargement à chaud des modules (\textit{Hot Module Reload}) \end{itemize} \item \textbf{Rolldown} : \textit{bundler} \begin{itemize} \item prend des modules JavaScript et les assemble en un seul module \item optimise le résultat pour la production (\textit{tree-shaking}) \end{itemize} \end{itemize} \end{itemize} \column{.2\textwidth} \includegraphics[width=\columnwidth]{img/vite.png} \includegraphics[width=\columnwidth]{img/rolldown.png} \end{columns} \end{frame} \begin{frame}{React : vue d'ensemble} \begin{itemize} \item Deux concepts pour développer avec React : \begin{itemize} \item \textbf{Composants} : gèrent la \textit{vue} (l'interface) \begin{itemize} \item prennent des \textit{props} (propriétés) en entrée, en lecture seule \item maintiennent un \textit{state} (état) dont les changements provoquent un re-rendu \end{itemize} \item \textbf{\textit{Hooks}} : gèrent le \textit{modèle} (la logique) derrière la vue \end{itemize} \item Composants et \textit{hooks} sont des \textbf{fonctions} ! \item Un composant retourne une sous-partie de l'arbre du DOM au format JSX~\footnote{\url{https://www.typescriptlang.org/docs/handbook/jsx.html}} \item Un \textit{hook} retourne des valeurs arbitraires (fonctions, tuples, etc.) \end{itemize} \end{frame} \begin{frame}[fragile]{React : exemple (composant)} \begin{minted}{tsx} import { useCounter } from "./hooks/useCounter"; // pages/Counter.tsx function Counter({ initialValue = 0 }: { initialValue?: number }) { const { count, increment, decrement } = useCounter(initialValue); return (

Count: {count}

); } export default Counter; \end{minted} \end{frame} \begin{frame}[fragile]{React : exemple (\textit{hook})} \begin{minted}{ts} import { useState } from "react"; // hooks/useCounter.ts export function useCounter(initialValue: number = 0) { const [count, setCount] = useState(initialValue); const increment = () => setCount(prev => prev + 1); const decrement = () => setCount(prev => prev - 1); return { count, increment, decrement }; } \end{minted} \end{frame} \begin{frame}[fragile]{React : exemple (instanciation)} \begin{minted}{tsx} export default function App() { return // prend un `number` } \end{minted} \vspace{.5cm} \begin{itemize} \item \textbf{\textit{Props}} du composant : la valeur initiale du compteur \texttt{initialValue} \item \texttt{count} est une \textbf{variable d'état}, \texttt{setCount} est une fonction d'écriture de l'état \item Cliquer sur les boutons $+$ et $-$ modifie \textbf{l'état} du composant et provoque son re-rendu \end{itemize} \end{frame} \begin{frame}{React : cycle de vie d'un composant} \begin{itemize} \item \textit{Mount} (montage) \begin{itemize} \item Phase de \textbf{création et d'insertion} d'un composant dans le DOM \item Initialise son état à partir des ses \textit{props} \item Est \textit{rendu} (retourne le JSX à afficher) \end{itemize} \item \textit{Update} (mise-à-jour) \begin{itemize} \item Phase de \textbf{re-rendu} d'un composant \item Déclenchée par un changement dans les \textit{props} ou dans l'état du composant \item Exécute les effets dont les \textbf{dépendances} comportent un changement \end{itemize} \item \textit{Unmount} (démontage) \begin{itemize} \item Phase de \textbf{suppression} d'un composant dans le DOM \item Déclenchée par un changement de condition, un changement de route, etc. \item Exécute les fonctions de nettoyage des \textit{effets} \end{itemize} \end{itemize} \end{frame} \begin{frame}[fragile]{React : \textit{hook} useState} \begin{block}{Modèle mental} La mémoire d'un composant, qui provoque les mises à jour de l'interface. \end{block} \vspace{.5cm} \begin{minted}[fontsize=\footnotesize]{tsx} function Counter() { const [count, setCount] = useState(0); return (

Count: {count}

); } \end{minted} \end{frame} \begin{frame}[fragile]{React : \textit{hook} useEffect} \begin{block}{Modèle mental} Réagir aux changements dans l'environnement. \end{block} \vspace{.5cm} \begin{minted}[fontsize=\footnotesize]{tsx} function Timer() { const [seconds, setSeconds] = useState(0); useEffect(() => { const interval = setInterval(() => { setSeconds(prev => prev + 1); }, 1000); return () => clearInterval(interval); // Fonction de nettoyage }, []); // Pas de dépendances : s'exécute une fois, au premier rendu return
Secondes : {seconds}
; } \end{minted} \end{frame} \begin{frame}[fragile]{React : \textit{hook} useRef} \begin{block}{Modèle mental} Une boîte qui survit aux rendus, sans affecter l'interface. \end{block} \begin{minted}[fontsize=\footnotesize]{tsx} function Tracker() { const [renderCount, setRenderCount] = useState(0); const clickCountRef = useRef(0); // Valeur mutable persistante // Mise-à-jour sans re-rendu const handleClick = () => clickCountRef.current += 1; // Force le re-rendu const handleRefresh = () => setRenderCount(prev => prev + 1); return (

Button cliqué (compteur): {clickCountRef.current} fois

Button cliqué (rendu): {renderCount} fois

); } \end{minted} \end{frame} \begin{frame}{React : routage} \begin{columns} \column{.7\textwidth} \begin{itemize} \item \textbf{React Router}~\footnotemark : gère la \textbf{navigation} au sein d'une application React \item Choisir le composant à rendre en fonction de l'\textbf{URL} : \begin{itemize} \item Routes : associer des \textit{composants} à des \textit{chemins} ; \item \texttt{Link} : naviguer sans recharger la page ; \item \texttt{useParams} : accéder aux parties dynamiques de l'URL ; \item \texttt{useNavigate} : navigation par programmation. \end{itemize} \end{itemize} \column{.3\textwidth} \includegraphics[width=\columnwidth]{img/react-router.png} \end{columns} \footnotetext{\url{https://reactrouter.com/}} \end{frame} \begin{frame}[fragile]{React : routage} \begin{minted}[fontsize=\small]{tsx} import React from "react"; import { useParams, useNavigate } from "react-router"; function Home() { return

Accueil

; } function User() { const { id } = useParams(); // paramètre de l'URL const navigate = useNavigate(); return (

Utilisateur {id}

); } \end{minted} \end{frame} \begin{frame}[fragile]{React : routage} \begin{minted}[fontsize=\small]{tsx} import { BrowserRouter, Routes, Route, Link } from "react-router"; function App() { return ( } /> } /> ); } export default App; \end{minted} \end{frame} \section{Authentification} \begin{frame}{Concepts généraux} \begin{columns} \column{.7\textwidth} \begin{itemize} \item Mécanisme de \textbf{contrôle d'accès} \item S'assurer de la légitimité d'une entité (humain ou système) \item Pour autoriser ses requêtes vers différentes ressources \end{itemize} \begin{center} \includegraphics[width=.33\columnwidth]{img/carte-bleue.jpg} \includegraphics[width=.3\columnwidth]{img/yubikey.jpg} \end{center} \column{.3\textwidth} \includegraphics[width=\columnwidth]{img/password-123.jpg} \end{columns} \end{frame} \begin{frame}{\textit{Stateful} : sessions et cookies} \begin{columns} \column{.5\textwidth} \begin{enumerate} \item Le client envoie une requête de connexion au serveur \item Si le client est autorisé... \item Le serveur retourne un identifiant de session dans un \textit{cookie} \item Le client peut demander une ressource protégée en envoyant sa requête et son cookie \item Si la session en cours est valide... \item Le serveur lui retourne la ressource demandée \end{enumerate} \column{.5\textwidth} \includegraphics[width=\columnwidth]{img/authentication-stateful.png} \end{columns} \end{frame} \begin{frame}{\textit{Stateless} : JWT (jetons)} \begin{columns} \column{.6\textwidth} \begin{enumerate} \item Le client envoie une requête de connexion au serveur \item Si le client est autorisé... \item Le serveur retourne un \textit{jeton} avec une période de validité \item Le client peut demander une ressource protégée en envoyant sa requête et son jeton \item Si le jeton est valide... \item Le serveur lui retourne la ressource demandée \end{enumerate} \column{.4\textwidth} \includegraphics[width=\columnwidth]{img/authentication-stateless.png} \end{columns} \end{frame} \begin{frame} \centering \includegraphics[width=.85\textwidth]{img/jwt-decoded.png} \addtocounter{footnote}{1} \footnotetext{\url{https://www.jwt.io/}} \end{frame} \section{Interactions} \begin{frame}{Requêtes/Réponses : \textit{Data Transfer Objects}} \begin{itemize} \item Un objet, sérialisable, pour franchir la frontière entre les processus~\footnote{\fullcite{fowlerPatterns2012}} \item Formalise les points de communications dans une application à plusieurs tiers \end{itemize} \vspace{.5cm} \centering \includegraphics[width=.8\columnwidth]{img/dto-fowler.png} \end{frame} \begin{frame}[fragile]{Requêtes/Réponses : \textit{Data Transfer Objects}} \begin{itemize} \item Mise en œuvre en TypeScript : \begin{itemize} \item Interface \textit{ad hoc} \item Type intersection \item Type \texttt{Omit} \end{itemize} \end{itemize} \begin{columns} \column{.5\textwidth} \begin{minted}[fontsize=\small]{ts} type User = { name: string; password: string; }; type UserWithoutPassword = Omit< User, "password" >; const user: UserWithoutEmail = { name: "Alice" }; \end{minted} \column{.5\textwidth} \begin{minted}[fontsize=\small]{ts} type UserWithoutPassword = { name: string; }; type User = UserWithoutPassword & { password: string }; const alice = { name: "Alice" }; const user: User = { ...alice, password: "123" }; \end{minted} \end{columns} \end{frame} \begin{frame}[fragile]{Protocole bidirectionnel : WebSocket} \begin{columns} \column{.55\textwidth} \underline{\textbf{Client :}} \vspace{.3cm} \begin{minted}[fontsize=\small]{ts} const ws = new WebSocket( `${WS_URL}/votes/${pollId}` ); \end{minted} \vspace{.5cm} \underline{\textbf{Serveur :}} \vspace{.3cm} \begin{minted}[fontsize=\small]{ts} if (!ctx.isUpgradable) { throw new APIException( APIErrorCode.BAD_REQUEST, 400, "WebSocket required" ); } const ws: WebSocket = ctx.upgrade(); \end{minted} \column{.45\textwidth} \includegraphics[width=\columnwidth]{img/websocket.png} \end{columns} \end{frame} % \begin{frame}{Protocole bidirectionnel : WebSocket} % \begin{columns} % \column{.7\textwidth} % \begin{enumerate} % \item \textit{Full-duplex} : ... % \end{enumerate} % \column{.3\textwidth} % \includegraphics[width=\columnwidth]{img/websocket-tp.png} % \end{columns} % \end{frame} \section{Déploiement de l'application} \begin{frame}{Nom d'hôte, port, adresse} \begin{center} \texttt{% {\color{BrickRed}https}://% {\color{MidnightBlue}foo.bar}.% {\color{Orchid}com}% [:{\color{LimeGreen}443}]/% {\color{YellowOrange}baz.html}% } \end{center} \invisible{ \begin{center} \includegraphics[width=.5\textwidth]{img/brace.png} \end{center} \begin{itemize} \item \texttt{{\color{BrickRed}https}} : protocole \item \texttt{{\color{MidnightBlue}foo.bar}} : nom d'hôte (\textit{hostname}) \item \texttt{{\color{Orchid}com}} : \textit{TLD} (\textit{Top-Level Domain}) \item \texttt{{\color{LimeGreen}443}} : port (HTTP : 80, HTTPS : 443) \item \texttt{{\color{YellowOrange}baz.html}} : nom de fichier \end{itemize} } \end{frame} \begin{frame}{Nom d'hôte, port, adresse} \begin{center} \texttt{% {\color{BrickRed}https}://% {\color{MidnightBlue}foo.bar}.% {\color{Orchid}com}% [:{\color{LimeGreen}443}]/% {\color{YellowOrange}baz.html}% } \end{center} \begin{center} \includegraphics[width=.5\textwidth]{img/brace.png} \end{center} \begin{itemize} \item \texttt{{\color{BrickRed}https}} : protocole \item \texttt{{\color{MidnightBlue}foo.bar}} : nom d'hôte (\textit{hostname}) \item \texttt{{\color{Orchid}com}} : \textit{TLD} (\textit{Top-Level Domain}) \item \texttt{{\color{LimeGreen}443}} : port (HTTP : 80, HTTPS : 443) \item \texttt{{\color{YellowOrange}baz.html}} : nom de fichier \end{itemize} \end{frame} \begin{frame}[fragile]{Oak : configuration dynamique côté serveur} \begin{minted}{ts} export const PROTOCOL = Deno.env.get("PROTOCOL") || "http"; export const HOSTNAME = Deno.env.get("HOSTNAME") || "localhost"; export const PORT = Number(Deno.env.get("PORT")) || 8000; export const BASE_URL = `${PROTOCOL}://${HOSTNAME}:${PORT}`; \end{minted} \end{frame} \begin{frame}[fragile]{Oak : configuration dynamique côté serveur} \begin{minted}{bash} # fichier .env à la racine du projet PROTOCOL=https HOSTNAME=api.coucou.localhost PORT=443 \end{minted} \end{frame} \begin{frame}[fragile]{Vite : configuration dynamique côté client} \begin{minted}{ts} // TypeScript triple-slash directive // include type declarations from the package vite/client /// const apiProtocol = import.meta.env.VITE_API_PROTOCOL || "http"; const wsProtocol = import.meta.env.VITE_WS_PROTOCOL || "ws"; const serverHost = import.meta.env.VITE_SERVER_HOST || "localhost"; const serverPort = import.meta.env.VITE_SERVER_PORT || "8000"; export const API_URL = `${apiProtocol}://${serverHost}:${serverPort}`; export const WS_URL = `${wsProtocol}://${serverHost}:${serverPort}`; \end{minted} \end{frame} \begin{frame}[fragile]{Vite : configuration dynamique côté client} \begin{minted}{bash} # fichier .env à la racine du projet VITE_API_PROTOCOL=https VITE_WS_PROTOCOL=ws VITE_SERVER_HOST=api.coucou.localhost VITE_SERVER_PORT=443 \end{minted} \end{frame} \begin{frame}{Serveur mandataire -- \textit{Forward proxy}} \centering \includegraphics[width=.8\textwidth]{img/proxy-forward.png} \end{frame} \begin{frame}{Serveur mandataire inverse -- \textit{Reverse proxy}} \centering \includegraphics[width=.8\textwidth]{img/proxy-reverse.png} \end{frame} \begin{frame}[fragile]{\textit{Reverse proxy}} \begin{columns} \column{.8\textwidth} \begin{itemize} \item \textbf{nginx} : créé par Igor Sysoev en 2004 \item Premier serveur web au monde (plus de 33\% des sites web) \end{itemize} \column{.2\textwidth} \includegraphics[width=\columnwidth]{img/nginx.png} \end{columns} \begin{minted}[fontsize=\footnotesize]{text} http { server { listen 80; server_name api.coucou.localhost; location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } } \end{minted} \end{frame} \begin{frame}{HTTPS : \textit{Hypertext Transfer Protocol Secure}} \begin{block}{À quoi ça sert ?} Vérifier l'\textbf{identité} d'un site web, grâce à un \textbf{certificat} d'authentification émis par une \textbf{autorité} de confiance, afin de \textbf{chiffrer} les communications \end{block} \centering \includegraphics[width=.7\columnwidth]{img/tls-univ-brest.png} % \begin{itemize} % \item Identité : % \item Certificat : % \item Autorité : % \item Chiffrer : % \begin{itemize} % \item Protocole (TLSv1.2, TLSv1.3) % \item Chiffrement (AES 128/256, etc.) % \end{itemize} % \end{itemize} \end{frame} \begin{frame}[fragile]{\texttt{mkcert} : créer un certificat} \begin{minted}{bash} mkcert \ -key-file univ-brest.fr.pem \ -cert-file univ-brest.fr.pem \ *.univ-brest.fr \end{minted} \vspace{.5cm} \begin{alertblock}{Ah bon ?} Est-ce que ce certificat fait foi ? \end{alertblock} \end{frame} \begin{frame} \centering \includegraphics[width=\textwidth]{img/tls-self-signed.png} \end{frame} \begin{frame}[fragile]{\textit{Reverse proxy} avec SSL} \begin{minted}[fontsize=\footnotesize]{text} http { server { listen 80; server_name api.coucou.localhost; return 301 https://$host$request_uri; } server { listen 443 ssl http2; server_name api.coucou.localhost; ssl_certificate ./certs/api-coucou.pem; ssl_certificate_key ./certs/api-coucou-key.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers on; location / { proxy_pass http://127.0.0.1:8000; ... } } } \end{minted} \end{frame} \begin{frame}{\textit{Cross-Origin Resource Sharing} (CORS)} \begin{itemize} \item \texttt{fetch} (ou \texttt{XMLHttpRequest}) : récupérer des ressources distantes lors de l'exécution des scripts de la page (au \textit{runtime}) \item Pour des raisons de sécurité, les navigateurs imposent une politique stricte : \textit{Same-Origin} \begin{itemize} \item $\text{Origine} = \text{Protocole} + \text{Nom d'hôte} + \text{Port}$ \end{itemize} \item \textbf{Problème} : que se passe-t-il si le serveur n'est pas à la même adresse (origine) que le client ? \item Par défaut : la connexion est \textbf{refusée} \end{itemize} \end{frame} \begin{frame} \centering \includegraphics[width=.7\textwidth]{img/cors-example.png} \addtocounter{footnote}{1} \footnotetext{\url{https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS}} \end{frame} \begin{frame}[fragile]{\textit{Cross-Origin Resource Sharing} (CORS)} \begin{itemize} \item \textbf{CORS} : mécanisme basé sur les en-têtes HTTP \item Permet à un serveur de déterminer les \textbf{origines} autorisées pour les clients qui s'y connectent \end{itemize} \vspace{.5cm} \begin{minted}{text} Access-Control-Allow-Origin: https://app.coucou.localhost Access-Control-Allow-Credentials: true Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS Access-Control-Allow-Headers: Authorization, Content-Type \end{minted} \end{frame} \begin{frame}[fragile]{Configurer CORS au niveau du serveur} Configurer le \textit{middleware} CORS : \vspace{.5cm} \begin{minted}{ts} app.use(oakCors({ origin: "https://app.coucou.localhost", credentials: true, methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"], allowedHeaders: ["Authorization", "Content-Type"], })); \end{minted} \end{frame} \section{Débuggage et profilage} \begin{frame} \centering \includegraphics[width=.7\textwidth]{img/eniac.jpg} {\small \textit{"U.S. Army Photo", from M. Weik, "The ENIAC Story" A technician changes a tube.}} \end{frame} \begin{frame} \begin{columns} \column{.3\textwidth} {\small \textit{The operators affixed the moth to the computer log, with the entry: "First actual case of bug being found". They put out the word that they had "debugged" the machine, thus introducing the term "debugging a computer program".}} \column{.7\textwidth} \includegraphics[width=\columnwidth]{img/first-computer-bug.jpg} \end{columns} \addtocounter{footnote}{1} \footnotetext{\url{https://en.wikipedia.org/wiki/Grace_Hopper}} \end{frame} \begin{frame}{\textit{What could go wrong...}} \begin{columns} \column{.2\textwidth} \includegraphics[width=.7\columnwidth]{img/bug.png} \column{.8\textwidth} \begin{itemize} \item Niveau client : connexion instable \item Niveau serveur : machine indisponible \item Niveau base de données : données corrompues \item Interactions API / BDD : transactions échouées \item Infrastructure : dépendance compromise \item Sécurité : clef privée publiée sur GitHub :-) \item Déploiement : certificats TLS expirés \item Gestion de l'état : cache périmé \end{itemize} \end{columns} \addtocounter{footnote}{1} \footnotetext{\url{https://xkcd.com/1700/}} \end{frame} \subsection{Outillage} \begin{frame}{\textit{Logging}} \begin{itemize} \item \textit{Log level} \begin{enumerate} \item Trace : \texttt{console.trace} \item Debug : \texttt{console.debug} \item Info : \texttt{console.info} \item Warning : \texttt{console.warn} \item Error : \texttt{console.error} \end{enumerate} \end{itemize} \end{frame} \begin{frame} \centering \includegraphics[width=.8\textwidth]{img/log-level.png} {\small \url{https://stackoverflow.com/a/64806781} (Taco Jan Osinga)} \end{frame} \subsection{Profilage des performances} \begin{frame}{Côté serveur} \begin{columns} \column{.5\textwidth} \begin{itemize} \item \texttt{deno run --v8-flags="--prof" main.ts} \begin{itemize} \item Crée un fichier .log dans le répertoire courant \item \textit{"V8 has built-in sample-based profiling. Profiling is turned off by default, but can be enabled via the \texttt{--prof} command-line option. The sampler records stacks of both JavaScript and C/C++ code."} \end{itemize} \item Lecture du fichier avec cpupro~\footnotemark \begin{itemize} \item Visualisation : \textit{flame graph}~\footnotemark \end{itemize} \end{itemize} \column{.4\textwidth} \includegraphics[width=\columnwidth]{img/flamegraph.png} \end{columns} \addtocounter{footnote}{-1} \footnotetext{{\url{https://discoveryjs.github.io/cpupro/}}} \addtocounter{footnote}{1} \footnotetext{\fullcite{greggFlameGraph2016}} \end{frame} \begin{frame}{Côté client : \textit{React Developer Tools}} \centering \includegraphics[width=.7\textwidth]{img/react-devtools.png} {\small \url{https://react.dev/learn/react-developer-tools}} \end{frame} \section{Auto-évaluation} \begin{frame}{Examens : sujets abordés} \begin{itemize} \item \underline{Pour le contrôle continu :} \begin{itemize} \item Développement d'une ou plusieurs fonctionnalité(s) supplémentaire(s) \item Modifications côté serveur et côté client \item Validées par un scénario de test et/ou des tests unitaires \end{itemize} \item \underline{Pour l'examen final :} \begin{enumerate} \item Modélisation et typage (TypeScript) \item Programmation asynchrone (Promise) \item Développement côté serveur (Oak) \item Développement côté client (React) \item Communications (WebSocket) et authentification (JWT) \item Déploiement (nginx) \end{enumerate} \end{itemize} \end{frame} \subsection{Modélisation et typage (TypeScript)} \begin{frame}[fragile]{Test de connaissances} \begin{alertblock}{Question} \begin{enumerate} \item À quoi sert la dernière ligne de cette définition ? \item Comment appelle-t-on une telle propriété ? \end{enumerate} \end{alertblock} \begin{minted}{ts} export interface PollOptionRow { id: string; poll_id: string; text: string; vote_count: number; [key: string]: SQLOutputValue; } \end{minted} \end{frame} \begin{frame}[fragile]{Test de connaissances} \begin{alertblock}{Question} \begin{enumerate} \item Comment appelle-t-on une fonction telle que définie ci-dessous ? \item Dans quelles situations a-t-on besoin d'une telle fonction ? \end{enumerate} \end{alertblock} \begin{minted}{ts} export function isPollOptionRow( obj: Record, ): obj is PollOptionRow; \end{minted} \end{frame} \begin{frame}[fragile]{Test de connaissances} \begin{alertblock}{Question} \begin{enumerate} \item Comment appelle-t-on le type donné ci-dessous ? \item Comment s'en sert-on et pourquoi ? \end{enumerate} \end{alertblock} \begin{minted}{ts} export type RegisterRequest = Omit< User, "id" | "isAdmin" | "createdAt" > & { password: string }; \end{minted} \end{frame} \subsection{Programmation asynchrone (Promise)} \begin{frame}[fragile]{Test de connaissances} \begin{alertblock}{Question} \begin{enumerate} \item Quel est le type de retour d'une fonction qui retourne une valeur après un délai (en millisecondes) ? \item Implanter une fonction qui résout ou rejette selon que l'entrée est respectivement positive ou négative. \end{enumerate} \end{alertblock} \begin{minted}[fontsize=\small]{ts} function delayedEcho(value: number, ms: number): Promise { // À compléter } // Test succès delayedEcho(10, 300) .then() // ... À compléter .catch() // ... À compléter // Test erreur delayedEcho(-5, 300) // ... À compléter .then() // ... À compléter .catch() // ... À compléter \end{minted} \end{frame} \begin{frame}[fragile]{Test de connaissances} \begin{alertblock}{Question} \begin{enumerate} \item Quelles sont les différences entre deux fonctions qui retournent une Promise lorsque l'une est déclarée \texttt{async} et l'autre non ? \item Utiliser \texttt{await} pour consommer la Promise dans \texttt{echoDouble} et retourner le double de la valeur initiale. \end{enumerate} \end{alertblock} \begin{minted}[fontsize=\small]{ts} function delayedEcho(value: number, ms: number): Promise; async function echoDouble(value: number): Promise { // À compléter } // Test echoDouble(10).then(result => { console.log(result); // 20 }); \end{minted} \end{frame} \begin{frame}[fragile]{Test de connaissances} \begin{alertblock}{Question} \begin{enumerate} \item Quelle est la différence entre \textbf{concurrence} et \textbf{parallélisme} ? \item Compléter la fonction ci-dessous pour faire la somme du résultat de deux appels à \texttt{delayedEcho} en 500 ms (plutôt que 1000 ms). \end{enumerate} \end{alertblock} \begin{minted}[fontsize=\small]{ts} function delayedEcho(value: number, ms: number): Promise; async function sumParallel(a: number, b: number): Promise { // À compléter (appels à delayedEcho avec son paramètre ms = 500) } // Test sumParallel(10, 20).then(result => { console.log(result); // retourne `30` en ~500 ms }); \end{minted} \end{frame} \subsection{Développement côté serveur (Oak)} \begin{frame}[fragile]{Test de connaissances} \begin{alertblock}{Question} \begin{enumerate} \item Quelles sont les méthodes HTTP utilisées dans le cadre d'une API REST ? \item Écrire un serveur Oak qui répond à \texttt{GET /hello/:name} avec \texttt{Hello, \{name\}}. \end{enumerate} \end{alertblock} \begin{minted}{ts} import { Application, Router } from "@oak/oak"; const router = new Router(); const app = new Application(); // À compléter \end{minted} \end{frame} \begin{frame}[fragile]{Test de connaissances} \begin{alertblock}{Question} \begin{enumerate} \item Quel code d'erreur HTTP doit-on retourner au client lorsqu'une ressource est introuvable ? \item Reprendre le code de la question précédente. Retourner une erreur lorsque le paramètre \texttt{name} vaut "Toto". \end{enumerate} \end{alertblock} \begin{minted}{ts} import { Application, Router } from "@oak/oak"; const router = new Router(); const app = new Application(); // À compléter \end{minted} \end{frame} \begin{frame}[fragile]{Test de connaissances} \begin{alertblock}{Question} \begin{enumerate} \item Qu'est-ce qu'un \textit{middleware} ? Quels sont ses paramètres ? Comment chaîne-t-on des \textit{middlewares} ? \item Ajouter un middleware à la route créée précédemment. Il doit logguer la méthode et l'URL de la requête émise par le client avant de traiter la requête. \end{enumerate} \end{alertblock} \begin{minted}{ts} export async function logMiddleware(ctx, next); // À compléter \end{minted} \end{frame} \subsection{Développement côté client (React)} \begin{frame}[fragile]{Test de connaissances} \begin{alertblock}{Question} \begin{enumerate} \item Qu'est-ce que l'\textbf{état} d'un composant ? \item Utiliser le \textit{hook} \texttt{useState} pour stocker un compteur dans l'état du composant, et incrémenter le compteur au clic. \end{enumerate} \end{alertblock} \begin{minted}[fontsize=\footnotesize]{tsx} import { useState } from "react"; export default function Counter() { // À compléter : déclarer un état count initialisé à 0 return (

Compteur : {/* afficher count */}

); } \end{minted} \end{frame} \begin{frame}[fragile]{Test de connaissances} \begin{alertblock}{Question} \begin{enumerate} \item Qu'est-ce qu'un \textbf{effet de bord} ? \item Utiliser le \textit{hook} \texttt{useEffect} et l'API \texttt{fetch} pour récupérer des données depuis le serveur (\texttt{http://localhost:8000/hello/Alice}). \end{enumerate} \end{alertblock} \begin{minted}[fontsize=\footnotesize,ignorelexererrors=true]{tsx} import { useEffect, useState } from "react"; type APIResponse = { message: string; }; export default function FetchMessage() { const [data, setData] = useState(null); useEffect(() => { // À compléter : fetch + setData }, []); return (
{data ?

{data.message}

:

Chargement...

}
); } \end{minted} \end{frame} \begin{frame}[fragile]{Test de connaissances} \begin{alertblock}{Question} \begin{enumerate} \item Dans l'API \texttt{fetch}, quel moyen est mis à notre disposition pour traiter une erreur lors de la récupération de données distantes ? \item Complétez le composant précédent pour traiter une requête vers \texttt{http://localhost:8000/hello/Toto} (qui retourne une erreur, donc). \end{enumerate} \end{alertblock} \begin{minted}[fontsize=\footnotesize,ignorelexererrors=true]{tsx} export default function FetchMessage() { const [data, setData] = useState(null); // À compléter : ajouter un état `error` useEffect(() => { // À compléter : fetch, if -> throw, catch -> setError }, []); if (/* erreur présente */) { return

Error: {/* message */}

; } } \end{minted} \end{frame} \subsection{Communications (WebSocket) et authentification (JWT)} \begin{frame}[fragile]{Test de connaissances} \begin{alertblock}{Question} \begin{enumerate} \item Qu'est-ce que signifie \textit{full-duplex} dans le contexte d'un canal de communications ? \item Écrire une route Oak qui ouvre un WebSocket avec le client, lui dit "Bonjour" et "Au revoir", et lui renvoie ses propres messages. \end{enumerate} \end{alertblock} \begin{minted}[ignorelexererrors=true]{ts} router.get("/ws", (ctx) => { const ws: WebSocket = ...; // À compléter ws.onopen = () => { ... }; // À compléter ws.onmessage = () => { ... }; // À compléter ws.onclose = () => { ... }; // À compléter }); \end{minted} \end{frame} \begin{frame}[fragile]{Test de connaissances} \begin{alertblock}{Question} \begin{enumerate} \item Quels sont les trois champs d'un \textit{JSON Web Token} ? Quelle dynamique existe-t-il entre eux ? \item Compléter la fonction \texttt{verifyJWT} qui vérifie la validité d'un JWT et retourne un objet \texttt{AuthPayload} en cas de succès. \end{enumerate} \end{alertblock} \begin{minted}[fontsize=\small]{ts} interface AuthPayload { username: string; exp: number; } export async function verifyJWT(token: string): Promise { // À compléter... } \end{minted} \end{frame} \begin{frame}[fragile]{Test de connaissances} \begin{alertblock}{Question} \begin{enumerate} \item Une fois le jeton récupéré côté client, comment le fait-on valoir auprès du serveur pour authentifier nos requêtes ? \item Compléter la fonction suivante, qui prend en entrée un jeton et une requête existante pour lui ajouter les en-têtes nécessaires à l'authentification : \end{enumerate} \end{alertblock} \begin{minted}{ts} const authFetch = async (token: string, input: RequestInfo) => { const res = await fetch( ... // À compléter ); }; \end{minted} \end{frame} \subsection{Déploiement (TLS et nginx)} \begin{frame}[fragile]{Test de connaissances} \begin{alertblock}{Question} \begin{enumerate} \item Expliquer le code ci-dessous. \item Les fichiers \texttt{.pem} ont été générés localement par \texttt{mkcert}. Que va-t-il se passer lors de la connexion d'un client ? Pourquoi ? \end{enumerate} \end{alertblock} \begin{minted}{ts} const listener = Deno.listenTls({ port: 443, hostname: "coucou.localhost", cert: await Deno.readTextFile("coucou.localhost.pem"), key: await Deno.readTextFile("coucou.localhost-key.pem"), }); \end{minted} \end{frame} % \begin{frame}[fragile]{Test de connaissances} % \begin{alertblock}{Question} % \begin{enumerate} % \item ... % \end{enumerate} % \end{alertblock} % \begin{minted}[fontsize=\small]{ts} % \end{minted} % \end{frame} \appendix \section{Bibliographie} \begin{frame}[allowframebreaks]{Références} \nocite{*} \printbibliography[heading=none] \end{frame} % \appendix % \section{Annexes} % \begin{frame}{Annexe 1 -- ...} % \end{frame} \end{document}