一、引言
在 Web 应用开发中,文件上传是一个常见的需求。而当涉及到上传大文件时,传统的一次性上传方式可能会面临诸多问题,比如网络不稳定导致上传失败需要重新上传,耗费大量时间和带宽资源等。这时,HTTP 断点续传技术就显得尤为重要,它能够让用户在上传大文件时,即使出现网络中断等情况,也无需从头开始,大大提高了上传的效率和用户体验。
二、HTTP 断点续传原理
HTTP 协议提供了Range
请求头来支持断点续传功能。Range
请求头用于指定请求资源的某个范围,服务器在接收到包含Range
请求头的请求后,会根据指定的范围返回对应的部分资源。
例如,当我们请求一个文件时,可以使用类似这样的Range
请求头:
ini
Range: bytes=500-999
这表示请求文件从第 500 个字节到第 999 个字节的内容。服务器接收到该请求后,会返回状态码206 Partial Content
,表示返回的是部分内容,并在响应头中包含Content-Range
字段,告知客户端返回的内容范围,如:
css
Content-Range: bytes 500-999/10000
其中10000
表示文件的总字节数。
三、大文件上传面临的问题
(一)网络不稳定
在上传大文件过程中,网络波动、信号弱等情况都可能导致上传中断。如果没有断点续传功能,用户只能重新开始上传,这对于大文件来说是非常耗时且令人沮丧的。
(二)内存占用
一次性读取整个大文件到内存中进行上传,会占用大量的内存资源,可能导致应用程序性能下降甚至崩溃。
(三)服务器压力
大文件上传会给服务器带来较大的负载,尤其是在高并发场景下,可能影响服务器的正常运行。
四、实现 HTTP 断点续传与大文件上传
(一)前端实现
在前端,我们可以使用Blob
和FormData
对象来实现大文件的分块上传。
- 文件分块 通过
Blob.slice()
方法将大文件分割成多个小块,例如:
javascript
const file = document.getElementById('fileInput').files[0];
const chunkSize = 1024 * 1024; // 1MB 一块
const chunks = [];
for (let i = 0; i < file.size; i += chunkSize) {
chunks.push(file.slice(i, i + chunkSize));
}
- 上传分块 使用
XMLHttpRequest
或Fetch API
发送每个分块,设置Range
请求头:
javascript
const uploadChunk = (index) => {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('POST', '/upload', true);
const start = index * chunkSize;
const end = Math.min(start + chunkSize, file.size);
xhr.setRequestHeader('Range', `bytes=${start}-${end - 1}`);
xhr.onload = () => {
if (xhr.status === 200 || xhr.status === 206) {
resolve();
} else {
reject(new Error(`Upload chunk ${index} failed with status ${xhr.status}`));
}
};
xhr.onerror = (error) => {
reject(error);
};
const formData = new FormData();
formData.append('file', chunks[index]);
xhr.send(formData);
});
};
- 断点续传逻辑 记录已经成功上传的分块,在重新上传时跳过已上传的部分:
javascript
let uploadedChunks = []; // 记录已上传的分块索引
// 假设从服务器获取已上传分块信息
// uploadedChunks = getUploadedChunksFromServer();
for (let i = 0; i < chunks.length; i++) {
if (!uploadedChunks.includes(i)) {
await uploadChunk(i);
uploadedChunks.push(i);
}
}
(二)后端实现(以 Node.js 为例)
后端需要接收前端上传的分块,并将其合并成完整的文件。同时,要处理Range
请求头,返回正确的响应。
- 接收分块并合并
javascript
const express = require('express');
const fs = require('fs');
const path = require('path');
const app = express();
const port = 3000;
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(express.static('public'));
app.post('/upload', (req, res) => {
const file = req.files.file;
const { range } = req.headers;
const [_, start, end] = range.match(/bytes=(\d+)-(\d+)/).map(Number);
const fileName = 'uploadedFile';
const filePath = path.join(__dirname, fileName);
const writeStream = fs.createWriteStream(filePath, { start, end });
file.stream.pipe(writeStream);
writeStream.on('finish', () => {
writeStream.close(() => {
res.status(200).send('Chunk uploaded successfully');
});
});
});
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});
- 处理续传请求 在服务器端,需要记录已上传的分块信息,以便在接收到续传请求时,能够正确处理。可以使用数据库或文件等方式记录。当接收到续传请求时,检查已上传的分块,跳过已上传部分,继续接收新的分块。
五、总结
HTTP 断点续传与大文件上传技术,通过巧妙利用 HTTP 协议的特性,解决了大文件上传过程中的诸多难题。在前端和后端的协同配合下,实现了高效、稳定的大文件上传,提升了用户体验,同时也减轻了服务器的压力。随着互联网应用对文件上传需求的不断增加,这种技术在未来的 Web 开发中将会发挥更加重要的作用。