Skip to content

SmartWeb 扩展开发

本页面向基于 SmartWeb 开发公共组件、认证模块、响应规范或服务器适配器的下游开发者。普通业务项目优先使用 RenderWebUserProvider 和组合注解;只有接入新的 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 的普通结果分派:

kotlin
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/过程链,适合鉴权失败或重定向。

公共库可以提供 render400render404 等工厂,但应统一设置状态码、Content-Type 和错误体,避免依赖 String 内容猜测。

用户解析:WebUserProvider

WebUserProvider 在服务器建立 WebActionContext 时解析用户,其结果写入 Context,并供权限过程使用:

kotlin
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 的路径属性:

kotlin
@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 解析,必须使用 GETPOSTPUTDELETEPATCH 等枚举名称且区分大小写。

这类注解只封装“路径 + 方法”。若要同时实现鉴权、事务或审计,应组合 Controller 过程注解,而不是修改 WebControllerLoader

模板引擎

自定义模板实现需要提供:

kotlin
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 的公开构造器:

kotlin
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 路由,或明确拒绝不支持的能力
    }
}

配置使用实现类全限定名:

properties
webServer.impl=com.example.web.JettyServer

WebApp 通过反射调用 new WebServerConfig(...),不是从 DI 获取服务器。命名服务器可分别使用 webServer.<name>.impl

Request / Response 适配责任

适配器必须把底层对象映射到 RequestResponse,然后调用 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 端到端测试。

发布下游扩展

建议扩展模块包含:

text
my-smartweb-extension/
├─ build.gradle.kts
├─ src/main/kotlin/...
├─ src/main/resources/conf/module/my-extension.properties
└─ src/test/kotlin/...IntegrationTest.kt

模块配置只放默认实现和扫描包,允许应用配置覆盖。不要把端口、密钥或环境地址写入 conf/module

发布前至少验证:

  1. 只引入扩展依赖即可被扫描和注册。
  2. 多实现、命名服务器与配置覆盖行为明确。
  3. 启停两次不会遗留端口、线程或临时文件。
  4. 401/403/500 不泄露 Token、堆栈或内部对象。
  5. 扩展声明支持的 SmartWeb、Rain 和服务器模块版本一致。

基于 Apache License 2.0 发布