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连接复用阻塞:
- 当请求较大本地JS文件时,通过
Deno.open获取的文件句柄,在以file.readable作为响应体返回后,Deno不会自动关闭该句柄,导致资源泄漏。 - 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
相关产品推荐
相关产品推荐

