qi-web 快速开始:用奇语写一个 Web 服务

几十行 100% 中文关键字的代码,就能跑起一个带路由、查询参数、JSON 响应和中间件的 HTTP 服务。本文每一段代码都用 qi 编译器真正跑通,并用 curl 验证。

qi-web 请求生命周期

为什么用 qi-web

qi-web 是用奇语(Qi)自己写的 Web 框架,使用体验对标 Express / Go Fiber,而热路径(请求解析、路由匹配、响应序列化)下沉到 Rust 加速,实测约 122k RPS。你写的是清爽的中文路由代码,跑的是接近原生的性能。

提示 阅读本文前,建议先看 奇语语言快速开始 掌握基础语法(保留字、FFI 返回值约定等)。本文只讲 qi-web 的用法。

第一个服务(最小可跑)

一个 qi-web 程序的骨架只有四步:创建应用 → 配置监听地址 → 注册路由 → 运行。注意每个注册函数都返回一个新的应用,必须把返回值回写到变量里。

最小服务.qi
包 主程序;

// 从 Web 包按需导入:逐个列出要用的符号
导入 Web::{ 应用, 上下文, 响应, 创建应用, 配置, 获取, 文本, 运行应用 };

// handler 签名固定为 函数(上下文): 响应
函数 主页处理(上下文值: 上下文) : 响应 {
    返回 文本("你好,这里是 qi-web");
}

函数 入口() {
    变量 应用值: 应用 = 创建应用();
    应用值 = 配置(应用值, "127.0.0.1", 43510);  // 用随机高位端口
    应用值 = 获取(应用值, "/", 主页处理);   // 必须回写返回值
    运行应用(应用值);                       // 阻塞,接受连接
}
提示 端口请用 3000 以上的随机高位端口(如 43510 / 6759),不要用 8080 / 3000 / 8000。把主机和端口提取成常量,避免多处硬编码。

路由与参数

路由方法名是中文:获取(GET)、提交(POST)、整体更新(PUT)、部分更新(PATCH)、删除(DELETE)。路径参数写成 {名称},查询参数用 查询参数 / 查询参数整数 取出。

提示 路由路径只能用 ASCII(如 /api/users/{id}),不要在路径里写中文,否则会崩溃。但响应体和 JSON 内容可以是任意中文。
参数.qi
导入 标准库.字符串;

// 查询参数:/api/search?q=关键字&page=2
// 查询参数 取字符串;查询参数整数 取整数并带默认值
函数 搜索(上下文值: 上下文) : 响应 {
    变量 关键字: 字符串 = 查询参数(上下文值, "q");
    变量 页码: 整数 = 查询参数整数(上下文值, "page", 1);
    如果 (字符串::字节长度(关键字) == 0) {
        返回 文本("请带上查询参数 ?q=...");
    }
    返回 JSON("{\"关键字\":\"" + 关键字 + "\",\"页码\":" + 整数转字符串(页码) + "}");
}

路由组让你给一批路由加统一前缀,避免重复写 /api:

路由组.qi
变量 接口组: 路由组 = 创建路由组("/api");
应用值 = 组获取(应用值, 接口组, "/search", 搜索);   // → GET /api/search

JSON 响应

响应助手覆盖常见场景:文本(...) 纯文本、HTML(...)、JSON(...)、重定向(...)。handler 必须返回一个 响应,不能返回裸字符串。读取 POST 请求体用 请求主体。

json_post.qi
// POST 路由:读取请求体并回显成 JSON
函数 创建用户(上下文值: 上下文) : 响应 {
    变量 主体: 字符串 = 请求主体(上下文值);
    如果 (字符串::字节长度(主体) == 0) {
        返回 文本("请在请求体里提交数据");
    }
    返回 JSON("{\"状态\":\"已创建\",\"收到\":\"" + 主体 + "\"}");
}

中间件

内置中间件一行挂载:使用日志(应用值) 打请求日志、使用跨域(应用值) 开 CORS。自定义中间件就是一个函数,签名为 函数(下一步, 上下文): 响应——你在调用 下一步(上下文) 前后做事情,比如计时、补响应头、鉴权,甚至提前返回。

中间件.qi
导入 标准库.时间;

// 先调用 下一步(上下文) 拿到响应,再给响应加一个头
函数 计时中间件(下一步: 函数(上下文): 响应, 上下文值: 上下文) : 响应 {
    变量 开始毫秒: 整数 = 时间.当前毫秒();
    变量 响应值: 响应 = 下一步(上下文值);
    变量 耗时: 整数 = 时间.当前毫秒() - 开始毫秒;
    返回 设置响应头(响应值, "X-耗时毫秒", 整数转字符串(耗时));
}

// 在 入口() 里挂载(注意顺序与回写)
应用值 = 使用日志(应用值);
应用值 = 使用跨域(应用值);
应用值 = 使用中间件(应用值, 计时中间件);
提示 不要把 handler 命名为 首页、获取 这类框架已经导出的名字,否则链接期会因符号撞名而失败。给自己的 handler 起一个不冲突的名字,比如 主页处理。

运行与验证

把上面这些拼到一个文件里(仓库里就是 qi-web/examples/教程_快速开始.qi),用 qi 编译器运行:

终端
$ qi run qi-web/examples/教程_快速开始.qi
服务已启动,监听 http://127.0.0.1:43510
启动服务器: http://127.0.0.1:43510
服务器运行中(并发处理),按 Ctrl+C 优雅关闭

另开一个终端,用 curl 真实打几个请求:

终端 — curl 验证
$ curl -i http://127.0.0.1:43510/
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
X-耗时毫秒: 0
Access-Control-Allow-Origin: *
Connection: keep-alive
Content-Length: 44

你好,这里是 qi-web 快速开始教程

$ curl "http://127.0.0.1:43510/api/search?q=qi&page=2"
{"关键字":"qi","页码":2}

$ curl -X POST -d 'x=1' http://127.0.0.1:43510/api/users
{"状态":"已创建","收到":"x=1"}

注意第一个响应里多出来的 X-耗时毫秒 头——那正是我们的计时中间件加上去的,Access-Control-Allow-Origin: * 则来自 使用跨域。同时服务端会打出请求日志:

终端 — 服务端日志
[请求] GET /
[响应] 200
[请求] GET /api/search
[响应] 200
[请求] POST /api/users
[响应] 200

下一步

你已经能写出一个带路由、参数、JSON 和中间件的 qi-web 服务了。继续深入: