当前位置: 首页 > news >正文

1.11 HTTP 文件上传的核心协议

 HTTP 文件上传是 Web 开发中的常见需求,涉及到特殊的请求格式和处理机制。

一、HTTP 文件上传的核心协议

1. 两种主要方式
  • multipart/form-data(主流)
    支持二进制文件和表单字段混合传输,由 Content-Type 头部标识。
  • application/x-www-form-urlencoded(传统表单)
    仅支持文本数据,文件会被编码为 Base64(体积增大 33%),已逐渐淘汰。
2. 关键请求头
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Length: 12345
  • boundary:分隔符,用于标记不同表单字段的边界。
  • Content-Length:请求体总长度(字节)。

3.请求体

请求体包含了实际要上传的数据。对于文件上传,数据被分割成多个部分,每部分由两部分组成:一部分是头部,描述了该部分的内容(如字段名和文件名),另一部分是实际的文件内容。每个部分都以--boundary开始,并以--boundary--结束

关键规则

  1. 字段头部与内容之间必须有空行
    空行由 \r\n\r\n(CRLF CRLF)组成,是协议的硬性规定。

  2. 分隔符与字段头部之间
    分隔符后可以紧跟字段头部(无需空行),但实际请求中可能存在一个换行符(取决于客户端实现)。

  3. 结束标记
    最后一个分隔符必须以 -- 结尾(如 -----------------------------1234567890--)。

二、请求报文结构详解

1. 基础格式
POST /upload HTTP/1.1
Host: example.com
Content-Type: multipart/form-data; boundary=---------------------------1234567890-----------------------------1234567890
Content-Disposition: form-data; name="textField"Hello, World!
-----------------------------1234567890
Content-Disposition: form-data; name="file"; filename="example.txt"
Content-Type: text/plainThis is the file content.
-----------------------------1234567890--
2. 核心组成部分
  • 分隔符(Boundary)
    由 Content-Type 中的 boundary 指定,用于分隔不同字段。
  • 字段头部(Headers)
    Content-Disposition: form-data; name="file"; filename="example.txt"
    Content-Type: text/plain
    
     
    • name:字段名(对应表单中的 name 属性)。
    • filename:文件名(可选,仅文件字段需要)。
    • Content-Type:文件 MIME 类型(默认 application/octet-stream)。
  • 字段内容(Body)
    文件的二进制数据或文本值。

 完整示例

POST /upload HTTP/1.1
Host: example.com
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryABC123
Content-Length: 12345------WebKitFormBoundaryABC123
Content-Disposition: form-data; name="username"JohnDoe
------WebKitFormBoundaryABC123
Content-Disposition: form-data; name="file"; filename="test.txt"
Content-Type: text/plainHello, this is the content of test.txt.
------WebKitFormBoundaryABC123--

三、服务器处理流程

1. 解析步骤
  1. 读取 Content-Type 中的 boundary
  2. 按分隔符分割请求体。
  3. 解析每个字段的头部和内容。
2. Node.js 示例(原生实现.学习使用 该案例未做安全防护,未做文件分割,大文件会导致内存溢出.)
const http = require('http');
const fs = require('fs');
const path = require('path');const server = http.createServer((req, res) => {if (req.method === 'POST') {// 获取 boundaryconst contentType = req.headers['content-type'];const boundary = `--${contentType.split('boundary=')[1]}`;const boundaryBuffer = Buffer.from(boundary);// 存储完整请求体(二进制形式)let requestBuffer = Buffer.from('');// 收集所有数据块(二进制形式)req.on('data', (chunk) => {requestBuffer = Buffer.concat([requestBuffer, chunk]);});req.on('end', () => {// 按 boundary 分割(使用二进制操作)const parts = splitBuffer(requestBuffer, boundaryBuffer);parts.forEach(part => {// 分离头部和内容(二进制形式)const headerEnd = part.indexOf('\r\n\r\n');if (headerEnd === -1) return;const headersBuffer = part.slice(0, headerEnd);const contentBuffer = part.slice(headerEnd + 4); // +4 跳过 \r\n\r\n// 解析头部(转换为字符串)const headers = parseHeaders(headersBuffer.toString());// 如果是文件,保存到磁盘if (headers.filename) {// 移除内容末尾的 \r\n--const endIndex = contentBuffer.indexOf('\r\n--');const fileContent = endIndex !== -1 ? contentBuffer.slice(0, endIndex) : contentBuffer;// 生成安全的文件名(添加时间戳)const ext = path.extname(headers.filename);const safeFilename = `${Date.now()}_${Math.random().toString(36).substring(2, 10)}${ext}`;const savePath = path.join(__dirname, 'uploads', safeFilename);// 直接写入二进制数据fs.writeFile(savePath, fileContent, (err) => {if (err) {console.error('保存文件失败:', err);res.statusCode = 500;res.end('服务器错误');}});console.log(`文件已保存: ${savePath}`);}});res.end('上传完成');});} else {//设置为utf-8编码res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });res.end(`<form method="post" enctype="multipart/form-data"><input type="file" name="file"><button type="submit">上传</button></form>`);}
});// 按 boundary 分割 Buffer
function splitBuffer(buffer, boundary) {const parts = [];let startIndex = 0;while (true) {const index = buffer.indexOf(boundary, startIndex);if (index === -1) break;if (startIndex > 0) {parts.push(buffer.slice(startIndex, index));}startIndex = index + boundary.length;// 检查是否到达末尾if (buffer.slice(index + boundary.length, index + boundary.length + 2).toString() === '--') {break;}}return parts;
}// 解析头部信息
function parseHeaders(headerText) {const headers = {};const lines = headerText.split('\r\n');lines.forEach(line => {if (!line) return;const [key, value] = line.split(': ');if (key === 'Content-Disposition') {const params = value.split('; ');params.forEach(param => {const [name, val] = param.split('=');if (val) headers[name] = val.replace(/"/g, '');});} else {headers[key] = value;}});return headers;
}// 创建上传目录
fs.mkdirSync(path.join(__dirname, 'uploads'), { recursive: true });server.listen(3000, () => {console.log('服务器运行在 http://localhost:3000');
});

四、 前端实现

  • 原生表单
    <form action="/upload" method="post" enctype="multipart/form-data"><input type="file" name="file"><input type="submit">
    </form>
    
  • AJAX 上传(使用 FormData
    const formData = new FormData();
    formData.append('file', fileInput.files[0]);fetch('/upload', {method: 'POST',body: formData
    });
    

五 总结

  • 协议核心multipart/form-data 格式通过分隔符实现多字段传输。
  • 安全要点:该案例不适合生产使用. 生产使用建议使用第三方库formidable 

相关文章:

  • 分享在日常开发中常用的ES6知识点【面试常考】
  • Notepad++如何列选
  • JVM深度解析:执行引擎、性能调优与故障诊断完全指南
  • 【深度解读】混合架构数据保护实战
  • 小米CR660X/TR60X系列,获取SSH权限后刷openwrt系统
  • OpenCV CUDA模块图像变形------对图像进行上采样操作函数pyrUp()
  • OpenCV图像金字塔
  • Flutter 导航与路由管理:Navigator 的深入解析与实践
  • 使用 DeepSeek 为 TDengine 创建专属知识库
  • 光谱相机叶绿素荧光成像技术的原理
  • 图像处理控件Aspose.Imaging教程:图像处理控件Aspose.Imaging教程:在Java中构建 SVG 图像调整器
  • 目标检测——YOLOv12算法解读
  • leetcode 路径总和III java
  • LeetCode 热题 100 链表篇|Java 通关全攻略:从基础到进阶的 20 道核心题解(附完整思路与代码)
  • 织梦dedecms内容页调用seotitle标题的写法
  • elastalert实现飞书机器人告警-docker
  • Go 语言:高并发编程的性能突围之路
  • 前端八股文 - CSS 篇
  • 网络编程之Modbus与HTTP
  • 网页中调用自定义字体可以通过 ‌CSS‌ 的 @font-face 规则实现
  • 做外贸那里发广告网站/推广软件的渠道有哪些
  • 专业做网站优化价格/手机网页设计
  • 分局网站建设/竞价推广培训课程
  • 网站建设的盈利模式/十大免费最亏的免费app
  • 企业电子商务网站建设问题/软件开发工具
  • 本公司经营网站建设/拉新推广怎么快速拉人