A edição Enterprise do sgcWebSockets traz um novo componente, TsgcWSAPIServer_OpenAPI, que transforma uma descrição OpenAPI 3 em um servidor REST em execução dentro da sua aplicação Delphi. Solte-o em um formulário, aponte-o para um servidor HTTP, entregue uma especificação — e as rotas, validação de requisições, respostas de erro e a documentação interativa Swagger UI ficam todas configuradas para você. Este post mostra como o componente funciona, as duas formas de utilizá-lo (spec-first e code-first), as opções de configuração que importam e um exemplo Delphi completo que você pode colar diretamente em um novo projeto.
O que o componente faz
TsgcWSAPIServer_OpenAPI é um servidor de API enxuto que se conecta ao TsgcWebSocketHTTPServer através do ponto de extensão existente para servidores de API. Você o associa a um servidor, carrega uma especificação OpenAPI 3.0, e ele faz quatro coisas em cada requisição HTTP recebida:
- casa a URL e o método HTTP com as rotas declaradas na especificação (incluindo parâmetros
{path}); - valida a requisição — campos obrigatórios, parâmetros de query, parâmetros de path e corpo da requisição — contra os schemas da especificação;
- dispara um evento
OnRequestcom ooperationIdresolvido e um objeto de contexto totalmente populado, de modo que seu código só precisa escrever a lógica de negócio; - serve dois endpoints integrados prontos para uso — a especificação bruta em
/openapi.jsone um Swagger UI interativo em/docs— com preflight CORS opcional em todas as rotas.
O resultado é que a especificação se torna a única fonte da verdade: altere um path, um parâmetro ou um código de resposta no JSON, reinicie, e o servidor passa a usar o novo contrato sem recompilar o código Delphi.
Spec-first: carregar um arquivo OpenAPI 3 existente
Se você já possui um documento OpenAPI 3 (por exemplo um petstore.json exportado de um designer de API) a integração é essencialmente três linhas — criar o componente, carregar a especificação, associar um servidor. Todo o resto é configuração e o handler OnRequest que produz as respostas reais.
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;
O evento OnRequest é despachado por operationId — a string que você escreveu ao lado de cada operação no YAML/JSON. Você escreve um ramo por operação, lê as entradas a partir do contexto e emite uma resposta:
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;
O objeto TsgcOpenAPIServerContext é o trabalhador braçal de cada handler. Ele expõe parâmetros de path e de query por nome (PathParamAsString, PathParamAsInteger, QueryParamAsString, QueryParamAsInteger, QueryParamAsBoolean), consulta de cabeçalhos via HeaderValue, o corpo como string (BodyAsString) ou JSON já parseado (BodyAsJSON), além de dois auxiliares de resposta: RespondJSON(code, content) para um payload normal e RespondError(code, title, detail), que produz um corpo application/problem+json no estilo RFC 7807, de modo que os clientes sempre recebam um formato de erro consistente.
Code-first: gerar a especificação a partir de atributos Delphi
O segundo modo é o fluxo inverso: você declara sua API como uma classe Delphi decorada com atributos, pede ao scanner para emitir o documento OpenAPI em tempo de execução, e alimenta esse documento de volta no mesmo componente. Não há nada para escrever à mão em 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;
A classe em si não precisa ter corpos reais — o scanner a lê via RTTI, a lógica real continua vivendo no seu handler OnRequest. Gerar a especificação e iniciar o servidor são poucas linhas:
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;
O conjunto de atributos cobre os casos mais comuns: roteamento (sgcHttpGet, sgcHttpPost, sgcHttpPut, sgcHttpDelete, sgcHttpPatch, sgcRoute), vinculação de parâmetros (sgcFromPath, sgcFromQuery, sgcFromHeader, sgcFromBody), validação (sgcRequired, sgcMinLength, sgcMaxLength, sgcRange, sgcPattern) e documentação (sgcSummary, sgcDescription, sgcTag, sgcResponse).
Configuração em detalhes
OpenAPIOptions é agrupado em três sub-objetos persistentes para que você possa vê-los todos no Object Inspector:
Endpoint—BasePathacrescenta um prefixo a cada rota e aos endpoints integrados/openapi.jsone/docs;SpecFilepode ser usado em tempo de design como alternativa aLoadFromFile;ServeSpeceServeSwaggerUIligam e desligam os dois endpoints integrados (ambos ativos por padrão).Validation— o interruptor principal éValidateRequest;ValidateRequired,ValidateQueryParams,ValidatePathParamseValidateRequestBodypermitem restringir o que é verificado. Quando a validação falha,OnValidationErroré disparado com a lista de problemas e um flagContinue— defina-o comoFalsepara rejeitar a requisição automaticamente.CORS— definaEnabled := Truee o servidor responderá às requisições preflightOPTIONSde cada rota, usandoAllowOrigins,AllowHeaderseAllowMethodscomo política de resposta.
Além de OnRequest, outros quatro eventos permitem que você se conecte ao pipeline: OnBeforeRequest (defina Accept := False para encerrar antecipadamente, útil para limitação de taxa ou logging), OnAfterRequest (pós-processamento depois que você produziu uma resposta), OnAuthenticate (defina Authenticated := True após verificar um token ou sessão) e OnException (captura genérica que permite alterar o status HTTP antes que o framework escreva o corpo do erro).
Swagger UI pronto para uso
Com ServeSwaggerUI := True o servidor publica uma página Swagger UI em BasePath + '/docs' que carrega a especificação a partir de BasePath + '/openapi.json'. Abra a URL em um navegador e você terá a experiência padrão de try-it-out, alimentada pelo seu próprio servidor em execução — sem build separado de documentação, sem exportação estática. Combinado com CORS, esta é a maneira mais rápida de entregar um backend a uma equipe de frontend ou a um parceiro que está se integrando à sua API.
Como obter
O componente faz parte da edição Enterprise do sgcWebSockets, registrado na página de paleta SGC OpenAPI. Duas demos completas — uma spec-first usando um JSON do Petstore, outra code-first usando um serviço de gerenciador de tarefas — são distribuídas em Demos\23.OpenAPI. Baixe a versão mais recente na página de download do sgcWebSockets.
Dúvidas, feedback ou ajuda para integrá-lo a um projeto existente? Entre em contato — você receberá uma resposta das pessoas que escreveram o código.
