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 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/bicheng/84650.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

安装 Poppler(Windows)

下载 Poppler&#xff08;Windows&#xff09;&#xff1a;https://github.com/oschwartz10612/poppler-windows/releases/ 解压在自己目录下 配置系统环境变量&#xff1a;把 poppler-xx.x.x\bin 目录加入你的环境变量 PATH 中。 检查是否配置成功 pdfinfo

Java学习笔记之:初识nginx

Java学习笔记之&#xff1a;初识nginx PS&#xff1a;虽然总结的都很简单&#xff0c;但是作为初学者并且本人记忆力较差所以每次学习新知识点后习惯性记录下来&#xff0c;这样加深一遍记忆并且便于日后复习。 介绍&#xff1a; Nginx是一款轻量级的Web服务器/反向代理服务器…

Middleware

中间件的定义&#xff1a;中间件是位于操作系统和应用程序之间的软件层&#xff0c;用于解决分布式系统中通信、数据共享、资源管理等共性问题。消息队列属于通信中间件&#xff0c;用于在分布式系统中传递消息&#xff0c;实现应用解耦、异步通信和流量削峰。解耦系统&#xf…

Mac如何配置ZSH并使用Oh-my-zsh?让你的终端更加实用、美观

前言 现在&#xff0c;越来越多的人趋向使用ZSH取代(Linux)原本的Bash作为自己的终端Shell。的确&#xff0c;ZSH才是适用于现代的Shell&#xff1a; 更丰富的命令提示更鲜明的演示标记更强大的插件支持 什么是ZSH 回答什么是ZSH前&#xff0c;我们先解释什么是Bash&#x…

C++11新标准

重点 auto 类型推导范围 for 迭代初始化列表变参模板 新类型 C11新增了类型 long long 和 unsigned long long&#xff0c;以支持64位(或更宽)的整型;新增了类型 char16_t和 char32_t&#xff0c;以支持 16位和 32 位的字符表示;还新增了“原始”字符串。 常量 nullptr nu…

SpringAI Prompt提示词

基本概念 Prompts提示词 ❝ 提示词的是引导AI模型输出的输入&#xff0c;提示词的正确性直接影响模型输出的。 Message消息 Message 接口封装了 Prompt 文本内容、一组元数据属性以及称为 MessageType 的分类。Spring AI消息API&#xff1a; 其中最重要的就是角色&#xff1a; …

力扣刷题——二分查找

数组是存放在连续内存空间上的相同类型数据的集合数组下标都是从0开始的数组内存空间的地址是连续的正是因为数组在内存空间的地址是连续的&#xff0c;所以我们在删除或者增添元素的时候&#xff0c;就难免要移动其他元素的地址。 使用二分查找法返回的元素下标可能不是唯一的…

黑群晖NAS部署DeepSeek模型与内网穿透实现本地AI服务

文章目录 前言1.安装Container Manager2. 启动ssh功能3. ssh连接黑群晖4. 安装Ollama5. 安装deepseek模型6. 安装open-webui图形界面7. 安装内网穿透7.1 下载cpolar套件7.2 配置群辉虚拟机7.3 配置公网地址小结 7.4 配置固定公网地址 总结 前言 在追求自建网络存储方案的极客群…

Rust 学习笔记:处理任意数量的 future

Rust 学习笔记&#xff1a;处理任意数量的 future Rust 学习笔记&#xff1a;处理任意数量的 future竞争的 future将控制权交给运行时构建我们自己的异步抽象 Rust 学习笔记&#xff1a;处理任意数量的 future 当两个 future 切换到三个 future 时&#xff0c;我们也必须从使用…

2025年TCP洪水攻击防护实战全解析:从协议对抗到AI智能防御

一、2025年TCP洪水攻击的新特征与挑战 AI驱动的自适应攻击 攻击者利用生成式AI动态调整SYN报文特征&#xff08;如载荷内容、发送频率&#xff09;&#xff0c;使攻击流量与正常业务流量的差异率低至0.5%&#xff0c;传统基于规则引擎的防御策略完全失效。 混合协议打击常态化…

二、集成开发环境(IDE)

