缘由
最近看到 DIYgod 大佬写的一篇文章:一个六岁开源项目的崩溃与新生,文中提到了他用 Homo Hono 框架替换了 RSSHub 项目原本使用的 Koa 框架。巧了,我正好也有几个项目在使用 Koa
框架,但是这也是第一次听说这个新框架,于是就打算学习一下看看,能不能用 Hono
框架来替换掉这个已经不太活跃的老框架。
安装
安装是必不可少的,在这里使用 pnpm 来安装:
pnpm create hono my-app
在选择完基本信息后,进入安装目录安装依赖并运行:
cd my-app
pnpm install
pnpm dev
程序将会默认运行在 3000
端口,若出现占用情况,可自行更换端口。( 3000
端口可太容易被占用了 )
请求和响应
Hono
基本上支持所有的请求类型,并且可返回多种类型的响应。
基本类型
常用的请求类型比如 GET
、POST
、PUT
、DELETE
请求:
import { serve } from "@hono/node-server";
import { Hono } from "hono";
const app = new Hono();
// GET
app.get("/", (c) => c.text("Hello Hono!"));
// POST
app.post("/", (c) => c.text("Hello Hono!"));
// PUT
app.put("/", (c) => c.text("Hello Hono!"));
// DELETE
app.delete("/", (c) => c.text("Hello Hono!"));
同时,Hono 也支持链式路线,这样能使代码更加清晰:
app
.get("/test", (c) => {
return c.text("GET /test");
})
.post((c) => {
return c.text("POST /test");
})
.put((c) => {
return c.text("PUT /test");
});
.delete((c) => {
return c.text("DELETE /test");
});
携带参数
在使用请求时,通常会携带参数,比如你想发送一个附带路径和参数的 GET 请求:
GET 127.0.0.1:3000/posts/114514?username=homo
那么你可以使用 req.param()
方法来获取路径参数,req.query()
方法来获取携带参数:
app.get("/posts/:id", (c) => {
// 获取路径参数
const id = c.req.param("id");
// 获取携带参数
const username = c.req.query("username");
return c.text(`获取由 ${username} 用户发表的 ID 为 ${id} 的文章`);
// 获取由 homo 用户发表的 ID 为 114514 的文章
});
返回 HTML
Hono
也支持返回 HTML 类型的响应,你需要先定义一个 JSX
组件,例如:
const View = () => {
return (
<html>
<body>
<h1>Hello Hono!</h1>
</body>
</html>
);
};
export default View;
然后便可在接口处返回:
app.get("/home", (c) => {
return c.html(Home());
});
中间件
CORS
CORS
算是后端服务不可缺少的设置了,负责允许设置跨域资源共享( CORS )的响应头,以允许跨域请求。
import { Hono } from "hono";
import { cors } from "hono/cors";
const app = new Hono();
// 应用到所有路由
app.use("*", cors());
// 单独对某个接口设置
app.use(
"/api2/*",
cors({
origin: "http://example.com",
allowHeaders: ["X-Custom-Header", "Upgrade-Insecure-Requests"],
allowMethods: ["POST", "GET", "OPTIONS"],
exposeHeaders: ["Content-Length", "X-Kuma-Revision"],
maxAge: 600,
credentials: true,
}),
);
CORS 参数
参数 | 类型 | 默认值 | 描述 |
---|---|---|---|
origin | string | string[] | (origin: string) => string | * | Access-Control-Allow-Origin 的值。可以是一个确定的字符串,一个字符串数组,或是一个回调函数,如 origin: (origin) => (origin.endsWith('.example.com') ? origin : 'http://example.com') |
allowMethods | string[] | ['GET', 'HEAD', 'PUT', 'POST', 'DELETE', 'PATCH'] | Access-Control-Allow-Methods 的值,标明允许哪些 HTTP 请求方法。 |
allowHeaders | string[] | [] | Access-Control-Allow-Headers 的值,标明在请求中允许设置哪些 HTTP 头部 |
maxAge | number | - | Access-Control-Max-Age 的值,指定预检请求的结果能够被缓存多长时间。 |
credentials | boolean | - | Access-Control-Allow-Credentials 的值,标明是否允许请求的凭证模式(如Cookies) |
exposeHeaders | string[] | [] | Access-Control-Expose-Headers 的值,列出哪些头部可以被客户端 JavaScript 代码访问 |
CSRF
CSRF
是一种攻击手段,攻击者通过欺骗用户的浏览器,让其代为发起恶意请求,通常是在用户不知情的情况下,使用用户的登录身份执行一些操作。CSRF 攻击发生在第三方网站上,但是请求是发给受信任站点的,所以请求会带上用户的认证信息,如 Cookie。为了防止 CSRF 攻击,开发人员需要确保安全地验证用户请求的来源。
import { Hono } from "hono";
import { csrf } from "hono/csrf";
const app = new Hono();
// 使用 origin 选项指定允许的来源
app.use(csrf({ origin: "myapp.example.com" }));
// 使用 origin 选项指定多个允许的来源
app.use(
csrf({
origin: ["www.example.com", "development.example.com"],
}),
);
同时还可以用函数的方式来进行匹配:
DANGER
强烈建议验证协议以确保与 $ 匹配,请永远不要使用 * 进行匹配
app.use(
"*",
csrf({
// 使用正则表达式检查来源 URL 是否匹配指定的模式
origin: (origin) => /https:\/\/(\w+\.)?myapp\.example\.com$/.test(origin),
}),
);
未完待续...