Émission d’un email en Java via un hébergement OVH

Envoyer un email technique depuis un code Java via un compte configuré chez OVH est assez simple si on a trouvé la bonne combinaison de paramètres

Voici la liste des réglages pour un compte que l’on va appeler publication@euromov.eu et dont le mot de passe a été généré, attention quand même à l’envoie d’un trop grand nombre d’emails, votre compte pourrait être bloqué, car il pourrait être bloqué pour spam.

Le réglage mail.debug est à mettre à true au besoin pour voir les messages d’échange entre le serveur et votre code Java:

Properties prop = new Properties();
prop.put("mail.debug", "false");
prop.put("mail.smtp.auth", "true");		
prop.put("mail.smtp.ssl.protocols", "TLSv1.2");
prop.put("mail.smtp.host", "ssl0.ovh.net");
prop.put("mail.smtp.starttls.enable", "true"); 
prop.put("mail.smtp.ssl.trust", "ssl0.ovh.net");
prop.put("mail.smtp.port", "587");
prop.put("mail.smtp.socketFactory.port", "587");
prop.put("mail.smtp.socketFactory.class","javax.net.ssl.SSLSocketFactory");	Session session = Session.getInstance(prop, new Authenticator() {
  @Override
   protected PasswordAuthentication getPasswordAuthentication() {
	return new PasswordAuthentication("publication@euromov.eu", "MOT_DE_PASSE_A_GENERER_DANS_CONSOLE_OVH");}
   });
		
MimeMessage message = new MimeMessage(session);
message.setFrom(new InternetAddress("publication@euromov.eu"));
message.setRecipients(
Message.RecipientType.TO, InternetAddress.parse("directeurs@toto.com"));
message.setSubject("Mail Subject");
String msg = "This is my first email using JavaMailer";

MimeBodyPart mimeBodyPart = new MimeBodyPart();
mimeBodyPart.setContent(msg, "text/html; charset=utf-8");

Multipart multipart = new MimeMultipart();
multipart.addBodyPart(mimeBodyPart);
message.setContent(multipart);
Transport.send(message);

Bon email, mais encore une fois attention aux spams.

Publié dans Développement | Laisser un commentaire

Langage R liaison base de données

Installation de WAMP : https://www.wampserver.com/ ou MAMP : https://www.mamp.info/en/downloads/

Supposons un accès à un fichier « sante.accdb » contenant une table data de cette nature :

id date_analyse      glycemie
1   2022-03-17       10
2   2022-03-18       20

On peut interroger en langage R en SQL vers Microsoft Accces.

En premier lieu il faut installer le module pour R pour accéder à Accces:

install.packages("RODBC")
library(RODBC)

Une fois l’installation réalisée de ce module, on peut ouvrir le fichier Accès et intérroger en SQL la base de données:

con <- odbcConnectExcel2007("C:/Users/utilisateur/Documents/sante.accdb")
resultat <- sqlQuery(con,"select date_analyse from data")
print( resultat )

On peut faire de manière similaire sur Mysql avec des commandes similaires, en premier lieu en installant le module pour la liaison avec le serveur de base de données Mysql :

install.packages("RMySQL")
library(RMysql)

L’accès à un serveur de base de données Mysql suppose plusieurs informations techniques, l’adresse du serveur de base de données (ici 127.0.0.1), un compte sur cette base de données (ici root), un mot de passe correspondant à ce compte (ici «  » pour indiquer mot de passe vide), un port de base de données ( ici et par défaut 3306 ) et enfin une base de données (ici sante)

DB <- dbConnect(MySQL(), user="root", host="127.0.0.1", password="",dbname="sante" , port=3306)

Une fois la connexion réalisée, on peut lister les tables dans cette base de données sante.

dbListTables(DB)

Et enfin poser des questions en SQL

resultat <- dbGetQuery(DB, "SELECT * FROM data")
print( resultat )
Publié dans Développement | Laisser un commentaire

Génération d’un PDF depuis une JSP avec iText5 et iText7

NOTA : la version utilisée dans cette première version est la iText5 une prochaine version utilisera la nouvelle version iText7

En premier lieu il faut récupérer les bibliothèques jar suivantes:

  • itextpdf-5.5.13.3.jar
  • log4j-1.2.16.jar
  • slf4j-api-1.7.9.jar
  • slf4j-log4j12-1.7.13.jar

Placer ces fichiers dans le dossier WebContent/WEB-INF/lib de votre application Dynamic Web Application , les sélectionner et faire click droit > Build Path > Add to Build Path

Vous pouvez maintenant coder une simple JSP pour générer un ficheir PDF. Attention, si vous cette erreur « getOutputStream() has already been called for this response » il faut retirer tout espace ou retour chariot qui pourrait être interprétés par la JSP comme du HTML à envoyer donc tout se trouve sur une seule ligne pour les tag JSP de type <%@page import %>:

<%@ page language="java" contentType="application/pdf; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%><%@page import="com.itextpdf.text.Document" %><%@page import="com.itextpdf.text.PageSize" %><%@page import="com.itextpdf.text.Paragraph" %><%@page import="com.itextpdf.text.pdf.PdfWriter" %><%
Document document = new Document(PageSize.A4, 36, 36, 36, 72);

PdfWriter writer = PdfWriter.getInstance(document, response.getOutputStream());
response.setContentType("application/pdf");
response.setHeader("Content-Disposition", "inline; filename=\"todo.pdf\"");

document.open();
document.add( new Paragraph ("Hello world"));
document.add( new Paragraph ("Bonjour le monde"));
document.close();
writer.close();
%>

Version iText7

Pour la version iText7, après avoir ajouté les bilbiothèques suivantes en remplacement de celles de iText5:

  • barcodes-7.2.1.jar
  • commons-7.2.1.jar
  • font-asian-7.2.1.jar
  • forms-7.2.1.jar
  • hyph-7.2.1.jar
  • io-7.2.1.jar
  • kernel-7.2.1.jar
  • layout-7.2.1.jar
  • log4j-1.2.16.jar
  • pdfa-7.2.1.jar
  • pdftest-7.2.1.jar
  • sign-7.2.1.jar
  • slf4j-api-1.7.9.jar
  • slf4j-log4j12-1.7.13.jar
  • styled-xml-parser-7.2.1.jar
  • svg-7.2.1.jar

