sgcOpenAPI 2026.6 — 独立 OpenAPI 服务器,Spec-First 或 Code-First

· 版本发布
sgcOpenAPI 2026.6 — 独立 OpenAPI 服务器,Spec-First 或 Code-First | eSeGeCe 博客

sgcOpenAPI 的下一个版本,2026.6,将于六月发布,带来一个无需安装 sgcWebSockets 就能托管的 OpenAPI 3.0 服务器。这需要两个组件:TsgcHTTPServer,也就是基于 Indy 的 HTTP 服务器,以及 TsgcWSAPIServer_OpenAPI,也就是你挂接到它上面的 API 组件。把 API 组件指向一份规范(或通过 RTTI 属性从 Delphi 类生成规范),赋值它的 Server 属性,启动 HTTP 服务器,即可拥有一个带自动提供的 Swagger UI 的、有文档化的 REST API。

本次更新的重头戏是 sgcOpenAPI 托管 HTTP 服务器时不再需要 sgcWebSockets。两个组件都完全由 sgcOpenAPI 提供、打包和安装。如果您已经在使用 sgcWebSockets,您熟悉的 TsgcWSServer_API_OpenAPI 组件会保持原样运作,因为它就是同一个类:两个产品都是从同一套引擎构建出来的。

你会得到什么

这一对组件带来三件事:

快速入门 — 最小示例

托管一个带 Swagger UI 的可用 OpenAPI 服务器,所需要的就是这些。请注意,API 组件本身没有 Active 属性:赋值 Server 就是挂接它,赋值 nil 就是分离它,此时 HTTP 服务器仍会继续运行。

uses
  sgcHTTP_Server, sgcWebSocket_Server_API_OpenAPI;

var
  oServer: TsgcHTTPServer;
  oOpenAPI: TsgcWSAPIServer_OpenAPI;
begin
  oServer := TsgcHTTPServer.Create(nil);
  oOpenAPI := TsgcWSAPIServer_OpenAPI.Create(nil);
  try
    oServer.Bindings.Add.Port := 8080;
    oOpenAPI.LoadFromFile('petstore.json');
    oOpenAPI.OnRequest := MyOnRequest;
    oOpenAPI.Server := oServer;
    oServer.Active := True;
    Readln;
  finally
    oOpenAPI.Free;
    oServer.Free;
  end;
end;

访问 http://localhost:8080/docs 查看 Swagger UI,访问 http://localhost:8080/openapi.json 获取规范。规范中定义的每一个操作都会被路由到您的 MyOnRequest 处理器,并附带已解析的 operationId 和完整构建好的请求上下文。

Spec-First — 加载已有的 OpenAPI 3.0 文件

如果您已经有一份 OpenAPI 3.0 的 JSON 文件(例如 Petstore、一份内部 API 合约或一份您想要模拟的公共架构),spec-first 是提供该服务最快的方式。LoadFromFile 读取并解析规范,根据 paths 部分构建路由表,然后将每一个传入请求与之匹配。服务器只读取 JSON,所以请先转换 YAML 契约,例如使用 sgcOpenAPI.exe

每条路由的 operationId 是分派的关键。在 OnRequest 中,您依次处理每一个操作:

uses
  sgcHTTP_Server, sgcHTTP_OpenAPI_Server,
  sgcHTTP_OpenAPI_Server_Engine, sgcWebSocket_Server_API_OpenAPI;

procedure TForm1.OnOpenAPIRequest(Sender: TObject;
  const aOperationId: string; const aContext: TsgcOpenAPIServerContext;
  var Handled: Boolean);
begin
  Handled := True;
  if aOperationId = 'listPets' then
    HandleListPets(aContext)
  else if aOperationId = 'getPetById' then
    HandleGetPetById(aContext)
  else if aOperationId = 'createPet' then
    HandleCreatePet(aContext)
  else
    Handled := False;
end;

procedure TForm1.HandleGetPetById(const aContext: TsgcOpenAPIServerContext);
var
  vId, vPetJSON: string;
begin
  vId := aContext.PathParamAsString('petId');
  vPetJSON := FPets.Values[vId];
  if vPetJSON <> '' then
    aContext.RespondJSON(200, vPetJSON)
  else
    aContext.RespondError(404, 'Not Found', 'Pet ' + vId + ' not found');
end;

TsgcOpenAPIServerContext 为请求中的所有内容提供了类型化访问器:用于模板段的 PathParamAsString / PathParamAsInteger,带默认值的 QueryParamAsString / QueryParamAsInteger / QueryParamAsBoolean,用于请求体的 BodyAsString / BodyAsJSON,以及用于任意传入头的 HeaderValue。要进行响应,可以使用辅助方法 RespondJSON(code, content)RespondError(code, title, detail),或者直接设置 Response.CodeResponse.ContentTypeResponse.Content 以获得完全控制。

Code-First — 从 Delphi 类生成规范

如果您更愿意用 Delphi 编写 API 合约并让规范自动生成,请用 RTTI 属性装饰一个类。TsgcOpenAPICodeFirstScanner 会遍历该类,构建一份完整的 OpenAPI 3.0 JSON 文档,然后您可以通过 LoadFromString 将其加载到服务器中。这需要 Delphi XE7 或更新版本(以支持扩展 RTTI)。

uses
  sgcHTTP_OpenAPI_Server_CodeFirst;

