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

· Versioni
sgcOpenAPI 2026.6 — Server OpenAPI Standalone, Spec-First o Code-First | Blog eSeGeCe

La prossima versione di sgcOpenAPI, la 2026.6, prevista per giugno, introduce un server OpenAPI 3.0 che puoi ospitare senza installare sgcWebSockets. Sono due i componenti coinvolti: TsgcHTTPServer, il server HTTP basato su Indy, e TsgcWSAPIServer_OpenAPI, il componente API che vi colleghi. Punta il componente API a una specifica (o generala da una classe Delphi con attributi RTTI), assegna la sua proprietà Server, avvia il server HTTP 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. Entrambi i componenti sono distribuiti, impacchettati e installati interamente da sgcOpenAPI. Se usi già sgcWebSockets, il componente TsgcWSServer_API_OpenAPI che già conosci continua a funzionare senza modifiche, perché è la stessa classe: entrambi i prodotti lo costruiscono a partire dallo stesso engine.

Cosa ottieni

La coppia porta con sé tre elementi:

Avvio rapido — l'esempio minimo

Questo è tutto ciò che serve per ospitare un server OpenAPI funzionante con Swagger UI. Nota che il componente API non ha una propria proprietà Active: assegnare Server lo collega, e assegnare nil lo scollega mentre il server HTTP continua a funzionare.

uses
  sgcHTTP_Server, sgcWebSocket_Server_API_OpenAPI;

var
  oServer: TsgcHTTPServer;
  oOpenAPI: TsgcWSAPIServer_OpenAPI;
begin
  oServer := TsgcHTTPServer.Create(nil);
  oOpenAPI := TsgcWSAPIServer_OpenAPI.Create(nil);
  try
    oServer.Bindings.Add.Port := 8080;
    oOpenAPI.LoadFromFile('petstore.json');
    oOpenAPI.OnRequest := MyOnRequest;
    oOpenAPI.Server := oServer;
    oServer.Active := True;
    Readln;
  finally
    oOpenAPI.Free;
    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 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. Il server legge JSON, quindi converti prima un contratto YAML, per esempio con sgcOpenAPI.exe.

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

uses
  sgcHTTP_Server, sgcHTTP_OpenAPI_Server,
  sgcHTTP_OpenAPI_Server_Engine, sgcWebSocket_Server_API_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_Server, sgcHTTP_OpenAPI_Server_CodeFirst,
  sgcWebSocket_Server_API_OpenAPI;

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

  oServer := TsgcHTTPServer.Create(nil);
  oServer.Bindings.Add.Port := 8081;

  oOpenAPI := TsgcWSAPIServer_OpenAPI.Create(nil);
  oOpenAPI.LoadFromString(vSpec);
  oOpenAPI.OnRequest := MyOnRequest;
  oOpenAPI.Server := oServer;

  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 sul componente API, raggruppata in cinque sotto-opzioni. Queste tre riguardano le impostazioni di uso quotidiano:

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

Sei eventi coprono 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.

I restanti due sotto-oggetti aggiungono i propri: Security pilota OnValidateAPIKey, OnValidateBasic e OnValidateBearer per i securitySchemes dichiarati dalla spec, e Mock risponde a un'operazione senza handler usando gli esempi propri della spec.

Demo

Due demo complete sono incluse con sgcOpenAPI 2026.6, entrambe ospitate dalla coppia standalone, quindi non è richiesta alcuna installazione di sgcWebSockets:

Aggiornamento

Se attualmente usi TsgcWSServer_API_OpenAPI con sgcWebSockets, non cambia nulla. La classe, le sue proprietà e i suoi eventi sono tutti preservati, e l'implementazione delega all'engine condiviso. TsgcWSAPIServer_OpenAPI è il discendente pubblicato di quella stessa classe, quindi l'unica cosa che sgcOpenAPI cambia è da dove proviene il package.

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.