Du coup le code pour la JSP est similaire avec l’utilisation d’un ByteArrayOutputStream pour gérer le PDF en mémoire.

<%@ page language="java" contentType="application/pdf; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%><%@ page import="com.itextpdf.io.source.ByteArrayOutputStream" %><%@ page import="com.itextpdf.kernel.pdf.PdfDocument" %><%@ page import="com.itextpdf.kernel.pdf.PdfWriter" %><%@ page import="com.itextpdf.layout.Document" %><%@ page import="com.itextpdf.layout.element.Paragraph" %><%
ByteArrayOutputStream memory = new ByteArrayOutputStream();
Document document = new Document(new PdfDocument(new PdfWriter(memory)));
document.add( new Paragraph ("Hello world"));
document.close();
ServletOutputStream servletOutputStream = response.getOutputStream();
servletOutputStream.write(memory.toByteArray());
servletOutputStream.flush();
servletOutputStream.close();
%>
Publié dans Développement | Laisser un commentaire

Unity3D et le MVC

L’utilisation de la conception MVC dans l’outil Unity3D n’est pas simple. Je ne suis pas sur que cela même pertinent. Mais je pense que cette conception permet d’approcher Unity3D différemment que ne le fait les tutoriels classiques.

Je me suis basé sur principalement 3 sources pour concevoir mon MVC:
https://www.toptal.com/unity-unity3d/unity-with-mvc-how-to-level-up-your-game-development
https://www.patrykgalach.com/2019/04/29/simple-mvc-for-unity/
https://www.gamedeveloper.com/programming/mvc-in-unity/

La conception en sous contrôleur a été retenu dans cet exemple. Ce qu’il faut voir c’est que chaque GameObject de la hiérarchie est associé à un script écris en C#.

La hiérarchie définie avec les GameObject est la suivante :

  • RootController
    • LocalController
      • UILocalRoot
        • UILocalViewCube

RootController est associé au script RootController dans le dossier Controllers
LocalController est associé au script LocalController dans le dossier Controllers
UILocalRoot est associé au script UILocalRoot dans le dossier Views
UILocalViewCube est associé au script UILocalViewCube dans le dossier Views

Le seul GameObject réel est un gameobject cube appelé UILocalViewCube . Le UILocalRoot est un groupement de GameObject car à terme il n’y aura pas qu’un seul cube.

On a bien visible la séparation entre la partie Contrôleur et la partie Vue du MVC. La partie Modèle serait lié au niveau du RootControlleur au besoin.

Une instance de LocalController est incluse dans RootController et si d’autres sous-contrôleur était nécessaire, ils seraient aussi ajoutés sous RootController:

    // Dans la classe RootController l'inclusion du controlleur local
    [Header("Controllers")]
    [SerializeField]
    private LocalController localController;

Le but du RootControlleur est d’engager ou de désengager des sous-controlleur selon le besoin. Les sous-contrôleurs peuvent se passer « la main » mais un seul n’est actif à un moment donné.

Tous les sous-contrôleurs dérivent d’une classe virtuelle un peu technique appelée SubController. Cette classe virtuelle contient une référence au RootControlleur pour assurer le passage de « la main » au besoin.

public abstract class SubController : MonoBehaviour
{
    [HideInInspector]
    public static RootController root;

    public static void CreateRootControllerReference(RootController rootController)
    {
        Debug.Log("CreateRootControllerReference");
        SubController.root = rootController;
    }

La méthode CreateRootControllerReference aurait dû être un constructeur de SubController mais Unity3D ne peut pas avoir une référence sur un RootControlleur tant qu’il n’est pas créé.

Donc dans RootController sur l’évènement Start on a l’appel à cette méthode statique:

    private void Start()
    {
        SubController.CreateRootControllerReference(this);

        ChangeController(ControllerTypeEnum.Local);
    }

On voit aussi qu’il a un appel à la méthode ChangeConbtroller pour donner la main au premier contrôleur (et unique dans notre exemple Local et qui ne contient qu’un cube)

LocalController recevra donc les évènements parce qu’il s’abonnera aux évènement de sa hierachie de classe:

public class LocalController : SubController<UILocalRoot>

Dans la notation ci-dessus un peu complexe, le LocalController est de type SubController mais de template <UILocalRoot> qui fait la liaison avec le UILocalViewCube

public class UILocalRoot : UIRoot
{    
    [SerializeField]
    private UILocalViewCube uiLocalViewCube;

Donc notre LocalController a la main sur les évènements d’une instance de uiLocalViewCube via cette opération abonnement sur action :

  public override void EngageController()
    {
        ui.UILocalViewCube.actionClick += doActionClick;
    }
    private void doActionClick()
    {
    }

Le actionClick de type UnityAction dans la classe UILocalViewCube déclenchera donc l’appel dans le LocalController de la méthode doActionClick()

La liaison est donc réalisée d’un évènement dans la classe UILocalViewCube associé à notre GameObject UILocalViewCube:

    public UnityAction actionClick;

    public void OnMouseDown()
    {
        actionClick?.Invoke();
    }

La remontée du message depuis sur le clic de la souris sur OnMouseDown vers le LocalController est donc un abonnement sur l’UnityAction actionClick vers une méthode doActionClick dans le LocalController

UILocalViewCube.OnMouseDown -> actionClick -> doActionClick

Maintenant supposons que cette doActrionClick doit permettre de changer de contrôleur (sinon cela resterait des méthodes dans le LocalController) :

 private void doActionClick(){
    root.ChangeController(RootController.ControllerTypeEnum.GameOver);
}

L’appel ci-dessus demande au RootController de faire un changement de contrôleur.

Il faut ajouter sur la partie vue que tous les différents UILocalRoot qui gèrent l’ensemble des GameObject de cette vue spécifique (appelé Local, mais on aurait put l’appelé Welcome pour l’écran de démarrage) doivent hériter de la classe UIRoot qui contient une méthode pour cacher ou d’écacher leur GameObject associé :

public class UIRoot : MonoBehaviour
{
    /// <summary>
    /// Method used to show UI.
    /// </summary>
    public virtual void ShowRoot()
    {
        gameObject.SetActive(true);
    }

