Programmation Javascript (NFA041)

Un peu de programmation coté serveur en javascript

Olivier Pons
(olivier.pons@lecnam.net)

2022

Petit rappel sur NodeJS

logo NodeJS

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.

Un interpréteur Javascript

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.

node monfichier.js

Javascript coté serveur : exemple d'un serveur http

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/'); 
      

remarque

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 ;-)

Testons ce serveur http

  1. Lancer ce code dans un terminal:

    node hello1.js
  2. se connecter dans un navigateur: http://localhost:1337

  3. ou à la main via telnet (sous windows utiliser putty)

NodeBook pons$ telnet localhost 1337
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
GET / HTTP/1.0

HTTP/1.1 200 OK
content-type: text/plain
Date: Wed, 4 Jan 2023 12:24:30 GMT
Connection: close

Hello, World!
Connection closed by foreign host.

Le serveur 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/'); 

Comprendre le code

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:

Il 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

La méthode createServerrenvoi 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é.

Condenser le code


(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);

      

Gérer le système de fichier

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/'); 

      

Le module FS

Lecture de la ligne de commande en node js

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

node prog.js arg0 arg1

Exercices

  1. Reprendre le code du serveur pour qu'il affiche le contenu d'un fichier dont le nom est passer en argument sur la ligne de commande
  2. Regarder la doc des modules path et url Et essayer de produire un serveur capable de servir les page demandées

le serveur

var 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.");