diff --git a/img/authentication-stateful.png b/img/authentication-stateful.png new file mode 100644 index 0000000..7047f9b Binary files /dev/null and b/img/authentication-stateful.png differ diff --git a/img/authentication-stateless.png b/img/authentication-stateless.png new file mode 100644 index 0000000..1e0570f Binary files /dev/null and b/img/authentication-stateless.png differ diff --git a/img/bug.png b/img/bug.png new file mode 100644 index 0000000..dd719ff Binary files /dev/null and b/img/bug.png differ diff --git a/img/carte-bleue.jpg b/img/carte-bleue.jpg new file mode 100644 index 0000000..49f94bd Binary files /dev/null and b/img/carte-bleue.jpg differ diff --git a/img/cors-example.png b/img/cors-example.png new file mode 100644 index 0000000..28ed724 Binary files /dev/null and b/img/cors-example.png differ diff --git a/img/dom-model.png b/img/dom-model.png new file mode 100644 index 0000000..8bdad33 Binary files /dev/null and b/img/dom-model.png differ diff --git a/img/dto-fowler.png b/img/dto-fowler.png new file mode 100644 index 0000000..3ddaa19 Binary files /dev/null and b/img/dto-fowler.png differ diff --git a/img/eniac.jpg b/img/eniac.jpg new file mode 100644 index 0000000..6dbe4ad Binary files /dev/null and b/img/eniac.jpg differ diff --git a/img/first-computer-bug.jpg b/img/first-computer-bug.jpg new file mode 100644 index 0000000..da5cc5f Binary files /dev/null and b/img/first-computer-bug.jpg differ diff --git a/img/http-cat.png b/img/http-cat.png new file mode 100644 index 0000000..51c114d Binary files /dev/null and b/img/http-cat.png differ diff --git a/img/jwt-decoded.png b/img/jwt-decoded.png new file mode 100644 index 0000000..33d04f3 Binary files /dev/null and b/img/jwt-decoded.png differ diff --git a/img/log-level.png b/img/log-level.png new file mode 100644 index 0000000..945028c Binary files /dev/null and b/img/log-level.png differ diff --git a/img/nginx.png b/img/nginx.png new file mode 100644 index 0000000..20816e1 Binary files /dev/null and b/img/nginx.png differ diff --git a/img/password-123.jpg b/img/password-123.jpg new file mode 100644 index 0000000..5c99117 Binary files /dev/null and b/img/password-123.jpg differ diff --git a/img/proxy-forward.png b/img/proxy-forward.png new file mode 100644 index 0000000..c63f9bb Binary files /dev/null and b/img/proxy-forward.png differ diff --git a/img/proxy-reverse.png b/img/proxy-reverse.png new file mode 100644 index 0000000..e9b8653 Binary files /dev/null and b/img/proxy-reverse.png differ diff --git a/img/react-devtools.png b/img/react-devtools.png new file mode 100644 index 0000000..1fd134d Binary files /dev/null and b/img/react-devtools.png differ diff --git a/img/react-router.png b/img/react-router.png new file mode 100644 index 0000000..d79363c Binary files /dev/null and b/img/react-router.png differ diff --git a/img/react.png b/img/react.png new file mode 100644 index 0000000..cac1027 Binary files /dev/null and b/img/react.png differ diff --git a/img/rolldown.png b/img/rolldown.png new file mode 100644 index 0000000..4d28e40 Binary files /dev/null and b/img/rolldown.png differ diff --git a/img/tls-self-signed.png b/img/tls-self-signed.png new file mode 100644 index 0000000..86400e0 Binary files /dev/null and b/img/tls-self-signed.png differ diff --git a/img/tls-univ-brest.png b/img/tls-univ-brest.png new file mode 100644 index 0000000..4e43ae0 Binary files /dev/null and b/img/tls-univ-brest.png differ diff --git a/img/vite.png b/img/vite.png new file mode 100644 index 0000000..8b23c91 Binary files /dev/null and b/img/vite.png differ diff --git a/img/websocket-tp.png b/img/websocket-tp.png new file mode 100644 index 0000000..874aa75 Binary files /dev/null and b/img/websocket-tp.png differ diff --git a/img/websocket.png b/img/websocket.png new file mode 100644 index 0000000..b5891c6 Binary files /dev/null and b/img/websocket.png differ diff --git a/img/yubikey.jpg b/img/yubikey.jpg new file mode 100644 index 0000000..30c83c3 Binary files /dev/null and b/img/yubikey.jpg differ diff --git a/res/authentication.drawio b/res/authentication.drawio new file mode 100644 index 0000000..35b9c9e --- /dev/null +++ b/res/authentication.drawio @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/proxy.drawio b/res/proxy.drawio new file mode 100644 index 0000000..0c7c3e8 --- /dev/null +++ b/res/proxy.drawio @@ -0,0 +1,241 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/react-hooks-lifecycle.drawio b/res/react-hooks-lifecycle.drawio new file mode 100644 index 0000000..121e8ab --- /dev/null +++ b/res/react-hooks-lifecycle.drawio @@ -0,0 +1,196 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/websocket.drawio b/res/websocket.drawio new file mode 100644 index 0000000..c724ed6 --- /dev/null +++ b/res/websocket.drawio @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/slides.bib b/slides.bib index ca3db06..63121d0 100644 --- a/slides.bib +++ b/slides.bib @@ -38,6 +38,26 @@ urldate = {2026-01-19} } +@book{fowlerPatterns2012, + title={Patterns of enterprise application architecture}, + author={Fowler, Martin}, + year={2012}, + publisher={Addison-Wesley} +} + +@inbook{Lannurien2023, + title = {{Serverless Cloud Computing: State of the Art and Challenges}}, + author = {Lannurien, Vincent and D'Orazio, Laurent and Barais, Olivier and Boukhobza, Jalil}, + year = 2023, + booktitle = {Serverless Computing: Principles and Paradigms}, + publisher = {Springer International Publishing}, + address = {Cham}, + pages = {275--316}, + doi = {10.1007/978-3-031-26633-1_11}, + isbn = {978-3-031-26633-1}, + editor = {Krishnamurthi, Rajalakshmi and Kumar, Adarsh and Gill, Sukhpal Singh and Buyya, Rajkumar}, +} + @misc{ParseDontValidate2019, author = {King, Alexis}, title = {Parse, Don’t Validate}, diff --git a/slides.cls b/slides.cls index ddd17f6..006cad9 100644 --- a/slides.cls +++ b/slides.cls @@ -50,22 +50,7 @@ {\thefield{entrytype}}} {\multicitedelim} {\usebibmacro{postnote}} -% blank footnotes using \footnote[]{} -\let\svthefootnote\thefootnote -\textheight 1in -\newcommand\blankfootnote[1]{% - \let\thefootnote\relax\footnotetext{#1}% - \let\thefootnote\svthefootnote% -} -\let\svfootnote\footnote -\renewcommand\footnote[2][?]{% - \if\relax#1\relax% - \blankfootnote{#2}% - \else% - \if?#1\svfootnote{#2}\else\svfootnote[#1]{#2}\fi% - \fi -} - + % captions \RequirePackage{caption} \captionsetup{font=scriptsize,labelfont=scriptsize} @@ -105,6 +90,10 @@ \molochset{subsectionpage=progressbar} % table of contents \setbeamertemplate{section in toc}[sections numbered] +\makeatletter +\patchcmd{\beamer@sectionintoc}{\vskip1.5em}{\vskip0.3em}{}{} +\patchcmd{\beamer@subsectionintoc}{\vskip0.75em}{\vskip0.15em}{}{} +\makeatother % smaller first-level bullet points \setbeamertemplate{itemize item}{\textbullet} % smaller bibliography entries @@ -123,15 +112,18 @@ } % fonts and symbols -\RequirePackage{pifont} -\newcommand{\cmark}{\color{YellowGreen}\ding{51}} -\newcommand{\xmark}{\color{BrickRed}\ding{55}} \RequirePackage{textcomp} \RequirePackage{emoji} -% markers +% circles \RequirePackage{circledsteps} \pgfkeys{/csteps/inner color=white} \pgfkeys{/csteps/fill color=black} +% markers +\RequirePackage{pifont} +\newcommand{\cmark}{{\color{YellowGreen}\ding{51}}} +\newcommand{\xmark}{{\color{BrickRed}\ding{55}}} +\newcommand{\imark}{{\color{Orange}\ding{109}}} +% pills \newcommand{\DONE}{% \CircledParamOpts{inner color=black, outer color=LimeGreen, fill color=LimeGreen}{1}{\textbf{DONE}} % } diff --git a/slides.pdf b/slides.pdf index cb7fe79..1b3802d 100644 Binary files a/slides.pdf and b/slides.pdf differ diff --git a/slides.tex b/slides.tex index 1a9635d..025cd88 100644 --- a/slides.tex +++ b/slides.tex @@ -48,11 +48,10 @@ \end{center} \end{frame} -\begin{framefont}{\footnotesize} \begin{frame}[t]{Plan du cours} \begin{columns} \column{0.6\textwidth} - \tableofcontents%[hideallsubsections] + \tableofcontents[hideallsubsections] \column{0.4\textwidth} \begin{center} @@ -60,7 +59,6 @@ \end{center} \end{columns} \end{frame} -\end{framefont} \section{Organisation de l'UE} @@ -101,48 +99,6 @@ \section{Introduction} -\begin{frame}{Contenu du cours} - \begin{itemize} - \item État et gestion de l'état - \begin{itemize} - \item Contrats - \begin{itemize} - \item Interfaces - \item \textit{Data Transfer Objects} - \end{itemize} - \item Persistance - \begin{itemize} - \item Base de données - \item Transactions - % \item Cache - \end{itemize} - \end{itemize} - \item Protocoles - \begin{itemize} - \item HTTP - \begin{itemize} - \item \textit{Status codes} - \item \textit{CORS} (\textit{Cross-Origin Resource Sharing}) - \end{itemize} - \item Authentification - \begin{itemize} - \item \textit{Tokens} - \end{itemize} - \end{itemize} - \item Performances et profilage - \begin{itemize} - \item Instrumentation (serveur) - \item Inspecteur (client) - \item Injection de trafic - \end{itemize} - \item Déploiement - \begin{itemize} - \item SSL, certificats - \item \textit{Reverse proxy} - \end{itemize} - \end{itemize} -\end{frame} - \begin{frame}{Technologies utilisées} \begin{itemize} \item Côté serveur : @@ -218,6 +174,8 @@ \centering \includegraphics[width=.8\textwidth]{img/scaling-monolith.png} + \addtocounter{footnote}{1} + \footnotetext{\fullcite{Lannurien2023}} \addtocounter{footnote}{1} \footnotetext{\fullcite{fowlerMicroservices}} \end{frame} @@ -226,6 +184,9 @@ \centering \includegraphics[width=.8\textwidth]{img/scaling-uservices.png} + \addtocounter{footnote}{-1} + \footnotetext{\fullcite{Lannurien2023}} + \addtocounter{footnote}{1} \footnotetext{\fullcite{fowlerMicroservices}} \end{frame} @@ -577,16 +538,16 @@ const makeNoise = (animal: Animal) => { \begin{frame}[fragile]{Exercice 3 : types au \textit{runtime}} \begin{minted}{ts} interface Dog { - kind: "dog"; // Tagged union + kind: "dog"; // Union discriminée bark: () => void; } interface Cat { - kind: "cat"; // Tagged union + kind: "cat"; // Union discriminée meow: () => void; } -type Animal = Dog | Cat; // Union type +type Animal = Dog | Cat; // Type union const makeNoise = (animal: Animal) => { if (animal.kind === "dog") animal.bark(); @@ -617,11 +578,11 @@ const makeNoise = (animal: Animal) => { \begin{frame}[fragile]{Exercice 3 : types au \textit{runtime}} \begin{minted}[fontsize=\footnotesize]{ts} interface Dog { - kind: "dog"; // Tagged union + kind: "dog"; // Union discriminée bark: () => void; } interface Cat { - kind: "cat"; // Tagged union + kind: "cat"; // Union discriminée meow: () => void; } type Animal = Dog | Cat; @@ -758,7 +719,7 @@ try { } catch (e) { console.error("Error reading file:", e); } finally { - // always close the file handle + // Toujours fermer le fichier if (file) { file.close(); } @@ -1324,28 +1285,36 @@ export function pollApiToRow( \end{frame} \begin{frame}{HTTP : codes de réponse} - \begin{itemize} - \item \texttt{100} à \texttt{199} : réponses informatives + \begin{columns} + \column{.5\textwidth} \begin{itemize} - \item \texttt{101 Switching Protocols} + \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} - \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} @@ -1859,7 +1828,7 @@ Deno.test({ \end{frame} \begin{frame}{Exercice 9 : un premier jeu de tests} - \begin{block}{Organisation des fichiers de l'application} + \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} @@ -1877,75 +1846,630 @@ Deno.test({ \section{Client web} +\subsection{Requêtes HTTP} + \begin{frame}{HTTP, encore} \begin{itemize} - \item API \texttt{fetch} + \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]{Un client web minimal avec TypeScript} +\begin{frame}[fragile]{Requêtes HTTP : \texttt{XMLHttpRequest}} \begin{minted}{js} - const foo = "bar"; +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}{Framework : React} - \begin{itemize} - \item Hooks +\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 \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
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} - +\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}