    /// <summary>
    /// Method used to hide UI.
    /// </summary>
    public virtual void HideRoot()
    {
        gameObject.SetActive(false);
    }
}

Et voici donc notre UILocalRoot qui gère l’ensemble des éléments graphiques:

public class UILocalRoot : UIRoot
{    
    [SerializeField]
    private UILocalViewCube uiLocalViewCube;
    public UILocalViewCube UILocalViewCube => uiLocalViewCube;

    public override void ShowRoot()
    {
        base.ShowRoot();
        uiLocalViewCube.ShowView();
    }

    public override void HideRoot()
    {
        uiLocalViewCube.HideView();
        base.HideRoot();
    }
}

En remontant voici notre contrôleur local LocalController point central des évènements :

public class LocalController : SubController<UILocalRoot>
{

    public override void EngageController()
    {
        ui.UILocalViewCube.actionClick += doActionClick;
        base.EngageController();
    }

    public override void DisengageController()
    {
        base.DisengageController();
        ui.UILocalViewCube.actionClick -= doActionClick;
    }

   private void doActionClick()
    {
        //TODO: code pour la gestion de l'évènement onClick sur le UILocalViewCube
    }
}

Le plus haut niveau avec un RootController qui ne contient donc deux sous-contrôleurs LocalControler et GameOverController qui doit être fabriqué par la suite :

public class RootController : MonoBehaviour
{
    public enum ControllerTypeEnum
    {
        Local,
        GameOver
    }

    [Header("Controllers")]
    [SerializeField]
    private LocalController localController;

    [SerializeField]
    private GameOverController gameOverController;

    private void Start()
    {
        SubController.CreateRootControllerReference(this);
        ChangeController(ControllerTypeEnum.Local);
    }
    public void ChangeController(ControllerTypeEnum controller)
    {
        DisengageControllers();
        switch (controller)
        {
            case ControllerTypeEnum.Local:
                localController.EngageController();
                break;
            case ControllerTypeEnum.GameOver:
                gameOverController.EngageController();
                break;
            default:
                break;
        }
    }

    public void DisengageControllers()
    {
        localController.DisengageController();
        gameOverController.DisengageController();
    }
}

À tester sur cette architecture très complexe si on ne sépare pas trop les éléments, mais j’aime bien avoir des sous-contrôleurs en charge chacun de groupe d’objets graphiques qui sont regroupé dans une classe dérivée de UIRoot (dans notre cas UILocalRoot).

Au final, la liste des scripts et leur utilisation, avec les controlleurs en premier dans le dossier Assets/Scripts/Controllers/:

  • RootController.cs -> Contrôleur principal lié au LocalController
  • LocalController.cs -> Contrôler spécialisé hérite de SubController une référence vers le RootController
  • SubController.cs -> Classe virtuelle dont dépend tous les sous-contrôleurs

Les vues ensuite dans le dossier Assets/Scripts/Views:

  • UIView.cs -> classe virtuelle dont tous les GameObject visuel doivent dériver pour avoir les méthodes de base ShowView et HideView.
  • UIRoot.cs -> classe virtuelle dont tous les GameObjects gérant les groupes de GameObjects doivent dériver pour avoir les méthodes de base ShowRoot et HideRoot. Ces méthodes appelerons sur chaque GameObject la demande de cacher ou de se rendre visible avec leur ShowView et HideView
  • UILocalRoot.cs -> assure la gestion des GameObjects lié à un LocalControler. Sa classe est dérivée de UIRoot
  • UILocalViewCube.cs -> Classe liée au GameObject Cube affiché à l’écran avec l’évènement OnMouseDown.

On peut voir UIView et UIRoot comme des interfaces pour que les descendants de ces méthodes (respectivement UILocalViewCube et UILocalRoot) aient les méthodes nécessaires en cas d’engagement ou de désengagement de leur fameux controleur LocalControler .


Il ne manque plus que d’ajouter les codes complets des fichiers manquants

UIView.cs :

using UnityEngine;

/// <summary>
/// Base class for every UI element and view.
/// </summary>
public class UIView : MonoBehaviour
{
    /// <summary>
    /// Method used to show view or element.
    /// </summary>
    public virtual void ShowView()
    {
        gameObject.SetActive(true);
    }

    /// <summary>
    /// Method used to hide view or element.
    /// </summary>
    public virtual void HideView()
    {
        gameObject.SetActive(false);
    }
}

UIRoot.cs

using UnityEngine;

/// <summary>
/// Base class for UI roots for different controllers.
/// </summary>
public class UIRoot : MonoBehaviour
{
    /// <summary>
    /// Method used to show UI.
    /// </summary>
    public virtual void ShowRoot()
    {
        gameObject.SetActive(true);
    }

    /// <summary>
    /// Method used to hide UI.
    /// </summary>
    public virtual void HideRoot()
    {
        gameObject.SetActive(false);
    }
}

UILocalViewCube.cs

using UnityEngine;
using UnityEngine.Events;

/// <summary>
/// UI Cube with event OnMouseDown
/// </summary>
public class UILocalViewCube : UIView
{
    public UnityAction actionClick;
    public UnityAction actionOver;


    public void OnMouseDown()
    {
        actionClick?.Invoke();
    }

    public void OnMouseOver()
    {

        actionOver?.Invoke();        
    }

}

UILocalRoot.cs

using UnityEngine;
/// <summary>
/// UI root class for Menu controller.
/// </summary>
public class UILocalRoot : UIRoot
{    
    [SerializeField]
    private UILocalViewCube uiLocalViewCube;
    public UILocalViewCube UILocalViewCube => uiLocalViewCube;

    public override void ShowRoot()
    {
        base.ShowRoot();
        uiLocalViewCube.ShowView();
    }

