Deno 入门教程 / 第 10 章:HTTP 服务器
第 10 章:HTTP 服务器
10.1 Deno 原生 HTTP 服务器
Deno 内置了高性能的 HTTP 服务器 API,无需任何第三方依赖。
基本用法
// 原生 Deno.serve API
Deno.serve({ port: 8000 }, (req: Request) => {
return new Response("Hello, Deno!");
});
console.log("服务器运行在 http://localhost:8000");
使用标准库 serve
import { serve } from "jsr:@std/http";
serve((req: Request) => {
const url = new URL(req.url);
if (url.pathname === "/") {
return new Response("首页", {
headers: { "content-type": "text/html; charset=utf-8" },
});
}
if (url.pathname === "/api/time") {
return Response.json({ time: new Date().toISOString() });
}
return new Response("404 Not Found", { status: 404 });
}, { port: 8000 });
路由处理
Deno.serve(async (req: Request) => {
const url = new URL(req.url);
const method = req.method;
// GET 请求
if (method === "GET" && url.pathname === "/users") {
return Response.json([
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
]);
}
// POST 请求
if (method === "POST" && url.pathname === "/users") {
const body = await req.json();
return Response.json({ id: 3, ...body }, { status: 201 });
}
// 路径参数
const userMatch = url.pathname.match(/^\/users\/(\d+)$/);
if (method === "GET" && userMatch) {
const id = parseInt(userMatch[1]);
return Response.json({ id, name: `User ${id}` });
}
return new Response("Not Found", { status: 404 });
});
优雅关机
const ac = new AbortController();
Deno.serve({
port: 8000,
signal: ac.signal,
onListen({ port }) {
console.log(`服务器启动在端口 ${port}`);
},
onShutdown() {
console.log("服务器关闭中...");
},
}, (req) => new Response("Hello!"));
// 捕获 SIGINT 信号,优雅关闭
Deno.addSignalListener("SIGINT", () => {
console.log("收到 SIGINT,正在关闭...");
ac.abort();
Deno.exit(0);
});
10.2 Oak 框架
Oak 是 Deno 上最流行的 Web 框架,类似 Node.js 的 Koa。
基本应用
import { Application, Router } from "jsr:@oak/oak";
const app = new Application();
const router = new Router();
// 定义路由
router.get("/", (ctx) => {
ctx.response.body = "Hello, Oak!";
ctx.response.type = "text/html";
});
router.get("/api/users", (ctx) => {
ctx.response.body = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
];
});
router.post("/api/users", async (ctx) => {
const body = await ctx.request.body.json();
ctx.response.status = 201;
ctx.response.body = { id: 3, ...body };
});
// 注册路由
app.use(router.routes());
app.use(router.allowedMethods());
// 启动服务器
app.listen({ port: 8000 });
console.log("Oak 服务器运行在 http://localhost:8000");
Oak 中间件
import { Application, Context, Next } from "jsr:@oak/oak";
const app = new Application();
// 中间件 1:日志记录
app.use(async (ctx: Context, next: Next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
console.log(`${ctx.request.method} ${ctx.request.url.pathname} - ${ms}ms`);
});
// 中间件 2:错误处理
app.use(async (ctx: Context, next: Next) => {
try {
await next();
} catch (err) {
console.error("服务器错误:", err);
ctx.response.status = 500;
ctx.response.body = { error: "服务器内部错误" };
}
});
// 中间件 3:CORS
app.use(async (ctx: Context, next: Next) => {
ctx.response.headers.set("Access-Control-Allow-Origin", "*");
ctx.response.headers.set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
ctx.response.headers.set("Access-Control-Allow-Headers", "Content-Type, Authorization");
if (ctx.request.method === "OPTIONS") {
ctx.response.status = 204;
return;
}
await next();
});
// 中间件 4:认证
app.use(async (ctx: Context, next: Next) => {
const authHeader = ctx.request.headers.get("Authorization");
if (!authHeader?.startsWith("Bearer ")) {
ctx.response.status = 401;
ctx.response.body = { error: "未授权" };
return;
}
ctx.state.user = { token: authHeader.slice(7) };
await next();
});
路由参数
const router = new Router();
// 路径参数
router.get("/users/:id", (ctx) => {
const id = ctx.params.id;
ctx.response.body = { id, name: `User ${id}` };
});
// 正则参数
router.get(/^\/files\/(.+)$/, (ctx) => {
const filepath = ctx.matches[1];
ctx.response.body = { path: filepath };
});
// 多个参数
router.get("/users/:userId/posts/:postId", (ctx) => {
const { userId, postId } = ctx.params;
ctx.response.body = { userId, postId };
});
静态文件服务
import { Application, send } from "jsr:@oak/oak";
const app = new Application();
app.use(async (ctx) => {
await send(ctx, ctx.request.url.pathname, {
root: "./public",
index: "index.html",
});
});
10.3 Hono 框架
Hono 是一个轻量、高性能的 Web 框架,支持多运行时(Deno、Node.js、Bun、Cloudflare Workers 等)。
基本应用
import { Hono } from "jsr:@hono/hono";
const app = new Hono();
app.get("/", (c) => c.text("Hello, Hono!"));
app.get("/json", (c) => c.json({ message: "Hello" }));
app.post("/users", async (c) => {
const body = await c.req.json();
return c.json({ id: 1, ...body }, 201);
});
Deno.serve(app.fetch);
Hono 路由
import { Hono } from "jsr:@hono/hono";
const app = new Hono();
// 路径参数
app.get("/users/:id", (c) => {
const id = c.req.param("id");
return c.json({ id });
});
// 查询参数
app.get("/search", (c) => {
const q = c.req.query("q");
const page = c.req.query("page") || "1";
return c.json({ query: q, page });
});
// 路由分组
const api = new Hono();
api.get("/users", (c) => c.json([]));
api.get("/posts", (c) => c.json([]));
app.route("/api", api);
// 嵌套路由组
const v1 = new Hono();
v1.get("/users", (c) => c.json({ version: 1 }));
const v2 = new Hono();
v2.get("/users", (c) => c.json({ version: 2 }));
app.route("/api/v1", v1);
app.route("/api/v2", v2);
Hono 中间件
import { Hono } from "jsr:@hono/hono";
import { cors } from "jsr:@hono/hono/cors";
import { logger } from "jsr:@hono/hono/logger";
import { basicAuth } from "jsr:@hono/hono/basic-auth";
const app = new Hono();
// 内置中间件
app.use("*", logger());
app.use("*", cors());
// 需要认证的路由
app.use("/admin/*", basicAuth({
username: "admin",
password: "secret",
}));
// 自定义中间件
app.use("*", async (c, next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
c.header("X-Response-Time", `${ms}ms`);
});
app.get("/", (c) => c.text("Hello"));
app.get("/admin/dashboard", (c) => c.text("Admin Panel"));
Hono 错误处理
import { Hono, HTTPException } from "jsr:@hono/hono";
const app = new Hono();
// 全局错误处理
app.onError((err, c) => {
console.error(err);
if (err instanceof HTTPException) {
return c.json({ error: err.message }, err.status);
}
return c.json({ error: "服务器内部错误" }, 500);
});
// 404 处理
app.notFound((c) => {
return c.json({ error: "页面未找到" }, 404);
});
app.get("/users/:id", (c) => {
const id = c.req.param("id");
if (id === "0") {
throw new HTTPException(404, { message: "用户不存在" });
}
return c.json({ id });
});
10.4 Oak vs Hono 对比
| 特性 | Oak | Hono |
|---|---|---|
| 风格 | Koa 风格 | Express 风格 |
| 中间件 | 洋葱模型(async/await) | 洋葱模型 |
| 路由 | Router 类 | 内置 |
| 生态 | Deno 专用 | 多运行时 |
| 体积 | 较大 | 极轻量 |
| 内置中间件 | 少 | 丰富(cors, auth, jwt 等) |
| 适合场景 | 大型应用 | 微服务、API |
10.5 实战:RESTful API
使用 Hono 构建 CRUD API
import { Hono } from "jsr:@hono/hono";
interface Todo {
id: number;
title: string;
completed: boolean;
}
const app = new Hono();
let todos: Todo[] = [];
let nextId = 1;
// 获取所有
app.get("/todos", (c) => {
return c.json(todos);
});
// 获取单个
app.get("/todos/:id", (c) => {
const id = parseInt(c.req.param("id"));
const todo = todos.find(t => t.id === id);
if (!todo) return c.json({ error: "未找到" }, 404);
return c.json(todo);
});
// 创建
app.post("/todos", async (c) => {
const { title } = await c.req.json();
const todo: Todo = { id: nextId++, title, completed: false };
todos.push(todo);
return c.json(todo, 201);
});
// 更新
app.put("/todos/:id", async (c) => {
const id = parseInt(c.req.param("id"));
const index = todos.findIndex(t => t.id === id);
if (index === -1) return c.json({ error: "未找到" }, 404);
const body = await c.req.json();
todos[index] = { ...todos[index], ...body };
return c.json(todos[index]);
});
// 删除
app.delete("/todos/:id", (c) => {
const id = parseInt(c.req.param("id"));
todos = todos.filter(t => t.id !== id);
return c.body(null, 204);
});
Deno.serve(app.fetch);
10.6 模板渲染
简单 HTML 模板
function renderTemplate(title: string, content: string): string {
return `<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>${title}</title>
<style>
body { font-family: sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
</style>
</head>
<body>
<h1>${title}</h1>
${content}
</body>
</html>`;
}
Deno.serve((req) => {
const url = new URL(req.url);
if (url.pathname === "/") {
const html = renderTemplate("欢迎", `
<p>这是一个 Deno Web 应用</p>
<a href="/about">关于</a>
`);
return new Response(html, {
headers: { "content-type": "text/html; charset=utf-8" },
});
}
return new Response("404", { status: 404 });
});
10.7 HTTPS 服务器
const cert = await Deno.readTextFile("./cert.pem");
const key = await Deno.readTextFile("./key.pem");
Deno.serve({
port: 443,
cert,
key,
}, (req) => {
return new Response("Hello, HTTPS!");
});
10.8 本章小结
| 工具 | 说明 | 适用场景 |
|---|---|---|
Deno.serve | 原生 API | 简单服务、学习 |
@std/http | 标准库 | 中等复杂度 |
Oak | Koa 风格框架 | 大型应用、企业项目 |
Hono | 轻量框架 | API 服务、微服务、多运行时 |
📖 扩展阅读
下一章:第 11 章:数据库操作 → 学习在 Deno 中使用数据库。