一个 Delphi 组件,把 OpenAPI 文档变成一个运行中、经过校验、自文档化的 REST 服务器。
TsgcWSAPIServer_OpenAPI,声明在 sgcWebSocket_Server_API_OpenAPI 单元中
把 Server 赋值为一个 TsgcHTTPServer、TsgcHTTPRESTServer 或 TsgcWebSocketHTTPServer。端口、绑定和 TLS 都由宿主服务器负责。
OpenAPI 3.0 和 3.1 文档,通过 LoadFromFile 和 LoadFromString 以 JSON 形式读取
从已有的文档走规范优先,或者从带特性标注的 Delphi 类走代码优先。代码优先需要 Delphi XE7 或更高版本。
随 sgcOpenAPI 一起提供。在 sgcWebSockets 中属于 Enterprise 版本,位于 SGC OpenAPI 面板页。
/openapi.json 提供文档,/docs 提供 Swagger UI,两者都在 OpenAPIOptions.Endpoint 中开启
同一个组件支持两种模式。从一份 JSON 契约开始,或者在 Delphi 中描述 API,让扫描器为你生成文档。
加载 petstore.json,用 LoadFromFile,在 OnRequest 内部按 operation id 分发,然后开始服务。路由、路径与查询参数绑定以及校验全部来自契约,你只需要编写业务逻辑。
最适合:拥有共享设计契约的团队、API-led 集成,或者将规范作为真理之源的多语言后端。
用 sgcServiceContract、sgcRoute、sgcHttpGet 以及 sgcFromPath / sgcFromQuery / sgcFromBody 参数特性标注一个普通的 Delphi 类。TsgcOpenAPICodeFirstScanner.GenerateSpec 会根据类的 RTTI 构建 OpenAPI 文档,你把它交给 LoadFromString,同一个 /openapi.json 端点就会发布它。
最适合:快速原型设计、内部服务,或将现有的 TIdHTTPServer / DataSnap REST 接口迁移到自文档化的 API。
创建组件,加载一份文档,把它挂接到一个 HTTP 服务器上。这就是全部配置。
uses sgcHTTP_Server, sgcHTTP_OpenAPI_Server, sgcWebSocket_Server_API_OpenAPI; procedure TForm1.FormCreate(Sender: TObject); begin FServer := TsgcHTTPServer.Create(Self); FServer.Port := 8080; FOpenAPI := TsgcWSAPIServer_OpenAPI.Create(Self); FOpenAPI.LoadFromFile('petstore.json'); // any OpenAPI 3.x document FOpenAPI.OpenAPIOptions.Endpoint.ServeSpec := True; FOpenAPI.OpenAPIOptions.Endpoint.ServeSwaggerUI := True; FOpenAPI.OnRequest := OpenAPIRequest; FOpenAPI.Server := FServer; // Server is the switch, there is no Active FServer.Active := True; end; // one event, dispatched by operation id procedure TForm1.OpenAPIRequest(Sender: TObject; const aOperationId: string; const aContext: TsgcOpenAPIServerContext; var Handled: Boolean); begin Handled := True; if aOperationId = 'getPetById' then aContext.RespondJSON(200, FPets.Values[aContext.PathParamAsString('petId')]) else Handled := False; end;
开箱即得的功能:
GET /pets/{petId} 会到达上面的处理程序,此时 aOperationId 为 getPetById,
GET /openapi.json 返回你加载的文档,
GET /docs 打开 Swagger UI。
OpenAPIOptions.Endpoint.BasePath 可以把整个接口移到某个前缀之下,TLS 和 HTTP/2 都由宿主服务器提供。
在 OpenAPI 文档中声明的参数会通过单一的类型化上下文读取并转换。开启校验后,错误的类型会在你的处理程序运行之前就返回 400 Bad Request。
// spec snippet // /pets: // get: // operationId: listPets // parameters: // - name: limit in: query schema: { type: integer, maximum: 100 } // - name: status in: query schema: { type: string, enum: [available, pending, sold] } // - name: X-Tenant-Id in: header required: true procedure TForm1.HandleListPets(const aContext: TsgcOpenAPIServerContext); var vLimit: Integer; vStatus: string; vTenant: string; begin vLimit := aContext.QueryParamAsInteger('limit', 20); // default 20 vStatus := aContext.QueryParamAsString ('status', 'available'); vTenant := aContext.HeaderValue ('X-Tenant-Id'); // required in the spec aContext.RespondJSON(200, PetRepo.List(vTenant, vStatus, vLimit)); end;
每一个传入的请求都会对照文档声明的 schema 进行检查。校验失败时会返回一份 RFC 7807 风格的问题文档,列出每一项错误,并且除非你特意允许,否则请求不会到达你的处理程序。
type、required、properties 和 additionalProperties,enum 和 const,minLength / maxLength,pattern,minimum / maximum 及其排他形式,multipleOf,items,minItems / maxItems,uniqueItems,nullable,not,以及 oneOf / anyOf / allOf。format 关键字会对 date、date-time、email、ipv4、uri 和 uuid 强制执行。
Validation.ValidateRequest 是总开关,单独打开就会校验所有范围。用 ValidateRequestBody、ValidateQueryParams、ValidatePathParams、ValidateHeaderParams 和 ValidateCookieParams 来缩小范围。无论选择哪个范围,EnforceRequired 始终生效。
OnValidationError 会把 operation id 和完整的失败列表交给你。它的 Continue 标志到达时是 False,所以除非你特意把它设为 True,否则请求会被拒绝。加载完成后,Validation.Warnings 会列出文档中用到但未被强制执行的每一个 schema 关键字,空列表就意味着没有遗漏。
{
"type": "about:blank",
"title": "Bad Request",
"status": 400,
"detail": "Request validation failed",
"errors": [
"/email: invalid email format",
"/age: must be <= 120",
"/status: value not in enum"
]
}
把 Security.EnforceSecurity 设为开启后,文档声明的 securitySchemes 就会应用到传入的请求上。你只需编写凭据查找逻辑,组件负责解析请求,并在查找结果为否时返回 401 或 403。
从 header、查询参数或 cookie 中读取,具体取决于该方案的声明。OnValidateAPIKey 会收到方案、名称、位置和 key,并通过 Valid 返回结果。
Authorization 请求头已经替你解析好。OnValidateBasic 会收到用户名和密码,并通过 Valid 返回结果。凭据永远不会写入日志。
Security.JWTSecret 用于验证令牌。原样使用即视为 HMAC 密钥,包含 -----BEGIN 的值则视为 PEM 公钥。ValidateExpiration、Issuer 和 Audience 会检查这些声明。
把 JWTSecret 留空,令牌就只会检查是否存在,这样 OnValidateBearer 就可以把它交给你自己的令牌服务,并通过 Valid 返回结果。
校验失败的请求会返回 401,如果请求已经通过身份验证只是权限不够,则返回 403。OnAuthenticate 最先运行,一旦你清除 Authenticated,就会以 401 拒绝请求。
Mock.Enabled 会用文档自带的示例和 schema,为一个还没有处理程序的操作生成响应,并附带 Mock.StatusCode,这样前端团队就能在实现完成之前先动手。
FOpenAPI.OpenAPIOptions.Security.EnforceSecurity := True; FOpenAPI.OpenAPIOptions.Security.JWTSecret := GetSecretFromEnvironment; FOpenAPI.OpenAPIOptions.Security.ValidateExpiration := True; FOpenAPI.OpenAPIOptions.Security.Issuer := 'https://auth.example.com'; FOpenAPI.OpenAPIOptions.Security.Audience := 'api.example.com'; FOpenAPI.OnValidateBearer := OpenAPIValidateBearer; procedure TForm1.OpenAPIValidateBearer(Sender: TObject; const aToken: string; const aContext: TsgcOpenAPIServerContext; var Valid: Boolean); begin Valid := MyTokenService.Verify(aToken); end;
无需外部依赖、无需 Node.js,部署流程中也不必构建文档。组件会自己生成这个页面,并读取你的服务器实际提供的文档。
你加载的文档,在 Endpoint.ServeSpec 开启时提供服务,始终与服务器实际路由的内容保持一致。把任何客户端生成器指向这个 URL 都可以,包括 sgcOpenAPI。
交互式 Swagger UI 页面,在 Endpoint.ServeSwaggerUI 开启时提供服务。可以试用操作、浏览 schema、查看示例,全部数据都来自你正在运行的服务器。
该页面默认从公共 CDN 加载 CSS 和 JavaScript。Endpoint.SwaggerUIBaseURL 可以固定版本,Endpoint.SwaggerUIAssetsPath 则可以让 swagger-ui.css 和 swagger-ui-bundle.js 从本地目录提供服务,这样即使在完全离线的机器上也能工作。
五个持久化的子对象,全部可见于对象检查器,也都可以在运行时赋值。
BasePath 会给每个路由和两个内置端点都加上前缀。ServeSpec 和 ServeSwaggerUI 用于开关它们。SpecFile 是懒加载的,只会在既不是这两者的第一个请求上加载,所以当文档必须从第一次调用起就完整时,请使用 LoadFromFile。
ValidateRequest 加上五个范围开关,以及 EnforceRequired。每次加载后,Warnings 会报告文档用到但该校验器未强制执行的 schema 关键字。
Enabled、AllowOrigins、AllowHeaders 和 AllowMethods。引擎只在其文档拥有的路径上盖这些响应头,所以要给宿主服务器拥有的路径设置相同的值。
EnforceSecurity、JWTSecret、ValidateExpiration、Issuer 和 Audience。内置检查无法判定的一切都会交给 OnValidateAPIKey、OnValidateBasic 或 OnValidateBearer。
Enabled 和 StatusCode。没有处理程序的操作会用文档自带的示例和 schema 来回答,这样契约在实现完成之前也能调用。
把 Handled 留在 False,引擎就会返回 501 Not Implemented 并指明该操作,而不是看起来像路由出错的 404。
TsgcWSAPIServer_OpenAPI 挂接到承载你的 WebSocket 端点、AI/LLM 流和静态文件的同一个 sgcWebSockets HTTP 服务器上。一个端口、一份 TLS 证书、一条日志流。
这里没有 Active 属性。给 Server 赋值就会挂接组件,把它设为 nil 就会分离,宿主服务器全程保持运行不受影响。分离之后,其文档拥有的路径会直接落到你原来的处理程序上。
每个请求都会先交给这个组件,它只回答其文档声明的路径。其余的请求照旧到达 OnCommandGet,所以一个契约优先的区域可以和手写的路由以及 DocumentRoot 提供的静态内容共存于同一个端口上。
端口、绑定、证书和 HTTP/2 协商都属于宿主服务器,所以 REST 接口会原样继承它们。把它挂接到一个 TsgcHTTPRESTServer 上,该服务器的 CORS、指标、健康检查和租户功能也同样适用。
带版本、契约测试,自动生成的 SDK 可供客户从 /openapi.json 下载。
能挺过重构的服务间契约 — 规范就是集成测试。
边缘设备从同一个 Delphi 二进制文件中既暴露文档化的 REST 控制面,又暴露 MQTT 或 WebSocket 遥测接口。
每个供应商的 webhook 载荷都变成类型化的 Pascal 记录 — Stripe、GitHub、Twilio、Slack — 内置校验与幂等性。
在不重写业务逻辑的前提下,把老旧的 DataSnap 或 RemObjects 后端包装在干净的 OpenAPI 接口之后。
把两三个上游 API 聚合到一份贴合消费者形态的规范背后 — 你的 SPA 或移动 App 只与一个类型化的端点对话。
把任意外部规范加载到与服务器使用的同一模型中 — 相同的校验、相同的类型系统、相同的安全原语。
1,195+ 个为 AWS、Azure、GCP、Stripe、GitHub、Kubernetes 等生成的 SDK — 你的服务器可以用同一族组件调用任何一个。
WebSocket、MQTT、AMQP、WebRTC、AI/LLM、IoT — HTTP 服务器能与你的 REST 接口一同承载的一切。
用 XAdES / PAdES / CAdES 对请求和响应体进行签名,面向受监管行业 — 每个操作都具有 eIDAS 级别的完整性。