+
+ 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 \texttt{useParams}
- \item \texttt{useState}
- \item \texttt{useRef}
- \item \texttt{useEffect}
+ \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}
- \item Functional Components
- \item React Router
- \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}{Bundling}
+\begin{frame}{IHM : paradigme}
\begin{itemize}
- \item Bundler
- \item Vite
+ \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}
-\begin{frame}{React : architecture}
+\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 Service Layer Pattern
+ \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}{Bundler}
-
+\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
+ );
+}
+ \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}
-
+\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}
-
+\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}{Data Transfer Objects}
-
+\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}{WebSockets}
-
+\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{%
@@ -1970,24 +2494,77 @@ 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}
+\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]{Reverse proxy}
+\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 coucou.localhost;
+ server_name api.coucou.localhost;
location / {
proxy_pass http://127.0.0.1:8000;
@@ -2001,36 +2578,188 @@ http {
\end{minted}
\end{frame}
-\begin{frame}{Certificats SSL}
+\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 Protocole (TLSv1.2, TLSv1.3)
- \item Algorithme de chiffrement : AES 128/256, etc.
+ \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}{CORS}
- TODO:
+\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{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}
+ \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}}
- TODO:
+ \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}
@@ -2044,21 +2773,380 @@ http {
\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~\footnote[frame]{\url{https://discoveryjs.github.io/cpupro/}}
+ \item Lecture du fichier avec cpupro~\footnotemark
\begin{itemize}
- \item Visualisation : \textit{flame graph}~\footnote[frame]{\fullcite{greggFlameGraph2016}}
+ \item Visualisation : \textit{flame graph}~\footnotemark
\end{itemize}
\end{itemize}
- \column{.5\textwidth}
+ \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}
-
+\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}