SmartWeb 扩展开发
本页面向基于 SmartWeb 开发公共组件、认证模块、响应规范或服务器适配器的下游开发者。普通业务项目优先使用 Render、WebUserProvider 和组合注解;只有接入新的 HTTP 运行时才继承 InternalWebServer。
扩展点地图
| 扩展点 | 适用场景 | 注册方式 | 稳定性建议 |
|---|---|---|---|
Render | 统一响应、重定向、错误页 | Action 返回或 render() 提前结束 | 首选 |
WebUserProvider | 从 Session、Cookie、Token 建立用户 | 实现类交给 Rain DI,必要时配置命名 Bean | 首选 |
@RequestMethods | 封装组织内的 HTTP Action 注解 | 元注解,无需 Loader | 首选 |
Controller ProcessProvider | 鉴权、审计、限流等调用过程 | Rain Controller 扩展机制 | 首选 |
TempleEngine | 接入服务端模板实现 | @AutoBind 收集实现 | 实验性 |
WsAction / KWsActionCreator | 封装 WebSocket 协议 | @NewWs Action 返回实例 | 服务器相关 |
InternalWebServer | 适配 Netty、Jetty 等 HTTP 服务器 | webServer.impl 指定实现类 | 高成本 |
Request / Response | 把底层 HTTP 对象适配给 SmartWeb | 由服务器适配器创建 | 高成本 |
选择原则:能用过程链解决的不要改服务器,能用 Render 解决的不要覆写响应主流程。
统一响应:Render
Render 可以直接完成响应,也可以返回一个值交回 SmartWeb 的普通结果分派:
fun accepted(body: Any): Render = Render { context, _ ->
context.resp.status = 202
context.resp.addHeader("X-Request-Id", requestId())
body
}
@PostAction("jobs")
fun createJob(): Render = accepted(jobService.create())- 返回
null:认为 Render 已完成处理,框架不再序列化内容;实现必须自行调用 Response 写出方法。 - 返回非 null:结果重新进入
buildResult,可以继续使用 JSON、String、File 等内置分派。 - 调用
render.render():通过ActionResult提前结束当前 Action/过程链,适合鉴权失败或重定向。
公共库可以提供 render400、render404 等工厂,但应统一设置状态码、Content-Type 和错误体,避免依赖 String 内容猜测。
用户解析:WebUserProvider
WebUserProvider 在服务器建立 WebActionContext 时解析用户,其结果写入 Context,并供权限过程使用:
class BearerUserProvider(
private val tokenService: TokenService,
) : WebUserProvider {
override fun invoke(context: WebActionContext): IUser? {
val token = context.req.header("Authorization")?.value
?.removePrefix("Bearer ")
?: return null
return tokenService.verify(token)
}
}接口带有 @AutoBind。实现类必须位于 rain.scanPackages 范围内;存在多个实现时,应按 Rain DI 的命名 Bean 规则提供 SmartWeb 所读取的实现,避免依赖不确定的集合顺序。
Provider 只负责身份建立,不应同时执行业务授权。权限判断放入 Before/ProcessProvider,并让返回用户实现 IUser.checkPermission。解析失败返回 null;需要区分“未登录”和“Token 非法”时,可把错误写入 Context,再由统一过程转换为 401。
自定义 Action 注解
@RequestMethods 可以组合组织内语义化注解。目标注解必须提供名为 value、类型为 String 的路径属性:
@RequestMethods("POST", "PUT")
@Target(AnnotationTarget.FUNCTION)
annotation class WriteAction(val value: String = "")
@WriteAction("profiles/{id}")
fun saveProfile(id: Long, body: ProfileInput): Profile = TODO()WebControllerLoader 会读取元注解,并反射调用 value()。缺少该属性或属性类型不正确会在启动加载时失败。HTTP 方法字符串最终通过 HttpMethod.valueOf 解析,必须使用 GET、POST、PUT、DELETE、PATCH 等枚举名称且区分大小写。
这类注解只封装“路径 + 方法”。若要同时实现鉴权、事务或审计,应组合 Controller 过程注解,而不是修改 WebControllerLoader。
模板引擎
自定义模板实现需要提供:
class MarkdownTempleEngine : TempleEngine {
override fun start(isDevMode: Boolean) = Unit
override fun close() = Unit
override fun getTemple(path: String): Temple? {
val source = load(path) ?: return null
return object : Temple {
override fun invoke(context: WebActionContext): String =
renderMarkdown(source, context.saves)
}
}
}多个引擎按注入列表顺序查找,首个返回非 null 的模板获胜。路径约定、Accept 条件和当前未接通的 start/close 限制见模板引擎。
Controller 模板是在 Loader 阶段解析的,而 ApplicationService.start 晚于所有 Loader,因此普通 ApplicationService 无法把初始化提前到模板发现之前。当前版本若必须扩展模板,只能让引擎在构造阶段完成发现所需初始化,再由单独的 ApplicationService 负责关闭;更稳妥的做法是先在 SmartWeb 主流程补齐模板引擎生命周期接线。
自定义服务器适配器
服务器实现必须提供接收 WebServerConfig 的公开构造器:
class JettyServer(config: WebServerConfig) : InternalWebServer(config) {
override val pool: CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
override fun start() {
// 启动底层服务器;收到请求后构造 Request/Response,调用 onRequest
}
override fun stop() {
// 停止监听、等待连接关闭并释放 pool
}
override fun createWsAction(path: String, action: WsAction) {
// 注册 WebSocket 路由,或明确拒绝不支持的能力
}
}配置使用实现类全限定名:
webServer.impl=com.example.web.JettyServerWebApp 通过反射调用 new WebServerConfig(...),不是从 DI 获取服务器。命名服务器可分别使用 webServer.<name>.impl。
Request / Response 适配责任
适配器必须把底层对象映射到 Request 和 Response,然后调用 onRequest(req, resp)。至少验证:
- URL、path、method、host、scheme、query 与重复参数。
- Header/Cookie 的大小写、重复值和编码。
- JSON Object、JSON Array、multipart、原始 InputStream 的能力边界。
- Response Header 写出时机、重复写入、Content-Length 和流关闭。
- Session Cookie、CORS、OPTIONS、HEAD 与不允许的方法。
- 上传总量、单文件、临时文件阈值和临时目录清理。
- 异常、客户端断开、协程取消和服务器停止时的资源释放。
不要用虚假的空数组或 null 静默表示实现支持某能力;应在模块文档中列出差异,并为支持矩阵编写 HTTP 端到端测试。
发布下游扩展
建议扩展模块包含:
my-smartweb-extension/
├─ build.gradle.kts
├─ src/main/kotlin/...
├─ src/main/resources/conf/module/my-extension.properties
└─ src/test/kotlin/...IntegrationTest.kt模块配置只放默认实现和扫描包,允许应用配置覆盖。不要把端口、密钥或环境地址写入 conf/module。
发布前至少验证:
- 只引入扩展依赖即可被扫描和注册。
- 多实现、命名服务器与配置覆盖行为明确。
- 启停两次不会遗留端口、线程或临时文件。
- 401/403/500 不泄露 Token、堆栈或内部对象。
- 扩展声明支持的 SmartWeb、Rain 和服务器模块版本一致。