Skip to Content
Mélodium 0.10.3 is now available!
DocsExemples06. API serveur HTTP

API serveur HTTP

Source: tutorial/06_http_server_api See in Playground

Un petit serveur HTTP avec trois routes : un point de terminaison de statut fixe, une route qui lit des métadonnées de requête depuis le contexte @HttpRequest, et une route qui analyse un corps JSON et répond avec un objet JSON construit à partir de celui-ci.

Exécution

cd tutorial/06_http_server_api melodium run Compo.toml --port 8080
curl http://127.0.0.1:8080/health curl http://127.0.0.1:8080/whoami curl -X POST http://127.0.0.1:8080/greet -d '{"name":"Sláine"}'
{"status":"ok"} {"path":"/whoami","route":"/whoami"} {"message":"thanks for the greeting!","received":"{\"name\":\"Sláine\"}"}

Optionnel : ajoutez --api-report et un jeton d’API (MELODIUM_API_TOKEN) pour voir la trace complète de cette exécution sur Cadence.CI.

Fonctionnement

Un seul modèle HttpServer est partagé par les trois routes. start ouvre le socket une seule fois au démarrage, et connection est instanciée une fois par route, créant une nouvelle piste, avec le contexte @HttpRequest disponible, pour chaque requête entrante correspondante :

treatment main(const port: u16 = 8080) model server: HttpServer(host=|from_ipv4(|localhost_ipv4()), port=port) { startup() start[http_server=server]() logReady: logInfoMessage(label="server", message="HTTP server ready") startup.trigger -> start.trigger startup.trigger -> logReady.trigger health[http_server=server]() whoami[http_server=server]() greet[http_server=server]() }

health, whoami et greet sont des traitements indépendants, chacun possédant sa propre instance de connection ; main se contente d’instancier les trois contre le même modèle server. Le flux de données a la même forme pour chaque route :

Déclencher une réponse depuis connection.started

GET /health renvoie un statut JSON fixe :

treatment health[http_server: HttpServer]() { connection[http_server=http_server](method=|get(), route="/health") status: emit<HttpStatus>(value=|ok()) headers: emit<StringMap>(value=|map([])) connection.started -> status.trigger,emit -> connection.status connection.started -> headers.trigger,emit -> connection.headers reply: emit<string>(value="{\"status\":\"ok\"}") asBytes: stream<string>() encoded: encode() connection.started -> reply.trigger,emit -> asBytes.block,stream -> encoded.text,data -> connection.data }

La réponse est déclenchée par connection.started, un Block<void> qui se déclenche dès que la connexion est acceptée, plutôt que par un déclencheur dérivé de connection.data (le corps entrant). Une requête GET n’a pas de corps, donc aucun flux ne démarre jamais sur connection.data, et tout ce qui est conditionné par le “premier octet du corps” ne se déclencherait tout simplement jamais, laissant la route bloquée. C’est le point à ne jamais rater sur chaque route de ce type de code : /greet (une requête POST avec un vrai corps) fonctionnerait dans les deux cas, ce qui est justement le piège, puisqu’un déclencheur dérivé du corps paraît correct jusqu’à ce qu’il soit testé sur une route sans corps.

Lire les métadonnées de requête avec @HttpRequest

GET /whoami lit directement @HttpRequest[route] et @HttpRequest[path], sans toucher au corps de la requête :

treatment whoami[http_server: HttpServer]() { connection[http_server=http_server](method=|get(), route="/whoami") status: emit<HttpStatus>(value=|ok()) headers: emit<StringMap>(value=|map([])) connection.started -> status.trigger,emit -> connection.status connection.started -> headers.trigger,emit -> connection.headers describe() connection.started -> describe.trigger,body -> connection.data } treatment describe() require @HttpRequest input trigger: Block<void> output body: Stream<byte> { info: emit<StringMap>(value=|insert(|entry("route", @HttpRequest[route]), "path", @HttpRequest[path])) asJson: fromStringMap() asText: toString<Json>() asStream: stream<StringMap>() encoded: encode() Self.trigger -> info.trigger,emit -> asStream.block,stream -> asJson.value,json -> asText.value,into -> encoded.text,data -> Self.body }

describe déclare require @HttpRequest : Mélodium n’autorise donc son utilisation qu’à l’intérieur d’une piste qui fournit réellement ce contexte, ce que connection garantit.

Renvoyer un corps JSON analysé

POST /greet analyse le corps JSON et reconstruit une réponse avec entry / insert et fromStringMap :

treatment greet[http_server: HttpServer]() { connection[http_server=http_server](method=|post(), route="/greet") status: emit<HttpStatus>(value=|ok()) headers: emit<StringMap>(value=|map([])) connection.started -> status.trigger,emit -> connection.status connection.started -> headers.trigger,emit -> connection.headers respondGreeting() connection.data -> respondGreeting.data,data -> connection.data }

Il n’existe pas d’accès champ par champ à une valeur Json analysée dans le paquet json lui-même : pour extraire "Sláine" de {"name": "Sláine"}, il faut le moteur JavaScript, couvert dans l’étape suivante du tutoriel. Ici, tout le corps est renvoyé tel quel comme une seule valeur de chaîne JSON, sans y accéder champ par champ.

Dépendances

[dependencies] std = "0.10.3" # flux de base, journalisation, structures de données http = "0.10.3" # client et serveur HTTP net = "0.10.3" # utilitaires d'adresses IP json = "0.10.3" # parsing et sérialisation JSON encoding = "0.10.3" # encodage / décodage UTF-8