Delphi 的 OpenAPI 服务器:TsgcWSAPIServer_OpenAPI

· 组件

sgcWebSockets Enterprise 版本带来了一个新组件 TsgcWSAPIServer_OpenAPI,它可以将 OpenAPI 3 描述文件转换为在 Delphi 应用程序内部运行的 REST 服务器。把它拖到窗体上,指向一个 HTTP 服务器,再给它一份规范文件 —— 路由、请求校验、错误响应以及实时 Swagger UI 文档便会自动为你接好。本文将介绍该组件的工作方式、驱动它的两种方法(规范优先与代码优先)、关键的配置选项,以及一个可直接粘贴到新项目中的完整 Delphi 示例。

组件的功能

TsgcWSAPIServer_OpenAPI 是一个轻量的 API 服务器,它通过现有的 API 服务器扩展点接入 TsgcWebSocketHTTPServer。你只需将其附加到某个服务器,加载一份 OpenAPI 3.0 规范,它就会对每个进入的 HTTP 请求执行四件事:

由此,规范成为唯一的事实来源:在 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 对象是每个处理函数的主力。它按名称暴露路径参数与查询参数(PathParamAsStringPathParamAsIntegerQueryParamAsStringQueryParamAsIntegerQueryParamAsBoolean),通过 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;

特性集涵盖了常见场景:路由(sgcHttpGetsgcHttpPostsgcHttpPutsgcHttpDeletesgcHttpPatchsgcRoute)、参数绑定(sgcFromPathsgcFromQuerysgcFromHeadersgcFromBody)、校验(sgcRequiredsgcMinLengthsgcMaxLengthsgcRangesgcPattern)以及文档(sgcSummarysgcDescriptionsgcTagsgcResponse)。

详细配置

OpenAPIOptions 被分组为三个持久化的子对象,方便你在对象检查器中一览无余:

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 下载页面获取最新版本。

有问题、反馈,或在把它接入现有项目时需要帮助?联系我们 — 你将收到来自代码作者本人的回复。