Bun HTTP 服务器
Bun 内置了高性能的 HTTP 服务器,使用简洁的 API 即可创建 Web 服务。本章介绍 Bun HTTP 服务器的使用方法。
基本服务器
最简示例
typescript
const server = Bun.serve({
port: 3000,
fetch(request) {
return new Response("Hello, Bun!");
},
});
console.log(`服务器运行在 http://localhost:${server.port}`);服务器配置
typescript
const server = Bun.serve({
// 端口号
port: 3000,
// 主机地址
hostname: "127.0.0.1",
// 请求处理函数
fetch(request, server) {
return new Response("Hello!");
},
// 错误处理
error(error) {
console.error("服务器错误:", error);
return new Response("服务器错误", { status: 500 });
},
});请求处理
Request 对象
typescript
Bun.serve({
port: 3000,
fetch(request) {
// 请求方法
console.log("方法:", request.method);
// 请求 URL
const url = new URL(request.url);
console.log("路径:", url.pathname);
console.log("查询参数:", url.searchParams);
// 请求头
console.log("Content-Type:", request.headers.get("content-type"));
console.log("User-Agent:", request.headers.get("user-agent"));
return new Response("OK");
},
});获取请求体
typescript
Bun.serve({
port: 3000,
async fetch(request) {
// JSON 请求体
if (request.headers.get("content-type")?.includes("application/json")) {
const body = await request.json();
console.log("JSON:", body);
}
// 文本请求体
const text = await request.text();
// FormData
const formData = await request.formData();
// ArrayBuffer
const buffer = await request.arrayBuffer();
return new Response("收到请求");
},
});表单数据
typescript
Bun.serve({
port: 3000,
async fetch(request) {
if (request.method === "POST") {
const formData = await request.formData();
// 获取字段
const name = formData.get("name");
const email = formData.get("email");
// 获取文件
const file = formData.get("avatar") as File;
if (file) {
console.log("文件名:", file.name);
console.log("文件大小:", file.size);
// 保存文件
await Bun.write(`./uploads/${file.name}`, file);
}
return Response.json({ name, email });
}
return new Response("请使用 POST 请求");
},
});响应处理
Response 对象
typescript
Bun.serve({
port: 3000,
fetch(request) {
// 文本响应
return new Response("Hello, World!");
// 带状态码
return new Response("未找到", { status: 404 });
// 带响应头
return new Response("Hello", {
status: 200,
headers: {
"Content-Type": "text/plain; charset=utf-8",
"X-Custom-Header": "custom-value",
},
});
},
});JSON 响应
typescript
Bun.serve({
port: 3000,
fetch(request) {
const data = {
message: "成功",
timestamp: new Date().toISOString(),
data: { id: 1, name: "Bun" },
};
// 使用 Response.json()
return Response.json(data);
// 带状态码
return Response.json({ error: "未找到" }, { status: 404 });
// 手动创建
return new Response(JSON.stringify(data), {
headers: { "Content-Type": "application/json" },
});
},
});HTML 响应
typescript
Bun.serve({
port: 3000,
fetch(request) {
const html = `
<!DOCTYPE html>
<html>
<head><title>Bun 服务器</title></head>
<body>
<h1>欢迎使用 Bun!</h1>
<p>当前时间: ${new Date().toLocaleString("zh-CN")}</p>
</body>
</html>
`;
return new Response(html, {
headers: { "Content-Type": "text/html; charset=utf-8" },
});
},
});文件响应
typescript
Bun.serve({
port: 3000,
async fetch(request) {
const url = new URL(request.url);
// 返回静态文件
if (url.pathname === "/image.png") {
const file = Bun.file("./public/image.png");
return new Response(file);
}
// 设置下载文件名
if (url.pathname === "/download") {
const file = Bun.file("./files/document.pdf");
return new Response(file, {
headers: {
"Content-Disposition": 'attachment; filename="document.pdf"',
},
});
}
return new Response("Not Found", { status: 404 });
},
});路由处理
简单路由
typescript
Bun.serve({
port: 3000,
fetch(request) {
const url = new URL(request.url);
const path = url.pathname;
const method = request.method;
// 首页
if (path === "/" && method === "GET") {
return new Response("首页");
}
// API 路由
if (path === "/api/users" && method === "GET") {
return Response.json([{ id: 1, name: "张三" }]);
}
if (path === "/api/users" && method === "POST") {
return Response.json({ message: "用户已创建" }, { status: 201 });
}
// 404
return new Response("Not Found", { status: 404 });
},
});路由参数
typescript
Bun.serve({
port: 3000,
fetch(request) {
const url = new URL(request.url);
const path = url.pathname;
// 匹配 /api/users/:id
const userMatch = path.match(/^\/api\/users\/(\d+)$/);
if (userMatch) {
const userId = userMatch[1];
return Response.json({ id: userId, name: `用户 ${userId}` });
}
// 匹配 /api/posts/:id/comments/:commentId
const commentMatch = path.match(/^\/api\/posts\/(\d+)\/comments\/(\d+)$/);
if (commentMatch) {
const [, postId, commentId] = commentMatch;
return Response.json({ postId, commentId });
}
return new Response("Not Found", { status: 404 });
},
});路由器类
typescript
// router.ts
type Handler = (request: Request, params: Record<string, string>) => Response | Promise<Response>;
interface Route {
method: string;
pattern: RegExp;
paramNames: string[];
handler: Handler;
}
class Router {
private routes: Route[] = [];
private pathToRegex(path: string): { pattern: RegExp; paramNames: string[] } {
const paramNames: string[] = [];
const pattern = path.replace(/:(\w+)/g, (_, name) => {
paramNames.push(name);
return "([^/]+)";
});
return { pattern: new RegExp(`^${pattern}$`), paramNames };
}
add(method: string, path: string, handler: Handler) {
const { pattern, paramNames } = this.pathToRegex(path);
this.routes.push({ method, pattern, paramNames, handler });
}
get(path: string, handler: Handler) { this.add("GET", path, handler); }
post(path: string, handler: Handler) { this.add("POST", path, handler); }
put(path: string, handler: Handler) { this.add("PUT", path, handler); }
delete(path: string, handler: Handler) { this.add("DELETE", path, handler); }
async handle(request: Request): Promise<Response> {
const url = new URL(request.url);
for (const route of this.routes) {
if (route.method !== request.method) continue;
const match = url.pathname.match(route.pattern);
if (match) {
const params: Record<string, string> = {};
route.paramNames.forEach((name, i) => {
params[name] = match[i + 1];
});
return route.handler(request, params);
}
}
return new Response("Not Found", { status: 404 });
}
}
// 使用
const router = new Router();
router.get("/", () => new Response("首页"));
router.get("/api/users", () => Response.json([{ id: 1 }]));
router.get("/api/users/:id", (req, params) =>
Response.json({ id: params.id })
);
router.post("/api/users", async (req) => {
const body = await req.json();
return Response.json({ created: body }, { status: 201 });
});
Bun.serve({
port: 3000,
fetch: (req) => router.handle(req),
});中间件模式
实现中间件
typescript
type Middleware = (
request: Request,
next: () => Promise<Response>
) => Promise<Response>;
function compose(middlewares: Middleware[]) {
return async (request: Request): Promise<Response> => {
let index = 0;
async function next(): Promise<Response> {
if (index >= middlewares.length) {
return new Response("Not Found", { status: 404 });
}
const middleware = middlewares[index++];
return middleware(request, next);
}
return next();
};
}
// 日志中间件
const logger: Middleware = async (req, next) => {
const start = Date.now();
const response = await next();
console.log(`${req.method} ${new URL(req.url).pathname} - ${Date.now() - start}ms`);
return response;
};
// CORS 中间件
const cors: Middleware = async (req, next) => {
const response = await next();
response.headers.set("Access-Control-Allow-Origin", "*");
response.headers.set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
return response;
};
// 处理中间件
const handler: Middleware = async (req, next) => {
return new Response("Hello!");
};
// 组合使用
const app = compose([logger, cors, handler]);
Bun.serve({
port: 3000,
fetch: app,
});静态文件服务
简单静态服务器
typescript
import path from "node:path";
const PUBLIC_DIR = "./public";
Bun.serve({
port: 3000,
async fetch(request) {
const url = new URL(request.url);
let filePath = path.join(PUBLIC_DIR, url.pathname);
// 目录默认返回 index.html
if (filePath.endsWith("/")) {
filePath += "index.html";
}
const file = Bun.file(filePath);
if (await file.exists()) {
return new Response(file);
}
return new Response("Not Found", { status: 404 });
},
});MIME 类型处理
typescript
const MIME_TYPES: Record<string, string> = {
".html": "text/html",
".css": "text/css",
".js": "application/javascript",
".json": "application/json",
".png": "image/png",
".jpg": "image/jpeg",
".gif": "image/gif",
".svg": "image/svg+xml",
".ico": "image/x-icon",
".woff": "font/woff",
".woff2": "font/woff2",
};
function getMimeType(filePath: string): string {
const ext = path.extname(filePath).toLowerCase();
return MIME_TYPES[ext] || "application/octet-stream";
}
Bun.serve({
port: 3000,
async fetch(request) {
const url = new URL(request.url);
const filePath = path.join("./public", url.pathname);
const file = Bun.file(filePath);
if (await file.exists()) {
return new Response(file, {
headers: { "Content-Type": getMimeType(filePath) },
});
}
return new Response("Not Found", { status: 404 });
},
});HTTPS 服务器
TLS 配置
typescript
Bun.serve({
port: 443,
// TLS 证书
tls: {
key: Bun.file("./certs/key.pem"),
cert: Bun.file("./certs/cert.pem"),
// 可选:CA 证书
// ca: Bun.file("./certs/ca.pem"),
// 可选:密码短语
// passphrase: "your-passphrase",
},
fetch(request) {
return new Response("安全连接!");
},
});自签名证书
bash
# 生成自签名证书(开发用)
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes服务器控制
停止服务器
typescript
const server = Bun.serve({
port: 3000,
fetch(request) {
return new Response("Hello!");
},
});
// 停止服务器
server.stop();
// 或设置立即停止
server.stop(true);重新加载
typescript
// 热重载支持
export default {
port: 3000,
fetch(request: Request) {
return new Response("Hello!");
},
};
// 运行: bun --hot server.ts获取服务器信息
typescript
const server = Bun.serve({
port: 3000,
fetch(request) {
return new Response("Hello!");
},
});
console.log("端口:", server.port);
console.log("主机:", server.hostname);
console.log("开发模式:", server.development);
console.log("待处理请求:", server.pendingRequests);完整 REST API 示例
typescript
// api-server.ts
interface User {
id: number;
name: string;
email: string;
}
const users: User[] = [
{ id: 1, name: "张三", email: "zhangsan@example.com" },
{ id: 2, name: "李四", email: "lisi@example.com" },
];
let nextId = 3;
const server = Bun.serve({
port: 3000,
async fetch(request) {
const url = new URL(request.url);
const path = url.pathname;
const method = request.method;
// CORS
if (method === "OPTIONS") {
return new Response(null, {
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE",
"Access-Control-Allow-Headers": "Content-Type",
},
});
}
// GET /api/users
if (path === "/api/users" && method === "GET") {
return Response.json(users);
}
// GET /api/users/:id
const getMatch = path.match(/^\/api\/users\/(\d+)$/);
if (getMatch && method === "GET") {
const user = users.find(u => u.id === parseInt(getMatch[1]));
if (user) {
return Response.json(user);
}
return Response.json({ error: "用户不存在" }, { status: 404 });
}
// POST /api/users
if (path === "/api/users" && method === "POST") {
const body = await request.json();
const newUser: User = {
id: nextId++,
name: body.name,
email: body.email,
};
users.push(newUser);
return Response.json(newUser, { status: 201 });
}
// PUT /api/users/:id
const putMatch = path.match(/^\/api\/users\/(\d+)$/);
if (putMatch && method === "PUT") {
const index = users.findIndex(u => u.id === parseInt(putMatch[1]));
if (index !== -1) {
const body = await request.json();
users[index] = { ...users[index], ...body };
return Response.json(users[index]);
}
return Response.json({ error: "用户不存在" }, { status: 404 });
}
// DELETE /api/users/:id
const deleteMatch = path.match(/^\/api\/users\/(\d+)$/);
if (deleteMatch && method === "DELETE") {
const index = users.findIndex(u => u.id === parseInt(deleteMatch[1]));
if (index !== -1) {
users.splice(index, 1);
return new Response(null, { status: 204 });
}
return Response.json({ error: "用户不存在" }, { status: 404 });
}
return Response.json({ error: "未找到" }, { status: 404 });
},
error(error) {
console.error(error);
return Response.json({ error: "服务器错误" }, { status: 500 });
},
});
console.log(`API 服务器运行在 http://localhost:${server.port}`);小结
本章介绍了:
- ✅ 创建基本 HTTP 服务器
- ✅ 处理请求和响应
- ✅ 路由处理和参数解析
- ✅ 中间件模式
- ✅ 静态文件服务
- ✅ HTTPS 配置
- ✅ 完整 REST API 示例
下一步
继续阅读 WebSocket 了解 Bun 的实时通信功能。