cron, node, nodejs, node.js, node js, express, expressjs, express.js, express js, ubuntu earnanswers.com Une bannière horizontale avec, de gauche à droite, le logo de Cron (une horloge), celui d'Ubuntu (un cercle orange), Node.js (en vert et gris), Express (en noir) et un symbole de crochet de programmation.

La meilleure conception : cron, node js, express et ubuntu

Publié: Édité: Tutoriel de programmation fr
Table des Matières
  1. Cron, Node Js, Express Et Ubuntu
  2. Configuration du point de terminaison HTTP(S)
  3. Configuration du router « cron curl poc »
  4. Controller Express
    • Remarque : cas d'erreur
  5. Controller en cas d'erreur Express
  6. Tâches Cron
    • Crontab
  7. Cron scripts
    • sh principal : cron-curl-poc-2.sh
    • Remarque 1
    • sh secondaire : trunc-cron-curl-poc-2.sh
  8. Partie 1 : Fermeture du point de terminaison HTTP(S) aux publics
    • Configuration NGINX
  9. Test 1 : cURL de l'extérieur
  10. Test 2 : cURL à partir du même serveur
  11. Partie 2 : Ajout de jeton pour vérifier l’intégrité
    • Remarque
    • Paramétrage script
  12. Partie 3 : Traitement Express JS du jeton
    • Modifications express
  13. Logs
    • Remarque
  14. Logs cron
  15. Logs express
  16. Logs en cas d’erreur dans Express
  17. Peut-on déboguer localement ?
  18. Conclusion

Cron, Node Js, Express Et Ubuntu

Cet article fait partie d'une série d'articles plus large.

Pour ceux qui ne sont pas à jour, veuillez vous référer à l'article suivant: liens pour créer un bot twitter personnalisé ia

En résumé, on veut créer un bot Twitter en utilisant Cron et Node.JS.

Je recommande de s'assurer que vous répondiez aux pré-requis et avez lu les articles préalables avant de continuer.

Nous allons s'assurer de plusieurs choses afin que lorsque nous devions ajouter le code des APIs de Twitter et d'OpenAI, cela sera simple.

Dans cet article, nous allons :

  1. S'assurer que nous disposons d'un point de terminaison (« endpoint ») HTTP(S) configuré pour traiter les requêtes cURL venant de nos scripts (.sh).
  2. S'assurer que la conception du code express puisse grandir en échelle.
  3. S'assurer que nos logs issus des scripts d'exécutions (.sh) sont lisibles.
  4. S'assurer que nos logs issus du code express sont lisibles.
  5. S'assurer que la réponse express au script (.sh) boucle les logs proprement.
  6. S'assurer que le code respecte au maximum la condition « ne te répète pas » (« do not repeat yourself »)
  7. S'assurer que l'on puisse déboguer notre code localement.
  8. S'assurer que les logs ne conservent que les impressions récentes automatiquement.
  9. S'assurer que notre point de terminaison HTTP(S) n'est accessible qu'uniquement à partir du même serveur où la tâche cron est hébergée.
  10. S'assurer de l'authenticité de la requête cURL exécutée par la tâche Cron.

Configuration du point de terminaison HTTP(S)

Pour ce faire, il existe de nombreuses ressources pour rouler un point de terminaison HTTP(S) localement puis comment le déployer. Pour vous initier, je recommande mon article : projet node js.

En ce moment, je dispose déjà d'un processus CI/CD pour créer un point de terminaison HTTP(S). Mon instance node roule sur les services nuages Linode, et j'utilise NGINX, PM2 et MySQL pour ce site web.

Donc j'apporte ces ajouts à ma base de code (« code base ») afin que je puisse répondre aux objectifs 1, 2, 4, 5, et 6 ci-haut.

Configuration du router « cron curl poc »

On met en place un point de terminaison HTTP(S) et un intergiciel (« middleware »).

Le point de terminaison HTTP(S) se charge de traiter les requêtes HTTP(S) de méthode GET vers le chemin /.

L'intergiciel, lui, s'exécute uniquement lorsqu'une erreur se produit au niveau du traitement préalable.

Capture d'écran du code JavaScript partiellement visible pour la configuration d'un routeur Express avec une route GET et un middleware de gestion des erreurs. Capture d'écran du code JavaScript partiellement visible pour la configuration d'un routeur Express avec une route GET et un middleware de gestion des erreurs.

