La edición Enterprise de sgcWebSockets incorpora un nuevo componente, TsgcWSAPIServer_OpenAPI, que convierte una descripción OpenAPI 3 en un servidor REST en ejecución dentro de su aplicación Delphi. Colóquelo en un formulario, apúntelo a un servidor HTTP, entréguele una especificación — y las rutas, la validación de solicitudes, las respuestas de error y la documentación interactiva de Swagger UI quedan conectadas por usted. Esta entrada explica cómo funciona el componente, las dos formas en que puede utilizarlo (spec-first y code-first), las opciones de configuración relevantes y un ejemplo completo de Delphi que puede pegar directamente en un nuevo proyecto.
Qué hace el componente
TsgcWSAPIServer_OpenAPI es un servidor de API ligero que se conecta a TsgcWebSocketHTTPServer mediante el punto de extensión de servidor de API existente. Lo asocia a un servidor, carga una especificación OpenAPI 3.0, y hace cuatro cosas en cada solicitud HTTP entrante:
- compara la URL y el método HTTP con las rutas declaradas en la especificación (incluyendo los parámetros
{path}); - valida la solicitud — campos requeridos, parámetros de consulta, parámetros de ruta y cuerpo de la solicitud — frente a los esquemas de la especificación;
- dispara un evento
OnRequestcon eloperationIdresuelto y un objeto de contexto completamente poblado, de modo que su código solo tiene que escribir la lógica de negocio; - sirve dos endpoints integrados de fábrica — la especificación en bruto en
/openapi.jsony una Swagger UI interactiva en/docs— con preflight CORS opcional en cada ruta.
El resultado es que la especificación se convierte en la única fuente de verdad: cambie una ruta, un parámetro o un código de respuesta en el JSON, reinicie, y el servidor adopta el nuevo contrato sin recompilar código Delphi.
Spec-first: cargar un archivo OpenAPI 3 existente
Si ya dispone de un documento OpenAPI 3 (por ejemplo un petstore.json exportado desde un diseñador de API) el cableado son esencialmente tres líneas — cree el componente, cargue la especificación, asocie un servidor. Todo lo demás es configuración y el manejador OnRequest que produce las respuestas reales.
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;
El evento OnRequest se despacha por operationId — la cadena que escribió junto a cada operación en el YAML/JSON. Escribe una rama por operación, lee las entradas desde el contexto y emite una respuesta:
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;
El objeto TsgcOpenAPIServerContext es el caballo de batalla de cada manejador. Expone los parámetros de ruta y de consulta por nombre (PathParamAsString, PathParamAsInteger, QueryParamAsString, QueryParamAsInteger, QueryParamAsBoolean), la consulta de cabeceras mediante HeaderValue, el cuerpo como cadena (BodyAsString) o como JSON ya analizado (BodyAsJSON), más dos ayudantes de respuesta: RespondJSON(code, content) para una carga útil normal y RespondError(code, title, detail), que produce un cuerpo application/problem+json al estilo RFC 7807 para que los clientes siempre reciban una forma de error consistente.
Code-first: generar la especificación a partir de atributos de Delphi
El segundo modo es el flujo inverso: usted declara su API como una clase Delphi decorada con atributos, le pide al escáner que emita el documento OpenAPI en tiempo de ejecución, y alimenta ese documento de vuelta al mismo componente. No hay nada que escribir a mano en 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 clase en sí no necesita cuerpos reales — el escáner la lee mediante RTTI, la lógica real sigue residiendo en su manejador OnRequest. Generar la especificación y arrancar el servidor son unas pocas líneas:
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;
El conjunto de atributos cubre los casos comunes: enrutamiento (sgcHttpGet, sgcHttpPost, sgcHttpPut, sgcHttpDelete, sgcHttpPatch, sgcRoute), enlace de parámetros (sgcFromPath, sgcFromQuery, sgcFromHeader, sgcFromBody), validación (sgcRequired, sgcMinLength, sgcMaxLength, sgcRange, sgcPattern) y documentación (sgcSummary, sgcDescription, sgcTag, sgcResponse).
Configuración en detalle
OpenAPIOptions está agrupado en tres sub-objetos persistentes para que pueda verlos todos en el Object Inspector:
Endpoint—BasePathantepone un prefijo a cada ruta y a los endpoints integrados/openapi.jsony/docs;SpecFilepuede usarse en tiempo de diseño como alternativa aLoadFromFile;ServeSpecyServeSwaggerUIactivan los dos endpoints integrados (ambos activos por defecto).Validation— el interruptor principal esValidateRequest;ValidateRequired,ValidateQueryParams,ValidatePathParamsyValidateRequestBodypermiten acotar qué se comprueba. Cuando la validación falla, se disparaOnValidationErrorcon la lista de problemas y una banderaContinue— póngala enFalsepara rechazar la solicitud automáticamente.CORS— establezcaEnabled := Truey el servidor responde a las solicitudes preflightOPTIONSde cada ruta, utilizandoAllowOrigins,AllowHeadersyAllowMethodscomo política de respuesta.
Más allá de OnRequest, otros cuatro eventos le permiten engancharse en la canalización: OnBeforeRequest (ponga Accept := False para cortocircuitar, útil para limitación de tasa o registro), OnAfterRequest (post-procesado una vez que ha producido una respuesta), OnAuthenticate (ponga Authenticated := True tras comprobar un token o sesión) y OnException (captura general que le permite cambiar el estado HTTP antes de que el framework escriba el cuerpo de error).
Swagger UI de fábrica
Con ServeSwaggerUI := True el servidor publica una página de Swagger UI en BasePath + '/docs' que carga la especificación desde BasePath + '/openapi.json'. Abra la URL en un navegador y obtendrá la experiencia estándar de try-it-out, alimentada desde su propio servidor en ejecución — sin compilación de documentación aparte, sin exportación estática. Combinado con CORS, esta es la forma más rápida de entregar un backend a un equipo de frontend o a un socio que integre contra su API.
Cómo obtenerlo
El componente forma parte de la edición Enterprise de sgcWebSockets, registrado en la página de paleta SGC OpenAPI. Dos demos completas — una spec-first usando un JSON de Petstore, otra code-first usando un servicio de gestor de tareas — se incluyen en Demos\23.OpenAPI. Descargue la última compilación desde la página de descarga de sgcWebSockets.
¿Preguntas, comentarios o ayuda para integrarlo en un proyecto existente? Póngase en contacto — recibirá una respuesta de las personas que escribieron el código.
