ESP8266 FSBrowser 示例深度解析:基于 ESP8266WebServer 的跨文件系统 Web 文件管理器
【免费下载链接】ArduinoESP8266 core for Arduino项目地址: https://gitcode.com/gh_mirrors/ard/Arduino
导读
本文围绕 Arduino 生态下 ESP8266 core 仓库中的FSBrowser官方示例展开,完整讲解如何构建一个基于 HTTP 请求与 HTML/JavaScript 前端的文件系统浏览器(FileSystem Browser),它可统一运行在 SPIFFS、LittleFS 与 SDFS 三种文件系统之上。读完本文,你将掌握 FSBrowser 的编译配置、文件系统烧录方法、浏览器访问与编辑流程、文件系统瘦身与内嵌页面等进阶选项,并从 FSBrowser.ino 的源码出发理解其 RESTful API 设计与底层实现原理。
FSBrowser 是什么
FSBrowser 是一个面向 ESP8266 的 Web 文件系统浏览器示例程序:ESP8266 通过 ESP8266WebServer 暴露一组 HTTP 接口,浏览器端则用 HTML/JavaScript 渲染文件树、文本编辑器和图片预览,从而实现对设备文件系统的浏览、编辑、上传、下载、新建、删除与重命名操作。
该统一版本由早期的FSWebServer、FSBrowser和SDWebServer三个示例合并而来(原始版权归 Hristo Gochkov,2015),其核心设计目标是同一套代码、同一个前端,同时兼容 SPIFFS、LittleFS 与 SDFS 三种文件系统。示例本体位于 libraries/ESP8266WebServer/examples/FSBrowser/FSBrowser.ino,随附的 readme.md 是该功能的官方使用说明,本文以其为骨架展开。
从源码结构看,示例的文件布局如下:
FSBrowser.ino:主程序,包含文件系统初始化、Wi-Fi/MDNS 连接、HTTP 路由与全部请求处理器;data/:需要烧录到文件系统根目录的 Web 前端资源,其中data/edit/index.htm是核心编辑器页面,data/index.htm、data/favicon.ico、data/pins.png为示例内容文件;extras/:构建期工具与产物,包括reduce_index.sh(压缩/内嵌脚本)、index.htm.gz(gzip 压缩版页面)与index_htm.h(可内嵌进固件的 C 头文件)。
快速上手:五步跑通 FSBrowser
第 1 步:选择文件系统
打开FSBrowser.ino,文件头部有三行#define宏,同一时刻只能启用其中一个:
// #define USE_SPIFFS #define USE_LITTLEFS // #define USE_SDFS- 取消注释
USE_SPIFFS:使用 SPIFFS(老式 SPI 闪存文件系统); - 取消注释
USE_LITTLEFS:使用 LittleFS(推荐的新式闪存文件系统); - 取消注释
USE_SDFS:使用 SDFS(SD 卡文件系统)。
如果三个宏全部注释,编译会直接报错,源码中显式写明了这一点(FSBrowser.ino):
#error Please select a filesystem first by uncommenting one of the "#define USE_xxx" lines at the beginning of the sketch.宏的选择同时决定了编译期绑定到哪个文件系统实例。在源码中,#if/#elif条件编译块会为fileSystem指针与fileSystemConfig配置对象赋值:
#if defined USE_SPIFFS #include <FS.h> const char* fsName = "SPIFFS"; FS* fileSystem = &SPIFFS; SPIFFSConfig fileSystemConfig = SPIFFSConfig(); #elif defined USE_LITTLEFS #include <LittleFS.h> const char* fsName = "LittleFS"; FS* fileSystem = &LittleFS; LittleFSConfig fileSystemConfig = LittleFSConfig(); #elif defined USE_SDFS #include <SDFS.h> const char* fsName = "SDFS"; FS* fileSystem = &SDFS; SDFSConfig fileSystemConfig = SDFSConfig(); #endif这种“运行时通过FS*抽象基类操作、编译期通过宏选择具体实现”的模式,正是整个 FSBrowser 能一套代码兼容三种文件系统的关键,也解释了 readme 中“统一版本”的由来。
第 2 步:填入 Wi-Fi 凭据
在源码中搜索STASSID,将占位符替换为你的网络信息:
#ifndef STASSID #define STASSID "your-ssid" #define STAPSK "your-password" #endif程序启动后会以 STA 模式连接该网络,并注册 mDNS 主机名fsbrowser(源码中的const char* host = "fsbrowser";),因此浏览器可用http://fsbrowser.local访问设备。
第 3 步:编译并烧录固件
在 Arduino IDE 中打开FSBrowser.ino,选择目标 ESP8266 开发板,直接编译上传即可。需要说明的是,示例本身还会初始化串口调试输出(115200 baud),连接过程与文件系统初始化结果都会打印在串口监视器上。
第 4 步:向文件系统烧录 Web 前端资源
固件本身不含前端页面(除非启用内嵌选项,见下文),因此必须把data文件夹的内容写入文件系统:
- SDFS 方案:把
data文件夹的内容(注意不是data文件夹本身)复制到一张 FAT/FAT32 格式化的 SD 卡根目录,并将 SD 卡连接到 ESP8266 的 SPI 端口; - SPIFFS 或 LittleFS 方案:使用 Arduino IDE 的 “ESP8266 Sketch Data Upload” 插件(即
esptool配合文件系统镜像工具),将data目录作为数据镜像烧录到闪存。
烧录完成后,设备上应存在/edit/index.htm(编辑器页面)与/index.htm(默认首页)等文件。
第 5 步:在浏览器中打开编辑器
确保设备与电脑在同一网络后,访问:
http://fsbrowser.local/edit如果 mDNS 在你的网络环境中不可用,也可以改用串口监视器中打印的 IP 地址,例如http://192.168.x.x/edit。
页面功能与前端交互设计
浏览器端页面由 data/edit/index.htm 实现(约 1100 行,单文件 HTML/JavaScript)。从脚本结构看,前端主要包含三大部分:
- 文件树(File Tree):默认占窗口宽度 20%(
#tree的width: 20%),以文件夹优先、字母序排列;文件条目显示大小(如1.23 KiB),并按扩展名区分文本文件(span.txt)、图片文件(span.img)与普通文件图标。 - 主面板(Editor / Preview):左侧 20% 为文件树,右侧 80% 为主面板。文本文件用 Ace.js 编辑器打开,图片文件用
<img>标签内联预览,其余格式显示“(file not found or format not supported)”。 - 顶栏(Header):包含文件选择框、路径输入框、Upload / MkDir / MkFile 按钮、Save / Discard / Help 编辑器按钮,以及右上角的文件系统状态条(FS 类型、已用/总容量进度条
<meter>)。
前端与后端的通信全部通过XMLHttpRequest完成,请求期间页面会通过半透明遮罩(#loading)与旋转 spinner 提示异步操作进行中。文件树支持右键上下文菜单,提供 Edit/Preview、Download、Rename/Move、Delete 等操作;文本编辑器则绑定了Ctrl-S(保存)与Ctrl-Alt-h(快捷键帮助)等快捷键。
前端对三种文件系统的差异化处理
前端会先请求/status接口获取fsInfo(包含type、isOk、totalBytes、usedBytes等字段),并据此做出差异:
fsInfo.type != "SPIFFS"时才显示 MkDir 按钮——因为 SPIFFS 没有真正的目录概念(见下文限制);- 刷新路径时,SPIFFS 一律刷新整棵树(无父节点概念),LittleFS/SDFS 则支持逐级递归刷新。
HTTP API 一览:前端与后端的契约
FSBrowser 的核心是一组围绕/edit的 RESTful 接口,路由在 FSBrowser.ino 中注册:
| HTTP 方法 | 路径 | 处理器 | 功能 |
|---|---|---|---|
| GET | /status | handleStatus | 返回 FS 类型、状态、容量 JSON |
| GET | /list?dir=路径 | handleFileList | 返回目录下的文件/文件夹列表(JSON) |
| GET | /edit | handleGetEdit | 返回编辑器页面(index.htm) |
| PUT | /edit | handleFileCreate | 新建文件/文件夹,或重命名/移动 |
| DELETE | /edit | handleFileDelete | 删除文件或文件夹(递归) |
| POST | /edit | replyOK+handleFileUpload | 文件上传(两个回调分别处理请求结束与分块数据) |
| 其他 | 任意 URI | handleNotFound | 兜底:优先尝试从文件系统读文件,失败返回 404 调试页 |
前端 JavaScript 中的httpList、httpCreate、httpRename、httpDelete、httpUpload、postFile等函数与这些路由一一对应。例如新建文件发送PUT /edit且FormData中携带path;重命名则额外携带src源路径;上传与保存文件使用POST /edit并携带data字段。
关键实现细节
- 列表接口使用 HTTP/1.1 分块响应:
handleFileList为避免在内存中拼装大字符串,调用了server.chunkedResponseModeStart(200, "text/json")开启分块模式(FSBrowser.ino)。若客户端是 HTTP/1.0(_currentVersion == 0),分块模式不可用,接口会返回 505 错误。对应库实现见 ESP8266WebServer.h。 - 文件读取自动处理目录索引与 gzip:
handleFileRead对以/结尾的路径自动追加index.htm;若文件不存在则尝试path + ".gz"(配合extras里的 gzip 页面),再通过server.streamFile(file, contentType)流式发送(FSBrowser.ino),streamFile的库实现见 ESP8266WebServer.h。 - 文件上传是分阶段回调:
handleFileUpload依据upload.status在UPLOAD_FILE_START(创建文件)、UPLOAD_FILE_WRITE(写入数据块,并校验写入字节数)、UPLOAD_FILE_END(关闭文件)三个状态间流转。 - 删除文件夹是递归的(带警告):
deleteRecursive先删除目录内所有子项再删除目录本身。源码注释明确提醒:嵌入式设备上递归可能导致栈溢出崩溃,此写法仅用于演示,生产系统不应照搬(FSBrowser.ino)。 - URL 解码:
handleNotFound中对 URI 调用ESP8266WebServer::urlDecode,以便正确处理含空格的路径。 - 文件系统不自动格式化:
setup()中fileSystemConfig.setAutoFormat(false)后调用fileSystem->setConfig(fileSystemConfig)与fileSystem->begin();挂载失败时不会格式化文件系统,而是返回"FS INIT ERROR"错误信息。
/status的 JSON 结构
handleStatus返回的 JSON 形如:
{"type":"LittleFS", "isOk":"true", "totalBytes":"1048576", "usedBytes":"32768","unsupportedFiles":""}type:当前选择的文件系统名(SPIFFS / LittleFS / SDFS);isOk:挂载是否成功;totalBytes/usedBytes:由FSInfo结构体获得的总容量与已用字节数;unsupportedFiles:仅 SPIFFS 模式非空,列出文件名不合规而被跳过的文件(见下文 SPIFFS 限制)。
存储空间优化:从 27KB 级压缩到 7KB 以内
data目录自带若干示例文件(index.htm、pins.png、favicon.ico等)。若 ESP8266 文件系统空间紧张,readme 给出了两个层面的瘦身手段:
- 删除示例文件:删除文件系统根目录下的示例文件,仅保留必需的
edit/index.htm; - 替换为 gzip 压缩版:把
data/edit/index.htm替换为extras/index.htm.gz(压缩后的编辑器页面)。readme 说明此压缩版不适合用于学习或调试,但能把整个文件系统占用压到7KB 以下。
注意:handleFileRead的“找不到文件时自动尝试.gz后缀”逻辑(见上文)使得index.htm.gz可以无缝替代index.htm,这正是该优化能够成立的原因。
在已有文件系统上使用 FSBrowser 的两种方式
如果你不想执行第 4 步(重新烧录整个文件系统),或者要在一个已有数据的文件系统上部署编辑器页面,readme 提供了两条路径:
方式一:用 cURL 上传页面
在data目录下打开命令行,执行:
curl -F file=@edit/index.htm;filename=/edit/index.htm fsbrowser.local/edit该命令通过 HTTP 的multipart/form-data上传方式,把本地的edit/index.htm直接写入设备文件系统的/edit/index.htm,无需重新烧录镜像。
方式二:把页面内嵌进固件
取消 FSBrowser.ino 处的宏注释并重新编译:
#define INCLUDE_FALLBACK_INDEX_HTM启用后,程序会把extras/index_htm.h中内嵌的 gzip 版页面编入固件(见 handleGetEdit 的兜底逻辑):当文件系统上既找不到/edit/index.htm也找不到/edit/index.htm.gz时,直接返回内嵌页面。该内嵌版本功能与原始页面等价,代价是固件体积增大。
改动页面后的重建流程(重要)
无论是使用 gzip 版还是INCLUDE_FALLBACK_INDEX_HTM内嵌版,每次修改index.htm后都必须:
- 在
extras目录下重新运行reduce_index.sh脚本; - 重新编译并上传固件。
reduce_index.sh的流水线是(reduce_index.sh):
- 用
html-minifier(kangax 的命令行版本)对data/edit/index.htm做压缩(去除注释、空白、可省略标签、冗余属性等); - 用
gzip压缩得到index.htm.gz; - 用
xxd -i把 gzip 字节流转换为 C 数组,写入自动生成的extras/index_htm.h(文件头明确警告“Auto-generated file. Please do not modify by hand”)。
脚本依赖xxd(VIM 软件包自带)、npm与全局安装的html-minifier,run 之前请先确保这三个工具可用。
依赖说明:Ace.js 编辑器与离线兜底
FSBrowser 的编辑功能依赖Ace.js 1.4.9文本编辑器,页面通过 CDN 加载(https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.9/ace.js)。因此,浏览器必须能访问互联网,编辑功能才能开箱即用。
若浏览器无外网(例如电脑直连 ESP8266 的 AP 热点),可把ace.js复制到文件系统的edit子目录,并按需附带插件与语言模式文件。readme 给出的一组典型文件为:
ace.js ext-keybinding_menu.js ext-searchbox.js mode-html.js worker-html.js worker-css.js worker-javascript.js mode-xml.js worker-xml.js mode-json.js worker-json.js前端逻辑会先尝试 CDN,失败后再从/edit/ace.js加载本地副本(见 index.htm 的脚本注入逻辑)。若本地也没有ace.js,页面自动降级为纯文本查看器,并显示警告信息(对应loadTxtPreview函数)。
路径约定与各文件系统限制
readme 中明确了 FSBrowser 的路径与命名约定,这些约定在前端 JS(如getParentFolder的路径解析注释)与后端源码中均有对应:
- 根目录:文件系统的根统一写作
/; - 路径必须以
/开头:SPIFFS 不支持不带前导斜杠的路径,后端checkForUnsupportedPath会将其判为!NO_LEADING_SLASH!; - 创建语义:路径以
/结尾表示“新建文件夹”,不以/结尾表示“新建文件”,与文件有无扩展名无关; - 目录索引:URL 不以文件名结尾时(含子文件夹),默认返回该目录下的
index.htm; - 8.3 文件名限制(SDFS/FAT16):FAT16 只支持 8.3 短文件名;SPIFFS 与 LittleFS 也有各自的命名限制。readme 提示 FAT 系列需查阅 8.3 文件名规范,SPIFFS/LittleFS 的限制细节见 ESP8266 文件系统官方文档;
- 目录支持差异:SDFS 与 LittleFS 支持真正的目录;SPIFFS 下所有文件都平铺在根上(文件名中虽可含
/字符,但并非真实层级)。
此外,readme 还提到SPIFFS 特有的文件名校验:后端在列出目录与创建文件时都会调用checkForUnsupportedPath(FSBrowser.ino),拒绝含双斜杠//、尾随斜杠/或缺少前导斜杠的文件名,并在/status的unsupportedFiles字段中汇总提示。
针对 SDFS(SD 卡)的专属配置
若选择USE_SDFS,需注意两个配置点:
- CS 引脚:SDFS 默认 CS 引脚为 GPIO4(
SDFSConfig(uint8_t csPin = 4, ...),见 SDFS.h)。如果你的 SD 卡 CS 引脚没有接到默认引脚,需取消 FSBrowser.ino 中fileSystemConfig.setCSPin(chipSelectPin);的注释,并把chipSelectPin改为实际 GPIO。setCSPin与setAutoFormat的链式配置接口定义在 SDFS.h。 - 自动格式化:与闪存文件系统一致,FSBrowser 对 SD 卡同样关闭了自动格式化(
setAutoFormat(false)),挂载失败时只报FS INIT ERROR,不会破坏卡上数据。
变更记录:从旧版 FSBrowser 到统一版的关键改动
readme 的 Changelog 部分回顾了该示例的演进,理解这些改动有助于排查移植问题:
适配 LittleFS(基于 SDFS)时的修复:
- 引入
#define宏选择文件系统; - 从
SD切换到SDFS; begin()不再支持参数,删除 SS 引脚参数并改为可选的fileSystemConfig配置;- LittleFS 的
open()第二个参数为必填,统一显式指定"r"(读)或"w"(写); - 因
FILE_WRITE在 LittleFS 下未声明,统一改用"w"字符串。
UI / 可用性改进:
- 文件系统挂载失败时不格式化,仅返回
FS INIT ERROR; - 文件树面板宽度改为比例的 20%,大屏下可显示长文件名;
- 为文件新增图标,与文件夹图标缩进对齐;换用更轻量中性的图标集,并为文本/图片文件提供专属图标;
- 条目排序:文件夹优先、再普通文件,各自按字母序排列;
- 文件名后显示文件大小;
- 右上角增加文件系统状态信息(类型、容量、剩余空间);
- 异步操作期间以半透明遮罩 + 状态文字明确提示;
- 点击文件后自动把文件名填入顶栏路径框;选择上传文件时默认落在上次点击的文件夹;
- 移除 8.3 小写文件名限制,支持无扩展名文件名与带扩展名的目录名;
- 改进文件树局部递归刷新(删除文件后刷新父目录、新建嵌套文件后展示所在文件夹);
- 为 Ace 编辑器增加 Save / Discard / Help 按钮、离开前未保存确认、保存后刷新树与状态;
- 移除右键菜单中无效的 “Upload” 项;
- 右键菜单新增 “Rename/Move” 功能;
- 支持通过把
index.htm内嵌进程序,在已有文件系统上直接使用。
TODO(未实现的想法):readme 中还列出了一些探讨中的方向,例如查询 SDFS 的 FAT 类型(FAT16/FAT32)以在 FAT16 上限制 8.3 文件名、增加可见的根节点/(不可删除并标注 FS 类型)、把 Mkdir/MkFile 移入右键菜单、实现拖拽移动、可选地把 SPIFFS 呈现为层级结构、同时挂载多个文件系统(SPIFFS + SDFS 或 LittleFS + SDFS)等。这些均为规划项,当前版本尚未实现。
测试清单:如何验证 FSBrowser 行为
readme 末节提供了针对三种文件系统的行为验证清单,可作为手工回归测试脚本。其核心操作为:MkFile(新建文件)、List(列表)、Edit(编辑)、Download(下载)、Delete(删除)、Upload(上传)、View image(预览图片)、Mkdir(新建目录)、嵌套文件创建与删除、以及对不支持文件名的创建尝试。清单按“8.3 短文件名”与“长文件名”两类用例,分别在根目录与子目录中执行。
以 LittleFS + 长文件名为例,覆盖的操作包括:
MkFile '/My text file 1.txt' / List / Edit / Download / Delete / Upload '/My image file 1.png' / View image / Delete image / Mkdir '/My Directory' MkFile '/My Directory/My text file 2.txt' / List / Edit / Download / Delete / Upload '/My Directory/My image file 2.png' / View image / Mkdir '/My Directory/My Subdirectory' Delete root folder '/My Directory' Create nested file '/My folder/My test file.txt' and delete file 'My test file.txt'该清单还专门覆盖了“删除根文件夹”“创建嵌套文件后删除”等边界场景,用于验证deleteRecursive与lastExistingParent(删除/移动后向上找回最近仍存在的祖先目录,见 FSBrowser.ino)的行为是否符合预期。
小结
FSBrowser 是学习 ESP8266WebServer 高级用法的绝佳范本:它以单文件 HTML 前端 + 一组 RESTful 路由实现了完整的文件管理能力,通过FS*抽象统一了 SPIFFS、LittleFS、SDFS 三种文件系统,并示范了分块响应、流式文件发送、分阶段上传回调、gzip 兜底、资源内嵌等嵌入式 Web 开发的典型技巧。无论是作为 Web 配置界面的基础,还是作为文件系统操作的参考实现,它都值得在动手前通读一遍源码。
进一步探索可以参考仓库中的相关文件:FSBrowser.ino、前端页面、readme、压缩脚本、SDFS 配置类 以及文件系统抽象层 FS.h。
【免费下载链接】ArduinoESP8266 core for Arduino项目地址: https://gitcode.com/gh_mirrors/ard/Arduino
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考