Controller Express

Analysons ce que le controller.cont1 fait pour traiter la requête en ce moment.

L'idée est que lors du cycle de vie de la requête, il y a ces trois opérations :

  • la récolte d'informations RSS ou autres.
  • la formulation du Tweet à l'aide de l'API d'open AI.
  • L'action de tweeter à partir d'un compte que nous contrôlons.

Pour l'instant le code récolte le log ID :

const logId = req.headers['x-log-id'];

Recueille le message log au cas de succès :

const expressLog = getExpressLog(__filename, req, logId, GoodLookingDateTime)

Sauvegarde le message dans un fichier texte approprié :

fs.appendFileSync(process.env['PATH_LOGS'], expressLog);

Puis génère le message de retour à la commande curl issu du script :

const message = 'Success' const endpointResponseMessage = getEndpointResponseMessage(req, message)

Puis répondre. On s'assure que notre message de retour s'incorpore parfaitement avec le log partiel et premier issu du script .sh (voir point 5 ci-haut).

return res.status(200).send(endpointResponseMessage);

Remarque : cas d'erreur

Il est important de tester tous les scénarios possibles pour que notre application soit robuste.

Pour ce faire, on peut artificiellement déclencher une erreur dans controller.cont1.

Cela aura pour effet de déclencher l'intergiciel (controller.error_cont1) de fin du cycle de vie de la requête.

Autrement (un scénario sans erreurs), controller.error_cont1 aurait été totalement contourné.

const error = new Error('Error description'); return next(error);

'
Capture d'écran du code JavaScript avec une fonction asynchrone, y compris les opérations du système de fichiers, la génération de journaux et une réponse réussie pour un point de terminaison Express.js. Capture d'écran du code JavaScript avec une fonction asynchrone, y compris les opérations du système de fichiers, la génération de journaux et une réponse réussie pour un point de terminaison Express.js.

Controller en cas d'erreur Express

Supposons le scénario auquel une erreur est déclenchée lors du traitement de la requête vers le point de terminaison HTTP(S) GET /croncurlpoc.

Voudrais-tu connaitre la source de cette erreur et lire le message d'erreur associé ? La réponse est, évidemment, oui !

Pour cela, l'intergiciel de fin de cycle, grâce à la nature de sa signature, permet la récolte de l'erreur et de traiter la requête.

S'il n'y a pas d'erreur, l'exécution de l'intergiciel (controller.error_cont1) n'a pas lieu.

Les opérations effectuées sont :

la récolte du log ID et la formulation de la date et de l'heure,

const GoodLookingDateTime = getGoodLookingDateTime() const logId = req.headers['x-log-id'];

la récupération et la sauvegarde du message d'erreur au fichier approprié,

const expressLog = getExpressLog(__filename, req, logId, GoodLookingDateTime, err) fs.appendFileSync(process.env['PATH_LOGS'], expressLog);

de répondre au script par un message approprié qui sera imprimé en log par ce même script .sh.

const endpointResponseMessage = getEndpointResponseMessage(req, message) return res.status(500).send(endpointResponseMessage);

Image du code JavaScript pour une fonction de gestion des erreurs dans une application Express.js qui enregistre les erreurs et envoie une réponse d'erreur au serveur. Image du code JavaScript pour une fonction de gestion des erreurs dans une application Express.js qui enregistre les erreurs et envoie une réponse d'erreur au serveur.

Tâches Cron

Crontab

crontab -e 0 0 * * * /home/[utilisateur]/scripts/cron-curl-poc-2.sh >> /home/[utilisateur]/logs/cron-curl-poc-2.txt 2>&1 0 12 * * * /home/[utilisateur]/scripts/cron-curl-poc-2.sh >> /home/[utilisateur]/logs/cron-curl-poc-2.txt 2>&1

Comme l'illustre ci-haut, notre script s'exécutera deux fois par jour, soit à minuit et midi tous les jours.

Le script cron-curl-poc-2.sh se charge d'exécuter une requête vers notre application express.

Veuillez lire cet article pour en apprendre plus sur ce qu'est cron, et comment le configurer sur Ubuntu : tâches récurrentes sur Ubuntu : Cron.

