OpenAPI-server voor Delphi: TsgcWSAPIServer_OpenAPI

· Componenten

De Enterprise-editie van sgcWebSockets bevat een nieuwe component, TsgcWSAPIServer_OpenAPI, die een OpenAPI 3-beschrijving omzet in een draaiende REST-server binnen uw Delphi-toepassing. Plaats hem op een formulier, koppel hem aan een HTTP-server, geef hem een spec — en de routes, requestvalidatie, foutreacties en live Swagger UI-documentatie worden voor u bedraad. Dit artikel laat zien hoe de component werkt, op welke twee manieren u hem kunt aansturen (spec-first en code-first), welke configuratieknoppen ertoe doen, en geeft een compleet Delphi-voorbeeld dat u direct in een nieuw project kunt plakken.

Wat de component doet

TsgcWSAPIServer_OpenAPI is een lichte API-server die via het bestaande uitbreidingspunt voor API-servers in TsgcWebSocketHTTPServer wordt aangesloten. U koppelt hem aan een server, laadt een OpenAPI 3.0-specificatie, en hij doet bij elk binnenkomend HTTP-verzoek vier dingen:

Het resultaat is dat de spec de enige bron van waarheid wordt: wijzig een pad, een parameter of een responscode in de JSON, herstart, en de server pikt het nieuwe contract op zonder dat u Delphi-code hoeft te hercompileren.

Spec-first: een bestaand OpenAPI 3-bestand laden

Als u al een OpenAPI 3-document hebt (bijvoorbeeld een petstore.json die u vanuit een API-designer hebt geëxporteerd) komt de bedrading neer op drie regels — component aanmaken, spec laden, server koppelen. De rest is configuratie en de OnRequest-handler die de werkelijke responses produceert.

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;

Het OnRequest-event wordt per operationId verstuurd — de string die u naast elke operatie in de YAML/JSON hebt gezet. U schrijft één branch per operatie, leest de invoer uit de context en stuurt een respons terug:

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;

Het TsgcOpenAPIServerContext-object is het werkpaard van elke handler. Het stelt pad- en queryparameters op naam beschikbaar (PathParamAsString, PathParamAsInteger, QueryParamAsString, QueryParamAsInteger, QueryParamAsBoolean), headeropzoeking via HeaderValue, de body als string (BodyAsString) of voorgeparste JSON (BodyAsJSON), plus twee response-helpers: RespondJSON(code, content) voor een normale payload en RespondError(code, title, detail), die een application/problem+json-body in RFC 7807-stijl produceert zodat clients altijd een consistente foutvorm krijgen.

Code-first: de spec genereren uit Delphi-attributen

De tweede modus is de omgekeerde flow: u declareert uw API als een Delphi-klasse die met attributen is voorzien, vraagt de scanner om het OpenAPI-document tijdens runtime te genereren, en voert dat document terug in dezelfde component. Er is niets met de hand in JSON te schrijven.

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;

De klasse zelf hoeft geen echte bodies te hebben — de scanner leest haar via RTTI uit, de eigenlijke logica blijft in uw OnRequest-handler. De spec genereren en de server starten kost enkele regels:

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;

De attributenset dekt de meest voorkomende gevallen: routing (sgcHttpGet, sgcHttpPost, sgcHttpPut, sgcHttpDelete, sgcHttpPatch, sgcRoute), parameterbinding (sgcFromPath, sgcFromQuery, sgcFromHeader, sgcFromBody), validatie (sgcRequired, sgcMinLength, sgcMaxLength, sgcRange, sgcPattern) en documentatie (sgcSummary, sgcDescription, sgcTag, sgcResponse).

Configuratie in detail

OpenAPIOptions is opgedeeld in drie persistente sub-objecten zodat u ze allemaal in de Object Inspector kunt zien:

Naast OnRequest bieden vier extra events u toegang tot de pijplijn: OnBeforeRequest (zet Accept := False om kortsluiting te maken, handig voor rate-limiting of logging), OnAfterRequest (nabewerking nadat u een respons hebt geproduceerd), OnAuthenticate (zet Authenticated := True nadat u een token of sessie hebt gecontroleerd) en OnException (vangnet waarmee u de HTTP-status kunt wijzigen voordat het framework de foutbody schrijft).

Swagger UI out of the box

Met ServeSwaggerUI := True publiceert de server een Swagger UI-pagina op BasePath + '/docs' die de spec laadt van BasePath + '/openapi.json'. Open de URL in een browser en u krijgt de bekende try-it-out-ervaring, gevoed door uw eigen draaiende server — geen aparte documentatiebuild, geen statische export. Gecombineerd met CORS is dit de snelste manier om een backend over te dragen aan een frontendteam of aan een partner die tegen uw API integreert.

Hoe u het krijgt

De component maakt deel uit van de Enterprise-editie van sgcWebSockets en is geregistreerd op de palette-pagina SGC OpenAPI. Twee complete demo's — één spec-first met een Petstore JSON, één code-first met een task manager-service — worden meegeleverd in Demos\23.OpenAPI. Download de meest recente build vanaf de sgcWebSockets downloadpagina.

Vragen, feedback of hulp nodig bij het inpassen in een bestaand project? Neem contact op — u krijgt antwoord van de mensen die de code geschreven hebben.