يمكنك إضافة مسارات إلى Bun.serve() باستخدام خاصية routes (للمسارات الثابتة والمعلمات والبدائل) أو عن طريق معالجة الطلبات غير المطابقة مع طريقة fetch.
يبني جهاز توجيه Bun.serve() على نهج الشجرة القائم على uWebSocket لإضافة فك ترميز معلمات المسار المعجل بـ SIMD و تخزين بنية JavaScriptCore لدفع حدود الأداء التي يسمح بها الأجهزة الحديثة.
الإعداد الأساسي
Bun.serve({
routes: {
"/": () => new Response("Home"),
"/api": () => Response.json({ success: true }),
"/users": async () => Response.json({ users: [] }),
},
fetch() {
return new Response("Unmatched route");
},
});تتلقى المسارات في Bun.serve() كائن BunRequest (الذي يمتد Request) وترجع Response أو Promise<Response>. هذا يجعل من السهل استخدام نفس الكود لإرسال واستقبال طلبات HTTP.
// مبسط للاختصار
interface BunRequest<T extends string> extends Request {
params: Record<T, string>;
readonly cookies: CookieMap;
}المسارات غير المتزامنة
Async/await
يمكنك استخدام async/await في معالجات المسارات لإرجاع Promise<Response>.
import { sql, serve } from "bun";
serve({
port: 3001,
routes: {
"/api/version": async () => {
const [version] = await sql`SELECT version()`;
return Response.json(version);
},
},
});Promise
يمكنك أيضًا إرجاع Promise<Response> من معالج المسار.
import { sql, serve } from "bun";
serve({
routes: {
"/api/version": () => {
return new Promise(resolve => {
setTimeout(async () => {
const [version] = await sql`SELECT version()`;
resolve(Response.json(version));
}, 100);
});
},
},
});أولوية المسار
تتم مطابقة المسارات بترتيب الخصوصية:
- المسارات الدقيقة (
/users/all) - مسارات المعلمات (
/users/:id) - مسارات البدائل (
/users/*) - التقاط الشامل العالمي (
/*)
Bun.serve({
routes: {
// الأكثر تحديدًا أولاً
"/api/users/me": () => new Response("Current user"),
"/api/users/:id": req => new Response(`User ${req.params.id}`),
"/api/*": () => new Response("API catch-all"),
"/*": () => new Response("Global catch-all"),
},
});معلمات المسار الآمنة للنوع
يقوم TypeScript بتحليل معلمات المسار عند تمريرها كحرفية سلسلة، بحيث سيظهر محررك الإكمال التلقائي عند الوصول إلى request.params.
import type { BunRequest } from "bun";
Bun.serve({
routes: {
// TypeScript يعرف شكل params عند تمريرها كحرفية سلسلة
"/orgs/:orgId/repos/:repoId": req => {
const { orgId, repoId } = req.params;
return Response.json({ orgId, repoId });
},
"/orgs/:orgId/repos/:repoId/settings": (
// اختياري: يمكنك صراحة تمرير نوع إلى BunRequest:
req: BunRequest<"/orgs/:orgId/repos/:repoId/settings">,
) => {
const { orgId, repoId } = req.params;
return Response.json({ orgId, repoId });
},
},
});يتم فك ترميز قيم معلمات المسار المشفرة بنسبة تلقائيًا. أحرف Unicode مدعومة. يتم استبدال unicode غير الصالح بحرف استبدال unicode &0xFFFD;.
الاستجابات الثابتة
يمكن أن تكون المسارات أيضًا كائنات Response (بدون دالة المعالج). يحسن Bun.serve() ذلك لتوزيع بدون تخصيص - مثالي لفحوصات الحالة، وإعادة التوجيه، والمحتوى الثابت:
Bun.serve({
routes: {
// فحوصات الحالة
"/health": new Response("OK"),
"/ready": new Response("Ready", {
headers: {
// تمرير رؤوس مخصصة
"X-Ready": "1",
},
}),
// إعادة التوجيه
"/blog": Response.redirect("https://bun.com/blog"),
// استجابات API
"/api/config": Response.json({
version: "1.0.0",
env: "production",
}),
},
});لا تستهلك الاستجابات الثابتة ذاكرة إضافية بعد التهيئة. يمكنك عمومًا توقع تحسن أداء بنسبة 15% على الأقل مقارنة بإرجاع كائن Response يدويًا.
يتم تخزين استجابات المسار الثابتة طوال عمر كائن الخادم. لإعادة تحميل المسارات الثابتة، استدعِ server.reload(options).
استجابات الملفات مقابل الاستجابات الثابتة
عند خدمة الملفات في المسارات، هناك سلوكان متميزان اعتمادًا على ما إذا كنت تقوم بتخزين محتوى الملف مؤقتًا أو خدمته مباشرة:
Bun.serve({
routes: {
// مسار ثابت - يتم تخزين المحتوى في الذاكرة عند بدء التشغيل
"/logo.png": new Response(await Bun.file("./logo.png").bytes()),
// مسار ملف - يتم قراءة المحتوى من نظام الملفات في كل طلب
"/download.zip": new Response(Bun.file("./download.zip")),
},
});المسارات الثابتة (new Response(await file.bytes())) تخزن المحتوى في الذاكرة عند بدء التشغيل:
- لا I/O لنظام الملفات أثناء الطلبات - يتم خدمة المحتوى بالكامل من الذاكرة
- دعم ETag - يولد ويصادق ETags تلقائيًا للتخزين المؤقت
- If-None-Match - يرجع
304 Not Modifiedعندما يتطابق ETag العميل - لا معالجة 404 - تسبب الملفات المفقودة أخطاء بدء التشغيل، وليس 40s وقت التشغيل
- استخدام الذاكرة - يتم تخزين محتوى الملف بالكامل في RAM
- الأفضل لـ: الأصول الثابتة الصغيرة، استجابات API، الملفات التي يتم الوصول إليها بشكل متكرر
مسارات الملفات (new Response(Bun.file(path))) تقرأ من نظام الملفات في كل طلب:
- قراءات نظام الملفات في كل طلب - يتحقق من وجود الملف ويقرأ المحتوى
- معالجة 404 مدمجة - يرجع
404 Not Foundإذا كان الملف غير موجود أو أصبح غير قابل للوصول - دعم Last-Modified - يستخدم وقت تعديل الملف لرؤوس
If-Modified-Since - If-Modified-Since - يرجع
304 Not Modifiedعندما لا يتغير الملف منذ النسخة المخزنة مؤقتًا للعميل - دعم طلب النطاق - يتعامل تلقائيًا مع طلبات المحتوى الجزئي مع رؤوس
Content-Range - نقل متدفق - يستخدم قارئ مخزن مؤقت مع معالجة الضغط الخلفي لاستخدام فعال للذاكرة
- فعال للذاكرة - يخزن مؤقتًا فقط قطع صغيرة أثناء النقل، وليس الملف بالكامل
- الأفضل لـ: الملفات الكبيرة، المحتوى الديناميكي، تحميلات المستخدمين، الملفات التي تتغير بشكل متكرر
تدفق الملفات
لتدفق ملف، أرجع كائن Response مع كائن BunFile كجسم.
Bun.serve({
fetch(req) {
return new Response(Bun.file("./hello.txt"));
},
});يمكنك إرسال جزء من ملف باستخدام طريقة slice(start, end) على كائن Bun.file. هذا يضبط تلقائيًا رؤوس Content-Range و Content-Length على كائن Response.
Bun.serve({
fetch(req) {
// تحليل رأس `Range`
const [start = 0, end = Infinity] = req.headers
.get("Range") // Range: bytes=0-100
.split("=") // ["Range: bytes", "0-100"]
.at(-1) // "0-100"
.split("-") // ["0", "100"]
.map(Number); // [0, 100]
// إرجاع شريحة من الملف
const bigFile = Bun.file("./big-video.mp4");
return new Response(bigFile.slice(start, end));
},
});معالج طلب fetch
يتعامل معالج fetch مع الطلبات الواردة التي لم تتم مطابقتها بواسطة أي مسار. يتلقى كائن Request ويرجع Response أو Promise<Response>.
Bun.serve({
fetch(req) {
const url = new URL(req.url);
if (url.pathname === "/") return new Response("Home page!");
if (url.pathname === "/blog") return new Response("Blog!");
return new Response("404!");
},
});يدعم معالج fetch async/await:
import { sleep, serve } from "bun";
serve({
async fetch(req) {
const start = performance.now();
await sleep(10);
const end = performance.now();
return new Response(`Slept for ${end - start}ms`);
},
});استجابات Promise مدعومة أيضًا:
Bun.serve({
fetch(req) {
// إعادة توجيه الطلب إلى خادم آخر.
return fetch("https://example.com");
},
});يمكنك أيضًا الوصول إلى كائن Server من معالج fetch. إنها الوسيطة الثانية الممررة إلى دالة fetch.
// يتم تمرير `server` كوسيطة ثانية إلى `fetch`.
const server = Bun.serve({
fetch(req, server) {
const ip = server.requestIP(req);
return new Response(`Your IP is ${ip.address}`);
},
});