crontab -e 0 0 1 * * /home/[utilisateur]/scripts/trunc-cron-curl-poc-2.sh

La tâche cron ci-haut s'exécute une fois par mois et aura pour effet de garder uniquement les 2500 lignes les plus récentes de nos fichiers texte logs.

La raison pour cela est qu'on souhaite automatiser la suppression de logs trop anciens donc non pertinents.

On s'assure du point 8 cité en début de texte.

Image d'un script bash pour l'enregistrement de messages et l'envoi d'une requête curl à un point de terminaison de serveur local. Image d'un script bash pour l'enregistrement de messages et l'envoi d'une requête curl à un point de terminaison de serveur local.

Cron scripts

sh principal : cron-curl-poc-2.sh

Ci-haut est une image du script principal et responsable de l'enclenchement de nôtre point de terminaison HTTP(S).

#!/bin/bash # Define the URL variable url="http://localhost:3001/croncurlpoc" # Generate a unique ID (using date and random number for simplicity) logId=$(date +%s%N)$RANDOM # Get the current date and time in a readable format currentDate=$(date +"%A %d %B %Y @ %I:%M %p")

On commence par spécifier le « shebang ». Celui-ci indique au système d'exploitation que ce script doit être exécuté à l'aide de l'interpréteur Bash.

Ensuite, on attribue une valeur à la variable « url ». Remarquez que l'on utilise « localhost » et non le nom de domaine, vu que l'application est sur le même serveur !

On génère un chiffre en guise d'ID de log en combinant la date et l'heure actuelle en nanoseconde (date +%s%N) avec un nombre aléatoire ($RANDOM).

On attribue une date lisible (date +"%A %d %B %Y @ %I:%M %p") à la variable « currentDate».

%A est le jour de semaine, %d est le jour du mois, %B est le mois, %Y est l'année, %I est l'heure, %M sont les minutes et %p est soit « AM » ou « PM ».

# Construct the log message logMessage=" ------------------------------------------------------------ This log is issued from cron executing $scriptPath Log ID: $logId Date and Time: $currentDate Making a curl request to: $url Response from endpoint: ..." # Echo the log message echo "$logMessage" # Perform the curl request without the progress meter curl -sS -H "X-Log-Id: $logId" $url

Apres, on attribue le debut du message log qu'on souhaite récupérer dans notre fichier (cron-curl-poc-2.txt). L'impression a lieu lors de l'execution d'echo. Le fichier d'impression est specifier par la tache cron. Vous pouvez vous referer a l'article: tâches récurrentes sur Ubuntu : Cron pour mieux comprendre.

Curl veut dire client url. En gros, c'est une commande qui permet de faire des requêtes HTTP(S).

-s veut dire silencieux (« silent »), utile pour ne pas encombrer nos logs avec des informations inutiles.

-S voulant dire montrer l'erreur (« show error »), permet d'imprimer au log, le message d'erreur, le cas échéant.

-H veut dire en-tête (« header »). Ce drapeau permet l'ajout d'en-têtes à vos requêtes HTTP(S). Les en-têtes HTTP permettent d'envoyer des informations supplémentaires. Dans notre cas, on ajoute l'ID pour le réceptionner dans notre code express et l'utiliser pour les logs express. L'ID servira donc dans les logs de chaque exécution de tâche cron et dans les logs de l'exécution du point de terminaison express correspondant. L'ID permettra d'identifier les logs correspondants entre logs express et logs cron par pair. l'ID permettra de faciliter le débogage échéant.

Remarque 1

La commande aurait curl et aurait pu bien fonctionner avec les url suivants :

url="localhost:3001/croncurlpoc"
url="https://nomdedomain.com/croncurlpoc"
url="https://nomdedomain.com/croncurlpoc"

Avec le troisième par contre, il faut ajouter le drapeau -L voulant dire emplacement (« location »).

Notre serveur web est configuré grâce à Nginx et il redirige les requêtes HTTP vers HTTPS. La réponse première est donc 301.

Curl ne suit pas les redirections. Raison pour laquelle l'exécution sans -L : la réponse est 301 « Moved Permanently » au lieu de la sortie attendue.

Pour que curl suive la redirection vers l'URL HTTPS, vous devez utiliser l'option -L. Cela indique à curl de suivre les redirections jusqu'à ce qu'il atteigne la destination finale.