    public override void HideRoot()
    {
        uiLocalViewCube.HideView();
        base.HideRoot();
    }
}

LocalController.cs

using UnityEngine;using System.Text;using System.Net.Sockets;using System.Threading;using System;
/// <summary>
/// Controller responsible for menu phase.
/// </summary>
public class LocalController : SubController<UILocalRoot>
{
    public override void EngageController()
    {
        ui.UILocalViewCube.actionClick += doActionClick;
        ui.UILocalViewCube.actionOver += doActionOver;
        base.EngageController();
    }
    public override void DisengageController()
    {
        base.DisengageController();
        ui.UILocalViewCube.actionClick -= doActionClick;
        ui.UILocalViewCube.actionOver -= doActionOver;
    }

    private void doActionClick()
    {
        Debug.Log("doActionClick");
    }

    private void doActionOver()
    {
        Debug.Log("doActionOver");
        root.ChangeController(RootController.ControllerTypeEnum.GameOver);
    }

}

RootController.cs

using UnityEngine;

/// <summary>
/// Root controller responsible for changing game phases with SubControllers.
/// </summary>
public class RootController : MonoBehaviour
{
    // SubControllers types.
    public enum ControllerTypeEnum
    {
        Local,
        GameOver
    }

    // References to the subcontrollers.
    [Header("Controllers")]
    [SerializeField]
    private LocalController localController;



    /// <summary>
    /// Unity method called on first frame to act as initialisation
    /// </summary>
    private void Start()
    {
        SubController.CreateRootControllerReference(this);
        ChangeController(ControllerTypeEnum.Local);
    }

    /// <summary>
    /// Method used by subcontrollers to change game phase.
    /// </summary>
    /// <param name="controller">Controller type.</param>
    public void ChangeController(ControllerTypeEnum controller)
    {
        // Reseting subcontrollers.
        DisengageControllers();
        // Enabling subcontroller based on type.
        switch (controller)
        {
            case ControllerTypeEnum.Local:
                Debug.Log("localController.EngageController()");
                localController.EngageController();
                break;
            case ControllerTypeEnum.GameOver:
                // gameOverController.EngageController();
                break;
            default:
                break;
        }
    }

    /// <summary>
    /// Method used to disable all attached subcontrollers.
    /// </summary>
    public void DisengageControllers()
    {
        localController.DisengageController();        
        gameOverController.DisengageController();
    }
}

SubController.cs

using UnityEngine;

/// <summary>
/// Base class for SubControllers with reference to Root Controller.
/// </summary>
public abstract class SubController : MonoBehaviour
{
    [HideInInspector]
    public static RootController root;
    public static void CreateRootControllerReference(RootController rootController)
    {
        SubController.root = rootController;
    }

    public virtual void EngageController()
    {
        gameObject.SetActive(true);
    }

    public virtual void DisengageController()
    {
        gameObject.SetActive(false);
    }
}

/// <summary>
/// Extending SubController class with generic reference UI Root.
/// </summary>
public abstract class SubController<T> : SubController where T : UIRoot
{
    [SerializeField]
    protected T ui;
    public T UI => ui;

    public override void EngageController()
    {
        base.EngageController();
        ui.ShowRoot();
    }

    public override void DisengageController()
    {
        base.DisengageController();
        ui.HideRoot();
    }
}
Publié dans Développement | Laisser un commentaire

Alternative tcpdump windows 10 avec pktmon

L’outil pktmon.exe disponible par défaut dans Windows 10 Pro permet de faire un peu de dump de paquet TCP.

Bien sûr, la solution de npcap/winpcap/wireshark est beaucoup plus performant, mais avec quelques commandes on peut extraire l’information utile. Je me concentre surtout sur la partie HTTP qui me permet de vérifier les échanges de données même si la limitation de pktmon à ne pas pouvoir fonctionner sur la boucle locale 127.0.0.1 est clairement une limitation pour moi.

Démarrons par ajouter un filtre sur le protocole TCP sur le port 80

 C:\Windows\system32\pktmon.exe filter add -t TCP  -p 80

On peut lister ces filtres avec la commande suivante

 C:\Windows\system32\pktmon.exe filter list

Filtres de paquet :
     # Nom    Protocole Port
     - ---    --------- ----
     1 <vide> TCP         80

Et pour démarrer la capture, on voit le rappel de la liste des paquets

 C:\Windows\system32\pktmon.exe  start --etw  --pkt-size 0

Filtres de paquet :
     # Nom    Protocole Port
     - ---    --------- ----
     1 <vide> TCP         80

Un fichier PktMon.etl va donc être fabriqué contenant les opérations que l’on fait avec son navigateur vers un port 80 d’un serveur web par exemple.

Mais je vous propose cette version du filtrage pour se concentrer sur la partie Web des requêtes :

C:\Windows\system32\pktmon.exe filter add Port80 -t TCP PSH -p 80 -m 00-AA-BB-CC-DD-EE

On ajoute un nom Port80 et on va récupérer que les paquets marqués TCP de type PUSH (codé PSH) sur le port 80 en provenance de la Mac Address de ma carte réseau 00-AA-BB-CC-DD-EE à remplacer par votre valeur.

Il suffit maintenant d’arrêter la capture est de convertir le fichier PktMon.etl en format lisible à moins d’utiliser l’outil d’analyse de Microsoft Network Monitor.

Le fichier ftp.txt est assez clair pour être lu et vérifier les packets TCP

C:\Windows\system32\pktmon.exe  stop

C:\Windows\system32\pktmon.exe  format PktMon.etl -o file.txt

Exemple d’accès sur un site web en méthode GET en regardant le contenu du fichier file.txt :

00-AA-BB-CC-DD-EE > 00-11-12-13-14-15, ethertype IPv4 (0x0800), length 457: 192.168.1.1.49676 > 213.186.33.2.80: Flags [P.], seq 2764848503:2764848906, ack 3028623659, win 513, length 403: HTTP: GET /accueil/?s=test HTTP/1.1

On peut retirer le filtre au besoin pour revenir dans l’état initial

C:\Windows\system32\pktmon.exe  filter remove

On peut aussi exporter dans un format plus verbeux avec l’option de formatage etl2txt:

C:\Windows\system32\pktmon.exe  etl2txt .\PktMon.etl v 3 -m -o file.txt

Cela va produire les entête HTTP des contenus des requêtes HTTP par exemple avec le fichier JavaScript suivant :

