HTTP 服务器处理
NodeJS 和 Deno 等服务器端 JS 平台具有用于监听网络接口的内置 API。它们提供请求和响应的封装器。
¥Server-Side JS platforms like NodeJS and Deno have built-in APIs for listening on network interfaces. They provide wrappers for requests and responses.
该演示主要关注 HTTP 服务器。其他演示涵盖其他 HTTP 用例:
¥This demo focuses on HTTP servers. Other demos cover other HTTP use cases:
-
"HTTP 下载" 涵盖下载文件
¥"HTTP Downloads" covers downloading files
-
"HTTP 上传" 涵盖上传文件
¥"HTTP Uploads" covers uploading files
概述
¥Overview
解析 POST 请求中的文件
¥Parsing Files in POST Requests
通常,服务器接收内容类型为 multipart/form-data
或 application/x-www-form-urlencoded
的表单数据。平台本身通常不提供 "正文解析" 功能,而是依靠社区提供模块来获取编码数据并将其拆分为表单字段和文件。
¥Typically servers receive form data with content type multipart/form-data
or
application/x-www-form-urlencoded
. The platforms themselves typically do not
provide "body parsing" functions, instead leaning on the community to supply
modules to take the encoded data and split into form fields and files.
NodeJS 服务器通常使用像 formidable
这样的解析器。在下面的示例中,formidable
将写入文件,XLSX.readFile
将读取文件:
¥NodeJS servers typically use a parser like formidable
. In the example below,
formidable
will write to file and XLSX.readFile
will read the file:
var XLSX = require("xlsx"); // This is using the CommonJS build
var formidable = require("formidable");
require("http").createServer(function(req, res) {
if(req.method !== "POST") return res.end("");
/* parse body and implement logic in callback */
(new formidable.IncomingForm()).parse(req, function(err, fields, files) {
/* if successful, files is an object whose keys are param names */
var file = files["upload"]; // <input type="file" id="upload" name="upload">
/* file.path is a location in the filesystem, usually in a temp folder */
var wb = XLSX.readFile(file.filepath);
// print the first worksheet back as a CSV
res.end(XLSX.utils.sheet_to_csv(wb.Sheets[wb.SheetNames[0]]));
});
}).listen(process.env.PORT || 3000);
XLSX.read
将接受 NodeJS 缓冲区以及 Uint8Array
、Base64 字符串、二进制字符串和普通字节数组。这涵盖了各种框架的接口类型。
¥XLSX.read
will accept NodeJS buffers as well as Uint8Array
, Base64 strings,
binary strings, and plain Arrays of bytes. This covers the interface types of
a wide variety of frameworks.
在 GET 请求中写入文件
¥Writing Files in GET Requests
通常,服务器库使用接受 Uint8Array
数据的响应 API。XLSX.write
和选项 type: "buffer"
将生成数据。要强制将响应视为附件,请设置 Content-Disposition
标头:
¥Typically server libraries use a response API that accepts Uint8Array
data.
XLSX.write
with the option type: "buffer"
will generate data. To force the
response to be treated as an attachment, set the Content-Disposition
header:
var XLSX = require("xlsx"); // This is using the CommonJS build
require("http").createServer(function(req, res) {
if(req.method !== "GET") return res.end("");
var wb = XLSX.read("S,h,e,e,t,J,S\n5,4,3,3,7,9,5", {type: "binary"});
res.setHeader('Content-Disposition', 'attachment; filename="SheetJS.xlsx"');
res.end(XLSX.write(wb, {type:"buffer", bookType: "xlsx"}));
}).listen(process.env.PORT || 3000);
NodeJS
当处理小文件时,工作最好在服务器响应处理函数中处理。该方法用于 "框架演示" 部分。
¥When processing small files, the work is best handled in the server response handler function. This approach is used in the "Framework Demos" section.
当处理大文件时,直接的方法会冻结服务器。NodeJS 为这个确切的用例提供了 "工作线程"。
¥When processing large files, the direct approach will freeze the server. NodeJS provides "Worker Threads" for this exact use case.
框架演示
¥Framework Demos
Express
¥The exposition has been moved to a separate page.
NestJS
¥The exposition has been moved to a separate page.
Fastify
¥The exposition has been moved to a separate page.
工作线程
¥Worker Threads
NodeJS "工作线程" 在 v14 中引入,并最终在 v16 中标记为稳定。与 AsyncResource
相结合,一个简单的线程池就可以在不阻塞服务器的情况下进行处理!官方 NodeJS 文档包含一个示例工作池实现。
¥NodeJS "Worker Threads" were introduced in v14 and eventually marked as stable
in v16. Coupled with AsyncResource
, a simple thread pool enables processing
without blocking the server! The official NodeJS docs include a sample worker
pool implementation.
此示例使用 ExpressJS 创建通用 XLSX 转换服务,但相同的方法适用于任何 NodeJS 服务器端框架。
¥This example uses ExpressJS to create a general XLSX conversion service, but the same approach applies to any NodeJS server side framework.
读取大文件时,强烈建议在主服务器进程中运行正文解析器。像 formidable
这样的主体解析器会将上传的文件写入文件系统,并且文件路径应该传递给工作线程(工作线程将负责读取和清理文件)。
¥When reading large files, it is strongly recommended to run the body parser in
the main server process. Body parsers like formidable
will write uploaded
files to the filesystem, and the file path should be passed to the worker (and
the worker would be responsible for reading and cleaning up the files).
child_process
模块也可以生成 命令行工具。本演示中未探讨该方法。
¥The child_process
module can also spawn command-line tools.
That approach is not explored in this demo.
Complete Example (click to show)
This demo was tested in the following environments:
NodeJS | Date | Dependencies |
---|---|---|
18.20.3 | 2024-06-30 | ExpressJS 4.19.2 + Formidable 2.1.2 |
20.15.0 | 2024-06-30 | ExpressJS 4.19.2 + Formidable 2.1.2 |
22.3.0 | 2024-06-30 | ExpressJS 4.19.2 + Formidable 2.1.2 |
- Create a new project with a ESM-enabled
package.json
:
mkdir sheetjs-worker
cd sheetjs-worker
echo '{ "type": "module" }' > package.json
- Install the dependencies:
npm i --save https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz express@4.19.2 formidable@2.1.2
- Create a worker script
worker.js
that listens for messages. When a message is received, it will read the file from the filesystem, generate and pass back a new XLSX file, and delete the original file:
/* load the worker_threads module */
import { parentPort } from 'node:worker_threads';
/* load the SheetJS module and hook to FS */
import { set_fs, readFile, write } from 'xlsx';
import * as fs from 'fs';
set_fs(fs);
/* the server will send a message with the `path` field */
parentPort.on('message', (task) => {
// read file
const wb = readFile(task.path, { dense: true });
// send back XLSX
parentPort.postMessage(write(wb, { type: "buffer", bookType: "xlsx" }));
// remove file
fs.unlink(task.path, ()=>{});
});
- Download
worker_pool.js
:
curl -LO https://xlsx.nodejs.cn/server/worker_pool.js
(this is a slightly modified version of the example in the NodeJS docs)
- Save the following server code to
main.mjs
:
/* load dependencies */
import os from 'node:os';
import process from 'node:process'
import express from 'express';
import formidable from 'formidable';
/* load worker pool */
import WorkerPool from './worker_pool.js';
const pool = new WorkerPool(os.cpus().length);
process.on("beforeExit", () => { pool.close(); })
/* create server */
const app = express();
app.post('/', (req, res, next) => {
// parse body
const form = formidable({});
form.parse(req, (err, fields, files) => {
// look for "upload" field
if(err) return next(err);
if(!files["upload"]) return next(new Error("missing `upload` file"));
// send a message to the worker with the path to the uploaded file
pool.runTask({ path: files["upload"].filepath }, (err, result) => {
if(err) return next(err);
// send the file back as an attachment
res.attachment("SheetJSPool.xlsx");
res.status(200).end(result);
});
});
});
// start server
app.listen(7262, () => { console.log(`Example app listening on port 7262`); });
- Run the server:
node main.mjs
Keep the server process running during the test.
- Test with the
pres.numbers
sample file. The following commands should be run in a new terminal window:
curl -LO https://xlsx.nodejs.cn/pres.numbers
curl -X POST -F upload=@pres.numbers http://localhost:7262/ -J -O
This will generate SheetJSPool.xlsx
.
其他平台
¥Other Platforms
Bun
Bun 提供了实现 Web 服务器的基本元素。
¥Bun provides the basic elements to implement a web server.
ElysiaJS
¥The exposition has been moved to a separate page.
Deno
许多托管服务(包括 Deno 部署)不提供脚本文件系统访问。
¥Many hosted services, including Deno Deploy, do not offer filesystem access from scripts.
这会破坏在正文解析中使用文件系统的 Web 框架。
¥This breaks web frameworks that use the filesystem in body parsing.
Deno 提供了实现 Web 服务器的基本元素。它不提供开箱即用的正文解析器。
¥Deno provides the basic elements to implement a web server. It does not provide a body parser out of the box.
Drash
在测试中,Drash 有一个内存主体解析器,可以处理 Deno 部署 上的文件上传。
¥In testing, Drash had an in-memory body parser which could handle file uploads on Deno Deploy.