前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
  • 基础篇

    • 环境搭建
    • node部署
    • 基础应用
  • 内置模块

    • 1.0 本地路径处理 path
    • 2.0 文件系统操作 fs
    • 3.1 基础调试 console
    • 3.2 本地调试远程服务器上的Node代码
    • 4.1 网络服务 http
    • 4.2 网络服务 http res
    • 4.3 网络服务 http req
    • 4.4 网络服务 http server
    • 4.5 网络服务 http client
    • 4.6 网络服务 https
    • 4.7 网络TCP net
    • 4.8 网络UDP dgram
    • 4.9 域名解析 dns
    • 5.0 网络地址解析 url
    • 5.1 URL查询字符串 querystring
    • 6.1 流操作 stream
    • 6.2 逐行读取 readline
    • 7.1 进程相关 process
    • 7.2 子进程 child
    • 8.1 二进制数据 buffer
    • 8.2 二进制解码 string_decoder
    • 9.1 事件机制 events
    • 9.2 实用工具模块 util
    • 9.3 数据加密 crypto
    • 9.4 MD5入门介绍及crypto模块的应用
    • 9.4 资源压缩 zlib
    • 9.5 集群 cluster
    • 9.6 v8
  • 进阶篇

    • 5分钟入门非对称加密用法
    • cluster
    • cookie_parser深入
    • crypto模块之理论篇
    • express+cookie_parser签名机制深入剖析
    • express+session实现简易身份认证
    • https
    • log4js入门实例
    • node8_napi
    • 使用 async 控制并发
    • 使用 eventproxy 控制并发
    • 使用 superagent 与 cheerio 完成简单爬虫
    • 基于express+muter的文件上传
    • 将图片转成datauri嵌入到html
    • 常用中间件 body_parser 实现解析
    • 日志模块morgan
    • 服务端字符编解码&乱码处理 charset_enc_dec
    • 测试用例mocha,should,istanbul
    • 调试日志打印debug模块
  • Express

    • 1.1 Express概览篇
    • 1.2 Express
  • Koa2

    • 1.0 koa2概览篇
    • 1.1 快速开始
    • 1.2 async await使用
    • 1.3 koa2简析结构
    • 1.4 koa中间件开发与使用
    • 10.1 单元测试
    • 11.1 开发debug
    • 12.1 快速启动
    • 12.2 框架设计
    • 12.3 分层操作
    • 12.4 数据库设计
    • 12.5 路由设计
    • 12.6 webpack4环境搭建
    • 12.7 使用react
    • 12.8 登录注册功能实现
    • 12.9 session登录态判断处理
    • 13.1 import export使用
    • 2.1 原生koa2实现路由
    • 2.2 koa router中间
    • 3.1 GET请求数据获取
    • 3.2 POST请求数据获取
    • 3.3 koa bodyparser中间件
    • 4.1 原生koa2实现静态资源服务器
    • 4.2 koa static中间件
    • 5.1 koa2使用cookie
    • 5.2 koa2实现session
    • 6.1 koa2加载模板引擎
    • 6.2 ejs模板引擎
    • 7.1 busboy模块
    • 7.2 上传文件简单实现
    • 7.3 异步上传图片实现
    • 8.1 mysql模块
    • 8.2 async await封装使用mysql
    • 8.3 项目建表初始化
    • 9.1 原生koa2实现JSONP
    • 9.2 koa jsonp中间件
  • 其他

    • 操作数据库
    • Session 与 Token
    • Cookie、Session、Token、JWT
    • Socket

# 原生koa2实现静态资源服务器

30 秒速记

  • 静态资源服务器就做一件事:把 URL 路径映射到磁盘上某个目录里的文件,读出来返回
  • 三步:拼本地路径 → 判断是文件、目录还是不存在 → 设 Content-Type 返回内容
  • 安全底线:拼完路径必须检查还在静态根目录里,不然 /../ 能读到服务器任意文件
  • 生产要考虑流式输出、缓存头、Range,实际一般交给 Nginx、CDN 或 koa-static

原生实现静态资源服务器,本质就是把请求路径拼到静态目录上,去磁盘找文件,找到就按类型返回,找不到就返回 404。 这个例子的价值是让你看清 koa-static 这类中间件内部在干什么。自己写的时候最容易漏的是安全:path.join(root, ctx.url) 遇到 /../../etc/passwd 会跑出根目录,必须拼完再检查一遍。还有性能,一次性 readFile 读大文件很费内存,正经做法是用 fs.createReadStream 流式返回,再加上缓存头。上线的话我一般不会自己写,交给 Nginx 或 CDN。

