You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Deno文件服务器客户端刷新时挂起问题求助

Deno文件服务器刷新页面时永久加载,仅请求本地大静态文件时触发

问题背景

我基于Deno官方示例实现了支持静态文件夹映射、自动添加Content-Type响应头的文件服务器。首次访问http://localhost:8080时,index.html可正常加载,但点击浏览器刷新按钮后,页面会永久加载且服务器接收不到任何请求;停止加载后再次刷新则恢复正常,该问题在所有浏览器均可复现。

排查确认问题与HTML中的<script src="js/protobuf.min.js"></script>直接相关:注释该行或替换为CDN地址后,一切正常,推测是服务器处理较大本地文件时存在资源管理问题。

服务器代码实现

import { WebServerSettings } from "../types.ts";

import {
    StatusCodes,
    ReasonPhrases
} from "https://deno.land/x/https_status_codes@v1.2.0/mod.ts";

import { mime } from "https://deno.land/x/mimetypes@v1.0.0/mod.ts";

export class WebServer {
    settings:WebServerSettings;

    constructor(settings:WebServerSettings){
        this.settings = settings;
    }

    async start(){
        const server = Deno.listen(this.settings);
        console.log(`Web Server up and running on port ${this.settings.port}`);

        for await (const connection of server){
            this.handle(connection).catch(err => console.error(err));
        }
    }

    private async handle(connection: Deno.Conn){
        const httpConnection = Deno.serveHttp(connection);
        for await (const requestEvent of httpConnection){
            const url = new URL(requestEvent.request.url);
            let filepath = decodeURIComponent(url.pathname);
            
            const root:string = (filepath.match(/\/[^\/]*/) || [""])[0];
            const local = new Map(Object.entries(this.settings.dirs)).get(root) || 
                this.settings.dirs.default + root;

            filepath = local + 
                filepath.replace(/^\/?$/, "/index.html")
                .replace(root, "");

            let file;
            try {
                file = await Deno.open(filepath, { read: true });
            } catch {
                const response = new Response(
                    ReasonPhrases.NOT_FOUND, { status: StatusCodes.NOT_FOUND });
    
                await requestEvent.respondWith(response);
                return;
            }

            const contentType = mime.getType(filepath) || "application/octet-stream";

            await requestEvent.respondWith(
                new Response(file.readable, { 
                    headers: {
                        "Content-Type": contentType
                    }}));
        }
    }
}

测试用HTML代码

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf8">
        <meta lang="en">
        <title>Client</title>
        <script src="js/protobuf.min.js"></script>
    </head>
    <body>
        Hello World
    </body>
</html>

问题原因分析

核心问题在于文件句柄未正确关闭导致HTTP连接复用阻塞:

  1. 当请求较大本地JS文件时,通过Deno.open获取的文件句柄,在以file.readable作为响应体返回后,Deno不会自动关闭该句柄,导致资源泄漏。
  2. HTTP/1.1默认启用keep-alive连接复用,未关闭的文件流会占用连接,使得浏览器后续的刷新请求无法通过该连接被服务器接收,表现为页面永久加载。

解决方案

方案1:手动关闭文件句柄(推荐)

在响应发送完成后,显式调用file.close()释放文件资源,确保连接可以正常复用:

const contentType = mime.getType(filepath) || "application/octet-stream";

const response = new Response(file.readable, { 
    headers: {
        "Content-Type": contentType
    }
});

await requestEvent.respondWith(response);
file.close(); // 响应发送完成后关闭文件句柄

方案2:使用using自动管理资源(Deno 1.30+)

如果使用Deno 1.30及以上版本,可利用using语句自动管理文件资源,无需手动关闭:

try {
    using file = await Deno.open(filepath, { read: true }); // 使用using声明自动释放资源
    const contentType = mime.getType(filepath) || "application/octet-stream";

    await requestEvent.respondWith(
        new Response(file.readable, { 
            headers: {
                "Content-Type": contentType
            }}));
} catch {
    const response = new Response(
        ReasonPhrases.NOT_FOUND, { status: StatusCodes.NOT_FOUND });

    await requestEvent.respondWith(response);
    return;
}

方案3:禁用HTTP连接复用(临时 workaround)

通过响应头显式关闭keep-alive,强制浏览器每次请求建立新连接:

await requestEvent.respondWith(
    new Response(file.readable, { 
        headers: {
            "Content-Type": contentType,
            "Connection": "close" // 禁用连接复用
        }}));

验证效果

修改完成后,刷新页面时浏览器的请求会被服务器正常接收,大文件加载和页面刷新均能正常工作。

内容的提问来源于stack exchange,提问作者lviggiani

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 15:15:27