sgcOpenAPI 2026.6 — Server OpenAPI Standalone, Spec-First o Code-First

· Versioni

La prossima versione di sgcOpenAPI — la 2026.6, prevista per giugno — introduce un componente del tutto nuovo: TsgcHTTPServer_OpenAPI. Si tratta di un unico componente Delphi autonomo che ospita un servizio OpenAPI 3.0 con un server HTTP integrato. Trascinalo su una form, puntalo a una specifica (o generala da una classe Delphi con attributi RTTI), imposta Active := True e avrai una REST API documentata con Swagger UI servita automaticamente.

La novità principale è che sgcOpenAPI non ha più bisogno di sgcWebSockets per ospitare un server HTTP. Il nuovo componente è distribuito, impacchettato e installato interamente da sgcOpenAPI. Se usi già sgcWebSockets, il precedente componente TsgcWSServer_API_OpenAPI continua a funzionare senza modifiche — entrambi condividono internamente lo stesso engine.

Cosa ottieni in un solo componente

TsgcHTTPServer_OpenAPI racchiude tre elementi:

Avvio rapido — l'esempio minimo

Questo è tutto ciò che serve per ospitare un server OpenAPI funzionante con Swagger UI:

uses
  sgcHTTPServer_OpenAPI;

var
  oServer: TsgcHTTPServer_OpenAPI;
begin
  oServer := TsgcHTTPServer_OpenAPI.Create(nil);
  try
    oServer.Bindings.Add.Port := 8080;
    oServer.LoadFromFile('petstore.json');
    oServer.OnRequest := MyOnRequest;
    oServer.Active := True;
    Readln;
  finally
    oServer.Free;
  end;
end;

Vai su http://localhost:8080/docs per la Swagger UI e su http://localhost:8080/openapi.json per la specifica. Ogni operazione definita nella specifica viene instradata al tuo handler MyOnRequest con l'operationId risolto e un contesto della richiesta completamente costruito.

Spec-First — carica un file OpenAPI 3.0 esistente

Se hai già un file JSON o YAML OpenAPI 3.0 (Petstore, un contratto API interno, uno schema pubblico di cui vuoi fare il mock), spec-first è il modo più rapido per servirlo. LoadFromFile legge e analizza la specifica, costruisce una tabella di route dalla sezione paths e confronta ogni richiesta in arrivo con essa.

L'operationId di ciascuna route è la chiave di dispatch. All'interno di OnRequest gestisci una per una le operazioni:

uses
  sgcHTTP_OpenAPI_Server, sgcHTTP_OpenAPI_Server_Engine,
  sgcHTTPServer_OpenAPI;

procedure TForm1.OnOpenAPIRequest(Sender: TObject;
  const aOperationId: string; const aContext: TsgcOpenAPIServerContext;
  var Handled: Boolean);
begin
  Handled := True;
  if aOperationId = 'listPets' then
    HandleListPets(aContext)
  else if aOperationId = 'getPetById' then
    HandleGetPetById(aContext)
  else if aOperationId = 'createPet' then
    HandleCreatePet(aContext)
  else
    Handled := False;
end;

procedure TForm1.HandleGetPetById(const aContext: TsgcOpenAPIServerContext);
var
  vId, vPetJSON: string;
begin
  vId := aContext.PathParamAsString('petId');
  vPetJSON := FPets.Values[vId];
  if vPetJSON <> '' then
    aContext.RespondJSON(200, vPetJSON)
  else
    aContext.RespondError(404, 'Not Found', 'Pet ' + vId + ' not found');
end;

TsgcOpenAPIServerContext ti fornisce accessor tipizzati per tutti gli elementi della richiesta: PathParamAsString / PathParamAsInteger per i segmenti template, QueryParamAsString / QueryParamAsInteger / QueryParamAsBoolean con valori di default, BodyAsString / BodyAsJSON per il corpo della richiesta e HeaderValue per qualsiasi header in arrivo. Per rispondere, usa gli helper RespondJSON(code, content) e RespondError(code, title, detail), oppure imposta direttamente Response.Code, Response.ContentType e Response.Content per il controllo completo.

Code-First — genera la specifica da una classe Delphi

