nodebook
Version:
Node.js • Apprendre par la pratique. Familiarisez-vous avec JavaScript, Node.js et l'écosystème de modules npm. Apprenez à concevoir et à déployer des *applications web* et des *outils en ligne de commande*.
1,328 lines (1,237 loc) • 385 kB
HTML
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="generator" content="Asciidoctor 2.0.9">
<title>Créer une application web</title>
<style>
#header,
#content,
#footer {
margin: 2rem auto;
max-width: 46rem;
}
#header {
margin-top: 0;
}
.title,
#toctitle {
color: var(--dark-accent);
}
a {
/* white-space: nowrap; */
}
img, iframe, video, audio {
max-width: 100%;
}
p {
font-weight: normal;
}
/* Taken out from book.css */
dl dt {
margin-bottom: 0.3125em;
font-weight: bold;
}
dl dd {
margin-bottom: 1.25em;
}
dt,
th.tableblock,
td.content,
div.footnote {
text-rendering: optimizeLegibility;
}
.literalblock pre,
.literalblock pre[class],
.listingblock pre,
.listingblock pre[class] {
overflow: auto;
word-wrap: break-word;
}
.literalblock pre.nowrap,
.literalblock pre[class].nowrap,
.listingblock pre.nowrap,
.listingblock pre[class].nowrap {
overflow-x: auto;
white-space: pre;
word-wrap: normal;
}
.listingblock {
margin: 0 0 2em;
}
.listingblock > .content {
position: relative;
}
.listingblock > .title {
font-weight: bold;
}
.listingblock code[data-lang]::before {
display: none;
content: attr(data-lang);
position: absolute;
font-size: 0.75em;
top: 1em;
right: 1em;
line-height: 1;
text-transform: uppercase;
color: #999;
}
.listingblock:hover code[data-lang]::before {
display: block;
}
.listingblock.terminal pre .command::before {
content: attr(data-prompt);
padding-right: 0.5em;
color: #999;
}
td.hdlist1,
td.hdlist2 {
vertical-align: top;
padding-right: 0.625em;
}
td.hdlist1 {
font-weight: bold;
}
.literalblock + .colist,
.listingblock + .colist {
margin-top: -1.5em;
}
.colist td:not([class]):first-child {
padding: 0.4em 0.75em 0 0.75em;
line-height: 1;
vertical-align: top;
}
.colist td:not([class]):first-child img {
max-width: none;
}
.colist td:not([class]):last-child {
padding: 0.25em 0;
}
/* Custom classes */
.line-through {
text-decoration: line-through;
}
.RemarquePreTitre,
#toctitle {
font-style: normal;
font-weight: bold;
}
.RemarquePreTitre::after {
content: "•";
padding-left: 5px;
}
.admonitionblock {
}
.admonitionblock > table,
.exampleblock {
--commented: rgba(17, 17, 68, .65);
--border-radius-base: 8px;
background-color: #fafafa;
border: 1px solid var(--dark-shade);
border-left: none;
border-right: none;
margin: 1.5em 0;
padding: 1em;
}
.exampleblock .title {
font-weight: bold;
}
.icon .title {
font-size: 2em;
}
.admonitionblock > table td.icon {
display: none;
vertical-align: middle;
}
@media screen and (min-width: 769px) {
.admonitionblock > table td.icon {
display: table-cell;
}
}
.admonitionblock > table td.icon {
padding-right: 1em;
}
.admonitionblock > table td.icon img {
max-width: none;
}
.colist ol {
margin-left: 1.5em; /* aligns with the listing edge */
padding-left: 0;
font-weight: bold; /* makes it stand out more */
}
.colist ol p {
margin: 0 0 .5em;
}
.listingblock:not(.prismjs) pre,
.language-bash.hljs {
background: #323232;
color: wheat;
margin: 0;
padding: 1rem;
}
.language-bash.hljs .hljs-built_in,
.language-bash.hljs .hljs-builtin-name {
color: white;
}
.language-bash.hljs .hljs-string {
color: lightgreen;
}
.language-bash.hljs .hljs-variable {
color: lightskyblue;
}
.keyseq {
font-weight: normal;
white-space: nowrap;
}
.language-bash.hljs .keyseq {
color: white;
}
.language-bash.hljs kbd {
background: transparent;
box-shadow: none;
color: white;
font-size: 0.8em;
font-weight: bold;
padding: 0.1em 0.4em;
}
.listingblock pre.highlightjs,
.listingblock pre.prismjs {
background-color: transparent;
margin: 0;
padding: 0;
}
.listingblock pre.highlightjs > code,
.listingblock pre.prismjs {
border-left: 4px solid var(--dark-accent);
padding-left: 1em;
font-size: .8em;
}
.listingblock pre.highlightjs > code.language-bash {
border-left-color: limegreen;
}
.token.comment .conum {
font-weight: normal;
}
.hdlist .hdlist1 {
text-align: right;
white-space: nowrap;
}
td > p:first-child {
margin-top: 0;
}
.hljs-comment {
font-style: normal !important;
}
#toc.toc2 a {
text-decoration: none;
white-space: normal;
}
#toc.toc2 a:hover,
#toc.toc2 a:focus {
text-decoration: underline;
}
#toc.toc2 ul {
list-style: none;
}
#toc.toc2 > ul {
padding-left: 0;
}
#toc.toc2 ul ul {
padding-left: 1em;
}
@media screen and (min-width: 769px) {
body {
padding-left: 25vw !important;
}
#header,
#content,
#footer {
margin-left: 0;
}
#toc.toc2 {
height: 100%;
left: 0;
max-width: 20vw;
overflow: auto;
padding: 1rem;
position: fixed;
top: 0;
z-index: 1000;
}
#toc.toc2 > ul {
font-size: 0.85em;
}
#toc li.active > a[href^="#"],
[id]:target {
background: #ffc;
}
}
.admonitionblock.context-callout > table {
border-width: 5px;
border-color: var(--brand-color);
}
</style>
<style type="text/css" class="prism-theme">/**
* prism.js default theme for JavaScript, CSS and HTML
* Based on dabblet (http://dabblet.com)
* @author Lea Verou
*/
code[class*="language-"],
pre[class*="language-"] {
color: black;
background: none;
text-shadow: 0 1px white;
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
font-size: 1em;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection,
code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection {
text-shadow: none;
background: #b3d4fc;
}
pre[class*="language-"]::selection, pre[class*="language-"] ::selection,
code[class*="language-"]::selection, code[class*="language-"] ::selection {
text-shadow: none;
background: #b3d4fc;
}
@media print {
code[class*="language-"],
pre[class*="language-"] {
text-shadow: none;
}
}
/* Code blocks */
pre[class*="language-"] {
padding: 1em;
margin: .5em 0;
overflow: auto;
}
:not(pre) > code[class*="language-"],
pre[class*="language-"] {
background: #f5f2f0;
}
/* Inline code */
:not(pre) > code[class*="language-"] {
padding: .1em;
border-radius: .3em;
white-space: normal;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: slategray;
}
.token.punctuation {
color: #999;
}
.namespace {
opacity: .7;
}
.token.property,
.token.tag,
.token.boolean,
.token.number,
.token.constant,
.token.symbol,
.token.deleted {
color: #905;
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted {
color: #690;
}
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string {
color: #9a6e3a;
background: hsla(0, 0%, 100%, .5);
}
.token.atrule,
.token.attr-value,
.token.keyword {
color: #07a;
}
.token.function,
.token.class-name {
color: #DD4A68;
}
.token.regex,
.token.important,
.token.variable {
color: #e90;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}
</style>
<link rel="stylesheet" href="https://oncletom.io/styles/blog.css?v=v3.3.2">
<!-- WebMentions -->
<link rel="pingback" href="https://webmention.io/oncletom.io/xmlrpc">
<link rel="webmention" href="https://webmention.io/oncletom.io/webmention">
<!-- Open Graph -->
<meta property="og:type" content="book">
<meta property="og:image" content="https://oncletom.io/images/publications/nodejs-cover.png">
<meta property="og:book:author" content="Thomas Parisot">
<meta property="og:book:isbn" content="978-2212139938">
<meta property="og:book:release_date" content="2018-12-06">
<meta property="og:book:tag" content="Node.js">
<meta property="og:book:tag" content="JavaScript">
<meta property="og:book:tag" content="npm">
<meta property="og:book:tag" content="Développement front-end">
<meta property="og:book:tag" content="Développement back-end">
<meta property="og:locale" content="fr_FR">
<meta property="og:site_name" content="Node.js • Apprendre par la pratique">
<!-- Twitter OpenGraph -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="@oncletom">
<meta name="twitter:creator" content="@oncletom">
<style type="text/css" class="extension-interactive-runner">[data-interactive-runtime="loaded"] .interactive--javascript code[data-lang]:before {
background-color: #fcfcfc;
border: 1px solid #0c2;
border-radius: 1px;
color: #0c2;
cursor: pointer;
display: inline-block;
font-weight: bold;
padding: .5em 1em;
top: -2em;
}
[lang="en"] [data-interactive-runtime="loaded"] .interactive--javascript code[data-lang]:before {
content: "▶ run code";
}
[lang="fr"] [data-interactive-runtime="loaded"] .interactive--javascript code[data-lang]:before {
content: "▶ voir le résultat";
}
.interactive--javascript code[data-label]:before {
content: attr(data-label);
}
.interactive iframe {
position: absolute;
visibility: hidden;
}
.interactive.status--loaded iframe {
position: inherit;
visibility: visible;
}
.interactive.status--loading {
opacity: .5;
}
</style>
<script class="extension-interactive-runner">
(function(d){
document.addEventListener('DOMContentLoaded', function(){
const script = d.createElement('script');
script.src = 'https://embed.runkit.com/';
script.async = true;
script.onload = function(){
function makeListingInteractive (element){
if (element.classList.contains('interactive--installed') || element.classList.contains('status--loading')) {
return;
}
const code = element.querySelector('code');
const nodeVersion = /interactive--runtime--node-([^\s]+)/.exec(element.className)[1];
const isEndpoint = element.classList.contains('interactive--endpoint');
const mode = isEndpoint ? 'endpoint' : null;
let preamble = '';
const source = code
.textContent
.replace(/\/\/\s*\(\d+\)$/gm, '')
.replace(/^["']?use strict["'][; ]*\n/, '');
if (isEndpoint) {
preamble = `process.nextTick(() => {
if (typeof module.exports === 'function') {
exports.endpoint = module.exports;
}
else if (typeof server !== 'undefined') {
exports.endpoint = server.listeners("request").pop();
}
});`
}
element.classList.add('status--loading');
// eslint-disable-next-line no-undef
RunKit.createNotebook({
nodeVersion,
element,
source,
mode,
preamble,
onLoad: function(ntbk) {
element.classList.add('interactive--installed');
element.classList.remove('status--loading');
element.classList.add('status--loaded');
code.parentNode.setAttribute('hidden', true);
ntbk.evaluate();
}
});
}
function installEvents () {
function getParent(el, condition) {
let parent = el;
while(parent = parent.parentNode) {
if (condition(parent)) {
return parent;
}
}
}
function hasClass(className) {
return function check(el) {
return el.classList.contains(className);
}
}
document.querySelector('body').addEventListener('click', function(el) {
if (el.target.classList.contains('language-javascript') || el.target.classList.contains('language-js')) {
const parentNode = getParent(
el.target,
hasClass('interactive--javascript')
);
makeListingInteractive(parentNode);
}
});
}
installEvents();
document.body.dataset.interactiveRuntime = 'loaded';
};
document.body.appendChild(script);
});
})(document);</script>
<style type="text/css">
.listingblock [data-bash-subs]::before {
content: attr(data-bash-subs) " ";
opacity: .5; }
.listingblock [data-bash-conum]::before {
content: "(" attr(data-bash-conum) ")";
font-weight: bold;
opacity: .7;
}</style>
<script>
(function(d){
d.addEventListener('DOMContentLoaded', function(){
const {origin} = window.location;
Array.from(document.querySelectorAll('a[href]'))
.filter(link => link.href.indexOf(origin) !== 0)
.forEach(link => {
link.setAttribute('target', '_blank');
link.setAttribute('rel', 'noopener');
});
});
})(document);</script>
<script>
(function(d){
d.addEventListener('DOMContentLoaded', function(){
const script = d.createElement('script');
script.src = 'https://unpkg.com/menuspy@1.3.0/dist/menuspy.js';
script.async = true;
script.onload = () => new MenuSpy(document.querySelector('#toc'), {enableLocationHash: false});
d.body.appendChild(script);
});
})(document);</script>
<style type="text/css">
#toc li.active > a[href^="#"] {
font-weight: bold;
}
#toc li.active > a[href^="#"]::before {
content: "▶ ";
display: inline-block;
position: absolute;
margin-left: -1.2em;
font-size: .8em;
margin-top: 3px;
}
</style>
</head>
<body class="book toc2 toc-left">
<div id="header">
<h1>Créer une application web</h1>
<div id="toc" class="toc2">
<div id="toctitle">Table des matières</div>
<ul class="sectlevel1">
<li><a href="#webapp">1. Composer son application web</a>
<ul class="sectlevel2">
<li><a href="#server">1.1. Démarrer un serveur HTTP</a></li>
<li><a href="#path">1.2. Répondre à un chemin (routing)</a></li>
<li><a href="#static">1.3. Répondre avec des fichiers statiques</a></li>
<li><a href="#arguments">1.4. Réagir aux arguments d’URL</a></li>
<li><a href="#post">1.5. Recevoir des données de formulaire (POST)</a></li>
<li><a href="#upload">1.6. Téléverser des fichiers</a></li>
<li><a href="#cookies">1.7. Garder un lien avec les cookies</a></li>
<li><a href="#templating">1.8. Structurer l’affichage avec les gabarits de présentation</a></li>
<li><a href="#dev">1.9. Pendant le développement : relancer le serveur automatiquement</a></li>
</ul>
</li>
<li><a href="#express">2. Organiser une application avec le framework Express</a>
<ul class="sectlevel2">
<li><a href="#setup">2.1. Configuration du framework</a></li>
<li><a href="#middleware">2.2. Greffer des extensions (middlewares)</a></li>
<li><a href="#views">2.3. Brancher les gabarits de présentation</a></li>
<li><a href="#frontend">2.4. Intégrer les ressources front-end (CSS, images, JavaScript)</a></li>
<li><a href="#database">2.5. Brancher une base de données</a></li>
<li><a href="#sessions">2.6. Sessions utilisateurs</a></li>
<li><a href="#logs">2.7. Tracer les actions (logs)</a></li>
</ul>
</li>
<li><a href="#modularity">3. Vers un code réutilisable et testable</a>
<ul class="sectlevel2">
<li><a href="#modulariser_le_code_des_routes">3.1. Modulariser le code des routes</a></li>
<li><a href="#tests.unit">3.2. Un code testable est un code indépendant du framework</a></li>
<li><a href="#deployment">3.3. Déployer automatiquement</a></li>
</ul>
</li>
<li><a href="#advanced">4. Pour aller plus loin</a>
<ul class="sectlevel2">
<li><a href="#advanced.server">4.1. Pourquoi lancer un serveur ?</a></li>
<li><a href="#http">4.2. Comprendre le modèle HTTP</a></li>
<li><a href="#database-choice">4.3. Quel(s) moteur(s) de base(s) de données choisir ?</a></li>
<li><a href="#security">4.4. Protéger nos applications</a></li>
<li><a href="#lambda">4.5. Une application minimaliste avec les Lambda</a></li>
</ul>
</li>
<li><a href="#conclusion">5. Conclusion</a></li>
</ul>
</div>
</div>
<div id="content">
<div id="preamble">
<div class="sectionbody">
<div class="admonitionblock tip context-callout">
<table>
<tr>
<td class="icon">
<div class="title">💡</div>
</td>
<td class="content">
<div class="paragraph">
<p>Vous êtes en train de lire
le Chapitre 7
du <a href="https://oncletom.io/node.js/">livre “Node.js”</a>, écrit par
<a href="https://oncletom.io">Thomas Parisot</a> et publié aux
<a href="https://editions-eyrolles.com/Livre/9782212139938">Éditions Eyrolles</a>.</p>
</div>
<div class="paragraph">
<p>L’ouvrage vous plaît ? Achetez-le sur <a href="https://amzn.to/2E58PEw">Amazon.fr</a> ou en
<a href="https://www.placedeslibraires.fr/livre/9782212139938">librairie</a>.
<a href="https://opencollective.com/nodebook">Donnez quelques euros</a> pour contribuer
à sa gratuité en ligne.</p>
</div>
</td>
</tr>
</table>
</div>
<div class="paragraph">
<p>Nous allons apprendre à composer et à tester une application web créée de toutes
pièces ou avec l’aide du framework Express.</p>
</div>
<div class="exampleblock">
<div class="content">
<div class="ulist">
<div class="title">Sommaire</div>
<ul>
<li>
<p>Composer son application web</p>
</li>
<li>
<p>Organiser une application avec le framework Express</p>
</li>
<li>
<p>Vers un code réutilisable et testable</p>
</li>
<li>
<p>Pour aller plus loin</p>
</li>
</ul>
</div>
</div>
</div>
<div class="quoteblock abstract">
<blockquote>
<div class="paragraph">
<p>Le modèle d’application web de Node se rapproche de celui de Ruby et diffère
de l’univers PHP.</p>
</div>
<div class="paragraph">
<p>Nous allons mieux comprendre le mécanisme de requête et de réponse HTTP en
créant une application web module par module, fonctionnalité par fonctionnalité.</p>
</div>
<div class="paragraph">
<p>Dans un second temps, nous organiserons notre code avec le framework Express.
Nous verrons en quoi notre application gagne en clarté, comment générer du HTML
de façon dynamique avec des informations issues d’une base de données.</p>
</div>
<div class="paragraph">
<p>Nous consoliderons notre savoir en organisant notre code de sorte à le
rendre plus résilient et testable – chose que nous apprendrons à faire pas à pas.</p>
</div>
</blockquote>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<div class="title">💬</div>
</td>
<td class="content">
<div class="title"><span class="RemarquePreTitre">Remarque</span> Versions de Node et npm</div>
<div class="paragraph">
<p>Le contenu de ce chapitre utilise les versions <strong>Node v10</strong>
et <strong>npm v6</strong>.
Ce sont les versions stables recommandées en 2019.</p>
</div>
</td>
</tr>
</table>
</div>
<div class="paragraph">
<p>Une application web est une <strong>construction applicative qui est à l’écoute</strong>
de connexions réseau initiées par un client – un navigateur, un automate, etc.
Elle est structurée autour de la lecture d’une requête entrante (lecture)
et de l’émission d’une réponse sortante (écriture).
Chaque <strong>requête porte en elle une intention</strong> (un chemin d’accès, une préférence
de format, des éléments d’identification) et implique une réponse en retour
(des données et des éléments pour les contextualiser).</p>
</div>
<div class="paragraph">
<p>L’ingénierie d’une application web consiste à comprendre les requêtes entrantes
et à construire une réponse appropriée à chaque fois, le plus rapidement possible.</p>
</div>
<div class="paragraph">
<p>Ce chapitre s’inscrit dans la continuité de la découverte du
<a href="../chapter-04/index.html#http">module <code>http</code></a>
(<a href="../chapter-04/index.html">chapitre 4</a>).
</p>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<div class="title">💬</div>
</td>
<td class="content">
<div class="title"><span class="RemarquePreTitre">Documentation</span> En-têtes HTTP</div>
<div class="paragraph">
<p>
Ce chapitre fait souvent référence à des en-têtes HTTP.
La documentation <em>MDN web docs</em>
les liste tous, avec le détail de leurs valeurs possibles :</p>
</div>
<div class="ulist">
<ul>
<li>
<p><span class="URL"><a href="https://developer.mozilla.org/fr/docs/Web/HTTP/Headers" class="bare">developer.mozilla.org/fr/docs/Web/HTTP/Headers</a></span></p>
</li>
</ul>
</div>
<div class="paragraph">
<p>C’est un onglet intéressant à ouvrir en parallèle de cette lecture
– je l’ai ouvert en permanence pour écrire ce chapitre.</p>
</div>
</td>
</tr>
</table>
</div>
</div>
</div>
<div class="sect1">
<h2 id="webapp">1. Composer son application web</h2>
<div class="sectionbody">
<div class="paragraph">
<p></p>
</div>
<div class="paragraph">
<p>Dans cette première section, nous allons nous focaliser sur la construction
d’une application web avec une approche modulaire.
Nous partirons du concept de requête et de réponse.
Petit à petit, nous allons greffer des modules pour comprendre et
donner du sens à leurs contenus respectifs.</p>
</div>
<div class="paragraph">
<p>Le <em>protocole HTTP</em> est le dialecte informatique utilisé et compris pour exprimer
les requêtes (émises par un client) et les réponses (émises par un serveur).
Les navigateurs web sont des clients tandis que notre application Node est un serveur.</p>
</div>
<div class="paragraph">
<p>Le logiciel <em>curl</em> (<span class="URL"><a href="https://curl.haxx.se" class="bare">curl.haxx.se</a></span>) est un client en ligne de commandes.
Il est souvent installé par défaut sur les distributions Linux, sur macOS et
à partir de Windows 7 – via le terminal <em>PowerShell</em>.</p>
</div>
<div class="paragraph">
<p>Utilisons <em>curl</em> pour observer le contenu d’une requête et de sa réponse.
</p>
</div>
<div class="listingblock">
<div class="title">Exemple de requête HTTP vers le site <span class="URL">perdu.com</span></div>
<div class="content">
<pre><span data-bash-subs="$"></span>curl -v http://perdu.com <span data-bash-conum="1"></span>
GET / HTTP/1.1 <span data-bash-conum="2"></span>
Host: perdu.com <span data-bash-conum="3"></span>
User-Agent: curl/7.54.0
Accept: */*</pre>
</div>
</div>
<div class="colist arabic">
<ol>
<li>
<p>Exécution de la requête.</p>
</li>
<li>
<p>Expression de la méthode, du chemin d’accès demandé et du protocole de discussion employé – ici, HTTP dans sa version <code>1.1</code>.</p>
</li>
<li>
<p>En-tête de requête.</p>
</li>
</ol>
</div>
<div class="paragraph">
<p>Un en-tête est exprimé sous la forme <code>Clé: Valeur</code>.
Chacun précise un élément de contexte.
Certains influencent plus que d’autres la réponse du serveur, si
celui-ci les comprend.</p>
</div>
<div class="paragraph">
<p>Voyons maintenant la réponse :
</p>
</div>
<div class="listingblock">
<div class="title">Exemple de réponse HTTP transmise en retour</div>
<div class="content">
<pre>HTTP/1.1 200 OK <span data-bash-conum="1"></span>
Date: Thu, 28 Jun 2018 19:02:27 GMT <span data-bash-conum="2"></span>
Server: Apache
Last-Modified: Thu, 02 Jun 2016 06:01:08 GMT
ETag: "cc-5344555136fe9"
Accept-Ranges: bytes
Content-Length: 204
Vary: Accept-Encoding
Content-Type: text/html <span data-bash-conum="3"></span>
<html><head><title>Vous Etes Perdu ?</title> …</html> <span data-bash-conum="4"></span></pre>
</div>
</div>
<div class="colist arabic">
<ol>
<li>
<p>Expression du statut de la réponse avec un code numérique et une version intelligible.</p>
</li>
<li>
<p>En-tête de réponse.</p>
</li>
<li>
<p>En-tête de réponse – celle-ci indique au client comment interpréter le corps du message.</p>
</li>
<li>
<p>Corps du message.</p>
</li>
</ol>
</div>
<div class="paragraph">
<p>La réponse dispose elle aussi d’en-têtes.
Cette fois, ils guident le client dans son interprétation du résultat.
Le corps du message est séparé par une ligne vide.
C’est la partie visible de la réponse dans un navigateur web, le contenu
qui s’affiche sous nos yeux.</p>
</div>
<div class="paragraph">
<p>Dans la prochaine section, nous visualiserons ces mêmes informations
à partir d’un serveur HTTP que nous allons créer par nous-même.
Nous retracerons plus en détail l’odyssée d’une requête HTTP dans la section
“<a href="#http">Comprendre le modèle HTTP</a>”, en fin de chapitre.</p>
</div>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<div class="title">💡</div>
</td>
<td class="content">
<div class="title"><span class="RemarquePreTitre">Pratique</span> Jouer avec les exemples dans un terminal</div>
<div class="paragraph">
<p>Les exemples titrés d’un nom de fichier peuvent être installés sur votre ordinateur.
Exécutez-les dans un terminal et amusez-vous à les modifier en parallèle de
votre lecture pour voir ce qui change.</p>
</div>
<div class="listingblock">
<div class="title">Installation des exemples via le module npm <code>nodebook</code></div>
<div class="content">
<pre><span data-bash-subs="$"></span>npm install --global nodebook
<span data-bash-subs="$"></span>nodebook install chapter-07
<span data-bash-subs="$"></span>cd $(nodebook dir chapter-07)</pre>
</div>
</div>
<div class="paragraph">
<p>La commande suivante devrait afficher un résultat qui confirme que vous êtes
au bon endroit :</p>
</div>
<div class="listingblock">
<div class="content">
<pre><span data-bash-subs="$"></span>node hello.js</pre>
</div>
</div>
<div class="paragraph">
<p>Suivez à nouveau les instructions d’installation pour rétablir les exemples
dans leur état initial.</p>
</div>
</td>
</tr>
</table>
</div>
<div class="sect2">
<h3 id="server">1.1. Démarrer un serveur HTTP</h3>
<div class="paragraph">
<p>
</p>
</div>
<div class="paragraph">
<p>Nous l’avons dit : une requête HTTP envoyée vers un hôte reçoit une réponse.
Cet hôte doit au préalable avoir installé et démarré un serveur HTTP qui
écoute ces demandes.</p>
</div>
<div class="paragraph">
<p>Le script d’exemple <code>server/start.js</code> répond à ce besoin.
Une fois démarré, il est joignable à l’adresse <code><a href="http://localhost:4000" class="bare">localhost:4000</a></code>.
Il affichera alors les en-têtes des requêtes et de leurs réponses :
</p>
</div>
<div class="listingblock">
<div class="content">
<pre><span data-bash-subs="$"></span>node server/start.js</pre>
</div>
</div>
<div class="listingblock interactive interactive--javascript interactive--runtime--node-v10 interactive--endpoint">
<div class="title">server/start.js</div>
<div class="content">
<pre class="highlight highlight-prismjs prismjs language-javascript"><code class="language-javascript" data-lang="javascript"><span class="token string">'use strict'</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> <span class="token punctuation">{</span>createServer<span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'http'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> server <span class="token operator">=</span> <span class="token function">createServer</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
server<span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">'request'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">request<span class="token punctuation">,</span> response</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
response<span class="token punctuation">.</span><span class="token function">setHeader</span><span class="token punctuation">(</span><span class="token string">'Content-Type'</span><span class="token punctuation">,</span> <span class="token string">'text/html'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> // <b class="conum">(1)</b>
response<span class="token punctuation">.</span><span class="token function">end</span><span class="token punctuation">(</span><span class="token string">'<h1>Hello World</h1>'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>request<span class="token punctuation">.</span>headers<span class="token punctuation">)</span><span class="token punctuation">;</span> // <b class="conum">(2)</b>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>response<span class="token punctuation">.</span><span class="token function">getHeaders</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> // <b class="conum">(3)</b>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
server<span class="token punctuation">.</span><span class="token function">listen</span><span class="token punctuation">(</span><span class="token number">4000</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
</div>
</div>
<div class="colist arabic">
<ol>
<li>
<p>Comme nous retournons du HTML au client, nous explicitons le type de contenu de la réponse.</p>
</li>
<li>
<p>Affiche les en-têtes de la requête reçue par le serveur – le contenu varie selon le client utilisé.</p>
</li>
<li>
<p>Affiche les en-têtes de la réponse – en l’occurrence <code>{ 'content-type': 'text/html' }</code>.
</p>
</li>
</ol>
</div>
<div class="paragraph">
<p>Nous avons composé les fondations minimales pour créer une application web
en mesure d’accepter des requêtes et de répondre quelque chose
d’arbitraire certes mais compréhensible par un navigateur web.</p>
</div>
<div class="paragraph">
<p><strong>Pourquoi avoir démarré le serveur sur le port 4000</strong> dans l’exemple précédent ?
C’est un choix arbitraire de ma part : nous pouvons démarrer un serveur HTTP
sur n’importe quel port tant qu’il est libre et supérieur ou égal à 1000.
Quand on cherche à se connecter à une adresse comme <span class="URL"><a href="http://localhost" class="bare">localhost</a></span> (HTTP)
et <span class="URL"><a href="https://localhost" class="bare">localhost</a></span> (HTTPS), la valeur du port vaut implicitement 80
et 443, respectivement.</p>
</div>
<div class="paragraph">
<p>Le module <code>npm</code> <em>get-port</em> (<span class="URL"><a href="https://npmjs.com/get-port" class="bare">npmjs.com/get-port</a></span>) retourne
un numéro de port parmi ceux disponibles sur le système d’exploitation.
</p>
</div>
<div class="listingblock">
<div class="content">
<pre><span data-bash-subs="$"></span>node server/port.js
http://localhost:51765</pre>
</div>
</div>
<div class="listingblock interactive interactive--javascript interactive--runtime--node-v10">
<div class="title">server/port.js</div>
<div class="content">
<pre class="highlight highlight-prismjs prismjs language-javascript"><code class="language-javascript" data-lang="javascript"><span class="token string">'use strict'</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> <span class="token punctuation">{</span>createServer<span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'http'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> getPort <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'get-port'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> server <span class="token operator">=</span> <span class="token function">createServer</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">getPort</span><span class="token punctuation">(</span><span class="token punctuation">{</span> port<span class="token punctuation">:</span> <span class="token number">4000</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">port</span> <span class="token operator">=></span> <span class="token punctuation">{</span> // <b class="conum">(1)</b>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token string">`http://localhost:</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>port<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> // <b class="conum">(2)</b>
server<span class="token punctuation">.</span><span class="token function">listen</span><span class="token punctuation">(</span>port<span class="token punctuation">)</span><span class="token punctuation">;</span> // <b class="conum">(3)</b>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
</div>
</div>
<div class="colist arabic">
<ol>
<li>
<p>Exprime une préférence pour retourner le port 4000 s’il est disponible.</p>
</li>
<li>
<p>Affiche <code><a href="http://localhost:4000" class="bare">localhost:4000</a></code> si le port est disponible ; sinon, un autre nombre.</p>
</li>
<li>
<p>Le serveur se met à l’écoute sur ce port.</p>
</li>
</ol>
</div>
<div class="paragraph">
<p>Pour vous en rendre compte, démarrez le script <code>server/start.js</code> pour utiliser
le port 4000 et démarrez ensuite <code>server/port.js</code>.</p>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<div class="title">💬</div>
</td>
<td class="content">
<div class="title"><span class="RemarquePreTitre">Performance</span> Programme de longue durée</div>
<div class="paragraph">
<p>Une application web est un programme qui tourne en continu, pendant des heures
et des journées entières.</p>
</div>
<div class="paragraph">
<p>Chaque requête entrante occupe 1 Ko de mémoire – davantage si nous
recevons des données de formulaire ou une pièce jointe.
Une application web peut en recevoir plusieurs centaines à plusieurs milliers
par seconde, selon la popularité du service.</p>
</div>
</td>
</tr>
</table>
</div>
</div>
<div class="sect2">
<h3 id="path">1.2. Répondre à un chemin (routing)</h3>
<div class="paragraph">
<p></p>
</div>
<div class="paragraph">
<p>Nous avons vu qu’une URL est un identifiant qui se décompose en plusieurs
parties grâce au <a href="../chapter-04/index.html#url">module <code>url</code></a>
(<a href="../chapter-04/index.html">chapitre 4</a>).
Une d’elles est le <em>chemin d’accès</em> à une ressource.<br>
Par exemple, le chemin de l’URL <span class="URL"><a href="http://localhost:4000/coucou" class="bare">localhost:4000/coucou</a></span> est <code>/coucou</code>.</p>
</div>
<div class="listingblock">
<div class="content">
<pre><span data-bash-subs="$"></span>node path/request-url.js</pre>
</div>
</div>
<div class="listingblock">
<div class="title">path/request-url.js</div>
<div class="content">
<pre class="highlight highlight-prismjs prismjs language-javascript"><code class="language-javascript" data-lang="javascript"><span class="token string">'use strict'</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> <span class="token punctuation">{</span>createServer<span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'http'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> server <span class="token operator">=</span> <span class="token function">createServer</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">listen</span><span class="token punctuation">(</span><span class="token number">4000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
server<span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">'request'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">request<span class="token punctuation">,</span> response</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>request<span class="token punctuation">.</span>url <span class="token operator">===</span> <span class="token string">'/'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> // <b class="conum">(1)</b>
response<span class="token punctuation">.</span><span class="token function">end</span><span class="token punctuation">(</span><span class="token string">'<a href="/hello">clique-moi</a>'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>request<span class="token punctuation">.</span>url <span class="token operator">===</span> <span class="token string">'/coucou'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> // <b class="conum">(2)</b>
response<span class="token punctuation">.</span><span class="token function">end</span><span class="token punctuation">(</span><span class="token string">'<a href="/">coucou !</a>'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
</div>
</div>
<div class="colist arabic">
<ol>
<li>
<p>Affiche un message spécifique au chemin <code>/</code>.</p>
</li>
<li>
<p>Affiche un autre message spécifique au chemin <code>/coucou</code>.</p>
</li>
</ol>
</div>
<div class="paragraph">
<p>Les deux seules ressources mises à disposition sur <span class="URL"><a href="http://localhost:4000" class="bare">localhost:4000</a></span>
sont accessibles avec les chemins <code>/</code> et <code>/coucou</code>.
Aucun autre chemin n’aboutira.</p>
</div>
<div class="paragraph">
<p>C’est d’ailleurs un problème puisque, en réalité, nous n’envoyons pas de réponse
pour un chemin inconnu.
Et c’est à nous de gérer ce cas de figure :</p>
</div>
<div class="listingblock">
<div class="content">
<pre><span data-bash-subs="$"></span>node path/404.js</pre>
</div>
</div>
<div class="listingblock">
<div class="title">path/404.js</div>
<div class="content">
<pre class="highlight highlight-prismjs prismjs language-javascript"><code class="language-javascript" data-lang="javascript"><span class="token string">'use strict'</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> <span class="token punctuation">{</span>createServer<span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'http'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> server <span class="token operator">=</span> <span class="token function">createServer</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">listen</span><span class="token punctuation">(</span><span class="token number">4000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
server<span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">'request'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">request<span class="token punctuation">,</span> response</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>request<span class="token punctuation">.</span>url <span class="token operator">===</span> <span class="token string">'/'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> // <b class="conum">(1)</b>
response<span class="token punctuation">.</span><span class="token function">end</span><span class="token punctuation">(</span><span class="token string">'<a href="/hello">clique-moi</a>'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">else</span> <span class="token punctuation">{</span>
response<span class="token punctuation">.</span>statusCode <span class="token operator">=</span> <span class="token number">404</span><span class="token punctuation">;</span> // <b class="conum">(2)</b>
response<span class="token punctuation">.</span><span class="token function">end</span><span class="token punctuation">(</span><span class="token string">'<h1>Page introuvable</h1>'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> // <b class="conum">(3)</b>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
</div>
</div>
<div class="colist arabic">
<ol>
<li>
<p>Seul le chemin <code>/</code> est disponible dans l’application.</p>
</li>
<li>
<p>Le code HTTP de la réponse est réglé sur <code>404</code>.</p>
</li>
<li>
<p>Une requête vers une page introuvable peut quand même recevoir du contenu.</p>
</li>
</ol>
</div>
<div class="paragraph">
<p>La prise en compte d’une ressource inconnue de notre application fait émerger
un nouveau concept : le <em>statut de la réponse</em>.
Ce statut est un code numérique qui donne des indications sur la ressource retournée.
Dans ce cas de figure, le statut <code>404</code> de la réponse indique au client de
ne pas considérer le contenu comme celui qui était demandé.<br>
Par défaut et sauf mention contraire, le statut est <code>200</code>.</p>
</div>
<table class="tableblock frame-all grid-all stretch">
<caption class="title">Tableau 1. Principaux codes HTTP et leur signification</caption>
<colgroup>
<col style="width: 14.2857%;">
<col style="width: 28.5714%;">
<col style="width: 57.1429%;">
</colgroup>
<thead>
<tr>
<th class="tableblock halign-left valign-top">Code</th>
<th class="tableblock halign-left valign-top">Raison</th>
<th class="tableblock halign-left valign-top">Explication</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>200</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>OK</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">La ressource demandée est retournée en réponse.</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>301</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>Moved Permanently</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">La ressource demandée a été déplacée.</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>304</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>Not Modified</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">La ressource n’a pas été modifiée depuis la dernière fois.</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>400</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>Bad Request</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">La requête est incomplète ou incompréhensible par le serveur.</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>401</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>Unauthorized</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">La ressource n’est accessible que sur preuve d’identification.</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>403</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>Forbidden</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">L’accès à la ressource est interdit.</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>404</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>Not Found</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">La ressource n’existe pas.</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>500</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>Internal Server Error</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Le serveur distant est en erreur.</p></td>
</tr>
</tbody>
</table>
<div class="paragraph">
<p>
</p>
</div>
<div class="paragraph">
<p>Les statuts HTTP sont importants dans la création d’applications web.
Leur code permet de vérifier que le client et le serveur se sont bien compris.<br>
Si une page d’erreur est affichée avec un statut <code>200</code>, le client sera dans
l’impossibilité de deviner qu’il ne s’agit pas du contenu attendu.</p>
</div>
<table class="tableblock frame-all grid-all stretch">
<caption class="title">Tableau 2. D’autres codes HTTP utiles à connaître</caption>
<colgroup>
<col style="width: 16.6666%;">
<col style="width: 16.6666%;">
<col style="width: 66.6668%;">
</colgroup>
<thead>
<tr>
<th class="tableblock halign-left valign-top">Code</th>
<th class="tableblock halign-left valign-top">Raison</th>
<th class="tableblock halign-left valign-top">Explication</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>101</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>Switching Protocol</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Le serveur indique basculer vers le protocole spécifié dans l’en-tête <code>Upgrade</code>.</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>201</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>Created</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">La ressource demandée a été créée.</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>202</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>Accepted</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">La demande a été acceptée et la ressource sera disponible ultérieurement.</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>204</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>No Content</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">La ressource demandée n’a pas de contenu.</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>302</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>Found</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">La ressource demandée est temporairement disponible à une autre adresse.</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>503</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>Service Unavailable</code></p></td>
<td class="