Serwer OpenAPI dla Delphi: TsgcWSAPIServer_OpenAPI

· Komponenty
Serwer OpenAPI dla Delphi: TsgcWSAPIServer_OpenAPI | Blog eSeGeCe

Edycja sgcWebSockets Enterprise dostarcza nowy komponent, TsgcWSAPIServer_OpenAPI, który zamienia opis OpenAPI 3 w działający serwer REST wewnątrz Twojej aplikacji Delphi. Upuść go na formularzu, wskaż serwer HTTP, przekaż specyfikację — a trasy, walidacja żądań, odpowiedzi błędów i dokumentacja na żywo w Swagger UI zostaną podłączone za Ciebie. Ten wpis przeprowadza przez to, jak działa komponent, dwa sposoby jego sterowania (spec-first i code-first), istotne pokrętła konfiguracyjne oraz kompletny przykład w Delphi, który możesz wkleić bezpośrednio do nowego projektu.

Co robi komponent

TsgcWSAPIServer_OpenAPI to lekki serwer API, który włącza się do TsgcWebSocketHTTPServer przez istniejący punkt rozszerzenia dla serwerów API. Podpinasz go do serwera, ładujesz specyfikację OpenAPI 3.0, a on dla każdego przychodzącego żądania HTTP wykonuje cztery rzeczy:

Efekt jest taki, że specyfikacja staje się jedynym źródłem prawdy: zmień ścieżkę, parametr lub kod odpowiedzi w JSON-ie, uruchom ponownie i serwer podejmuje nowy kontrakt bez rekompilacji kodu Delphi.

Spec-first: załaduj istniejący plik OpenAPI 3

Jeśli masz już dokument OpenAPI 3 (na przykład petstore.json wyeksportowany z projektanta API), podłączenie sprowadza się w istocie do trzech linii — utwórz komponent, załaduj specyfikację, podłącz serwer. Cała reszta to konfiguracja i obsługa zdarzenia OnRequest, która produkuje faktyczne odpowiedzi.

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;

Zdarzenie OnRequest jest dystrybuowane per operationId, ciąg, który zapisałeś obok każdej operacji w dokumencie. Piszesz jedną gałąź na operację, czytasz wejścia z kontekstu i emitujesz odpowiedź:

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;

Obiekt TsgcOpenAPIServerContext jest koniem roboczym każdego handlera. Udostępnia parametry ścieżki i zapytania po nazwie (PathParamAsString, PathParamAsInteger, QueryParamAsString, QueryParamAsInteger, QueryParamAsBoolean), odczyt nagłówków przez HeaderValue, ciało jako tekst (BodyAsString) lub wstępnie sparsowany JSON (BodyAsJSON), a także dwa pomocnicze metody odpowiedzi: RespondJSON(code, content) dla zwykłego ładunku oraz RespondError(code, title, detail), który zapisuje ciało w formacie RFC 7807 z polami type, title, status i detail, dzięki czemu klienci zawsze otrzymują spójny kształt błędu.

Code-first: generuj specyfikację z atrybutów Delphi

Drugi tryb to przepływ odwrotny: deklarujesz swoje API jako klasę Delphi udekorowaną atrybutami, prosisz skaner o wyemitowanie dokumentu OpenAPI w czasie uruchomienia i przekazujesz ten dokument z powrotem do tego samego komponentu. Nie ma nic do napisania ręcznie w 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;

Sama klasa nie musi mieć rzeczywistych ciał metod — skaner czyta ją przez RTTI, a właściwa logika nadal żyje w Twoim handlerze OnRequest. Wygenerowanie specyfikacji i uruchomienie serwera to kilka linii:

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;

Zestaw atrybutów pokrywa typowe przypadki: routing (sgcHttpGet, sgcHttpPost, sgcHttpPut, sgcHttpDelete, sgcHttpPatch, sgcRoute), wiązanie parametrów (sgcFromPath, sgcFromQuery, sgcFromHeader, sgcFromBody), walidację (sgcRequired, sgcMinLength, sgcMaxLength, sgcRange, sgcPattern) oraz dokumentację (sgcSummary, sgcDescription, sgcTag, sgcResponse).

Konfiguracja w szczegółach

OpenAPIOptions jest pogrupowany w pięć trwałych pod-obiektów, dzięki czemu możesz zobaczyć je wszystkie w Inspektorze Obiektów. Trzy z nich zawierają ustawienia używane na co dzień:

Poza OnRequest kolejne zdarzenia pozwalają wpiąć się w potok: OnBeforeRequest (ustaw Accept := False, aby przerwać obsługę odpowiedzią 403 Forbidden, przydatne do ograniczania szybkości lub logowania), OnAfterRequest (post-przetwarzanie po wyprodukowaniu odpowiedzi), OnAuthenticate (wyczyść Authenticated, aby odpowiedzieć 401 Unauthorized po sprawdzeniu tokenu lub sesji) oraz OnException (uniwersalny przechwytujący wszystko, który pozwala zmienić status HTTP zanim framework zapisze ciało błędu). Pozostałe dwa pod-obiekty, Security i Mock, dodają OnValidateAPIKey, OnValidateBasic i OnValidateBearer dla schematów zadeklarowanych w specyfikacji oraz sztuczną odpowiedź dla operacji, dla której nikt jeszcze nie napisał kodu.

Swagger UI od razu po wyjęciu z pudełka

Przy ServeSwaggerUI := True serwer publikuje stronę Swagger UI pod BasePath + '/docs', która ładuje specyfikację z BasePath + '/openapi.json'. Otwórz URL w przeglądarce i otrzymasz standardowe doświadczenie try-it-out, zasilane z Twojego własnego działającego serwera — bez osobnego budowania dokumentacji, bez statycznego eksportu. W połączeniu z CORS jest to najszybszy sposób na przekazanie backendu zespołowi frontendowemu lub partnerowi integrującemu się z Twoim API.

Jak go zdobyć

Komponent jest częścią edycji Enterprise sgcWebSockets, zarejestrowanym na stronie palety SGC OpenAPI. Dwa kompletne dema — jedno spec-first wykorzystujące JSON Petstore, jedno code-first wykorzystujące usługę menedżera zadań — są dostarczane w Demos\23.OpenAPI. Pobierz najnowszą kompilację ze strony pobierania sgcWebSockets.

Pytania, opinie lub pomoc w podłączeniu komponentu do istniejącego projektu? Skontaktuj się z nami — otrzymasz odpowiedź od osób, które napisały kod.