强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

第 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 对比

特性OakHono
风格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标准库中等复杂度
OakKoa 风格框架大型应用、企业项目
Hono轻量框架API 服务、微服务、多运行时

📖 扩展阅读


下一章第 11 章:数据库操作 → 学习在 Deno 中使用数据库。