Se preferisci scrivere il contratto API in Delphi e lasciare che la specifica venga generata, decora una classe con attributi RTTI. TsgcOpenAPICodeFirstScanner percorre la classe, costruisce un documento JSON OpenAPI 3.0 completo e tu lo carichi nel server con LoadFromString. Richiede Delphi XE7 o successivo (per l'RTTI estesa).

uses
  sgcHTTP_OpenAPI_Server_CodeFirst;

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

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

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

I corpi dei metodi sono stub — esistono solo perché il compilatore emetta l'RTTI per essi. Il vero lavoro avviene in OnRequest, dove il dispatch usa l'operationId che lo scanner deriva dal nome di ciascun metodo (ListTasks, CreateTask, GetTask…).

Passa la classe allo scanner all'avvio e carica la specifica generata nel server:

uses
  sgcHTTP_OpenAPI_Server_CodeFirst, sgcHTTPServer_OpenAPI;

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

  oServer := TsgcHTTPServer_OpenAPI.Create(nil);
  oServer.LoadFromString(vSpec);
  oServer.Bindings.Add.Port := 8081;
  oServer.OnRequest := MyOnRequest;
  oServer.Active := True;
end;

Gli attributi coprono i metadati comuni: sgcServiceContract popola il blocco info di OpenAPI, sgcRoute imposta il path a livello di classe o di metodo, sgcHttpGet / Post / Put / Delete / Patch / Head / Options sceglie il verbo, sgcSummary e sgcDescription documentano l'operazione, sgcTag la raggruppa nella Swagger UI, sgcResponse(code, description) dichiara ciascuna risposta e sgcFromPath / FromQuery / FromBody / FromHeader insieme a sgcRequired descrivono ciascun parametro.

Configurazione — OpenAPIOptions

Tutta la configurazione lato server risiede in OpenAPIOptions, raggruppata in tre sotto-opzioni:

oServer.OpenAPIOptions.Endpoint.BasePath        := '/api';
oServer.OpenAPIOptions.Endpoint.ServeSpec       := True;   // /openapi.json
oServer.OpenAPIOptions.Endpoint.ServeSwaggerUI  := True;   // /docs

oServer.OpenAPIOptions.CORS.Enabled             := True;
oServer.OpenAPIOptions.CORS.AllowOrigins        := '*';
oServer.OpenAPIOptions.CORS.AllowHeaders        := 'Content-Type, Authorization';
oServer.OpenAPIOptions.CORS.AllowMethods        := 'GET, POST, PUT, DELETE, PATCH, OPTIONS';

oServer.OpenAPIOptions.Validation.ValidateRequest     := True;
oServer.OpenAPIOptions.Validation.ValidateRequestBody := True;
oServer.OpenAPIOptions.Validation.ValidateQueryParams := True;
oServer.OpenAPIOptions.Validation.ValidatePathParams  := True;
oServer.OpenAPIOptions.Validation.ValidateRequired    := True;

Con la validazione attiva, ogni richiesta in arrivo viene controllata rispetto ai JSON Schema dichiarati nella specifica prima di raggiungere il tuo handler — campi obbligatori, tipi, formati, enum, intervalli. In caso di errore viene scatenato l'evento OnValidationError con l'elenco degli errori e un flag per accettare o rifiutare la richiesta.

Eventi

Il componente espone sei eventi per il ciclo di vita della richiesta:

OnBeforeRequest: scatta prima del dispatch; imposta Accept := False per rifiutare con un 403 Forbidden. Utile per rate-limiting, logging o gate per singola route.

OnAuthenticate: scatta prima dell'handler principale; imposta Authenticated := False per rifiutare con 401 Unauthorized. Esamina header, cookie o parametri di query per decidere.

OnValidationError: scatta quando la validazione fallisce; riceve l'elenco degli errori. Imposta Continue := False per rifiutare con 400 Bad Request.

OnRequest: l'evento principale di dispatch. Guarda aOperationId, scrivi la risposta in aContext.Response, imposta Handled := True.

OnAfterRequest: scatta dopo che l'handler è ritornato — ideale per metriche o audit logging.

OnException: scatta se un'eccezione non gestita esce dal tuo handler. Modifica aResponseCode se vuoi qualcosa di diverso da 500 Internal Server Error.

Demo

Due demo complete sono incluse con sgcOpenAPI 2026.6, entrambe basate solo sul nuovo componente standalone — senza alcuna installazione di sgcWebSockets:

Aggiornamento

Se attualmente usi TsgcWSServer_API_OpenAPI con sgcWebSockets, non cambia nulla — la classe, le proprietà e gli eventi sono tutti preservati e l'implementazione ora delega allo stesso engine condiviso che alimenta il nuovo componente standalone. Puoi mantenere il codice esistente così com'è, oppure migrare una form alla volta a TsgcHTTPServer_OpenAPI per eliminare la dipendenza da sgcWebSockets.

sgcOpenAPI 2026.6 sarà disponibile sulla pagina dei download a giugno.

Domande, feedback o aiuto per la migrazione? Mettiti in contatto — riceverai una risposta dalle persone che hanno scritto il codice.