宿主总览
通用主机统一应用启动/停止、配置、日志和 DI;Web 主机在通用主机之上扩展 HTTP 服务器和中间件管道。两者共享同一套底座,不是两套独立体系。
适用场景
- 需要理解 Spire 应用的启动流程、生命周期和优雅关闭机制
- 需要编写后台服务(
IHostedService/BackgroundService) - 需要理解通用主机和 Web 主机的差异与继承关系
- 需要理解中间件管道的洋葱模型和四种中间件注册形式
核心问题
宿主层解决的是"应用怎么跑起来"的问题——不是业务逻辑怎么写,而是启动顺序、生命周期管理、配置加载、DI 组装这些所有程序共享的基础流程。核心问题是:
- 通用主机和 Web 主机的关系 — Web 主机继承通用主机的全部能力,再加 HTTP 层,一个字母都不少
- 中间件管道的工作原理 — 洋葱模型、
next()调用、四种中间件注册形式的适用场景
能力全景
两个主机,一个底座
Spire 提供两层主机抽象。它们不是两套独立的东西——Web 主机继承通用主机的全部能力,再加 HTTP 层。
| 主机 | 一句话概括 | 入口 | 适用场景 |
|---|---|---|---|
| 通用主机 | 统一启动/停止、配置、日志、DI 和生命周期管理 | Host.createBuilder(args) | 控制台工具、后台服务、定时任务——任何非 Web 程序 |
| Web 主机 | 在通用主机之上扩展 HTTP 服务器和中间件管道 | WebHost.createBuilder(args) | Web 应用、API 服务——需要处理 HTTP 请求的程序 |
关键事实:WebHost.createBuilder() 内部与 Host.createBuilder() 执行完全相同的初始化流程(环境识别、配置加载、日志注册),再额外组装 HTTP 层。通用主机的能力全盘继承,一个字母都不少。
通用主机:所有程序的统一底座
没有通用主机时,每个程序都要手搭配置、日志、DI,启动顺序靠程序员自觉,关闭流程没人管。通用主机把这些重复劳动收进三步。
三步跑起一个程序
import soulsoft_extensions_hosting.*
main(args: Array<String>): Int64 {
let builder = Host.createBuilder(args) // ① 创建构建器:自动加载环境、配置、日志、DI
builder.services.addHostedService<MyWorker>() // ② 注册你的服务
let host = builder.build() // ③ 构建:固化容器
host.run() // ④ 启动 + 阻塞等待关闭信号
return 0
}启动流程
host.run() 内部走的是严格顺序。理解这个顺序,你就理解了优雅关闭、生命周期回调在什么时候触发。
start()
├── 1. 打印环境信息
├── 2. 执行启动校验器(IStartupValidator)
├── 3. 注册信号处理器(Linux/macOS: SIGINT/SIGTERM)
├── 4. 启动所有 IHostedService
└── 5. 触发 onStarted 回调
[外部信号: Ctrl+C / SIGTERM]
└── 6. 触发 onStopping 回调
└── 7. stopApplication() 解除阻塞
waitForShutdown()
├── 8. 阻塞直到 stopApplication() 被调用
stop()
├── 9. 停止所有 IHostedService
└── 10. 触发 onStopped 回调
close()
└── 11. 释放 DI 容器托管服务:主机负责生命周期,你负责业务
托管服务(IHostedService / BackgroundService)是通用主机最核心的扩展点。主机在启动时调用 start(),在关闭时调用 stop()——你的业务逻辑只需实现这两个方法。
import soulsoft_extensions_hosting.*
import soulsoft_extensions_logging.*
class DataSyncWorker <: BackgroundService {
private let logger: ILogger
public init(factory: ILoggerFactory) {
logger = factory.createLogger<DataSyncWorker>()
}
public func run(): Unit {
while (!Thread.currentThread.hasPendingCancellation) {
logger.info("同步数据...")
sleep(Duration.second * 30)
}
logger.info("数据同步已停止")
}
}
main(args: Array<String>): Int64 {
let builder = Host.createBuilder(args)
builder.services.addHostedService<DataSyncWorker>()
let host = builder.build()
host.run()
return 0
}要点:addHostedService<T>() 注册为 Singleton,不能直接注入 Scoped 服务。需要 Scoped 时注入 IServiceScopeFactory 手动创建作用域。
完整用法见 通用主机
Web 主机:在通用主机之上加 HTTP
Web 主机继承通用主机的全部初始化流程,然后额外做两件事:启动 HTTP 服务器、组装中间件管道。
三步跑起一个 Web 应用
import soulsoft_web_http.*
import soulsoft_web_hosting.*
main(args: Array<String>): Int64 {
let builder = WebHost.createBuilder(args) // ① 同 Host.createBuilder + 组装 HTTP 层
let app = builder.build() // ② 固化
app.use { context, next => // ③ 添加中间件
context.response.write("Hello, World!")
next(context)
}
app.run() // ④ 启动 HTTP 服务器 + 阻塞
return 0
}中间件管道:洋葱模型
每个 HTTP 请求进入一条管道,逐个穿过中间件。每个中间件在 next() 之前是"请求阶段"(读入水侧),之后是"响应阶段"(读出水侧)。
请求(入水口) 响应(出水口)
│ ▲
▼ │
╔═════╧══════╗ ╔═════╤══════╗
║ 日志段 ║ ║ 日志段 ║
║ 记录方法、 ╞═════ next() ════════╡ 记录状态码 ║
║ 路径 ║ ║ ║
╚═════╤══════╝ ╚═════╤══════╝
│ ▲
│ next() │
▼ │
╔═════╧══════╗ ╔═════╤══════╗
║ 认证段 ║ ║ 认证段 ║
║ Token有效 ╞═════ next() ════════╡ (无操作) ║
║ → 放行 ║ ║ ║
║ Token无效 ║ ║ ║
║ → 关阀门 ║ ║ ║
╚═════╤══════╝ ╚═════╤══════╝
│ ▲
│ ╔═══════════════╗ │
└═══════╣ U 型弯(终端)╠════════════┘
╚═══════════════╝三条规则:
- 每个中间件决定是否调用
next()——调用则继续,不调用则短路 next()之前是请求阶段,之后是响应阶段——同一个中间件可以同时在两个阶段做事- 响应沿调用栈逆序返回——中间件 1 → 2 → 3 进入,3 → 2 → 1 返回
中间件的四种形式
// 形式一:(RequestDelegate) -> RequestDelegate — 初始化与请求处理分离
app.use {
next => { // ① build() 时执行一次(初始化)
let logger = resolveLogger()
return { context => // ② 每个请求执行(处理逻辑)
logger.info("${context.request.path}")
next(context)
}
}
}
// 形式二:(HttpContext, RequestDelegate) -> Unit — 最简形式
app.use { context, next =>
next(context)
}
// 形式三:IMiddleware 实例 — 可复用中间件
app.use(AuthMiddleware())
// 形式四:泛型 — DI 容器自动解析构造函数依赖
app.use<AuthMiddleware>([])完整用法见 Web 主机
能力矩阵与组合效应
| 能力 | 所属主机 | 一句话概括 | 核心类型 |
|---|---|---|---|
| 环境识别 | 通用 | 自动读取环境名,切换配置和日志级别 | IHostEnvironment |
| 配置加载 | 通用 | JSON → 环境变量 → 命令行,后加载覆盖先加载 | IConfiguration (自动注册) |
| 日志系统 | 通用 | 控制台输出 + 配置文件控制过滤规则 | ILoggerFactory (自动注册) |
| DI 容器 | 通用 | 统一注册出入口,构造器自动注入 | ServiceCollection → IServiceProvider |
| 生命周期 | 通用 | onStarted / onStopping / onStopped 三大回调 | IHostApplicationLifetime |
| 托管服务 | 通用 | 主机管生命周期,你写业务逻辑 | IHostedService / BackgroundService |
| 优雅关闭 | 通用 | Ctrl+C → 停止托管服务 → 释放容器,不漏资源 | ConsoleLifetime |
| 启动校验 | 通用 | validateOnBuild / validateScopes 提前暴露 DI 配置错误 | 非生产环境自动开启 |
| HTTP 服务器 | Web | 监听端口,接收请求,驱动管道 | IServer (自动注册) |
| 中间件管道 | Web | 洋葱模型串联请求处理,use() 注册 | RequestDelegate |
| HTTP 上下文 | Web | 请求/响应封装,参数解析 | HttpContext |
| 静态文件根路径 | Web | wwwroot/ 目录 | IWebHostEnvironment.webRootPath |
| 服务器配置 | Web | 超时、请求体大小、TLS 等底层参数 | configureServer() |
组合效应
| 组合 | 涉及能力 | 组合价值 | 示例 |
|---|---|---|---|
| 后台服务 + DI + 配置 | 托管服务 · DI · 配置 | 托管服务通过构造器注入 IOptions<T>,参数从 JSON 读取 | addHostedService<OrderCleanupWorker>() |
| Web 应用 + 生命周期 | Web 主机 · 通用主机生命周期 | 启动后 onStarted 发就绪信号给 K8s,收到 SIGTERM 后 onStopping 排空请求再退出 | host.lifetime.onStarted { signalReady() } |
| 中间件 + DI | 中间件管道 · DI 容器 | use<TMiddleware>() 自动从 DI 解析中间件依赖 | app.use<ExceptionHandlerMiddleware>() |
| 全栈部署 | Web 主机 · 中间件管道 · 静态文件 | 前端构建到 wwwroot/,后端一个进程同时托管 API 和 SPA | useFileServer() + mapControllers() |
无论你写的是后台服务还是 Web 应用,都是同一条公式:
1. createBuilder(args) → 自动加载环境、配置、日志、DI 容器
2. services.addXxx() → 注册你的服务
3. build() → 固化容器
4. (Web 特有)useXxx() → 挂载中间件
5. run() → 启动 + 阻塞等待关闭信号通用主机用步骤 1→2→3→5,Web 主机在步骤 3 和 5 之间多了步骤 4(挂载中间件)。除此之外,完全相同。
推荐阅读顺序
当前边界
- 已覆盖:通用主机的完整生命周期、托管服务模型、环境识别、Web 主机的 HTTP 服务器和中间件管道、四种中间件注册形式
- 未覆盖:第三方日志 Provider 实现、自定义配置源、分布式托管服务编排