sh secondaire : trunc-cron-curl-poc-2.sh

On configure le script si bas en plus.

Ce script roulera tous les mois, à la première minute de la première heure du mois.

L'intérêt de ce script est qu'à chaque mois seules les 2500 lignes les plus récentes de nos fichiers textes seront conservées.

Pour ce faire, revoyons le code pour comprendre :

LOG_FILE1="/home/[utilisateur]/logs/cron-curl-poc-2.txt"

On définit le premier chemin du fichier texte.

TEMP_FILE1="$(mktemp)"

$(mktemp) crée un nouveau fichier temporaire et retourne le chemin du fichier jadis créé.

La commande mktemp est un utilitaire Unix/Linux standard qui crée un fichier temporaire de manière à s'assurer qu'aucun autre fichier ne porte le même nom.

Lorsque vous utilisez mktemp dans un environnement Unix ou Linux, il crée le fichier temporaire dans le répertoire /tmp par défaut, sauf si la variable d'environnement TMPDIR est définie sur un répertoire différent.

Si vous deviez faire écho à la sortie de $(mktemp), vous verriez quelque chose comme :

/tmp/tmp.[numero]

Ensuite,

tail -n 2500 "$LOG_FILE1" > "$TEMP_FILE1"

« Tail » permet d'obtenir en sortie le nombre voulu de lignes. En utilisant le drapeau n on précise le nombre exact de lignes. Le code ci-haut redirige ensuite la sortie et l'ajoute au fichier temporaire.

Enfin, on remplace le fichier journal d'origine par la version tronquée.

mv "$TEMP_FILE1" "$LOG_FILE1"

On applique des commandes similaires pour le deuxième fichier texte :

LOG_FILE2="/home/[utilisateur]/logs/HTTP-GET-croncurlpoc-2.txt"

Image d'un script bash pour tronquer les fichiers à l'aide de la commande tail. Image d'un script bash pour tronquer les fichiers à l'aide de la commande tail.

Partie 1 : Fermeture du point de terminaison HTTP(S) aux publics

Configuration NGINX

Vous trouverez votre fichier de configuration Nginx au chemin suivant :

/etc/nginx/sites-available/[nom de fichier de votre site web]

On ferme le point de terminaison HTTP(S) pour qu'il ne soit exécutable qu'uniquement à partir du même serveur dans lequel il est hébergé.

Pour ce faire, on modifie le fichier de configuration Nginx relatif au site web de la manière suivante :

# Server block for handling HTTPS requests server {     listen [::]:000 ssl; …     # Location block for /croncurlpoc     location /croncurlpoc {         # Allow only the server itself         allow 127.0.0.1;         allow ::1; # For IPv6 localhost         deny all; …     } … }

Comme vous le voyez, on permet les requêtes venant du même serveur 127.0.0.1 et ::1 (« localhost ») et on nie le reste (« deny all »).

Ensuite, exécutez ces commandes pour tester la nouvelle configuration et pour redémarrer le service.

sudo nginx -t sudo systemctl reload nginx

Test 1 : cURL de l'extérieur

Faisons un cURL à partir de ma machine maison et voyons si les modifications Nginx de fermeture ont pris effet :

Effectivement, nous voyons que le point de terminaison n'est pas accessible au public.

Capture d'écran de la ligne de commande affichant une commande curl entraînant une erreur « 403 Forbidden » provenant d'un serveur Nginx sur Ubuntu Capture d'écran de la ligne de commande affichant une commande curl entraînant une erreur « 403 Forbidden » provenant d'un serveur Nginx sur Ubuntu

Test 2 : cURL à partir du même serveur

Faisons un cURL à partir de notre serveur et voyons si le point de terminaison reste accessible à partir du même serveur :

Effectivement, nous voyons que le point de terminaison est accessible à partir du même serveur.

Sortie de ligne de commande montrant une requête curl réussie vers un point de terminaison de serveur local avec un en-tête « X-Log-Id » personnalisé. Sortie de ligne de commande montrant une requête curl réussie vers un point de terminaison de serveur local avec un en-tête « X-Log-Id » personnalisé.

Partie 2 : Ajout de jeton pour vérifier l’intégrité

Remarque