上节我们在终端演示了python虚拟环境的用法&#xff0c;但终端不方便代码编写和调试&#xff0c;本节介绍两种常用的python集成开发环境&#xff1a;Pycharm和Jupter Notebook。Pycharm是最流行的Python IDE&#xff0c;下载网址&#xff1a;下载 PyCharm&#xff1a;JetBrains…

芯片电感需求趋势及选型关键因素

随着AI产业的快速发展&#xff0c;数据中心、AI芯片、服务器等算力基础设施对于芯片电感等电子元件的要求不断提升。另一方面&#xff0c;电子产品向高功率密度和小型化方向发展&#xff0c;电源模块趋于小型化、低电压、大电流&#xff0c;也对芯片电感提出了小型化、轻量化、…

Vue3+Element Plus表单验证实战:从零实现用户管理

前言 在Vue3项目开发中&#xff0c;表单验证是保证数据完整性和准确性的重要环节。Element Plus作为Vue3的流行UI组件库&#xff0c;提供了强大的表单验证功能。本文将基于一个用户管理模块的实战案例&#xff0c;详细介绍Vue3中如何使用Element Plus实现完整的表单验证流程。…

力扣上C语言编程题:合并区间(涉及数组)

一. 简介 本文记录力扣网上涉及数组方面的编程题&#xff0c;主要以 C语言实现。 二. 力扣上C语言编程题&#xff1a;合并区间&#xff08;涉及数组&#xff09; 以数组 intervals 表示若干个区间的集合&#xff0c;其中单个区间为 intervals[i] [starti, endi] 。请你合并所…

SEO长尾词与关键词优化实战

内容概要 在SEO优化体系中&#xff0c;核心关键词与长尾词的协同作用直接影响流量获取效率与用户触达精度。本文将从基础概念切入&#xff0c;系统梳理核心关键词的筛选标准与竞争强度评估方法&#xff0c;并深入探讨长尾词在细分场景下的价值定位。通过分析用户搜索行为与意图…

博图SCL语言教程:灵活加、减计数制作自己的增减计数器(CTUD)

博图SCL语言教程&#xff1a;使用SCL实现增减计数器(CTUD) 一、什么是增减计数器(CTUD)&#xff1f; 增减计数器&#xff08;Up-Down Counter&#xff09;是PLC编程中的基础功能块&#xff0c;具有以下特性&#xff1a; CU (Count Up)&#xff1a;上升沿触发计数值增加 CD (…

Android 应用被kill问题排查和处理

一、背景 博主有一款应用市场应用,同样的应用,在Android 10上开启三个下载正常,在Android 14上开启下载安装,很频繁被kill。首先想到的是,是不是应用内存太高,导致被kill,通过工具分析内存也不高,后面就想到是不是系统本身分配给应用的内存就不高,后来通过排查,确实是和系统的…

从代码学习深度强化学习 - 多臂老虎机 PyTorch版

文章目录 前言创建多臂老虎机环境多臂老虎机算法基本框架(基类)1. ε-贪心算法 (Epsilon-Greedy)2. 随时间衰减的ε-贪婪算法 (Decaying ε-Greedy)3. 上置信界算法 (Upper Confidence Bound, UCB)4. 汤普森采样算法 (Thompson Sampling)总结前言 欢迎来到“从代码学习深度强化…

Android学习之Window窗口

Android Window机制学习笔记 在使用Window Flag实现界面全屏功能时&#xff0c;发现自身对Android Window机制缺乏系统认知&#xff0c;因此进行了专项学习与整理。 本文主要参考以下优质资料&#xff1a; Android的Window详解Android官方Window文档 Window基本概念 1. Win…

华为云 Flexus+DeepSeek 征文|搭建部署Dify-LLM推理引擎,赋能AI Agent智能体实现动态联网搜索能力

华为云 Flexus 云服务器 X 实例专门为 AI 应用场景设计。它提供了强大的计算能力&#xff0c;能够满足 DeepSeek 模型以及后续搭建 AI Agent 智能体过程中对于数据处理和模型运行的高要求。在网络方面&#xff0c;具备高速稳定的网络带宽&#xff0c;这对于需要频繁联网搜索信息…