Написание HTTP-сервера
В Deno есть встроенный API HTTP-сервера: функция Deno.serve, которая поддерживает HTTP/1.1 и HTTP/2 и работает с веб-стандартными объектами Request и Response. На этой странице рассматривается написание серверов с её помощью: от первого обработчика до маршрутизации, статических файлов, TLS, WebSocket и завершения работы.
Сервер «Hello World»
Функция Deno.serve принимает функцию-обработчик, которая вызывается для каждого входящего запроса и должна возвращать ответ (или промис, разрешающийся в ответ).
Ниже приведён пример сервера, который возвращает ответ «Hello, World!» на каждый запрос:
Deno.serve((_req) => {
return new Response("Hello, World!");
});
Обработчик также может возвращать Promise<Response>, а значит, может быть функцией async.
Чтобы запустить этот сервер, можно использовать команду deno run:
deno run --allow-net server.ts
В Deno.serve коллекции примеров есть множество других примеров использования.
Прослушивание определённого порта
По умолчанию Deno.serve будет прослушивать порт 8000, но его можно изменить, передав номер порта в объекте параметров первым или вторым аргументом:
// To listen on port 4242.
Deno.serve({ port: 4242 }, handler);
// To listen on port 4242 and bind to 0.0.0.0.
Deno.serve({ port: 4242, hostname: "0.0.0.0" }, handler);
Анализ входящего запроса
Большинство серверов не отвечают одинаково на каждый запрос. Вместо этого ответ меняется в зависимости от различных характеристик запроса: HTTP-метода, заголовков, пути или содержимого тела.
Запрос передаётся первым аргументом функции-обработчика. Вот пример извлечения различных частей запроса:
Deno.serve(async (req) => {
console.log("Method:", req.method);
const url = new URL(req.url);
console.log("Path:", url.pathname);
console.log("Query parameters:", url.searchParams);
console.log("Headers:", req.headers);
if (req.body) {
const body = await req.text();
console.log("Body:", body);
}
return new Response("Hello, World!");
});
Обратите внимание: вызов req.text() может завершиться ошибкой, если пользователь закроет соединение до получения тела целиком. Обязательно обработайте этот случай. Такое может произойти при использовании любых методов чтения тела запроса, например req.json(), req.formData(), req.arrayBuffer(), req.body.getReader().read(), req.body.pipeTo() и т. д.
Ответы с реальными данными
Большинство серверов не отвечают «Hello, World!» на каждый запрос. Вместо этого они могут возвращать разные заголовки, коды состояния и содержимое тела (в том числе потоки).
Вот пример ответа с кодом состояния 404, телом в формате JSON и пользовательским заголовком:
Deno.serve((req) => {
const body = JSON.stringify({ message: "NOT FOUND" });
return new Response(body, {
status: 404,
headers: {
"content-type": "application/json; charset=utf-8",
},
});
});
Ответ с потоком
Тела ответов также могут быть потоками. Вот пример ответа, который каждую секунду возвращает поток с повторяющимся сообщением «Hello, World!»:
Deno.serve((req) => {
let timer: number;
const body = new ReadableStream({
async start(controller) {
timer = setInterval(() => {
controller.enqueue("Hello, World!\n");
}, 1000);
},
cancel() {
clearInterval(timer);
},
});
return new Response(body.pipeThrough(new TextEncoderStream()), {
headers: {
"content-type": "text/plain; charset=utf-8",
},
});
});
Обратите внимание на функцию cancel выше. Она вызывается, когда клиент закрывает соединение. Важно обработать этот случай, иначе сервер будет бесконечно накапливать сообщения в очереди и в итоге исчерпает память.
Обратите внимание: поток тела ответа «отменяется», когда клиент закрывает соединение. Обязательно обработайте этот случай. Это может проявиться в виде ошибки при вызове write() для объекта WritableStream, связанного с объектом ReadableStream тела ответа (например, через TransformStream).
Маршрутизация запросов
Для сервера с несколькими конечными точками сопоставляйте URL с помощью встроенного веб-API URLPattern:
const userPattern = new URLPattern({ pathname: "/users/:id" });
Deno.serve((req) => {
const match = userPattern.exec(req.url);
if (match) {
const id = match.pathname.groups.id;
return new Response(`User ${id}`);
}
if (new URL(req.url).pathname === "/") {
return new Response("Home");
}
return new Response("Not found", { status: 404 });
});
Стандартная библиотека также включает небольшой маршрутизатор, который сопоставляет пары «шаблон и метод» с обработчиками: route из @std/http. Для промежуточного ПО, более сложных деревьев маршрутов или дополнительных возможностей фреймворков используйте Oak или Hono.
Раздача статических файлов
Чтобы раздавать файлы из каталога, используйте serveDir из @std/http:
import { serveDir } from "jsr:@std/http/file-server";
Deno.serve((req) => serveDir(req, { fsRoot: "./public" }));
serveDir обрабатывает типы содержимого и запросы диапазонов, а также защищает от обхода каталогов. Запустите его с правами на чтение каталога: deno run -N -R server.ts. Для разового запуска файлового сервера без написания кода этот же модуль можно использовать как CLI: deno run -RN jsr:@std/http/file-server ./public.
Корректное завершение работы
Deno.serve возвращает объект HttpServer, метод shutdown() которого прекращает приём новых соединений, позволяя завершиться уже выполняющимся запросам. Для корректного завершения в рабочей среде объедините его с обработчиком сигнала:
const server = Deno.serve((_req) => new Response("Hello"));
Deno.addSignalListener("SIGINT", async () => {
console.log("shutting down");
await server.shutdown();
});
Также можно передать AbortSignal через параметр signal, чтобы связать время жизни сервера с другой логикой.
Поддержка HTTPS
Чтобы обслуживать HTTPS, передайте в параметрах cert и key. Оба значения должны содержать данные сертификата и закрытого ключа в формате PEM, а не пути к файлам.
Deno.serve({
port: 8443,
cert: Deno.readTextFileSync("./cert.pem"),
key: Deno.readTextFileSync("./key.pem"),
}, (_req) => new Response("Hello over HTTPS!"));
Запустите сервер, предоставив доступ к сети и права на чтение двух файлов:
deno run --allow-net --allow-read=cert.pem,key.pem server.ts
Для локальной разработки можно создать самоподписанный сертификат с коротким сроком действия с помощью OpenSSL:
openssl req -x509 -newkey rsa:2048 -nodes -days 1 \
-keyout key.pem -out cert.pem \
-subj "/CN=localhost" \
-addext "subjectAltName=DNS:localhost"
Затем проверьте, отвечает ли сервер. Флаг -k указывает curl принять самоподписанный сертификат — используйте его только для локального тестирования:
$ curl -k https://localhost:8443/
Hello over HTTPS!
В рабочей среде используйте сертификат, выданный доверенным центром, например Let's Encrypt, а не самоподписанный. API среды выполнения остаётся тем же; меняются только источники cert и key.
Поддержка HTTP/2
При использовании API HTTP-сервера Deno поддержка HTTP/2 включена «автоматически». Достаточно создать сервер — он будет одинаково обрабатывать запросы HTTP/1 и HTTP/2.
Также поддерживается HTTP/2 через незашифрованное соединение с предварительным согласованием.
Автоматическое сжатие тела
HTTP-сервер может автоматически сжимать тела ответов, но по умолчанию эта функция отключена. Включите её для отдельного сервера с помощью automaticCompression: true или для всего процесса, задав DENO_SERVE_AUTOMATIC_COMPRESSION=1:
Deno.serve({ automaticCompression: true }, () => new Response("hello"));
Сжатие выполняется внутри Deno, поэтому оно быстрое и эффективное. Deno поддерживает gzip и brotli. После включения сжатие тела происходит при соблюдении следующих условий:
- Запрос содержит заголовок
Accept-Encoding, указывающий, что клиент поддерживаетbrдля Brotli илиgzip. Deno учитывает предпочтение, заданное значением quality value в заголовке. - Ответ содержит заголовок
Content-Typeс типом, который можно сжимать. (Список основан наjshttp/mime-db; фактический список приведён в коде.) - Размер тела ответа превышает 64 байта.
При сжатии тела ответа Deno устанавливает заголовок Content-Encoding, указывающий тип сжатия, а также корректирует или добавляет заголовок Vary, чтобы обозначить, какие заголовки запроса повлияли на ответ.
Помимо описанной выше логики, есть несколько причин, по которым ответ не будет сжат автоматически:
- Ответ содержит заголовок
Content-Encoding. Это означает, что сервер уже выполнил какое-либо кодирование. - Ответ содержит заголовок
Content-Range. Это означает, что сервер отвечает на запрос диапазона, где байты и диапазоны согласуются вне контроля внутренних механизмов Deno. - Ответ содержит заголовок
Cache-Controlсо значениемno-transform. Это означает, что сервер не хочет, чтобы Deno или последующие прокси изменяли ответ.
Сигнал прерывания запроса
По историческим причинам Deno.serve вызывает событие abort у объекта signal запроса даже в том случае, если обработчик завершился успешно. Это вызывает проблемы с некоторыми прокси-библиотеками Node (например, http-proxy), которые воспринимают прерывание как реальный сбой на вышестоящем сервере. Передайте --unstable-no-legacy-abort, чтобы включить исправленное поведение: signal будет прерываться только при фактическом отключении клиента. Использование прежнего поведения теперь приводит к предупреждению об устаревшем API, поскольку исправленное поведение станет поведением по умолчанию.
Работа с WebSocket
Deno может переключить входящие HTTP-запросы на WebSocket. Это позволяет обрабатывать конечные точки WebSocket на HTTP-серверах.
Чтобы переключить входящий запрос Request на WebSocket, используйте функцию Deno.upgradeWebSocket. Она возвращает объект, состоящий из Response и веб-стандартного объекта WebSocket. Возвращённый ответ следует использовать для ответа на входящий запрос.
Поскольку протокол WebSocket симметричен, объект WebSocket идентичен объекту, который можно использовать для обмена данными на стороне клиента. Документацию по нему можно найти на MDN.
Deno.serve((req) => {
if (req.headers.get("upgrade") != "websocket") {
return new Response(null, { status: 426 });
}
const { socket, response } = Deno.upgradeWebSocket(req);
socket.addEventListener("open", () => {
console.log("a client connected!");
});
socket.addEventListener("message", (event) => {
if (event.data === "ping") {
socket.send("pong");
}
});
return response;
});
После переключения на WebSocket соединение, по которому он был создан, нельзя использовать для передачи HTTP-трафика.
Обратите внимание: пока что WebSocket поддерживается только в HTTP/1.1.
Экспорт fetch по умолчанию
Ещё один способ создать HTTP-сервер в Deno — экспортировать функцию fetch по умолчанию. API fetch инициирует HTTP-запрос для получения данных по сети и встроен в среду выполнения Deno.
export default {
fetch(request) {
const userAgent = request.headers.get("user-agent") || "Unknown";
return new Response(`User Agent: ${userAgent}`);
},
} satisfies Deno.ServeDefaultExport;
Запустить этот файл можно командой deno serve:
deno serve server.ts
Сервер запустится и выведет сообщение в консоль. Откройте браузер и перейдите по адресу http://localhost:8000/, чтобы увидеть информацию об User-Agent.
Интерфейс Deno.ServeDefaultExport определяет структуру экспортов по умолчанию, которые можно использовать с командой deno serve. Чтобы обеспечить правильную проверку типов кода, добавьте satisfies Deno.ServeDefaultExport в export default { ... }.
Развитие этих примеров
Вероятно, вы захотите расширить эти примеры и создать более сложные серверы. Всё описанное здесь построено на веб-стандартных Request/Response, поэтому его можно сочетать с библиотеками маршрутизации и фреймворками экосистемы — например, использовать Oak или Hono для промежуточного ПО и маршрутизации либо выбрать полноценный фреймворк. Общие сведения о создании веб-приложений с помощью Deno см. в разделе «Веб-разработка».
Последнее обновление:
© 2018–2025 the Deno authors
Licensed under the MIT License.
https://docs.deno.com/runtime/fundamentals/http_server