Server OpenAPI per Delphi: TsgcWSAPIServer_OpenAPI

· Componenti

L'edizione Enterprise di sgcWebSockets include un nuovo componente, TsgcWSAPIServer_OpenAPI, che trasforma una descrizione OpenAPI 3 in un server REST funzionante all'interno della tua applicazione Delphi. Lo trascini su una form, lo colleghi a un server HTTP, gli fornisci una specifica — e le rotte, la validazione delle richieste, le risposte di errore e la documentazione live di Swagger UI vengono configurate automaticamente. Questo articolo illustra come funziona il componente, i due modi in cui puoi utilizzarlo (spec-first e code-first), le opzioni di configurazione importanti e un esempio Delphi completo che puoi incollare direttamente in un nuovo progetto.

Cosa fa il componente

TsgcWSAPIServer_OpenAPI è un server API leggero che si integra con TsgcWebSocketHTTPServer tramite il punto di estensione API-server esistente. Lo colleghi a un server, carichi una specifica OpenAPI 3.0, e fa quattro cose ad ogni richiesta HTTP in entrata:

Il risultato è che la specifica diventa l'unica fonte di verità: cambia un percorso, un parametro o un codice di risposta nel JSON, riavvia, e il server adotta il nuovo contratto senza ricompilare il codice Delphi.

Spec-first: caricare un file OpenAPI 3 esistente

Se hai già un documento OpenAPI 3 (ad esempio un petstore.json esportato da un designer di API) il collegamento è essenzialmente di tre righe — crea il componente, carica la specifica, collega un server. Tutto il resto è configurazione e il gestore OnRequest che produce le risposte effettive.

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'evento OnRequest viene inviato per ogni operationId — la stringa che hai scritto accanto a ciascuna operazione nel YAML/JSON. Scrivi un ramo per ogni operazione, leggi gli input dal contesto ed emetti una risposta:

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'oggetto TsgcOpenAPIServerContext è il cavallo da tiro di ogni gestore. Espone i parametri di percorso e di query per nome (PathParamAsString, PathParamAsInteger, QueryParamAsString, QueryParamAsInteger, QueryParamAsBoolean), la ricerca degli header tramite HeaderValue, il corpo come stringa (BodyAsString) o JSON già analizzato (BodyAsJSON), più due helper di risposta: RespondJSON(code, content) per un payload normale e RespondError(code, title, detail), che produce un corpo application/problem+json in stile RFC 7807 affinché i client ottengano sempre una forma di errore coerente.

Code-first: generare la specifica dagli attributi Delphi

La seconda modalità è il flusso inverso: dichiari la tua API come una classe Delphi decorata con attributi, chiedi allo scanner di emettere il documento OpenAPI a runtime, e reinserisci quel documento nello stesso componente. Non c'è nulla da scrivere a mano in 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 stessa non necessita di corpi reali — lo scanner la legge tramite RTTI, la logica vera e propria vive ancora nel tuo gestore OnRequest. Generare la specifica e avviare il server richiede poche righe:

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;

Il set di attributi copre i casi più comuni: routing (sgcHttpGet, sgcHttpPost, sgcHttpPut, sgcHttpDelete, sgcHttpPatch, sgcRoute), binding dei parametri (sgcFromPath, sgcFromQuery, sgcFromHeader, sgcFromBody), validazione (sgcRequired, sgcMinLength, sgcMaxLength, sgcRange, sgcPattern) e documentazione (sgcSummary, sgcDescription, sgcTag, sgcResponse).

Configurazione in dettaglio

OpenAPIOptions è raggruppato in tre sotto-oggetti persistenti in modo da poterli vedere tutti nell'Object Inspector:

Oltre a OnRequest, altri quattro eventi ti permettono di intervenire nella pipeline: OnBeforeRequest (imposta Accept := False per interrompere, utile per limitazione di frequenza o logging), OnAfterRequest (post-elaborazione una volta prodotta una risposta), OnAuthenticate (imposta Authenticated := True dopo aver verificato un token o una sessione) e OnException (catch-all che ti permette di cambiare lo stato HTTP prima che il framework scriva il corpo dell'errore).

Swagger UI pronto all'uso

Con ServeSwaggerUI := True il server pubblica una pagina Swagger UI in BasePath + '/docs' che carica la specifica da BasePath + '/openapi.json'. Apri l'URL in un browser e ottieni l'esperienza standard try-it-out, alimentata dal tuo server in esecuzione — nessuna build di documentazione separata, nessuna esportazione statica. Combinato con CORS, è il modo più veloce per consegnare un backend a un team frontend o a un partner che si integra con la tua API.

Come ottenerlo

Il componente fa parte dell'edizione Enterprise di sgcWebSockets, registrato nella pagina della palette SGC OpenAPI. Due demo complete — una spec-first che utilizza un JSON Petstore, una code-first che utilizza un servizio di task manager — sono incluse in Demos\23.OpenAPI. Scarica l'ultima build dalla pagina di download di sgcWebSockets.

Domande, feedback o aiuto per integrarlo in un progetto esistente? Contattaci — otterrai una risposta dalle persone che hanno scritto il codice.