sgcWebSockets Enterprise 版本带来了一个新组件 TsgcWSAPIServer_OpenAPI,它可以将 OpenAPI 3 描述文件转换为在 Delphi 应用程序内部运行的 REST 服务器。把它拖到窗体上,指向一个 HTTP 服务器,再给它一份规范文件 —— 路由、请求校验、错误响应以及实时 Swagger UI 文档便会自动为你接好。本文将介绍该组件的工作方式、驱动它的两种方法(规范优先与代码优先)、关键的配置选项,以及一个可直接粘贴到新项目中的完整 Delphi 示例。
组件的功能
TsgcWSAPIServer_OpenAPI 是一个轻量的 API 服务器,它通过现有的 API 服务器扩展点接入 TsgcWebSocketHTTPServer。你只需将其附加到某个服务器,加载一份 OpenAPI 3.0 规范,它就会对每个进入的 HTTP 请求执行四件事:
- 将 URL 与 HTTP 方法与规范中声明的路由(包括
{path}参数)进行匹配; - 根据规范中的 schema 对请求进行校验 —— 必填字段、查询参数、路径参数与请求体;
- 触发
OnRequest事件,并提供已解析的operationId与填充完整的上下文对象,让你的代码只需编写业务逻辑; - 开箱即用地提供两个内置端点 —— 位于
/openapi.json的原始规范和位于/docs的交互式 Swagger UI —— 同时可选地为每个路由提供 CORS 预检支持。
由此,规范成为唯一的事实来源:在 JSON 中修改一个路径、参数或响应码,重启即可,服务器将拾起新契约而无需重新编译 Delphi 代码。
规范优先:加载已有的 OpenAPI 3 文件
如果你已经有一份 OpenAPI 3 文档(例如从 API 设计器导出的 petstore.json),接线基本上只需三行 —— 创建组件、加载规范、附加服务器。其余只是配置以及负责生成实际响应的 OnRequest 处理函数。
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;
OnRequest 事件按 operationId 分发 —— 也就是你在 YAML/JSON 中为每个操作写下的字符串。你为每个操作编写一个分支,从上下文中读取输入并发出响应:
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;
TsgcOpenAPIServerContext 对象是每个处理函数的主力。它按名称暴露路径参数与查询参数(PathParamAsString、PathParamAsInteger、QueryParamAsString、QueryParamAsInteger、QueryParamAsBoolean),通过 HeaderValue 查询请求头,以字符串形式(BodyAsString)或预解析的 JSON(BodyAsJSON)获取请求体,并提供两个响应辅助方法:用于常规负载的 RespondJSON(code, content),以及 RespondError(code, title, detail),后者会生成一个符合 RFC 7807 风格的 application/problem+json 响应体,让客户端始终收到一致的错误结构。
代码优先:从 Delphi 特性生成规范
第二种模式是反向流程:将 API 声明为一个用特性修饰的 Delphi 类,让扫描器在运行时生成 OpenAPI 文档,再将该文档反馈回同一个组件。完全不必手写 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;
类本身不需要真实的方法体 —— 扫描器通过 RTTI 读取它,实际逻辑仍然位于你的 OnRequest 处理函数中。生成规范并启动服务器只需几行代码:
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;
特性集涵盖了常见场景:路由(sgcHttpGet、sgcHttpPost、sgcHttpPut、sgcHttpDelete、sgcHttpPatch、sgcRoute)、参数绑定(sgcFromPath、sgcFromQuery、sgcFromHeader、sgcFromBody)、校验(sgcRequired、sgcMinLength、sgcMaxLength、sgcRange、sgcPattern)以及文档(sgcSummary、sgcDescription、sgcTag、sgcResponse)。
详细配置
OpenAPIOptions 被分组为三个持久化的子对象,方便你在对象检查器中一览无余:
Endpoint—BasePath会为每个路由以及内置的/openapi.json与/docs端点添加前缀;SpecFile可以在设计期作为LoadFromFile的替代方案使用;ServeSpec与ServeSwaggerUI用于开关这两个内置端点(默认均已开启)。Validation— 总开关是ValidateRequest;ValidateRequired、ValidateQueryParams、ValidatePathParams与ValidateRequestBody可让你缩小校验范围。当校验失败时,OnValidationError会触发,并附带问题列表与一个Continue标志 —— 将其设为False即可自动拒绝该请求。CORS— 将Enabled := True,服务器将为每个路由响应预检的OPTIONS请求,并使用AllowOrigins、AllowHeaders与AllowMethods作为响应策略。
除 OnRequest 之外,还有另外四个事件可让你接入管线:OnBeforeRequest(将 Accept := False 即可短路请求,适用于限流或日志记录)、OnAfterRequest(在生成响应后进行后处理)、OnAuthenticate(在校验令牌或会话后将 Authenticated := True)以及 OnException(捕获所有异常,允许你在框架写入错误响应体之前更改 HTTP 状态码)。
开箱即用的 Swagger UI
当 ServeSwaggerUI := True 时,服务器会在 BasePath + '/docs' 发布一个 Swagger UI 页面,该页面从 BasePath + '/openapi.json' 加载规范。在浏览器中打开该 URL,你将获得标准的 try-it-out 体验,数据来自你自己运行的服务器 —— 无需独立的文档构建,也无需静态导出。结合 CORS,这是把后端交付给前端团队或与你的 API 对接的合作伙伴最快的方式。
获取方式
该组件是 sgcWebSockets Enterprise 版本的一部分,注册在 SGC OpenAPI 控件面板页中。两个完整的示例 —— 一个基于 Petstore JSON 的规范优先示例,一个基于任务管理器服务的代码优先示例 —— 随源码发行于 Demos\23.OpenAPI。请从 sgcWebSockets 下载页面获取最新版本。
有问题、反馈,或在把它接入现有项目时需要帮助?联系我们 — 你将收到来自代码作者本人的回复。
