Mirajv1.0
FR

23. API REST : MIRAJ sur HTTP et HTTPS

Le point d'accès REST de miraj-server ouvre les bases à toute application qui parle HTTP : application web ou mobile, script, outil d'automatisation, autre service. Il ne demande ni pilote ni bibliothèque cliente : des requêtes HTTP et du JSON.

Il sert trois familles d'accès, ouvertes séparément sur le serveur et pour chaque client :

FamilleCheminsUsage
Endpointschemins que vous définissez (GET /clients/{id})Des API écrites en SQL (CREATE ENDPOINT) : l'application n'appelle que ce que vous avez prévu, avec des paramètres typés
Tables/api/{base}/{table}[/{clé}]Lire et écrire les lignes d'une table (filtres, tri, pagination)
SQLPOST /api/sqlSQL libre, pour un outil interne de confiance
  Application (web, mobile, script)
         │   HTTP ou HTTPS : GET https://hôte:7009/clients/42
         │   Authorization: Bearer mja_…   (ou Basic compte:mot de passe)
         ▼
  ┌─────────────────────────────── miraj-server ───────────────────────────────┐
  │  port 7007 : protocole réseau de MIRAJ                                     │
  │  port 7009 : point d'accès REST (--rest ON)                                │
  │     famille ouverte sur le serveur ? ─► client authentifié ?               │
  │     famille permise au jeton ? ─► droits SQL du compte (table, colonne,    │
  │     EXECUTE sur l'endpoint) ─► instruction SQL ─► verrous, déclencheurs,   │
  │     clés étrangères, journal, transactions, réplication                    │
  └────────────────────────────────────────────────────────────────────────────┘

Chaque requête ouvre une session MIRAJ sous le compte du client, exécute une instruction SQL ordinaire, puis ferme la session : rien n'est jamais écrit directement dans les fichiers de données, et les droits SQL du compte s'appliquent toujours, jusqu'à la colonne (chapitre 10).

23.1 Éditions concernées#

Le point d'accès REST existe dans toutes les éditions, Express comprise.

23.2 Activation et configuration#

23.2.1 Variables#

Comme le port principal : valeur par défaut, puis miraj_config.xml, puis ligne de commande (la ligne de commande l'emporte, voir 11.2). Désactivé par défaut.

Variable (miraj_config.xml)ArgumentDéfautRôle
rest--rest ON|OFFOFFOuvre ou non le point d'accès
rest_port--rest-port <port>7009Port TCP, distinct de port et de mcp_port ; 0 : choisi par le système, affiché au démarrage
rest_bind--rest-bind <adresse>127.0.0.1Adresse d'écoute ; hors de la boucle locale, TLS obligatoire
rest_endpoints--rest-endpoints ON|OFFONFamille endpoints ouverte sur le serveur
rest_tables--rest-tables ON|OFFOFFFamille tables ouverte sur le serveur
rest_sql--rest-sql ON|OFFOFFFamille SQL ouverte sur le serveur
rest_basic--rest-basic ON|OFFONConnexion par compte et mot de passe (Authorization: Basic) acceptée
rest_basic_access--rest-basic-access <familles>ENDPOINTSFamilles permises aux connexions par mot de passe : ALL, NONE, ou ENDPOINTS, TABLES, SQL séparés par des virgules
rest_idle_timeout--rest-idle-timeout <s>60Connexion HTTP persistante fermée après ce délai sans requête
rest_statement_timeout--rest-statement-timeout <s>30Durée maximale d'une instruction (fraction permise ; 0 : sans limite)
rest_max_rows--rest-max-rows <n>1000Lignes rendues au plus par une réponse (1 à 1 000 000)
rest_cors_origins--rest-cors-origins <origines>videOrigines admises par CORS : vide (aucune), *, ou https://app.exemple.com,https://…

Une famille fermée sur le serveur n'existe pas pour les clients : ses chemins répondent 404, même à un jeton ACCESS ALL.

23.2.2 Exemples#

<miraj>
    <rest>ON</rest>
    <rest_bind>0.0.0.0</rest_bind>
    <tls_cert>C:\miraj\cert.pem</tls_cert>
    <tls_key>C:\miraj\key.pem</tls_key>
    <rest_cors_origins>https://app.exemple.com</rest_cors_origins>
</miraj>
miraj-server.exe --root D:\miraj\data --log --rest ON
miraj-server.exe --root D:\miraj\data --log --rest ON --rest-tables ON --rest-sql ON --rest-port 8443 --rest-bind 0.0.0.0 --tls-cert cert.pem --tls-key key.pem

Démarrez toujours le serveur avec --log : chaque requête est alors inscrite (REST GET /clients/42 -> 200, jeton mobile, compte app@%, 3 ms, depuis 10.0.0.5).

23.2.3 Démarrage et erreurs de configuration#

SituationMessage ou effet
Point d'accès ouvertMIRAJ <version> <édition> - point d'accès REST sur 127.0.0.1:7009, accès ENDPOINTS. (, TLS avec un certificat)
rest = OFF et une variable rest_* donnéeNote, serveur démarré, port REST fermé
rest_port égal à port ou à mcp_portServeur arrêté
rest_bind hors de la boucle locale sans certificatServeur arrêté : donnez --tls-cert et --tls-key (les mêmes que le port principal)

Avec un certificat, toutes les connexions du point d'accès sont chiffrées (HTTPS).

23.3 Authentification#

Chaque requête (sauf GET /api/health) présente l'une des deux formes :

FormeEn-têteQuand l'utiliser
Jeton d'APIAuthorization: Bearer mja_…Applications et services : révocable, limité à des familles, des endpoints et des bases
Compte et mot de passeAuthorization: Basic base64(compte:mot de passe)Outils d'administration, scripts ponctuels. Refusé en HTTP clair hors de la boucle locale (401 « Basic authentication requires HTTPS ») ; familles limitées par rest_basic_access

Les échecs d'authentification sont comptés par adresse, comme sur le port principal : retard croissant, puis blocage de l'adresse à max_connect_errors (FLUSH HOSTS la débloque).

23.3.1 Jetons d'API#

CREATE API TOKEN nom [FOR compte]
    [ACCESS { ALL | famille [, famille …] }]     -- défaut : ACCESS ENDPOINTS
    [DATABASES (base, …)]
    [EXPIRE { NEVER | INTERVAL n DAY }];
--  famille := ENDPOINTS [([base.]nom, …)] | TABLES | SQL

ALTER API TOKEN nom ACCESS …;        -- familles remplacées, secret inchangé, dès la requête suivante
DROP API TOKEN [IF EXISTS] nom;
SHOW API TOKENS;                     -- Name, User, Host, Access, Databases, Created, Expires

CREATE API TOKEN rend le secret du jeton (mja_…) une seule fois : seule son empreinte est gardée, dans le coffre des comptes (répliqué dans un cluster). Exemples :

-- Application mobile : seulement les API prévues pour elle
CREATE API TOKEN mobile FOR 'app'@'%' ACCESS ENDPOINTS;
-- Partenaire : deux endpoints précis, 90 jours
CREATE API TOKEN partenaire FOR 'partenaire'@'%' ACCESS ENDPOINTS (ventes.catalogue, ventes.stock) EXPIRE INTERVAL 90 DAY;
-- Outil interne : tables et SQL libre sur une base
CREATE API TOKEN outil FOR 'dev'@'%' ACCESS TABLES, SQL DATABASES (recette);

Un jeton ne donne aucun droit : il restreint ceux du compte. Sa famille permise, puis les privilèges SQL du compte (EXECUTE sur l'endpoint, SELECT sur la table ou la colonne…) sont contrôlés tous les deux. Une session ouverte par jeton ne gère jamais de jetons (9034).

Créer un jeton pour son propre compte est libre ; pour un autre compte, il faut CREATE USER.

23.3.2 Ordre des contrôles#

ÉtapeRefus
Famille ouverte sur le serveur (rest_endpoints, rest_tables, rest_sql)404
Client authentifié (jeton valide, non expiré ; mot de passe juste ; compte non verrouillé)401
Famille permise au jeton (ACCESS) ou aux connexions par mot de passe (rest_basic_access)403, erreur 9047
Endpoint permis au jeton (ACCESS ENDPOINTS (…))403, erreur 9047
Droits SQL du compte403 (1044, 1142, 1143, 1370…)

23.4 Endpoints : des API écrites en SQL#

Un endpoint associe une méthode et un chemin HTTP à un corps SQL (une instruction, ou un bloc BEGIN … END comme une procédure). C'est la façon recommandée d'ouvrir des données : l'application n'a accès qu'aux opérations prévues, avec des paramètres typés, et peut ne détenir que EXECUTE sur ses endpoints.

CREATE [OR REPLACE] [DEFINER = compte] ENDPOINT [IF NOT EXISTS] [base.]nom
    { GET | POST | PUT | PATCH | DELETE } '/chemin/{param}/…'
    [PARAMS (nom type [DEFAULT valeur], …)]
    [RETURNS { ROWS | ONE | NONE | AFFECTED }]
    [COMMENT 'texte'] [SQL SECURITY { DEFINER | INVOKER }]
    AS instruction | BEGIN … END;

DROP ENDPOINT [IF EXISTS] [base.]nom;
SHOW ENDPOINTS [FROM base] [LIKE 'motif'];
SHOW CREATE ENDPOINT [base.]nom;

23.4.1 Exemple complet#

USE gestion;

-- Fiche d'un client : une ligne, 404 s'il n'existe pas
CREATE ENDPOINT fiche_client GET '/clients/{id}' PARAMS (id INT) RETURNS ONE
AS SELECT id, nom, ville FROM clients WHERE id = :id;

-- Liste filtrée, paramètre facultatif
CREATE ENDPOINT liste_clients GET '/clients' PARAMS (ville VARCHAR(40) DEFAULT NULL, limite INT DEFAULT 50)
AS SELECT id, nom, ville FROM clients WHERE :ville IS NULL OR ville = :ville ORDER BY nom LIMIT :limite;

-- Création : 201 et lignes insérées
CREATE ENDPOINT nouveau_client POST '/clients' PARAMS (nom VARCHAR(80), ville VARCHAR(40)) RETURNS AFFECTED
AS INSERT INTO clients (nom, ville) VALUES (:nom, :ville);

-- Traitement en plusieurs instructions
CREATE ENDPOINT solder_facture POST '/factures/{id}/solder' PARAMS (id INT) RETURNS ONE
AS BEGIN
    DECLARE reste DECIMAL(12,2);
    SELECT montant - paye INTO reste FROM factures WHERE id = :id FOR UPDATE;
    UPDATE factures SET paye = montant WHERE id = :id;
    INSERT INTO reglements (facture, montant) VALUES (:id, reste);
    SELECT :id AS facture, reste AS regle;
END;

-- L'application n'a que le droit d'appeler ces endpoints
CREATE USER 'app'@'%' IDENTIFIED BY '…';
GRANT EXECUTE ON ENDPOINT gestion.fiche_client TO 'app'@'%';
GRANT EXECUTE ON ENDPOINT gestion.liste_clients TO 'app'@'%';
GRANT EXECUTE ON ENDPOINT gestion.nouveau_client TO 'app'@'%';
CREATE API TOKEN mobile FOR 'app'@'%';
GET  /clients/42                     → 200 {"id": 42, "nom": "Ali", "ville": "Oran"}
GET  /clients?ville=Oran&limite=10   → 200 [{"id": 42, …}, …]
POST /clients   {"nom": "Lina", "ville": "Alger"}   → 201 {"affected_rows": 1, "last_insert_id": 43}
POST /factures/7/solder              → 200 {"facture": 7, "regle": 120.50}

23.4.2 Chemin et méthode#

  • Le chemin commence par / ; ses segments sont fixes (lettres, chiffres, -, _, ., ~) ou des paramètres {nom}. Les chemins sous /api et /mcp sont réservés. Erreur 9045 sinon.
  • Deux endpoints ne peuvent pas répondre à la même méthode sur un chemin de même forme (GET /clients/{id} et GET /clients/{code}), toutes bases confondues : erreur 9044. Un segment fixe l'emporte sur un paramètre (/clients/nouveaux avant /clients/{id}).
  • Chemin connu appelé avec une autre méthode : 405 avec l'en-tête Allow.

23.4.3 Paramètres#

  • Déclarés dans PARAMS (nom type [DEFAULT valeur]) ; un paramètre du chemin non déclaré est un VARCHAR(255).
  • Le corps lit un paramètre par :nom, jamais par son nom nu : WHERE id = :id compare la colonne id au paramètre id.
  • Valeurs prises, par nom, dans le chemin ({id}), la requête (?ville=Oran) et, pour POST, PUT et PATCH, les clés d'un corps JSON objet. Celles du chemin et de la requête sont des chaînes converties au type déclaré ; celles du corps gardent leur type JSON (null, nombre, booléen, chaîne ; octets {"hex": "…"} ou {"base64": "…"} ; géométrie {"wkt": "POINT(1 2)"}).
  • Paramètre sans valeur ni DEFAULT, ou valeur pour un paramètre inconnu : 400, erreur 9046. Un même nom donné deux fois (chemin et requête, par exemple) : 400.

23.4.4 Réponse (RETURNS)#

RETURNSRéponse
ROWS (défaut)200, tableau d'objets du dernier résultat du corps ([] sans résultat) ; au-delà de rest_max_rows, lignes tronquées et en-tête X-Miraj-Truncated: true
ONE200, objet de la première ligne du dernier résultat ; 404 sans ligne
NONE204 sans corps
AFFECTED{"affected_rows": n, "last_insert_id": id} ; 201 pour POST, 200 sinon

Les valeurs sont écrites en JSON comme par le point d'accès MCP : décimaux exacts, entiers au-delà de 2^53 en chaîne, dates et heures en texte SQL, octets en chaîne hexadécimale "0x…", géométries en objet {"wkt": "POINT(1 2)", "srid": 0}.

23.4.5 Droits#

  • Créer un endpoint : CREATE ROUTINE sur la base ; le supprimer : ALTER ROUTINE.
  • L'appeler : EXECUTE sur l'endpoint (GRANT EXECUTE ON ENDPOINT base.nom TO compte) ou sur sa base (GRANT EXECUTE ON base.* …, qui ouvre tous ses endpoints) ; sinon 403, erreur 1370.
  • SQL SECURITY DEFINER (défaut) : le corps s'exécute avec les droits de son définisseur — l'appelant n'a besoin d'aucun droit sur les tables. SQL SECURITY INVOKER : avec ceux de l'appelant, colonnes comprises (1143 pour une colonne refusée).
  • SHOW GRANTS écrit GRANT EXECUTE ON ENDPOINT `gestion`.`fiche_client` TO … ; information_schema.ENDPOINT_PRIVILEGES les liste.

Les endpoints ne sont ni des procédures ni des fonctions : CALL ne les trouve pas, information_schema.ROUTINES ne les montre pas ; ils figurent dans SHOW ENDPOINTS et information_schema.ENDPOINTS. Ils sont sauvegardés avec la base (BACKUP, miraj-dump) et répliqués dans un cluster comme les procédures.

23.5 Tables : /api/{base}/{table}#

Famille ouverte par rest_tables = ON et le jeton (ACCESS TABLES). Chaque requête devient une instruction SQL paramétrée sous les droits du compte.

RequêteEffet
GET /api/{base}/{table}Lignes (100 par défaut, au plus rest_max_rows)
GET /api/{base}/{table}/{clé}La ligne de clé primaire clé (404 sans elle) ; clé de plusieurs colonnes : valeurs séparées par des virgules, dans l'ordre des colonnes de la table
POST /api/{base}/{table}Insère un objet JSON, ou un tableau d'objets aux mêmes clés ; 201
PATCH /api/{base}/{table}/{clé}Modifie les colonnes du corps ; 200 et affected_rows
PATCH /api/{base}/{table}?filtresModifie les lignes filtrées (un filtre au moins)
DELETE /api/{base}/{table}/{clé}Supprime la ligne ; 204, 404 sans elle
DELETE /api/{base}/{table}?filtresSupprime les lignes filtrées (un filtre au moins)

Paramètres de lecture : select=a,b ; order=a.desc,b ; limit ; offset ; filtres colonne[.op]=valeur, op parmi eq (défaut), ne, gt, ge, lt, le, like, in (valeurs séparées par des virgules), null (true ou false). Exemple : GET /api/gestion/clients?ville=Oran&nom.like=A%25&order=nom&limit=20.

Sans select, la réponse contient les colonnes que le compte peut lire : toutes s'il a SELECT sur la table, sinon seulement celles qui lui sont accordées. Demander, filtrer, trier ou écrire une colonne refusée : 403, erreur 1143.

23.6 SQL libre : POST /api/sql#

Famille ouverte par rest_sql = ON et le jeton (ACCESS SQL).

{ "sql": "SELECT id, nom FROM gestion.clients WHERE ville = :ville", "params": { "ville": "Oran" }, "max_rows": 100 }

params : tableau pour les ?, objet pour les :nom. Réponse :

{ "results": [ { "columns": [{"name": "id", "type": "int"}, {"name": "nom", "type": "varchar(40)"}],
                 "rows": [[42, "Ali"]], "row_count": 1, "truncated": false,
                 "affected_rows": null, "last_insert_id": null } ],
  "warnings": [] }

Plusieurs instructions séparées par ; donnent plusieurs résultats. En erreur, la réponse porte error et les résultats des instructions déjà exécutées. Chaque requête est une session neuve : une transaction ne s'étend pas d'une requête à l'autre (utilisez un bloc d'instructions, ou un endpoint).

23.7 Autres chemins#

CheminRôle
GET /api/health{"status": "ok", "version": "…", "edition": "…"}, sans authentification (sonde de disponibilité)
OPTIONS …Réponse préalable CORS pour une origine de rest_cors_origins

23.8 Erreurs#

Corps : {"error": {"code": 1143, "sqlstate": "42000", "message": "…"}} (code et sqlstate à null pour une erreur HTTP sans code SQL).

StatutCas
400JSON invalide, paramètre manquant ou inconnu (9046), SQL invalide (1064), colonne inconnue (1054), valeur hors type
401Authentification absente ou refusée ; Basic en HTTP clair hors boucle locale
403Adresse bloquée, famille ou endpoint non permis (9047), droits SQL (1044, 1142, 1143, 1370), gestion de jetons par jeton (9034)
404Famille fermée sur le serveur, chemin inconnu, ligne absente (ONE, clé), base ou table inconnue
405Méthode non servie sur ce chemin (Allow)
409Doublon de clé (1062), clé étrangère (1451, 1452), conflit de verrou (1205, 1213)
503max_connections atteint (1040)
504Délai de l'instruction dépassé (1969) ou instruction interrompue (1317)

23.9 Bonnes pratiques#

  • Préférez les endpoints : l'application ne détient que EXECUTE sur ses API, le SQL reste sur le serveur, et un changement de schéma ne touche que le corps des endpoints.
  • Un compte par application, un jeton par déploiement (ACCESS ENDPOINTS (…) au plus juste, EXPIRE), révoqué par DROP API TOKEN.
  • Gardez rest_tables et rest_sql fermés en production, ou réservés à des jetons d'outils internes limités à une base.
  • Hors de la boucle locale, le point d'accès exige TLS ; placez-le derrière un pare-feu et limitez rest_cors_origins aux origines de vos applications.

Voir aussi#