Lorsqu'un script est exécuté via cron, il s'exécute dans un environnement minimal.

Cela signifie que de nombreuses variables d'environnement définies dans votre shell interactif (comme celles de .bashrc ou .bash_profile) ne sont pas disponibles pour les scripts exécutés via cron.

Paramétrage script

L'ajout d'un jeton en tant qu'en tête est optionnel. On s'est déjà assuré que les requêtes ne puissent venir que du même serveur, mais on ne sait jamais si un hacker dispose des capacités d'usurper l'identité et de se faire passer pour un « localhost ».

On utilisera donc un jeton en guise de mot de passe. Pour le configurer, on élabore une requête test cURL qui dispose du jeton en entête :

Commençons par générer un jeton :

openssl rand -hex 16

La requête typique cURL devient

curl -sS -H "X-Log-Id: [id ici]" -H "X-Token-Cron: [jeton ici]" http://localhost:3001/croncurlpoc

Sur notre serveur, on place la valeur du jeton en tant que variable d'environnement.

La raison pour laquelle on fait cela est que le script qui utilise cette valeur puisse l'invoquer à partir du nom de la variable et non de sa valeur propre.

Si on invoque le jeton de sa valeur propre, on risque qu'un hacker gagne accès à ce script et puisse l'utiliser. Pour enregistrer la variable d'environnement, on le fait a partir d'un autre script :

/home/[utilisateur]/scripts/env_vars.sh

On ajoute cela au fichier, puis contrôle +X, Y (Yes), Entrer.

export CRON_CURL_SECRET_TOKEN='[votre jeton ici]'

On s'assure que not permission ressemble a ca :

-rw-r--r-- 1 [utilisateur] [utilisateur] 67 Jan 9 15:14 env_vars.sh

Pour ce faire on execute :

sudo chown [utilisateur]:[utilisateur] /home/[utilisateur]/scripts/env_vars.sh chmod 644 /home/[utilisateur]/scripts/env_vars.sh

On s'assure que le propriétaire et le groupe du fichier sont l'utilisateur, puis on accorde au propriétaire la permission de lecture et d'écriture. Le groupe et les autres par rapport au fichier n'ont que la possibilité de lire le fichier.

Ensuite on modifie le code bash script auquel l'execution est planifier en compteur par cron.

#!/bin/bash source /home/[utilisateur]/scripts/env_vars.sh # Define the URL variable url="http://localhost:3001/croncurlpoc" …

Source est une commande intégrée du shell Bash. Il lit et exécute les commandes du fichier donné (dans ce cas, /home/[utilisateur]/scripts/env_vars.sh) dans l'environnement shell actuel. L'exécution du script importé est fais sur le meme processus que le script importateur.

A partir du script principal (/home/[utilisateur]/scripts/HTTP-GET-croncurlpoc-2.sh) on importe une variable d'environment jeton a partir d'un autre script (/home/[utilisateur]/scripts/env_vars.sh). On réduit les permission de ce dernier pour des raisons de securité. L'intérêt de tous ca est de stocker le jeton dans un endroit secure, auquel cron dispose de la possibilité d'accès.

Partie 3 : Traitement Express JS du jeton

Modifications express

Ensuite, on modifie le code express en ajoutant l'intergiciel suivant. Cet intergiciel s'incorpore bien juste avant controller.cont1. On nomme l'intergiciel: middleware.mid1.

L'image de l'intergiciel est ci-bas.

Le code est assez simple :

On récupère la valeur du jeton à partir de la variable d'environnement.

const cronCurlSecretToken = process.env['CRON_CURL_SECRET_TOKEN'];

On récupère la valeur du jeton à partir de la requête cURL.

const cronCurlRequestToken = req.headers['x-token-cron'];

Si les deux ne sont pas égaux, on retourne un HTTP(S) 403 Non autorisé (« Unauthorized »).

if (cronCurlRequestToken !== cronCurlSecretToken) { const message = 'Unauthorized' const endpointResponseMessage = getEndpointResponseMessage(req, message) return res.status(403).send(endpointResponseMessage); }

S'ils sont égaux, on passe au controller (controller.cont1) qui se charge du log et de la réponse.

return next()

