Serveur OpenAPI pour Delphi : TsgcWSAPIServer_OpenAPI

· Composants

L'édition Enterprise de sgcWebSockets propose un nouveau composant, TsgcWSAPIServer_OpenAPI, qui transforme une description OpenAPI 3 en serveur REST opérationnel dans votre application Delphi. Déposez-le sur une fiche, reliez-le à un serveur HTTP, fournissez-lui une spécification — et les routes, la validation des requêtes, les réponses d'erreur et la documentation Swagger UI en direct sont câblées pour vous. Cet article explique comment fonctionne le composant, les deux façons de le piloter (spec-first et code-first), les options de configuration qui comptent, ainsi qu'un exemple Delphi complet que vous pouvez coller directement dans un nouveau projet.

Ce que fait le composant

TsgcWSAPIServer_OpenAPI est un serveur d'API léger qui se branche sur TsgcWebSocketHTTPServer via le point d'extension API-server existant. Vous l'attachez à un serveur, vous chargez une spécification OpenAPI 3.0, et il effectue quatre opérations à chaque requête HTTP entrante :

Résultat : la spécification devient l'unique source de vérité. Changez un chemin, un paramètre ou un code de réponse dans le JSON, redémarrez, et le serveur récupère le nouveau contrat sans recompiler le code Delphi.

Spec-first : charger un fichier OpenAPI 3 existant

Si vous disposez déjà d'un document OpenAPI 3 (par exemple un petstore.json exporté depuis un concepteur d'API), le câblage tient essentiellement en trois lignes — créer le composant, charger la spécification, attacher un serveur. Tout le reste relève de la configuration et du gestionnaire OnRequest qui produit les réponses réelles.

uses
  sgcWebSocket, sgcWebSocket_Classes,
  sgcWebSocket_Server_API_OpenAPI,
  sgcHTTP_OpenAPI_Server;

var
  WSServer: TsgcWebSocketHTTPServer;
  FOpenAPI: TsgcWSAPIServer_OpenAPI;
begin
  WSServer := TsgcWebSocketHTTPServer.Create(nil);
  WSServer.Port := 8080;

  FOpenAPI := TsgcWSAPIServer_OpenAPI.Create(nil);
  FOpenAPI.OnRequest := OnOpenAPIRequest;
  FOpenAPI.OnBeforeRequest := OnOpenAPIBeforeRequest;
  FOpenAPI.OnValidationError := OnOpenAPIValidationError;

  // Configuration
  FOpenAPI.OpenAPIOptions.Endpoint.ServeSpec := True;
  FOpenAPI.OpenAPIOptions.Endpoint.ServeSwaggerUI := True;
  FOpenAPI.OpenAPIOptions.CORS.Enabled := True;
  FOpenAPI.OpenAPIOptions.Validation.ValidateRequest := True;
  FOpenAPI.OpenAPIOptions.Validation.ValidateRequired := True;
  FOpenAPI.OpenAPIOptions.Validation.ValidateRequestBody := True;

  // Load spec and attach to server
  FOpenAPI.LoadFromFile('petstore.json');
  FOpenAPI.Server := WSServer;

  WSServer.Active := True;
  // Swagger UI:   http://localhost:8080/docs
  // Raw spec:     http://localhost:8080/openapi.json
end;

L'événement OnRequest est distribué par operationId — la chaîne que vous avez écrite à côté de chaque opération dans le YAML/JSON. Vous écrivez une branche par opération, vous lisez les entrées depuis le contexte et vous émettez une réponse :

procedure TForm1.OnOpenAPIRequest(Sender: TObject;
  const aOperationId: string; const aContext: TsgcOpenAPIServerContext;
  var Handled: Boolean);
var
  vId: Int64;
  vLimit: Integer;
begin
  Handled := True;
  if aOperationId = 'listPets' then
  begin
    vLimit := aContext.QueryParamAsInteger('limit', 100);
    aContext.RespondJSON(200, BuildPetsJSON(vLimit));
  end
  else if aOperationId = 'getPetById' then
  begin
    vId := aContext.PathParamAsInteger('petId');
    if FindPet(vId) then
      aContext.RespondJSON(200, PetAsJSON(vId))
    else
      aContext.RespondError(404, 'Not Found',
                            Format('Pet %d not found', [vId]));
  end
  else
    Handled := False;
end;

L'objet TsgcOpenAPIServerContext est la pièce maîtresse de chaque gestionnaire. Il expose les paramètres de chemin et de requête par leur nom (PathParamAsString, PathParamAsInteger, QueryParamAsString, QueryParamAsInteger, QueryParamAsBoolean), la consultation des en-têtes via HeaderValue, le corps sous forme de chaîne (BodyAsString) ou de JSON pré-analysé (BodyAsJSON), ainsi que deux aides à la réponse : RespondJSON(code, content) pour une charge utile classique et RespondError(code, title, detail), qui produit un corps application/problem+json conforme à la RFC 7807 pour que les clients obtiennent toujours une forme d'erreur cohérente.