	HTTP/1.1 200 OK
	date: Wed, 23 Feb 2022 15:11:35 GMT
	content-type: application/javascript
	content-length: 229
	server: Apache
	last-modified: Tue, 10 Dec 2019 18:53:39 GMT
	accept-ranges: bytes
	cache-control: max-age=900
	expires: Wed, 23 Feb 2022 15:26:35 GMT
	vary: Accept-Encoding
	x-iplb-request-id: C1339D28:28B3_D5BA2102:0050_62164EA7_6F6E:1CD33
	x-iplb-instance: 29005
	
	
	
	var $j = jQuery.noConflict();
	
	$j(document).ready(function() {
	    // Toggle Single Bibtex entry
	    $j('a.papercite_toggle').click(function() {
		$j( "#" + $j(this).attr("id") + "_block" ).toggle();
		return false;
	    });
	});

Ensuite on pourra utiliser les outils de développements des navigateurs pour une lecture plus simple, mais la liaison TPC/IP sur le contenu est clairement lisible avec cet outil fourni par défaut.

L’option suivante est soit de faire plus de réseau avec tcpdump ou wireshark qui sont dédiés aux couches réseaux soit de partir en développement web avec les outils embarqués dans les navigateurs Web.


La partie que je chercherai à ajouter prochainement, est la capture de la boucle locale via des outils tierce comme indiqué sur cette page : https://wiki.wireshark.org/CaptureSetup/Loopback

Je pense que cette option semble prometteuse même si je n’arrive pas encore à la faire fonctionner : https://web.archive.org/web/20150726062624/http://ig2600.blogspot.com/2011/03/powershell-script-to-enable-windows-to.html

Publié dans Développement | Laisser un commentaire

Ajout de l’authentification One-Time-Password à taiga.io

La nouvelle version de taiga.io avec docker inclus un installateur bien simplifié.

Il me parait utile d’y ajouter simplement l’authentification OTP qui permet d’accepter le mot de passe ajouté avec 6 chiffres calculé par l’horloge à partir d’un code. Vous trouverez des explications sur ce processus dans cet article: https://en.wikipedia.org/wiki/One-time_password

Le seul fichier à éditer pour inclure l’OTP dans taiga est le fichier /taiga-back/taiga/users/services.py dans le containeur taiga-docker_taiga-back_1.

En premier lieu, il faut ajouter au containeur taiga-docker_taiga-back_1 le module pyotp:

docker exec -it taiga-docker_taiga-back_1 pip install pyotp

Ensuite on peut copier sur la machine hôte le fichier services.py pour y ajouter l’OTP:

docker cp taiga-docker_taiga-back_1:/taiga-back/taiga/users/services.py  services.py 

Nous pouvons donc éditer le fichier services.py pour y adjoindre et modifier le programme, en premier lieu en important le module pyotp avant toutes les fonctions:

import pyotp

Puis on va chercher la fonction get_and_validate_user qui a cette forme:

def get_and_validate_user(*, username: str, password: str) -> bool:
    """
    Check if user with username/email exists and specified
    password matchs well with existing user password.

    if user is valid,  user is returned else, corresponding
    exception is raised.
    """

    user = get_user_by_username_or_email(username)


    if not user.check_password(password) or not user.is_active or user.is_system:
        raise exc.WrongArguments(_("Username or password does not matches user."))

    return user

Nous allons ajouter l’extraction des 6 derniers symboles du mot de passe pour trouver le code PIN à valider par OTP avec le code fourni en paramètre. Donc la nouvelle version de la fonction get_and_validate_user

def get_and_validate_user(*, username: str, password: str) -> bool:
    """
    Check if user with username/email exists and specified
    password matchs well with existing user password.

    if user is valid,  user is returned else, corresponding
    exception is raised.
    """

    user = get_user_by_username_or_email(username)

    """
    PJE   verification OTP
    """
    totp = pyotp.TOTP("base32secret3232")
    passwordPIN = password
    password = passwordPIN[:-6]
    pin = totp.verify( passwordPIN[-6:] , valid_window=2 )
    if pin is False:
        raise exc.WrongArguments(_("Username or password does not matches user PIN."))

    """
    / PJE   verification OTP
    """


    if not user.check_password(password) or not user.is_active or user.is_system:
        raise exc.WrongArguments(_("Username or password does not matches user."))

    return user

En incluant dans la fonction d’authentification, l’extraction des 6 derniers symboles fournis par l’utilisateur, on peut ajouter une authentification avec code OTP. On note une nouvelle exception envoyé par le serveur en cas d’erreur sur le code PIN.

NOTA: je pensais avoir un problème de Timezone dans le containeur mais au final, je pense que c’est valide comme installation. J’ai testé quelques solutions de changement dans le containeur de timezone mais finalement je pense que je peux aussi m’appuyer sur l’argument for_time de la fonction verify : https://pyauth.github.io/pyotp/_modules/pyotp/totp.html#TOTP.verify

Publié dans Développement | Laisser un commentaire

Génération des classes Java Entity depuis un fichier JDL

Je trouve très pratique pour définir les entités l’outil en ligne de Jhipster JDL-Studio.

Prenons une application avec plusieurs liaisons OneToMany d’une liste de recette dont chaque recette a une liste de commentaires et une liste d’ingrédients.

À partir du fichier jhipster-jdl.jdl téléchargé depuis le JDL Studio, je souhaite fabriquer les entités en Java pour les intégrer dans un développement.

Je n’ai pas trouvé toutes les options d’automatisation, mais en ligne de commande après avoir installé Jhipster, on peut lancer les 2 lignes suivantes  dans un dossier contenant le fichier jhipster-jdl.jdl.

La première permet de fabriquer le projet en répondant à 8 questions par validation automatique.