Code JavaScript pour une fonction middleware Express qui recherche un jeton secret dans les en-têtes de requête et envoie une réponse « Non autorisé » s'il ne correspond pas avec celui enregistré. Code JavaScript pour une fonction middleware Express qui recherche un jeton secret dans les en-têtes de requête et envoie une réponse « Non autorisé » s'il ne correspond pas avec celui enregistré.

Logs

Vu que les éléments du système entier sont mis en place, il ne suffit que de lancer la tâche cron et de voir les logs qu'il produit en logs.

crontab -e # POC 2 curl to localhost:3000/croncurlpoc # Run the script every minute * * * * * /home/[utilisateur]/scripts/cron-curl-poc-2.sh >> /home/[utilisateur]/logs/cron-curl-poc-2.txt 2>&1

On laisse la tâche exécuter notre point de terminaison HTTP(S) deux fois en deux minutes pour produire deux paires de logs.

Remarque

Remarquez dans ce qui suit, qu'à chaque log d'ID issu de la tâche cron correspond un log de même ID issu d'Express.js. Ceux-ci sont de paire, car une tâche cron engendre une exécution de code Express.js.

Logs cron

Ci-dessous est une image montrant à ce que ressemblent les logs produits par l'exécution de la tâche cron.

Le chemin des logs que j'avais choisis pour mettre ce fichier est le suivant :

/home/[utilisateur]/logs/cron-curl-poc-2.txt

Sortie de ligne de commande affichant le contenu d'un fichier journal avec des requêtes curl réussies effectuées par un script de tâche cron. Sortie de ligne de commande affichant le contenu d'un fichier journal avec des requêtes curl réussies effectuées par un script de tâche cron.

Logs express

Ci-dessous est une image montrant à ce que ressemblent les logs produits par l'exécution du point de terminaison HTTP(S) dans Express.js.

Le chemin des logs que j'avais choisis pour mettre ce fichier est le suivant :

/home/[utilisateur]/logs/HTTP-GET-croncurlpoc-2.txt

Sortie du terminal affichant le contenu du fichier journal Express.js, indiquant les requêtes GET réussies vers un point de terminaison avec des horodatages. Sortie du terminal affichant le contenu du fichier journal Express.js, indiquant les requêtes GET réussies vers un point de terminaison avec des horodatages.

Logs en cas d’erreur dans Express

Voici ce qui en résulte, des logs, lorsqu'une erreur est émise par le code du traitement des requêtes HTTP(S) (voir « Remarque : cas d'erreur » en début d'article).

Affichage en ligne de commande des journaux d'erreurs de la tâche cron et d'Express.js avec des horodatages correspondants indiquant qu'une erreur s'est produite lors de la requête. Affichage en ligne de commande des journaux d'erreurs de la tâche cron et d'Express.js avec des horodatages correspondants indiquant qu'une erreur s'est produite lors de la requête.

Peut-on déboguer localement ?

J'utilise nodemon dans mon environnement local. Sous VS Code, j'ai pu connecter un débugueur à l'instance de nodemon. La procédure pour configurer le débugueur n'est pas couverte ici.

En lançant une requête curl à mon point de terminaison local, je suis capable de déboguer. La condition pour que cela marche comme prévu nécessite une chose.

Assurez-vous de référencer le chemin vers vos logs par l'intermédiaire de variables d'environnement :

process.env['PATH_LOGS']

Mettez-y le chemin vers vos logs en fonction de l'environnement (production ou développement).

Conclusion

Dans cette phase du projet, on a établi les conditions nécessaires afin de pouvoir incorporer le code d'open AI et de Twitter.

On dispose de mécanismes qui nous permettent de grandir la base de code, de déboguer, de lire les logs, d'identifier les erreurs échéant, de gérer la mémoire, et bien plus.

Les articles qui suivent seront dédiés en premier lieu à l'API de Twitter, puis en second lieu à la récolte d'informations RSS ou autres, puis en troisième lieu à l'API d'open AI.

On espère avoir des robots qui tweetent du contenu intéressant automatiquement. On espère que vous apprenez à travers ce contenu.

On espère que cette documentation génère du trafic à notre site web (Earnanswers) et que cela nous ouvre des portes pour des contrats de travaux.

Veuillez soumettre votre email si vous voulez que Earnanswers vous rende des services de développement web.

Mahdi Furry

Lectures supplémentaires