type
  [sgcServiceContract('Task Manager API',
    'A simple task management demo', '1.0.0')]
  [sgcRoute('/api/v1')]
  TTaskManagerService = class
  public
    [sgcHttpGet]
    [sgcRoute('/tasks')]
    [sgcSummary('List all tasks')]
    [sgcTag('Tasks')]
    [sgcResponse(200, 'A list of tasks')]
    procedure ListTasks([sgcFromQuery] const status: string); virtual;

    [sgcHttpPost]
    [sgcRoute('/tasks')]
    [sgcSummary('Create a new task')]
    [sgcTag('Tasks')]
    [sgcResponse(201, 'Task created successfully')]
    procedure CreateTask([sgcFromBody] const body: string); virtual;

    [sgcHttpGet]
    [sgcRoute('/tasks/{taskId}')]
    [sgcSummary('Get a task by ID')]
    [sgcTag('Tasks')]
    [sgcResponse(200, 'The requested task')]
    [sgcResponse(404, 'Task not found')]
    procedure GetTask([sgcFromPath][sgcRequired]
      const taskId: Integer); virtual;
  end;

方法体只是占位 — 它们的存在只是为了让编译器为其生成 RTTI。实际工作发生在 OnRequest 中,由扫描器从每个方法名(ListTasksCreateTaskGetTask…)派生的 operationId 进行分派。

在启动时将类交给扫描器,并将生成的规范加载到服务器中:

uses
  sgcHTTP_Server, sgcHTTP_OpenAPI_Server_CodeFirst,
  sgcWebSocket_Server_API_OpenAPI;

var
  oScanner: TsgcOpenAPICodeFirstScanner;
  oServer: TsgcHTTPServer;
  oOpenAPI: TsgcWSAPIServer_OpenAPI;
  vSpec: string;
begin
  oScanner := TsgcOpenAPICodeFirstScanner.Create;
  try
    vSpec := oScanner.GenerateSpec(TTaskManagerService);
  finally
    oScanner.Free;
  end;

  oServer := TsgcHTTPServer.Create(nil);
  oServer.Bindings.Add.Port := 8081;

  oOpenAPI := TsgcWSAPIServer_OpenAPI.Create(nil);
  oOpenAPI.LoadFromString(vSpec);
  oOpenAPI.OnRequest := MyOnRequest;
  oOpenAPI.Server := oServer;

  oServer.Active := True;
end;

这些属性涵盖了常见的元数据:sgcServiceContract 填充 OpenAPI 的 info 块,sgcRoute 在类级或方法级设置路径,sgcHttpGet / Post / Put / Delete / Patch / Head / Options 选择动词,sgcSummarysgcDescription 为操作编写文档,sgcTag 在 Swagger UI 中对其进行分组,sgcResponse(code, description) 声明每个响应,而 sgcFromPath / FromQuery / FromBody / FromHeadersgcRequired 一起用于描述每个参数。

配置 — OpenAPIOptions

所有服务端配置都位于 API 组件的 OpenAPIOptions 之下,分为五个子选项。以下三个承载着日常设置:

oServer.OpenAPIOptions.Endpoint.BasePath        := '/api';
oServer.OpenAPIOptions.Endpoint.ServeSpec       := True;   // /openapi.json
oServer.OpenAPIOptions.Endpoint.ServeSwaggerUI  := True;   // /docs

oServer.OpenAPIOptions.CORS.Enabled             := True;
oServer.OpenAPIOptions.CORS.AllowOrigins        := '*';
oServer.OpenAPIOptions.CORS.AllowHeaders        := 'Content-Type, Authorization';
oServer.OpenAPIOptions.CORS.AllowMethods        := 'GET, POST, PUT, DELETE, PATCH, OPTIONS';

oServer.OpenAPIOptions.Validation.ValidateRequest     := True;
oServer.OpenAPIOptions.Validation.ValidateRequestBody := True;
oServer.OpenAPIOptions.Validation.ValidateQueryParams := True;
oServer.OpenAPIOptions.Validation.ValidatePathParams  := True;
oServer.OpenAPIOptions.Validation.ValidateRequired    := True;

启用验证后,每一个传入请求在到达您的处理器之前都会按照规范中声明的 JSON Schemas 进行检查 — 必填字段、类型、格式、枚举、范围。失败会触发 OnValidationError 事件,并附带错误列表和一个用于接受或拒绝该请求的标志。

事件

请求生命周期由六个事件覆盖:

OnBeforeRequest: 在分派前触发;设置 Accept := False 可以用 403 Forbidden 拒绝。适用于限流、日志记录或按路由的网关。

OnAuthenticate: 在主处理器之前触发;设置 Authenticated := False 可以用 401 Unauthorized 拒绝。检查头、Cookie 或查询参数来决定。

OnValidationError: 验证失败时触发;接收错误列表。设置 Continue := False 可以用 400 Bad Request 拒绝。

OnRequest: 主分派事件。查看 aOperationId,将响应写入 aContext.Response,设置 Handled := True

OnAfterRequest: 在处理器返回后触发 — 非常适合做指标统计或审计日志。

OnException: 如果有未处理的异常从您的处理器中冒出,则触发此事件。如果您希望返回 500 Internal Server Error 以外的内容,请调整 aResponseCode

剩下的两个子选项各有各的作用:Security 为规范声明的 securitySchemes 驱动 OnValidateAPIKeyOnValidateBasicOnValidateBearerMock 则会用规范自带的示例,为一个还没有处理程序的操作生成响应。

演示

sgcOpenAPI 2026.6 附带两个完整的演示,二者都由这一对独立组件承载,因此不需要安装 sgcWebSockets:

升级

如果您目前在 sgcWebSockets 中使用 TsgcWSServer_API_OpenAPI,不会有任何变化。这个类、它的属性和事件全部保留,实现也委托给同一套共享引擎。TsgcWSAPIServer_OpenAPI 正是同一个类对外发布的后代,所以 sgcOpenAPI 改变的只是这个包来自哪里。

sgcOpenAPI 2026.6 将于六月在下载页面提供。

有问题、反馈或需要迁移帮助?联系我们 — 您将得到编写这些代码的人的回复。