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 :
| Famille | Chemins | Usage |
|---|---|---|
| Endpoints | chemins 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) |
| SQL | POST /api/sql | SQL 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) | Argument | Défaut | Rôle |
|---|---|---|---|
rest | --rest ON|OFF | OFF | Ouvre ou non le point d'accès |
rest_port | --rest-port <port> | 7009 | Port 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.1 | Adresse d'écoute ; hors de la boucle locale, TLS obligatoire |
rest_endpoints | --rest-endpoints ON|OFF | ON | Famille endpoints ouverte sur le serveur |
rest_tables | --rest-tables ON|OFF | OFF | Famille tables ouverte sur le serveur |
rest_sql | --rest-sql ON|OFF | OFF | Famille SQL ouverte sur le serveur |
rest_basic | --rest-basic ON|OFF | ON | Connexion par compte et mot de passe (Authorization: Basic) acceptée |
rest_basic_access | --rest-basic-access <familles> | ENDPOINTS | Familles 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> | 60 | Connexion HTTP persistante fermée après ce délai sans requête |
rest_statement_timeout | --rest-statement-timeout <s> | 30 | Durée maximale d'une instruction (fraction permise ; 0 : sans limite) |
rest_max_rows | --rest-max-rows <n> | 1000 | Lignes rendues au plus par une réponse (1 à 1 000 000) |
rest_cors_origins | --rest-cors-origins <origines> | vide | Origines 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.pemDé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#
| Situation | Message ou effet |
|---|---|
| Point d'accès ouvert | MIRAJ <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ée | Note, serveur démarré, port REST fermé |
rest_port égal à port ou à mcp_port | Serveur arrêté |
rest_bind hors de la boucle locale sans certificat | Serveur 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 :
| Forme | En-tête | Quand l'utiliser |
|---|---|---|
| Jeton d'API | Authorization: Bearer mja_… | Applications et services : révocable, limité à des familles, des endpoints et des bases |
| Compte et mot de passe | Authorization: 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, ExpiresCREATE 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#
| Étape | Refus |
|---|---|
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 compte | 403 (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/apiet/mcpsont 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}etGET /clients/{code}), toutes bases confondues : erreur 9044. Un segment fixe l'emporte sur un paramètre (/clients/nouveauxavant/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 unVARCHAR(255). - Le corps lit un paramètre par
:nom, jamais par son nom nu :WHERE id = :idcompare la colonneidau paramètreid. - Valeurs prises, par nom, dans le chemin (
{id}), la requête (?ville=Oran) et, pourPOST,PUTetPATCH, 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)#
RETURNS | Ré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 |
ONE | 200, objet de la première ligne du dernier résultat ; 404 sans ligne |
NONE | 204 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 ROUTINEsur la base ; le supprimer :ALTER ROUTINE. - L'appeler :
EXECUTEsur 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écritGRANT EXECUTE ON ENDPOINT `gestion`.`fiche_client` TO …;information_schema.ENDPOINT_PRIVILEGESles 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ête | Effet |
|---|---|
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}?filtres | Modifie les lignes filtrées (un filtre au moins) |
DELETE /api/{base}/{table}/{clé} | Supprime la ligne ; 204, 404 sans elle |
DELETE /api/{base}/{table}?filtres | Supprime 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#
| Chemin | Rô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).
| Statut | Cas |
|---|---|
| 400 | JSON invalide, paramètre manquant ou inconnu (9046), SQL invalide (1064), colonne inconnue (1054), valeur hors type |
| 401 | Authentification absente ou refusée ; Basic en HTTP clair hors boucle locale |
| 403 | Adresse bloquée, famille ou endpoint non permis (9047), droits SQL (1044, 1142, 1143, 1370), gestion de jetons par jeton (9034) |
| 404 | Famille fermée sur le serveur, chemin inconnu, ligne absente (ONE, clé), base ou table inconnue |
| 405 | Méthode non servie sur ce chemin (Allow) |
| 409 | Doublon de clé (1062), clé étrangère (1451, 1452), conflit de verrou (1205, 1213) |
| 503 | max_connections atteint (1040) |
| 504 | Dé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
EXECUTEsur 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é parDROP API TOKEN. - Gardez
rest_tablesetrest_sqlfermé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_originsaux origines de vos applications.
Voir aussi#
- Chapitre 10 — Comptes et privilèges : privilèges par colonne,
EXECUTEpar endpoint. - Chapitre 20 — Serveur MCP : même transport HTTP, pour les assistants IA.
- Chapitre 15 — Codes d'erreur : 1143, 9044 à 9047.