added labs recap, http stuff, oak routing and middleware

This commit is contained in:
khannurien
2026-01-28 21:12:03 +00:00
parent 210c044ed6
commit 9730dbe550
21 changed files with 1119 additions and 2570 deletions

View File

@@ -44,19 +44,19 @@
\begin{frame}{Introduction}
\begin{center}
\url{https://info-demo-sor.univ-brest.fr}
\url{http://info-demo-sor.univ-brest.fr:3000}
\end{center}
\end{frame}
\begin{framefont}{\small}
\begin{framefont}{\footnotesize}
\begin{frame}[t]{Plan du cours}
\begin{columns}
\column{0.5\textwidth}
\column{0.6\textwidth}
\tableofcontents%[hideallsubsections]
\column{0.5\textwidth}
\column{0.4\textwidth}
\begin{center}
\includegraphics[width=0.9\columnwidth]{img/logos.png}
\includegraphics[width=\columnwidth]{img/screenshot.png}
\end{center}
\end{columns}
\end{frame}
@@ -69,7 +69,7 @@
\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 pour l'\textbf{authentification} (avec ou sans état) d'un client auprès d'un serveur ;
\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 :
@@ -167,7 +167,7 @@
\end{itemize}
\end{frame}
\section{Cas d'usage}
\section{Cas d'étude applicatif}
\begin{frame}{Présentation de l'application}
\begin{block}{Description}
@@ -210,20 +210,26 @@
\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{fowlerMicroservices}}
\end{frame}
\begin{frame}{Monolithes ou microservices}
\centering
\includegraphics[width=.8\textwidth]{img/scaling-uservices.png}
\footnotetext{\fullcite{fowlerMicroservices}}
\end{frame}
\section{JavaScript, TypeScript et runtime}
\section{JavaScript, TypeScript et \textit{runtime}}
\begin{frame}{JavaScript}
\centering
@@ -267,7 +273,8 @@
\centering
\includegraphics[width=.7\columnwidth]{img/jetbrains-ecosystem-2025-platforms.png}
\footnote[]{\url{https://devecosystem-2025.jetbrains.com/tools-and-trends}}
\addtocounter{footnote}{1}
\footnotetext{\url{https://devecosystem-2025.jetbrains.com/tools-and-trends}}
\end{frame}
\begin{frame}{TypeScript}
@@ -313,6 +320,8 @@
\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}
@@ -382,6 +391,26 @@ 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<string, string | number | boolean> = {
@@ -583,12 +612,6 @@ const makeNoise = (animal: Animal) => {
\end{minted}
\end{frame}
\begin{frame}[fragile]{Généricité}
\begin{itemize}
\item \texttt{SomeContainer<T extends SomeData>}
\end{itemize}
\end{frame}
\begin{frame}[fragile]{Exercice 3 : types au \textit{runtime}}
\begin{minted}[fontsize=\footnotesize]{ts}
interface Dog {
@@ -612,22 +635,157 @@ makeNoise(dog);
\end{minted}
\end{frame}
\begin{frame}{Gestion des erreurs : exceptions}
\begin{frame}[fragile]{Type Guards : \textit{compile time} et \textit{run time}}
\begin{itemize}
\item Exceptions
\item \texttt{try}/\texttt{catch}
\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}
\begin{frame}{Programmation asynchrone}
TODO: Exemple (requête API) avec figure
\subsection{Généricité}
\begin{frame}[fragile]{Généricité}
\begin{itemize}
\item \texttt{SomeContainer<T extends SomeData>}
\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<T extends SomeData> {
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 {
// always close the file handle
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}
@@ -644,7 +802,8 @@ makeNoise(dog);
\centering
\includegraphics[width=.8\textwidth]{img/promises.png}
\footnote[]{\url{https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise}}
\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}
@@ -742,7 +901,23 @@ try {
\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}
@@ -871,6 +1046,8 @@ contents.forEach((result, i) => {
\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
@@ -919,9 +1096,35 @@ contents.forEach((result, i) => {
\begin{frame}{Gestion des dépendances}
\begin{columns}
\column{.8\textwidth}
Outils (npm, etc.)
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}
Modules (ESM vs CommonJS) et imports
\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}
@@ -931,11 +1134,156 @@ contents.forEach((result, i) => {
\section{Serveur web}
\begin{frame}{HTTP}
\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<string, SQLOutputValue> | undefined}
\begin{itemize}
\item Peut être \texttt{undefined} si introuvable !
\end{itemize}
\item Liste : \texttt{Record<string, SQLOutputValue>[]}
\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<string, SQLOutputValue>
): 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}
@@ -998,6 +1346,27 @@ contents.forEach((result, i) => {
\end{itemize}
\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}.
@@ -1038,14 +1407,14 @@ async function handleConn(conn: Deno.Conn) {
\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" \
--data "hello world"
-H "Content-Type: text/plain" \
-d "hello world"
\end{minted}
\vspace{1cm}
\begin{columns}
\column{.5\textwidth}
\column{.4\textwidth}
\begin{minted}{text}
POST / HTTP/1.1
Host: localhost:8080
@@ -1057,17 +1426,19 @@ Content-Length: 11
hello world
\end{minted}
\column{.5\textwidth}
\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 TODO: new Date().toUTCString();
\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}
@@ -1079,6 +1450,7 @@ hello world
\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: "..." \}}
@@ -1090,22 +1462,233 @@ hello world
\end{columns}
\end{frame}
\begin{frame}{JSON}
\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<T> {
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...
\end{itemize}
\vspace{.5cm}
\begin{minted}{ts}
export type APIResponse<T> = APISuccess<T> | 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<Poll> = {
success: true,
data: poll,
};
// Oak donne passe implicitement le code HTTP OK
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<Poll> = {
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<Poll> = {
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}{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}{Middleware : CORS}
TODO: à mettre côté client ?
\begin{frame}[fragile]{Middleware}
\begin{minted}[fontsize=\small]{ts}
interface HelloWorldState {
hello?: string;
world?: string;
}
async function hello(ctx: Context<HelloWorldState>, next: Next) {
ctx.state.hello = "Hello";
await next();
}
async function world(ctx: Context<HelloWorldState>, next: Next) {
ctx.state.world = "world";
await next();
}
const router = new Router<HelloWorldState>();
router.get("/hello", hello, world, (ctx) => {
ctx.response.body = `${ctx.state.hello}, ${ctx.state.world}!`;
});
const app = new Application<HelloWorldState>();
\end{minted}
\end{frame}
\begin{frame}{Middleware : Gestion des erreurs}
\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}{Base de données}
\begin{columns}
\column{.8\textwidth}
@@ -1147,7 +1730,7 @@ CREATE TABLE thing_options (
\vspace{.5cm}
\begin{minted}[fontsize=\footnotesize]{ts}
// Persist vote
// Enregistrer le vote
const voteId = crypto.randomUUID();
const now = new Date().toISOString();
db.prepare(
@@ -1156,7 +1739,7 @@ db.prepare(
"VALUES (?, ?, ?, ?, ?);",
).run(voteId, pollId, optionId, userId || null, now);
// Increment tally
// Incrémenter le compteur
db.prepare(
"UPDATE poll_options SET vote_count = vote_count + 1 WHERE id = ?;",
).run(optionId);
@@ -1173,18 +1756,47 @@ db.prepare(
\begin{minted}{ts}
try {
db.prepare("BEGIN").run(); // Start transaction
db.prepare("BEGIN").run(); // Début de transaction
db.prepare("...").run(); // Query 1
db.prepare("...").run(); // Query 2
db.prepare("...").run(); // Requête 1
db.prepare("...").run(); // Requête 2
db.prepare("COMMIT").run(); // Commit transaction
db.prepare("COMMIT").run(); // Transaction validée
} catch {
db.prepare("ROLLBACK").run(); // Rollback on any error
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}
% TODO: pas de paradigme précis, responsabilité côté developpeur
% TODO: reprendre la terminologie du framework (routes, middleware, etc.)
\begin{center}
\includegraphics[width=.5\textwidth]{img/point.png}
\end{center}
\end{frame}
\begin{frame}[fragile]{Exercice : ...}
TODO: proposer une organisation
TODO: montrer les directives export / import associées
\end{frame}
\subsection{Fiabilité et tests}
\begin{frame}[fragile]{Tests}
\begin{minted}{ts}
import { assertEquals, assert } from "@std/assert";
@@ -1205,6 +1817,10 @@ Deno.test({
\end{minted}
\end{frame}
\begin{frame}[fragile]{Exercice ...}
TODO: écrire des tests pour une ou deux routes
\end{frame}
\section{Client web}
\begin{frame}{HTTP, encore}
@@ -1266,10 +1882,6 @@ Deno.test({
\section{Interactions}
\begin{frame}{REST}
\end{frame}
\begin{frame}{Data Transfer Objects}
\end{frame}
@@ -1304,6 +1916,18 @@ Deno.test({
\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}
\end{frame}
\begin{frame}[fragile]{Reverse proxy}
\begin{minted}[fontsize=\footnotesize]{text}
http {
@@ -1330,8 +1954,33 @@ http {
\end{itemize}
\end{frame}
\begin{frame}{CORS}
TODO:
\end{frame}
\section{Débuggage et profilage}
\begin{frame}{\textit{What could go wrong...}}
\begin{itemize}
\item Niveau client
\item Niveau serveur
\item Niveau base de données
\item Interactions API / BDD
\item Infrastructure
\item Sécurité
\item Déploiement
\item Gestion de l'état
\end{itemize}
\end{frame}
\subsection{Outillage}
\begin{frame}{\textit{Logging}}
TODO:
\end{frame}
\subsection{Profilage des performances}
\begin{frame}{Côté serveur}
\begin{columns}
\column{.5\textwidth}
@@ -1361,6 +2010,7 @@ http {
\section{Bibliographie}
\begin{frame}[allowframebreaks]{Références}
\nocite{*}
\printbibliography[heading=none]
\end{frame}