理解「原生koa2实现静态资源服务器」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。

工程中还要验证退出码、资源清理和异常输入。属于 network 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。

面试官追问

追问 1访问不存在的 /js/missing.js,页面显示 404 文字,这样算对吗?
参考回答

文字对了,但这个例子没设状态码,实际返回的是 200。浏览器和 CDN 会把它当成功的 JS 缓存起来,要写 ctx.status = 404。

追问 2path.join(staticRoot, ctx.url) 有什么安全问题?
参考回答

请求 /../../etc/passwd 时(用 curl --path-as-is 就能发出去),拼出来的路径会跑到根目录外面。拼完要检查:if (!full.startsWith(staticRoot + path.sep)) return ctx.status = 403。

追问 3为什么生产上一般不用这种手写的静态服务?
参考回答

缓存协商、gzip、Range 断点续传、目录穿越防护这些都要自己补,Nginx 和 CDN 早就做好了,性能也高一大截。手写版用来理解原理就够了。

追问 4静态目录写成 './static',从别的目录启动服务就找不到文件了,为什么?
参考回答

path.resolve 处理相对路径时按 process.cwd() 算,也就是你执行命令的目录。用 path.join(__dirname, './static'),按入口文件所在位置算,在哪启动都一样。

# 原生koa2实现静态资源服务器

# 前言

30 秒速记

  • 请求静态资源只有三种结果:是文件 → 返回内容,是目录 → 列目录或返回首页,不存在 → 404
  • 判断方式:fs.stat 看 isFile() 还是 isDirectory(),报 ENOENT 就是不存在
  • 文件要按扩展名带对 Content-Type,不然浏览器可能不执行 JS、不应用 CSS
  • 目录列表要不要展示是安全决策,线上一般关掉,访问目录时返回 index.html 或 403

一个静态资源请求打过来,服务端无非三种处理:是文件就返回内容,是目录就列出里面的东西或返回首页,啥都没有就 404。 判断靠 fs.stat,返回的对象上有 isFile() 和 isDirectory(),文件不存在会报 ENOENT。这三种情况里最需要拍板的是目录:开发时列目录很方便,线上把目录结构暴露出去等于给别人送地图,一般直接关掉,访问目录时返回 index.html 或者 403。

一个http请求访问web服务静态资源,一般响应结果有三种情况

  • 访问文本,例如js,css,png,jpg,gif
  • 访问静态目录
  • 找不到资源,抛出404错误

面试官追问

追问 1图片请求 /image/logo.png 返回的却是一段目录列表 HTML,会是什么原因?
参考回答

多半是 logo.png 实际是个目录,或者路径拼错落到了上一级目录上。用 fs.statSync(fullPath).isDirectory() 打出来看一下就清楚,同时浏览器那边因为类型不对,图片显示不出来。

追问 2验收用例怎么设计,才能覆盖这三种结果?
参考回答

至少四条:一个 JS 文件、一张图片、一个目录、一个不存在的路径。分别断言状态码、Content-Type 和内容,不能只看后缀猜结果。

追问 3安全评审要求不能展示目录内容,代码该怎么改?
参考回答

isDirectory() 分支里先找 index.html,有就返回它,没有就 ctx.status = 403。目录列表功能整个关掉,只在本地开发时开。

追问 4单页应用希望找不到的路径都返回 index.html,图片 404 也这样处理吗?
参考回答

不行。前端路由兜底只该给页面路由用,/assets/xxx.png 这种静态资源找不到必须是真 404,不然浏览器拿到一段 HTML 当图片解析,排查起来很麻烦。一般按有没有扩展名来区分。

# 原生koa2 静态资源服务器例子

30 秒速记

  • 例子分三块:入口 index.js 管路由和响应,util/content.js 判断资源类型,dir.js、file.js 各管一种
  • 主流程:ctx.url 拼到静态根目录 → stat 判断 → 文件读内容、目录生成链接列表
  • 跑起来自己试四种请求:根路径、index.html、一个 JS、一个不存在的文件
  • 这个例子用同步 API 和全量读取,讲原理够用,不能直接拿去上线