Code-first : générer la spécification à partir d'attributs Delphi

Le second mode est le flux inverse : vous déclarez votre API sous forme de classe Delphi décorée d'attributs, vous demandez au scanner d'émettre le document OpenAPI à l'exécution, et vous réinjectez ce document dans le même composant. Il n'y a rien à écrire à la main en JSON.

uses
  sgcHTTP_OpenAPI_Server_CodeFirst;

type
  [sgcServiceContract('Task Manager API',
                      'A simple task manager', '1.0.0')]
  [sgcRoute('/api/v1')]
  TTaskService = class
  public
    [sgcHttpGet]
    [sgcRoute('/tasks')]
    [sgcSummary('List all tasks')]
    [sgcResponse(200, 'A list of tasks')]
    procedure ListTasks([sgcFromQuery] const status: string); virtual;

    [sgcHttpGet]
    [sgcRoute('/tasks/{taskId}')]
    [sgcSummary('Get a task by ID')]
    [sgcResponse(200, 'The requested task')]
    [sgcResponse(404, 'Task not found')]
    procedure GetTask([sgcFromPath][sgcRequired]
                      const taskId: Integer); virtual;

    [sgcHttpPost]
    [sgcRoute('/tasks')]
    [sgcSummary('Create a new task')]
    [sgcResponse(201, 'Task created')]
    procedure CreateTask([sgcFromBody] const body: string); virtual;
  end;

La classe elle-même n'a pas besoin de vrais corps de méthodes — le scanner la lit via la RTTI, et la logique réelle reste dans votre gestionnaire OnRequest. Générer la spécification et démarrer le serveur tient en quelques lignes :

var
  oScanner: TsgcOpenAPICodeFirstScanner;
  vSpec: string;
begin
  oScanner := TsgcOpenAPICodeFirstScanner.Create;
  try
    vSpec := oScanner.GenerateSpec(TTaskService);
  finally
    oScanner.Free;
  end;

  FOpenAPI.LoadFromString(vSpec);
  FOpenAPI.Server := WSServer;
  WSServer.Active := True;
end;

L'ensemble d'attributs couvre les cas courants : routage (sgcHttpGet, sgcHttpPost, sgcHttpPut, sgcHttpDelete, sgcHttpPatch, sgcRoute), liaison de paramètres (sgcFromPath, sgcFromQuery, sgcFromHeader, sgcFromBody), validation (sgcRequired, sgcMinLength, sgcMaxLength, sgcRange, sgcPattern) et documentation (sgcSummary, sgcDescription, sgcTag, sgcResponse).

Configuration détaillée

OpenAPIOptions est regroupé en trois sous-objets persistants pour que vous puissiez tous les voir dans l'Inspecteur d'objets :

Au-delà de OnRequest, quatre autres événements permettent de vous greffer sur le pipeline : OnBeforeRequest (positionnez Accept := False pour court-circuiter, utile pour la limitation de débit ou la journalisation), OnAfterRequest (post-traitement une fois la réponse produite), OnAuthenticate (positionnez Authenticated := True après avoir vérifié un jeton ou une session) et OnException (filet de sécurité global qui vous laisse modifier le statut HTTP avant que le framework n'écrive le corps d'erreur).

Swagger UI prêt à l'emploi

Avec ServeSwaggerUI := True, le serveur publie une page Swagger UI sur BasePath + '/docs' qui charge la spécification depuis BasePath + '/openapi.json'. Ouvrez l'URL dans un navigateur et vous obtenez l'expérience standard « try-it-out », alimentée par votre propre serveur en cours d'exécution — pas de build de documentation séparé, pas d'export statique. Combiné à CORS, c'est le moyen le plus rapide de remettre un backend à une équipe frontend ou à un partenaire qui s'intègre à votre API.

Pour l'obtenir

Le composant fait partie de l'édition Enterprise de sgcWebSockets, enregistré sur la page de palette SGC OpenAPI. Deux démos complètes — l'une en spec-first à partir d'un JSON Petstore, l'autre en code-first à partir d'un service de gestion de tâches — sont livrées dans Demos\23.OpenAPI. Téléchargez la dernière version depuis la page de téléchargement de sgcWebSockets.

Des questions, des retours ou besoin d'aide pour intégrer le composant dans un projet existant ? Contactez-nous — vous recevrez une réponse des personnes qui ont écrit le code.