 jhipster --skip-cache --skip-git --skip-install --skip-prompts --skip-client --skip-user-management  --db sql --skip-checks  --skip-fake-data --base-name App --application-type microservices   --skip-checks

Les questions seront validées par la touche « entrée » automatiquement :

? What is your default Java package name? com.mycompany.myapp
? Which *type* of database would you like to use? SQL (H2, PostgreSQL, MySQL, MariaDB, Oracle, MSSQL)
? Which *production* database would you like to use? PostgreSQL
? Which *development* database would you like to use? H2 with disk-based persistence
? Which cache do you want to use? (Spring cache abstraction) Ehcache (local cache, for a single node)
? Do you want to use Hibernate 2nd level cache? Yes
? Would you like to use Maven or Gradle for building the backend? Maven
? Which other technologies would you like to use?

La seconde commande va fabriquer les entités en Java depuis le fichier jhipster-jdl.jdl :

jhipster jdl jhipster-jdl.jdl --skip-client

Repondez-yes aux 2 questions d’overwrite sans inquiétudes, nous n’allons pas exploiter d’autres fonctions que la génération des entités.

Vous trouverez dans le dossier src/main/java/com/mycompany/myapp/domain/ les fichiers java des entités avec les liaisons réalisées et les propriétés créées.

Il s’agit d’une utilisation d’une toute petite fonctionnalité de Jhipster mais je trouve cela très pratique.

Publié dans Développement | Laisser un commentaire

Gestion de fichiers images dans Springboot

La gestion de l’upload de fichiers images dans Springboot s’appuie sur plusieurs classes et fichiers.

FileUploadController agit comme un contrôleur qui va recevoir la demande d’upload de fichier (via l’url /upload) et la demande d’afficher l’image ( via l’url /files/{filename:.+} ). nCette classe contient une référence sur une classe FileSystemStorageService implementant l’interface StorageService.

StorageService est une simple interface qui permet de définir les actions classiques d’un service Spring de stockage.

FileSystemStorageService implémente de façon réelle l’interface StorageService pour la gestion du lieu de stockage des fichiers. Par ailleurs, pour une raison de sécurité, on peut stocker les fichiers uploadés dans un dossier dont l’accès est protégé pour éviter l’upload de fichiers critiques sur le serveur en devinant l’URL.

upload.html est un fichier gérant le formulaire HTML permettant d’envoyer un fichier simplement sans javascript encore une fois pour simplifier cet exemple.

Après les liaisons entre les fichiers, voici le contenu très simplifié de ces derniers:

<!DOCTYPE html><html lang="en"><head>
  <title>upload</title>
<body>
<form method="post" enctype="multipart/form-data" action="/upload">
 <div>
   <label for="file">Sélectionner le fichier à envoyer</label>
   <input type="file" id="file" name="file" >
 </div> <div>
   <button>Envoyer</button>
 </div>
</form>
</body></html>

L’upload d’un fichier image se fait vers le contrôleur FileUploadController via l’action /upload, cette classe permet aussi l’affichage d’une ressource image dans le navigateur (il manque la gestion des exceptions à coder en plus) :

@Controller
public class FileUploadController {
  // Service spring de stockage des fichiers
  private final StorageService storageService;
  // Constructeur de la classe avec l'activation du service
  @Autowired
  public FileUploadController(StorageService storageService) {
    this.storageService = storageService;
  }

  @GetMapping("/files/{filename:.+}")
  @ResponseBody
   public ResponseEntity<Resource> serveFile(@PathVariable String filename) {
  Resource file = storageService.loadAsResource(filename);
  String mimeType = URLConnection.guessContentTypeFromName(file.getFilename());
   long contentLength = 0;
   try {
	contentLength = file.contentLength();
   } catch (IOException e) { }
		
   InputStream fileInputStream = null;
   try {   fileInputStream = file.getInputStream();
    } catch (IOException e) {}
		
   return ResponseEntity.ok().contentLength( contentLength )
	.contentType(MediaType.parseMediaType( mimeType ))
	.body(new InputStreamResource( fileInputStream ));
  }

//Upload d'un fichier 
@PostMapping("/upload")
public String handleFileUpload(@RequestParam("file") MultipartFile file, RedirectAttributes redirectAttributes) {
   storageService.store(file);
   return "redirect:/";
}

}

L’interface StorageService permet de définir les méthodes à implémenter par FileSystemStorageService.

public interface StorageService {
    void init();
    String store(MultipartFile file);
    Stream<Path> loadAll();
    Path load(String filename);
    Resource loadAsResource(String filename);
    void deleteAll();
}

Enfin la classe FileSystemStorageService permet de créer un service qui va stocker les fichiers dans un dossier ./Stockage dans le projet Spring (la gestion des exceptions est à coder en plus):

@Service
public class FileSystemStorageService implements StorageService {
  private final Path rootLocation;
  @Autowired
  public FileSystemStorageService() {
    this.rootLocation = Paths.get("./Stockage/");
   }
  @Override
  @PostConstruct
  public void init() {
     try {
	Files.createDirectories(rootLocation);
    } catch (IOException e) {}
   }

  @Override
   public String store(MultipartFile file) {
String filename = StringUtils.cleanPath(file.getOriginalFilename());
  try {
  if (file.isEmpty()) {}  
  if (filename.contains("..")) { }
   try (InputStream inputStream = file.getInputStream()) {
    Files.copy(inputStream, this.rootLocation.resolve(filename), StandardCopyOption.REPLACE_EXISTING);
    }
   } catch (IOException e) {	}
   return filename;
}

@Override
public Stream<Path> loadAll() {
 try {
    return Files.walk(this.rootLocation, 1).filter(path -> !path.equals(this.rootLocation)).map(this.rootLocation::relativize);
 } catch (IOException e) {	return null; }

}

@Override
public Path load(String filename) {
  return rootLocation.resolve(filename);
}

@Override
public Resource loadAsResource(String filename) {
  Path file = load(filename);
  Resource resource = null;
  try {
     resource = new UrlResource(file.toUri());
   if (resource.exists() || resource.isReadable()) {
      return resource;
   } else {
     System.out.println("File not found " + filename); 
   }
  } catch (MalformedURLException e) { }
		return resource;
}

@Override
public void deleteAll() {
  FileSystemUtils.deleteRecursively(rootLocation.toFile());
}

}

Le code du contrôleur FileUploadController permet d’afficher le fichier image dans le navigateur, si on souhaite proposer en téléchargement un fichier, la méthode serveFile doit être modifiée:

    Resource file = storageService.loadAsResource(filename);
   return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + file.getFilename() + "\"").body(file);

Merci à cet article (https://spring.io/guides/gs/uploading-files/) qui a permit la rédaction de ce contenu.

Publié dans Développement, IMT Mines Alès | Laisser un commentaire

Programmation par aspect avec Java

L’idée de base dans la programmation par Aspect est d’extraire du flot normal d’exécution d’un code les parties très techniques.

Dans Spring on a classiquement la partie du code qui gère le Controleur de l’entité Todo via cette méthode :

@RequestMapping(value = "/todos", method = RequestMethod.GET)
public String listeTodos() {
   
  System.out.println("listeTodos");

  List<Todo> listTodo = todoDao.listTodo();
  String json = listTodo.stream()
		.map( todo -> todo.toJson() )
		.collect( Collectors.joining( "," , "[" , "]") );
		
   return json;
}

Pourtant, on pourrait vouloir faire un log de cet appel en ajoutant classiquement un logger au sein de cette méthode listeTodos() sauf que le principe de la programmation par Aspect est de sortir cette partie technique en proposant l’interception de l’appel de cette méthode listeTodos avec un code qui s’exécute avant et/ou après l’appel à listeTodos.

De manière concrète on a cet enchainement :

  • Appel à une méthode technique avant listeTodos
  • Appel à listeTodos
  • Appel à une méthode technique après listeTodos (optionnel)

En premier lieu, il faut ajouter sur la méthode principal l’ajout des fonctionnaltiés de programmation par Aspect:

@SpringBootApplication
@Configuration
@EnableAspectJAutoProxy
public class DemoApplication {
}

Ensuite, il faut coder une classe qui va assurer l’interception avant et après dans cet exemple avec les critères d’interceptions :

@Component
@Aspect
public class RestLogAspect {
	    
    @Around("execution(* fr.ema.ceris.todo.demo.TodoControleur..*(..)) && @annotation(org.springframework.web.bind.annotation.RequestMapping)")
    public Object restLog(ProceedingJoinPoint joinPoint) throws Throwable {
    	
        // Partie @Before 
        Object returnValue = null;
        Throwable throwable = null;
        try {
            System.out.println("Appel à TodoControleur avant");
            
            returnValue = joinPoint.proceed();       

            System.out.println("Appel à TodoControleur après");
 
        } catch (Throwable throwable1) {
            throwable = throwable1;
            System.out.println(String.format("[RestLogAspect] : %s", throwable1.getMessage()));
        }


        //Partie  @After 
        
        return returnValue;
    }

}

L’annotation @Around indique que l’appel à cette méthode sera fait avant et après les méthodes ayant les 2 critères suivants (notation && entre les 2 critères) :

  • execution(* fr.ema.ceris.todo.demo.TodoControleur..*(..)) : une classe se trouvant dans ce package avec ce nom de classe
  • @annotation(org.springframework.web.bind.annotation.RequestMapping), une annotion de type RequestMapping

Messages de la console :

Appel à TodoControleur avant
listeTodos
Appel à TodoControleur après

Nous avons donc un affichage des messages dans la console avant et après l’appel à la méthode listeTodos ce qui permet d’instrumentaliser le code fonctionnel par un code technique.

La valeur de la variable returnValue est aussi le résultat de l’appel à la fonction listeTodos donc le contenu json de la variable que l’on pourrait donc formater ou compresser avant renvoi vers la partie front du site web.

On aurait aussi pu utiliser l’annotation @Before à la place de l’annotation @Around pour avoir une exécution avant la méthode listeTodos et/ou l’annoation @After pour avoir une exécution après la méthode listeTodos. L’annotation @Around est un excellent exemple car elle permet de disposer des effets avant et après l’appel.

Publié dans Développement | Laisser un commentaire

Visual Code xdebug et le rechargement automatique de Chrome

Dans la suite de l’article sur « Ligne de commande pour recharger une page Chrome« , j’ai eu l’objectif d’utiliser Visual Code pour lancer xdebug avec le langage de programmation PHP et avoir l’auto refresh de Chrome lié avec la sauvegarde d’un fichier dans Visual Code.

Etape 1 : xdebug

Xdebug doit être configuré et installer dans Wamp et l’édition du fichier php.ini d’Apache de l’outil Wamp devrait contenir ce genre d’informations

[xdebug]
zend_extension="c:/Developpement/wamp64-3.2.3/bin/php/php7.4.9/zend_ext/php_xdebug-2.9.6-7.4-vc15-x86_64.dll"
;PJE xdebug.remote_enable = off
xdebug.remote_enable=1

Notez que je vous propose d’utiliser le « remote debug » de xdebug. Bien sûr, dans l’affichage de la page phpinfo, il faut vérifier que l’extension xdebug est bien activé :

Plus bas dans cette page on pourra vérifier que xdebug a les bons réglages dans la partie qui est dédié à xdebug.

Information que xdebug est actif dans la page phpinfo

Etape 2: Visual Code

Dans Visual Code, il faut activer l’extension PHP Debug pour disposer des outils de liaison entre Visual Code et xdebug. L’extension que j’ai utilisé est de Felix Becker : https://marketplace.visualstudio.com/items?itemName=felixfbecker.php-debug.

Nous partons du principe que dans le dossier www de Wamp se trouve un sous-dossier dans lequel notre développement sera réalisé.

Pour éviter d’utiliser un fichier tasks.json, j’ai préparé une double configuration dans le fichier launch.json a créé dans le dossier de travail de notre projet. Ce fichier doit permettre d’indiquer à la fois à Visual Code d’écouter les appels de xdebug pour un débogage et de lancer un navigateur chrome avec les options de débogage à distance pour relancer l’affichage de la page en cas de modificaiton du programme php, d’une page html ou d’un fichier CSS ou Javascript.

Le fichier launch.json dont on modifiera les chemins de Chromium et l’URL d’appel en fonction des besoins du projet Web:

{
    // Utilisez IntelliSense pour en savoir plus sur les attributs possibles.
    // Pointez pour afficher la description des attributs existants.
    // Pour plus d'informations, visitez : https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [        
        {
            "type": "chrome",
            "request": "launch",
            "name": "Chromium",         
            "program": "C:/Users/pierre.jean/AppData/Local/Chromium/Application/chrome.exe",
            "runtimeArgs": [
               "--disable-web-security",
               "--auto-open-devtools-for-tabs",
               "--remote-debugging-port=9222",
               "http://127.0.0.1/${workspaceFolderBasename}/${relativeFile}?XDEBUG_SESSION_START"
             ],
            "webRoot": "${workspaceRoot}"
            },        
        {
            "name": "Xdebug",
            "type": "php",
            "request": "launch",
            "port": 9000,
        },
    ]
    ,"compounds": [
    {
      "name": "Xdebug & Chromium",
      "configurations": ["Chromium","Xdebug"],      
    }
  ]
}

L’appel au débogage doit être la première fois sur le choix « Xdebug & Chromium » pour lancer l’écoute sur l’URL http://127.0.0.1/dossier/index.php?XDEBUG_SESSION_START et ouvrir un navigateur Chromium avec les outils de développement Web ouvert.

Etape 3: Sauver un fichier relancer l’auto refresh de Chrome

Pour exploiter l’auto-refresh de Chrome/Chromium, j’ai pensé chercher l’équivalent des « builders » d’Eclipse, au final, la philosophie de Visual Code est d’utiliser une extension (https://marketplace.visualstudio.com/items?itemName=wk-j.save-and-run) dont l’objectif est de lancer automatiquement un programme debug.cmd qui est une variation de l’ancien programme.

L’extension Save And Run doit être programmé en modifiant le fichier settings.json se trouvant dans le dossier « C:\Users\pierre.jean\AppData\Roaming\Code\User\ » voici ma proposition de réglages pour déclencher le programme debug.cmd.


    "saveAndRun": {
      "commands": [
       {
          "match": "\\.(php|html|css|js)$",
          "cmd": "${workspaceRoot}/debug.cmd",
          "useShortcut": false,
          "silent": false
       }
      ]
    }    

Le fichier debug.cmd se trouvera dans le dossier de notre projet Web mais il pourrait être ailleurs. Ce fichier s’appuie encore une fois sur le programme websocat_win64.exe ( pour la communication par WebSocket), disponible sur cette page https://github.com/vi/websocat/releases/.

Bien sûr, devant la complexité de la programmation en batch/cmd je me suis posé la question de développer un programme complet capable de faire les appels WebSocket, capable de trier les objets json des réponses et pour simplifier l’installation selon les systèmes d’exploitations.

Etape 4: debug.cmd pour faire l’auto-refresh de la page

Le programme debug.cmd cherche dans la réponse à l’URL de débogage de Chrome http://127.0.0.1:9222/json un objet json contenant le mot « page » puis en cherchant l’URI pour le WebSocket avec le numéro unique qui permet de demander le rechargement de la page.

@echo off
 2 SETLOCAL ENABLEDELAYEDEXPANSION
 3 
 4 set count=1
 5 set pattern1="""type"": ""page"","
 6 set pattern2="webSocketDebuggerUrl"
 7 
 8 set findPage=0
 9 FOR /F "tokens=* USEBACKQ" %%F IN (`curl -s  http://127.0.0.1:9222/json`) DO (
10   REM find only page not web developper page with 'type' : 'page'
11   REM echo %%F | find %pattern1% 
12   for /f "tokens=2" %%I in ('echo %%F ^| find %pattern1%') DO (
13      if "%ERRORLEVEL%" == "0" (
14         set findPage=1
15      )
16   )
17 
18   REM find now webSocketDebuggerUrl
19   REM echo "!findPage!"
20   if !findPage!  == 1 (
21     REM find with 'webSocketDebuggerUrl' : 'ws://...'
22     for /f "tokens=2" %%L in ('echo %%F ^| find %pattern2%') DO (
23       if "%ERRORLEVEL%" == "0" (
24         set var=%%L
25       )
26     )
27   )
28 )
29 set str=%var:"devtoolsFrontendUrl":=%
30 set str=%str:,= %
31 set str=%str:"/devtools/inspector.html?=%
32 set str=%str: =%
33 set str=%str:"=%
34 set str=%str:ws=%
35 set str=!str:~1!
36 set str=ws://%str%
37 
38 echo {"id":0,"method":"Page.reload","params":{"ignoreCache":true}} | websocat_win64.exe -n1 %str% > NUL
39 
40 ENDLOCAL

J’ai essayé de rajouter une méthode pour trouver uniquement la page dont j’ai le nom passé en paramètre et gérer le cas de plusieurs onglets de Chrome. Malheureusement la performance est trop faible en batch/cmd. Il faudrait donc un vrai programme pour gérer cette situation et conserver en fait l’identifiant de l’onglet chrome à débogger une fois pour toute la session.

Conclusion

Donc xdebug est activé dans Wamp, Visual Code dispose d’une extension PHP Debug capable d’écouter les appels de xdebug. Visual Code a une configuration launch.json capable de lancer l’écoute et un navigateur qui va ouvrir une URL avec le paramètre XDEBUG_SESSION_START pour lancer xdebug.

Il manque que d’ajouter une extension Save and Run à Visual Code pour déclencher à la sauvegarde d’un fichier php du projet un auto-refresh de Chrome qui a été démarré avec les options de développement Web.

Le « Proof-of-concept » est réalisé grâce à cet enchainement. Mais pour commencer facilement, faite juste l’installation des 2 extensions, créer un sous-dossier dans le dossier www de Wamp, ajouter les fichiers dans ce dossier debug.cmd, launch.json et websocat_win64.exe et cela devrait commencer à fonctionner si votre fichier php.ini de Wamp a xdebug activé aussi.

Publié dans Développement | Laisser un commentaire