这个例子用纯 Koa 加 fs、path 搭了一个能用的静态服务器,拆成几个小模块各管一件事。 入口文件接请求、算 MIME 类型、写响应;content.js 拿到本地路径后判断是文件、目录还是不存在,分别交给 file.js 和 dir.js。看懂它,再去看 koa-static 和 koa-send 的源码会轻松很多。拉下来跑的时候别只访问首页,把不存在的路径、中文文件名都试一下,能看出这种简化写法的边界在哪。

demo源码

https://github.com/poetries/daily-code-practice/tree/master/node/koa/koa2-demo/static-server/

面试官追问

追问 1只看示例仓库链接,怎么现场讲清楚它做了什么?
参考回答

说清楚一条请求的路径:ctx.url 拼成本地路径,existsSync 判断存在,statSync 区分文件和目录,最后按扩展名设 Content-Type 返回。讲完这条线就够了。

追问 2静态目录下放一个中文名的文件,比如 说明.txt,能访问到吗?
参考回答

访问不到。浏览器会把路径编码成 %E8%AF%B4...,代码直接拿 ctx.url 去拼路径,没做 decodeURIComponent,磁盘上找不到这个名字。

追问 3请求 /index.html?v=123 带了版本号参数,为什么就 404 了?
参考回答

ctx.url 包含查询字符串,拼出来的文件名是 index.html?v=123,磁盘上当然没有。应该用 ctx.path,它不带问号后面那段。

追问 4能直接把这个目录结构复制到生产项目里用吗?
参考回答

不建议。同步读文件会阻塞事件循环,没防目录穿越,没有缓存头。生产直接上 koa-static,或者让 Nginx 去托管静态文件。

# 代码目录

30 秒速记

  • static/:要对外提供的资源,css、image、js 和 index.html
  • util/content.js:总调度,判断请求的是文件、目录还是不存在
  • dir.js + walk.js:读目录、拼成带链接的 HTML 列表;file.js:读文件内容
  • mimes.js:扩展名到 Content-Type 的映射表,新增文件类型改这里
  • index.js:启动入口,算 MIME、写响应

目录结构按职责拆得很清楚:static 放对外的资源,util 放工具函数,index.js 是入口。 content.js 是调度中心,拿到本地路径后判断是什么,再分给 file.js 或 dir.js;walk.js 负责把目录里的子目录和文件列出来,mimes.js 是一张扩展名对应类型的表。这样拆的好处是排查问题时能直接定位:类型不对看 mimes.js,目录列表不对看 walk.js,读不到文件看 file.js。

├── static # 静态资源目录
│   ├── css/
│   ├── image/
│   ├── js/
│   └── index.html
├── util # 工具代码
│   ├── content.js # 读取请求内容
│   ├── dir.js # 读取目录内容
│   ├── file.js # 读取文件内容
│   ├── mimes.js # 文件类型列表
│   └── walk.js # 遍历目录内容
└── index.js # 启动入口文件
@前端进阶之旅: 代码已经复制到剪贴板

面试官追问

追问 1全写在 index.js 里也能跑,拆这么多文件有必要吗?
参考回答

这么小的项目拆不拆差别不大,但拆开后每个模块能单独测、单独替换,比如把 file.js

