Un peu de programmation coté serveur en javascript
Olivier Pons
(olivier.pons@lecnam.net)
2022
Un Interpréteur Javascript (basé sur le moteur V8 de Chrome)
Un environnement de développement en javascript qui permet de gérer le coté serveur.
Basé sur des Entrées/Sorties (IO) Asynchrone
Node dispose d'une API formée d'un ensemble de modules :
http
, fs
, net
,
buffer
, dns
, path
...
Nous utiliserons et ferons abondamment référence à la documentation de l'API sur le site de node
Il existe aussi des très nombreuse bibliothèques externes gérables grace au Node Package Manager npm.
Cela va du petit utilitaire comme mkdir ou framework web comme express.
Si vous ne l'installer pas sur votre machine vous pouvez utiliser replit.
On rappel que node peut s'utiliser de façon interactive grace à une boucle d'interaction : (Read Eval Print Loop)
NodeBook pons$ node
> 2*3;
6
> var a=56;
undefined
> a*4;
224
> function plus1(x) {return x+1;}
undefined
> plus1(a);
57
> plus1;
[Function: plus1]
>
ou comme un interpréteur (avec une « compilation » Just In Time ) quand on lui passe un fichier en argument.
Pour créer et gérer des serveurs http
, l'API node
propose le module http
// charger le module http
var http = require('http');
//créer le serveur http
http.createServer(function (requete, reponse) {
//écrire le header http
reponse.writeHead(200, {'content-type': 'text/plain'});
//écrire le message et signaler que la communication est finie.
reponse.end("Hello, World!\n");
}).listen(1337,"localhost"); // On choisit un numero de port
//si second paramètre est localhost
// le serveur est visible qu'en local (dev !!!)
//si le second paramètre estvomis visible de partout
console.log('le serveur tourne sur le port 1337/');
Le choix du port 1337 est anecdotique.
Les numéros de port TCP/IP inférieurs à 1024 sont spéciaux en ce sens que les utilisateurs normaux (non root) ne sont pas autorisés à y exécuter des serveurs. Il s'agit d'une fonction de sécurité, dans la mesure où si vous vous connectez à un service sur l'un de ces ports, vous pouvez être à peu près sûr que vous avez le vrai, et non un faux qu'un pirate informatique a mis en place pour vous. Généralement les serveur web tourne sur le port 80 qui est le port par défaut du protocol http.
Lorsque vous exécutez un serveur en test ou en développement, vous utiliserez donc des numéro de port compris en 1025 et 65000.
Notons, que même en production, il vaut mieux ne pas faire tourner le
serveur sous root. Et utiliser les iptables
et systemd
ou
des alternatives pour gérer les redirections de port, les redémarrages
système, etc. (mais cela dépasse le cadre ce cours ;-)
http
Lancer ce code dans un terminal:
node hello1.js
se connecter dans un navigateur: http://localhost:1337
ou à la main via telnet (sous windows utiliser putty)
http
« décomposé »Pour ceux qui ont encore du mal avec les fonction de rappel et l'ordre supérieur, on decompose un peu.
// charger le module http
var http = require('http');
//creer le serveur avec en argument la fonction associee à l'evenenent
//request qui correspond a une requete sur le serveur
var serveur=http.createServer(reponseAUnEvenementRequest);
//definition de la fonction
function reponseAUnEvenementRequest (requete, response) {
//écrire le header http
response.writeHead(200, {'content-type' : 'text/plain'});
//écrire le message et signaler que la communication est finie.
response.end("Hello, World!\n");
}
serveur.listen(1337,"localhost"); // si second paramètre :
// visible que de localhost pour le dev
//si second paramètre omis visible de partout
console.log('Server running on 1337/');
require('http')
renvoie un objet
http
.
On peut l'observer dans la boucle REPL :
% node
Welcome to Node.js v19.1.0.
Type ".help" for more information.
> let objectAObserver=require('http')
undefined
> objectAObserver
{
_connectionListener: [Function: connectionListener],
METHODS: [
'ACL', 'BIND', 'CHECKOUT',
'CONNECT', 'COPY', 'DELETE',
'GET', 'HEAD', 'LINK',
'LOCK', 'M-SEARCH', 'MERGE',
'MKACTIVITY', 'MKCALENDAR', 'MKCOL',
'MOVE', 'NOTIFY', 'OPTIONS',
'PATCH', 'POST', 'PROPFIND',
'PROPPATCH', 'PURGE', 'PUT',
'REBIND', 'REPORT', 'SEARCH',
'SOURCE', 'SUBSCRIBE', 'TRACE',
'UNBIND', 'UNLINK', 'UNLOCK',
'UNSUBSCRIBE'
],
STATUS_CODES: {
'100': 'Continue',
'101': 'Switching Protocols',
'102': 'Processing',
'103': 'Early Hints',
'200': 'OK',
'201': 'Created',
'202': 'Accepted',
'203': 'Non-Authoritative Information',
'204': 'No Content',
'205': 'Reset Content',
'206': 'Partial Content',
'207': 'Multi-Status',
'208': 'Already Reported',
'226': 'IM Used',
'300': 'Multiple Choices',
'301': 'Moved Permanently',
'302': 'Found',
'303': 'See Other',
'304': 'Not Modified',
'305': 'Use Proxy',
'307': 'Temporary Redirect',
'308': 'Permanent Redirect',
'400': 'Bad Request',
'401': 'Unauthorized',
'402': 'Payment Required',
'403': 'Forbidden',
'404': 'Not Found',
'405': 'Method Not Allowed',
'406': 'Not Acceptable',
'407': 'Proxy Authentication Required',
'408': 'Request Timeout',
'409': 'Conflict',
'410': 'Gone',
'411': 'Length Required',
'412': 'Precondition Failed',
'413': 'Payload Too Large',
'414': 'URI Too Long',
'415': 'Unsupported Media Type',
'416': 'Range Not Satisfiable',
'417': 'Expectation Failed',
'418': "I'm a Teapot",
'421': 'Misdirected Request',
'422': 'Unprocessable Entity',
'423': 'Locked',
'424': 'Failed Dependency',
'425': 'Too Early',
'426': 'Upgrade Required',
'428': 'Precondition Required',
'429': 'Too Many Requests',
'431': 'Request Header Fields Too Large',
'451': 'Unavailable For Legal Reasons',
'500': 'Internal Server Error',
'501': 'Not Implemented',
'502': 'Bad Gateway',
'503': 'Service Unavailable',
'504': 'Gateway Timeout',
'505': 'HTTP Version Not Supported',
'506': 'Variant Also Negotiates',
'507': 'Insufficient Storage',
'508': 'Loop Detected',
'509': 'Bandwidth Limit Exceeded',
'510': 'Not Extended',
'511': 'Network Authentication Required'
},
Agent: [Function: Agent] { defaultMaxSockets: Infinity },
ClientRequest: [Function: ClientRequest],
IncomingMessage: [Function: IncomingMessage],
OutgoingMessage: [Function: OutgoingMessage],
Server: [Function: Server],
ServerResponse: [Function: ServerResponse],
createServer: [Function: createServer],
validateHeaderName: [Function: __node_internal_],
validateHeaderValue: [Function: __node_internal_],
get: [Function: get],
request: [Function: request],
setMaxIdleHTTPParsers: [Function: setMaxIdleHTTPParsers],
maxHeaderSize: [Getter],
globalAgent: [Getter/Setter]
}
On a:
METHODS
qui contient les méthodes du
protocol http (et des extensionss), notamment GET
,
POST
, HEAD
...STATUS_CODES
qui liste les code de retour du
protocol httpIl y a une méthode createServer
qui
permet de créer le serveur.
Cette méthode prend en argument une fonction de
rappel
La fonction de rappel est Anonyme et a deux
arguments.
Elle est automatiquement associé à l'evenement
'request'
Le premier argument est un objet de type
http.ServerRequest
et correspond à la requête du client au
serveur
(method
,url
,encoding
,...)
Créer en interne par le server (pas par l''utilisateur) et est passé
comme premier paramètre lors de l'appel de la fonction suite à un
événement request
.
Il permet par exemple de récupérer l'url.
request.url
Le second est un object de type http.ServerResponse
est
crée en interne par le server (pas par l'utilisateur) et est passé comme
second paramètre lors de l'appel de la fonction suite a un événement
request
.
Il a les méthodes
response.writeHead(codeDeRetour, [texte de retour], [entete])
pour envoyer un entête`response.write(morceau de données, [encodage])
pour
envoyer des données (bufferisée , l'envoi a lieu au moment du
end
)response.end([donnes], [encodage])
pour déclencher
l'envoi (s' il y a des données c'est équivalent àresponse.write(donnes);response.end()
)res.write("Hello, World!\n");res.end()
La méthode createServer
renvoi un object de type
Server
qui hérite de la méthode listen
.
La méthode listen
permet de lancer l'écoute du server sur
un port donnée. On peut aussi specifier dans le second argument les
machine qui peuvent se connecter. En production on ne le met
généralement pas (sauf si on veut restreindre l'accès). En development,
on le met généralement à localhost
pour éviter de donner à
tous, accès, par erreur, à un code erroné.
(function (port){
require('http').createServer(function (request, response) {
// Ecrire le header http
response.writeHead(200, {'content-type': 'text/plain'});
// Ecrire le message et signaler que la communication est finie.
response.end("Hello, World Min!\n");
}).listen(port,"localhost");
console.log('Server running on '+port); })(1338);
Afficher Hello word
c'est bien !
Mais on voudrait servir des page html
, du css
du js
.
Avec le module fs
// charger les modules
var http = require('http');
var fs = require('fs');
// créer le serveur
http.createServer(function (requete, reponse) {
// ouvrir et lire le fichier hello1.js
fs.readFile('hello1.js', 'utf8', function(err, data) {
reponse.writeHead(200, {'Content-Type': 'text/plain'});
if (err)
reponse.write('Could not find or open file for reading\n');
else
// si il n'y a pas d'erreur on écrit le fichier sur le client
reponse.write(data);
reponse.end();
});
}).listen(8125, function() { console.log('branche port 8125');});
console.log('Server running on 8125/');
fs.readFile(filename, [encoding], [callback])
Pour passer passer des argument a progemme node (par exemple un port, une page a servir par defaut...) on peut les passer en ligne de commande
on peut récupérer passer des données en ligne de commande grace à
process.argv
du module proces
c'est un tableau. Case 0: node, Case 1: nom du script, Cases suivante arguments
Exemple de traitement
path
et url
Et
essayer de produire un serveur capable de servir les page demandéesvar http = require("http");
var fs = require("fs");
var path = require("path");
var url = require("url");
/* s'amuser a suivre les header avec liveHTTPheader http */
function sendError(errCode, errString, response)
{
response.writeHead(errCode, {"Content-Type": "text/plain"});
response.write(errString + "\n");
response.end();
return;
}
function sendFile(err, file, response)
{
if(err) return sendError(500, err, response);
response.writeHead(200);
response.write(file);
response.end();
}
function getFile(exists, response, localpath)
{
if(!exists) return sendError(404, '404 Not Found', response);
fs.readFile(localpath,
function(err, file){ sendFile(err, file, response);});
}
function getFilename(request, response)
{
var urlpath = url.parse(request.url).pathname; // ce qui suit le domaine ou IP et port mais avant la query
var localpath = path.join(process.cwd(), urlpath); // si le rep courant est la racine
//path.exists est deprecated in v0.8 utiliser fs.exists
fs.exists(localpath, function(result) { getFile(result, response, localpath)});
}
var server = http.createServer(getFilename);
server.listen(1339);
console.log("le serveur tourne port 1339.");