基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
  • 基础篇

    • 环境搭建
    • node部署
    • 基础应用
  • 内置模块

    • 1.0 本地路径处理 path
    • 2.0 文件系统操作 fs
    • 3.1 基础调试 console
    • 3.2 本地调试远程服务器上的Node代码
    • 4.1 网络服务 http
    • 4.2 网络服务 http res
    • 4.3 网络服务 http req
    • 4.4 网络服务 http server
    • 4.5 网络服务 http client
    • 4.6 网络服务 https
    • 4.7 网络TCP net
    • 4.8 网络UDP dgram
    • 4.9 域名解析 dns
    • 5.0 网络地址解析 url
    • 5.1 URL查询字符串 querystring
    • 6.1 流操作 stream
    • 6.2 逐行读取 readline
    • 7.1 进程相关 process
    • 7.2 子进程 child
    • 8.1 二进制数据 buffer
    • 8.2 二进制解码 string_decoder
    • 9.1 事件机制 events
    • 9.2 实用工具模块 util
    • 9.3 数据加密 crypto
    • 9.4 MD5入门介绍及crypto模块的应用
    • 9.4 资源压缩 zlib
    • 9.5 集群 cluster
    • 9.6 v8
  • 进阶篇

    • 5分钟入门非对称加密用法
    • cluster
    • cookie_parser深入
    • crypto模块之理论篇
    • express+cookie_parser签名机制深入剖析
    • express+session实现简易身份认证
    • https
    • log4js入门实例
    • node8_napi
    • 使用 async 控制并发
    • 使用 eventproxy 控制并发
    • 使用 superagent 与 cheerio 完成简单爬虫
    • 基于express+muter的文件上传
    • 将图片转成datauri嵌入到html
    • 常用中间件 body_parser 实现解析
    • 日志模块morgan
    • 服务端字符编解码&乱码处理 charset_enc_dec
    • 测试用例mocha,should,istanbul
    • 调试日志打印debug模块
  • Express

    • 1.1 Express概览篇
    • 1.2 Express
  • Koa2

    • 1.0 koa2概览篇
    • 1.1 快速开始
    • 1.2 async await使用
    • 1.3 koa2简析结构
    • 1.4 koa中间件开发与使用
    • 10.1 单元测试
    • 11.1 开发debug
    • 12.1 快速启动
    • 12.2 框架设计
    • 12.3 分层操作
    • 12.4 数据库设计
    • 12.5 路由设计
    • 12.6 webpack4环境搭建
    • 12.7 使用react
    • 12.8 登录注册功能实现
    • 12.9 session登录态判断处理
    • 13.1 import export使用
    • 2.1 原生koa2实现路由
    • 2.2 koa router中间
    • 3.1 GET请求数据获取
    • 3.2 POST请求数据获取
    • 3.3 koa bodyparser中间件
    • 4.1 原生koa2实现静态资源服务器
    • 4.2 koa static中间件
    • 5.1 koa2使用cookie
    • 5.2 koa2实现session
    • 6.1 koa2加载模板引擎
    • 6.2 ejs模板引擎
    • 7.1 busboy模块
    • 7.2 上传文件简单实现
    • 7.3 异步上传图片实现
    • 8.1 mysql模块
    • 8.2 async await封装使用mysql
    • 8.3 项目建表初始化
    • 9.1 原生koa2实现JSONP
    • 9.2 koa jsonp中间件
  • 其他

    • 操作数据库
    • Session 与 Token
    • Cookie、Session、Token、JWT
    • Socket
完整面试题地址:
作者:程序员poetry
扫码关注作者公众号:「前端进阶之旅」 每天分享技术干货
前端进阶之旅公众号二维码

原生koa2实现静态资源服务器|Nodejs专题系列

# 原生koa2实现静态资源服务器

30 秒速记

  • 静态资源服务器就做一件事:把 URL 路径映射到磁盘上某个目录里的文件,读出来返回
  • 三步:拼本地路径 → 判断是文件、目录还是不存在 → 设 Content-Type 返回内容
  • 安全底线:拼完路径必须检查还在静态根目录里,不然 /../ 能读到服务器任意文件
  • 生产要考虑流式输出、缓存头、Range,实际一般交给 Nginx、CDN 或 koa-static

原生实现静态资源服务器,本质就是把请求路径拼到静态目录上,去磁盘找文件,找到就按类型返回,找不到就返回 404。 这个例子的价值是让你看清 koa-static 这类中间件内部在干什么。自己写的时候最容易漏的是安全:path.join(root, ctx.url) 遇到 /../../etc/passwd 会跑出根目录,必须拼完再检查一遍。还有性能,一次性 readFile 读大文件很费内存,正经做法是用 fs.createReadStream 流式返回,再加上缓存头。上线的话我一般不会自己写,交给 Nginx 或 CDN。

理解「原生koa2实现静态资源服务器」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。

工程中还要验证退出码、资源清理和异常输入。属于 network 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。

面试官追问

追问 1访问不存在的 /js/missing.js,页面显示 404 文字,这样算对吗?
参考回答

文字对了,但这个例子没设状态码,实际返回的是 200。浏览器和 CDN 会把它当成功的 JS 缓存起来,要写 ctx.status = 404。

追问 2path.join(staticRoot, ctx.url) 有什么安全问题?
参考回答

请求 /../../etc/passwd 时(用 curl --path-as-is 就能发出去),拼出来的路径会跑到根目录外面。拼完要检查:if (!full.startsWith(staticRoot + path.sep)) return ctx.status = 403。

追问 3为什么生产上一般不用这种手写的静态服务?
参考回答

缓存协商、gzip、Range 断点续传、目录穿越防护这些都要自己补,Nginx 和 CDN 早就做好了,性能也高一大截。手写版用来理解原理就够了。

追问 4静态目录写成 './static',从别的目录启动服务就找不到文件了,为什么?
参考回答

path.resolve 处理相对路径时按 process.cwd() 算,也就是你执行命令的目录。用 path.join(__dirname, './static'),按入口文件所在位置算,在哪启动都一样。

# 原生koa2实现静态资源服务器

# 前言

30 秒速记

  • 请求静态资源只有三种结果:是文件 → 返回内容,是目录 → 列目录或返回首页,不存在 → 404
  • 判断方式:fs.stat 看 isFile() 还是 isDirectory(),报 ENOENT 就是不存在
  • 文件要按扩展名带对 Content-Type,不然浏览器可能不执行 JS、不应用 CSS
  • 目录列表要不要展示是安全决策,线上一般关掉,访问目录时返回 index.html 或 403

一个静态资源请求打过来,服务端无非三种处理:是文件就返回内容,是目录就列出里面的东西或返回首页,啥都没有就 404。 判断靠 fs.stat,返回的对象上有 isFile() 和 isDirectory(),文件不存在会报 ENOENT。这三种情况里最需要拍板的是目录:开发时列目录很方便,线上把目录结构暴露出去等于给别人送地图,一般直接关掉,访问目录时返回 index.html 或者 403。

一个http请求访问web服务静态资源,一般响应结果有三种情况

  • 访问文本,例如js,css,png,jpg,gif
  • 访问静态目录
  • 找不到资源,抛出404错误

面试官追问

追问 1图片请求 /image/logo.png 返回的却是一段目录列表 HTML,会是什么原因?
参考回答

多半是 logo.png 实际是个目录,或者路径拼错落到了上一级目录上。用 fs.statSync(fullPath).isDirectory() 打出来看一下就清楚,同时浏览器那边因为类型不对,图片显示不出来。

追问 2验收用例怎么设计,才能覆盖这三种结果?
参考回答

至少四条:一个 JS 文件、一张图片、一个目录、一个不存在的路径。分别断言状态码、Content-Type 和内容,不能只看后缀猜结果。

追问 3安全评审要求不能展示目录内容,代码该怎么改?
参考回答

isDirectory() 分支里先找 index.html,有就返回它,没有就 ctx.status = 403。目录列表功能整个关掉,只在本地开发时开。

追问 4单页应用希望找不到的路径都返回 index.html,图片 404 也这样处理吗?
参考回答

不行。前端路由兜底只该给页面路由用,/assets/xxx.png 这种静态资源找不到必须是真 404,不然浏览器拿到一段 HTML 当图片解析,排查起来很麻烦。一般按有没有扩展名来区分。

# 原生koa2 静态资源服务器例子

30 秒速记

  • 例子分三块:入口 index.js 管路由和响应,util/content.js 判断资源类型,dir.js、file.js 各管一种
  • 主流程:ctx.url 拼到静态根目录 → stat 判断 → 文件读内容、目录生成链接列表
  • 跑起来自己试四种请求:根路径、index.html、一个 JS、一个不存在的文件
  • 这个例子用同步 API 和全量读取,讲原理够用,不能直接拿去上线

这个例子用纯 Koa 加 fs、path 搭了一个能用的静态服务器,拆成几个小模块各管一件事。 入口文件接请求、算 MIME 类型、写响应;content.js 拿到本地路径后判断是文件、目录还是不存在,分别交给 file.js 和 dir.js。看懂它,再去看 koa-static 和 koa-send 的源码会轻松很多。拉下来跑的时候别只访问首页,把不存在的路径、中文文件名都试一下,能看出这种简化写法的边界在哪。

demo源码

https://github.com/poetries/daily-code-practice/tree/master/node/koa/koa2-demo/static-server/

面试官追问

追问 1只看示例仓库链接,怎么现场讲清楚它做了什么?
参考回答

说清楚一条请求的路径:ctx.url 拼成本地路径,existsSync 判断存在,statSync 区分文件和目录,最后按扩展名设 Content-Type 返回。讲完这条线就够了。

追问 2静态目录下放一个中文名的文件,比如 说明.txt,能访问到吗?
参考回答

访问不到。浏览器会把路径编码成 %E8%AF%B4...,代码直接拿 ctx.url 去拼路径,没做 decodeURIComponent,磁盘上找不到这个名字。

追问 3请求 /index.html?v=123 带了版本号参数,为什么就 404 了?
参考回答

ctx.url 包含查询字符串,拼出来的文件名是 index.html?v=123,磁盘上当然没有。应该用 ctx.path,它不带问号后面那段。

追问 4能直接把这个目录结构复制到生产项目里用吗?
参考回答

不建议。同步读文件会阻塞事件循环,没防目录穿越,没有缓存头。生产直接上 koa-static,或者让 Nginx 去托管静态文件。

# 代码目录

30 秒速记

  • static/:要对外提供的资源,css、image、js 和 index.html
  • util/content.js:总调度,判断请求的是文件、目录还是不存在
  • dir.js + walk.js:读目录、拼成带链接的 HTML 列表;file.js:读文件内容
  • mimes.js:扩展名到 Content-Type 的映射表,新增文件类型改这里
  • index.js:启动入口,算 MIME、写响应

目录结构按职责拆得很清楚:static 放对外的资源,util 放工具函数,index.js 是入口。 content.js 是调度中心,拿到本地路径后判断是什么,再分给 file.js 或 dir.js;walk.js 负责把目录里的子目录和文件列出来,mimes.js 是一张扩展名对应类型的表。这样拆的好处是排查问题时能直接定位:类型不对看 mimes.js,目录列表不对看 walk.js,读不到文件看 file.js。

├── static # 静态资源目录
│   ├── css/
│   ├── image/
│   ├── js/
│   └── index.html
├── util # 工具代码
│   ├── content.js # 读取请求内容
│   ├── dir.js # 读取目录内容
│   ├── file.js # 读取文件内容
│   ├── mimes.js # 文件类型列表
│   └── walk.js # 遍历目录内容
└── index.js # 启动入口文件
@前端进阶之旅: 代码已经复制到剪贴板

面试官追问

追问 1全写在 index.js 里也能跑,拆这么多文件有必要吗?
参考回答

这么小的项目拆不拆差别不大,但拆开后每个模块能单独测、单独替换,比如把 file.js 改成流式读取,其他文件都不用动。演示原理时这种拆法也更好讲。

追问 2请求 /image/logo.png 却走进了 dir.js,从哪查起?
参考回答

从 content.js 查,那里决定走哪个分支。打印一下拼出来的本地路径和 stat.isDirectory() 的结果,通常是路径拼错了。

追问 3新加了 .webp 图片,能读到但浏览器显示不出来,改哪个文件?
参考回答

改 mimes.js,加一行 webp: 'image/webp'。没有映射的话 Content-Type 不对,入口文件判断不是图片,也就不会走二进制写出的分支。

追问 4dir.js 和 walk.js 为什么要分开?
参考回答

walk.js 只管读出目录里有哪些项,返回数组;dir.js 负责把数组拼成带链接的 HTML。分开以后想改成返回 JSON 目录,只换 dir.js 就行。

# 代码解析

30 秒速记

  • 主流程:path.join(静态根目录, ctx.url) → existsSync → statSync 分目录和文件
  • 目录 → dir.js 生成链接列表;文件 → file.js 用 readFileSync(path, 'binary') 读
  • 入口按扩展名查 mimes 设 Content-Type;图片走 ctx.res.write(content, 'binary'),其他给 ctx.body
  • 三个隐患:不存在时没设 404 状态码、用 ctx.url 拼路径没防 /../、同步全量读取阻塞事件循环
  • 改进方向:ctx.path + 解码 + 根目录校验,fs.createReadStream 直接赋给 ctx.body

这段代码的主线是把请求路径映射到本地文件,然后按是目录还是文件分别处理,最后根据扩展名设类型返回。 content.js 用 existsSync 判存在、statSync 判类型,不存在就返回一段 404 文字,目录交给 dir.js 生成链接列表,文件交给 file.js 用 binary 编码读出来。入口文件里图片绕过 Koa 直接用原生 ctx.res.write 写二进制,其他内容赋给 ctx.body。讲原理很直观,但它全是同步 API,读一个大文件时整个进程都卡着,而且没做路径校验和状态码,这些是上线前必须补的。

← 3.3 koa bodyparser中间件4.2 koa static中间件 →

fe