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

C#实现HTTP服务器:处理文件上传---解析MultipartFormDataContent

完整项目托管地址:https://github.com/sometiny/http

HTTP还有重要的一块:文件上传。
这篇文章将详细讲解下,前面实现了同一个链接处理多个请求,为了方便,我们独立写了一个HTTP基类,专门处理HTTP请求。
https://github.com/sometiny/http/blob/main/src/Http/HttpServerBase.cs
本类实现了简单的路由功能,路由功能后续可以使用正则或path2regexp去处理,以处理更复杂的路由请求。
增加了对静态文件的处理,没匹配到的路由都会进入OnResource逻辑。
增加了WebRoot和UploadTempDir的设置,WebRoot目录下的静态文件在HTTP请求时都会自动加载,不需要单独写路由。
UploadTempDir用来临时保存上传的文件。

1、上传简介
上传文件时,使用Content-Type: multipart/form-data; boundary=[BOUNDARY]标头来告诉服务器,请求实体为multipart/form-data编码。
服务器根据编码协议解析multipart/form-data的内容即可,其中[BOUNDARY]为一个请求实体“块”的结束或开始标识,用于解析实体内容。
在浏览器中,为form标签增加enctype="multipart/form-data"属性时,浏览器会自动生成对应的上传标头。
例如下面的标头,为Chrome浏览器生成的:

Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryuHXBXxnxXp0aCz08

2、上传时到底传送了什么格式的数据
话不多说,直接上代码,直观点。
先从这里https://github.com/sometiny/http/tree/main/bin/Release把web目录及其内容放到你自己的Debug或Release编译结果目录下。

实现一个测试服务器
注意,继承的是HttpServerBase基类,在OnReceivedPost方法显示下浏览器发送的内容。

public class HttpServer : HttpServerBase
{public HttpServer() : base(){//设置Web根目录//方便输出静态文件WebRoot = Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "web"));UplaodTempDir = AppDomain.CurrentDomain.BaseDirectory + "uploads";//注册一些路由RegisterRoute("/", OnIndex);RegisterRoute("/post", OnReceivedPost);}/// <summary>/// 首页路由处理程序,跳转到index.html/// </summary>/// <param name="request"></param>/// <param name="stream"></param>private bool OnIndex(HttpRequest request, Stream stream){//跳转到页面HttpResponser responser = new ChunkedResponser(301);responser.ContentType = "text/html; charset=utf-8";responser["Location"] = "/index.html";responser.Write(stream, "Redirect To '/index.html'");responser.End(stream);return true;}/// <summary>/// 处理POST数据/// </summary>/// <param name="request"></param>/// <param name="stream"></param>/// <returns></returns>private bool OnReceivedPost(HttpRequest request, Stream stream){HttpResponser responser = new ChunkedResponser();responser.ContentType = "text/html; charset=utf-8";responser.Write(stream, "<style type=\"text/css\">body{font-size:12px;}</style>");responser.Write(stream, "<h4>上传表单演示</h4>");responser.Write(stream, $"<a href=\"/index.html\">返回</a><br />");responser.Write(stream, $"ContentType:{request.ContentType}<br />");responser.Write(stream, $"Boundary:{request.Boundary}<br />");///这里输出下浏览器发送来的请求responser.Write(stream, $"<pre>{Encoding.UTF8.GetString( request.RequestBody)}</pre>");responser.End(stream);return true;}
}

运行服务器,浏览器访问:http://127.0.0.1:4189/index.html,展示如下一个表单。


我们什么都不上传,直接点提交,看看服务器收到些什么内容。

绿色部分就是我们收到的请求实体内容。

编码分析
实例中的[BOUNDARY]为:----WebKitFormBoundarydyAItGK9UU5xVhZq
3、首行:--[BOUNDARY]\r\n 在boundary前面补两个中横线(-)。
首行读取完毕后,即开始表单项的读取。

4、非文件表单项:
每行一个标头,直到空行,空行后表单内容开始。这里跟Http请求

Content-Disposition: form-data; name="name"测试hello world!
------WebKitFormBoundarydyAItGK9UU5xVhZq

说明:

Content-Disposition: form-data; name="[表单项名称]"\r\n\r\n[内容]\r\n--[BOUNDARY]

5、文件表单项
标头的读取和结束标志跟上面的一致。
只是Content-Disposition会多一个filename属性,因为我们没选择文件,filename值为空。
同时,会提供一个Content-Type标头来标识文件类型。
不排除序应用程序会提供更多的标头,我们只要读取到空行,关心我们需要的标头即可。
 

Content-Disposition: form-data; name="image"; filename=""
Content-Type: application/octet-stream------WebKitFormBoundarydyAItGK9UU5xVhZq

说明:

Content-Disposition: form-data; name="[表单项名称]"; filename=""\r\nContent-Type: application/octet-stream\r\n\r\n[文件内容]\r\n--[BOUNDARY]

6、如何确定结束标识之后是下一个表单项,还是请求实体的结尾。
读取到表单内容结束标识后,再往前读取两个字节。
如果两个字节为\r\n,代表后面还有其他的表单项。
如果两个字节为--,代表所有表单项已读取完毕,请求实体也读完了。

7、实现上传请求实体的解析。
解析使用了两个类。
将请求实体解析为Form和Files:https://github.com/sometiny/http/blob/main/src/Http/Utils/HttpMultipartFormDataParser.cs
Multipart数据读取的辅助流:https://github.com/sometiny/http/blob/main/src/Http/Streams/MultipartReadStream.cs
辅助流里面实现了核心的数据解析,内部用到了BoyerMoore字符串查找算法。
我们主要是讲解协议原理,协议解析这部分可以不用关心,我写的数据解析也可能不是很严格(我不会告诉你,我写完后就看不懂了)。

修改我们上面实现的服务器中OnReceivePost方法,我们这次把上传的表单和文件列出来。

private bool OnReceivedPost(HttpRequest request, Stream stream)
{HttpResponser responser = new ChunkedResponser();responser.ContentType = "text/html; charset=utf-8";responser.Write(stream, "<style type=\"text/css\">body{font-size:12px;}</style>");responser.Write(stream, "<h4>上传表单演示</h4>");responser.Write(stream, $"<a href=\"/index.html\">返回</a><br />");responser.Write(stream, $"ContentType:{request.ContentType}<br />");responser.Write(stream, $"Boundary:{request.Boundary}<br />");#region 输出解析后的上传内容responser.Write(stream, $"<h5>上传表单数据:</h5>");foreach (string formName in request.Form.Keys){responser.Write(stream, $"{formName}: {request.Form[formName]}<br />");}responser.Write(stream, $"<h5>上传文件列表:</h5>");foreach (FileItem file in request.Files){responser.Write(stream, $"{file.Name}: {file.FileName}, {file.TempFile}<br />");}#endregion#region 输出解析前的上传内容,不能同时与上面代码块运行//responser.Write(stream, $"<pre style=\"font-family:'microsoft yahei',arial; color: green\">{Encoding.UTF8.GetString( request.RequestBody)}</pre>");#endregionresponser.End(stream);return true;
}

运行服务器,浏览器访问:http://127.0.0.1:4189/index.html,现在我们选择几个文件,为了方便演示,建议选择有少量文本的文本文件。
头像我选择了两个文件,微信选择了一个。

提交表单。下面可以看到,服务器正确处理了表单数据和三个文件数据。
FileItem对象保存了表单名,文件名,文件类型和文件的临时保存路径,可以将文件移动到应用实际的目录。

移除对OnReceivedPost中对输出解析前的上传内容代码块的注释,并且把输出解析后的上传内容代码块注释掉,刷新页面,可以查看原始未解析的数据。可以对照我们前面对上传数据的编码分析查看下。

8、总结
     1、文件上传主要是增加了Content-Type的设置,使服务器能正确处理上传的内容。
     2、请求实体的解析部分,因为不像HttpRequest一样有Content-Length来标识具体的长度,只能用boundary去分析什么时候开始解析,什么时候结束解析。
     3、对于上传的请求,请求实体解析后,ResponseBody就取不到内容了,所以要想看到请求的具体内容,不能调用Form或Files方法,因为这两个方法一旦调用,上传请求就会被自动解析了。
 

如果使用第三方去解析的话---HttpMultipartParser,无论.net framwork 版本还是net core版本都可以使用,参考地址:  https://github.com/Http-Multipart-Data-Parser/Http-Multipart-Data-Parser,reader/parser of the HTTP multipart/form-data content sent from Windows Runtime via MultipartFormDataContent class. nuget 包管理器直接搜索安装即可使用。

MultipartReader---http://dul.codeplex.com/ ,ASP.NET reader/parser of the HTTP multipart/form-data content sent from Windows Runtime via MultipartFormDataContent class. nuget 包管理器直接搜索安装即可使用。

原文链接:C#实现HTTP服务器:(10)处理文件上传_c#请求浦发银行公共文件上传接口-CSDN博客

相关文章:

C#实现HTTP服务器:处理文件上传---解析MultipartFormDataContent

完整项目托管地址&#xff1a;https://github.com/sometiny/http HTTP还有重要的一块&#xff1a;文件上传。 这篇文章将详细讲解下&#xff0c;前面实现了同一个链接处理多个请求&#xff0c;为了方便&#xff0c;我们独立写了一个HTTP基类&#xff0c;专门处理HTTP请求。 ht…...

leetcoed0044. 通配符匹配 hard

1 题目&#xff1a;通配符匹配 官方难度&#xff1a;难 给你一个输入字符串 (s) 和一个字符模式 ( p ) &#xff0c;请你实现一个支持 ‘?’ 和 ‘*’ 匹配规则的通配符匹配&#xff1a; ‘?’ 可以匹配任何单个字符。 ‘*’ 可以匹配任意字符序列&#xff08;包括空字符序…...

蓝桥杯嵌入式第十二届程序设计题

一、题目概览 设计一个小型停车计费系统 二、分模块实现 1、LCD void disp_proc() {if(view0){char text[30];sprintf(text," Data");LCD_DisplayStringLine(Line2,(uint8_t *)text);sprintf(text," CNBR:%d ",Cnum);LCD_DisplayStri…...

第十四届MathorCup高校数学建模挑战赛-C题:基于 LSTM-ARIMA 和整数规划的货量预测与人员排班模型

目录 摘要 一、 问题重述 1.1 背景知识 1.2 问题描述 二、 问题分析 2.1 对问题一的分析 2.2 对问题二的分析 2.3 对问题三的分析 2.4 对问题四的分析 三、 模型假设 四、 符号说明 五、 问题一模型的建立与求解 5.1 数据预处理 5.2 基于 LSTM 的日货量预测模型 5.3 日货量预测…...

python多态、静态方法和类方法

目录 一、多态 二、静态方法 三、类方法 一、多态 多态&#xff08;polymorphism&#xff09;是面向对象编程中的一个重要概念&#xff0c;指的是同样的方法调用可以在不同的对象上产生不同的行为。在Python中&#xff0c;多态是通过方法的重写&#xff08;override&#x…...

DTMF从2833到inband的方案

概述 freeswitch是一款简单好用的VOIP开源软交换平台。 之前的文章中介绍过通过dialplan拨号计划配置的方法&#xff0c;实现2833到inband的转换&#xff0c;但是实际生产环境中的场景会更复杂&#xff0c;无法预先在dialplan中设置好相关参数和函数。 环境 CentOS 7.9 fr…...

在Vue 3 + TypeScript + Vite 项目中安装和使用 SCSS

在Vue 3 TypeScript Vite 项目中安装和使用 SCSS 1、安装 SCSS 的相关依赖 npm install sass --save-dev2、配置 Vite 对于 Vue 3&#xff0c;Vite 已经内置了对 SCSS 的支持&#xff0c;通常不需要额外的配置。但是&#xff0c;如果需要自定义配置&#xff0c;可以在路径…...

Uni-app入门到精通:tabBar节点实现多页面的切换

tabBar节点用于实现多页面的切换。对于一个多tabBar应用&#xff0c;可以通过tabBar节点配置项指定一级导航栏&#xff0c;以及tabBar切换时显示的对应页面。在pages.json中提供tabBar节点配置&#xff0c;不仅是为了方便快速开发导航&#xff0c;更重要的是提示App平台和小程序…...

Qt正则表达式QRegularExpression

在 Qt 中&#xff0c;正则表达式是处理文本的强大工具&#xff0c;它能够帮助我们匹配、搜索和替换特定的字符串模式。自 Qt 5 起&#xff0c;QRegularExpression 类提供了对 ECMAScript 标准的正则表达式支持&#xff0c;这使得它在处理各种复杂的字符串任务时变得更加高效和灵…...

Go 语言规范学习(3)

文章目录 Properties of types and valuesRepresentation of valuesUnderlying types【底层类型】Core types【核心类型】Type identityAssignabilityRepresentabilityMethod sets BlocksDeclarations and scopeLabel scopesBlank identifierPredeclared identifiersExported i…...

小林coding-17道Java基础面试题

1.说一下Java的特点?Java 的优势和劣势是什么&#xff1f;Java为什么是跨平台的&#xff1f;JVM、JDK、JRE三者关系&#xff1f;为什么Java解释和编译都有&#xff1f; jvm是什么?编译型语言和解释型语言的区别&#xff1f; Python和Java区别是什么&#xff1f; 2.八种基本的…...

ETCD --- ​租约(Lease)​详解

一、租约的核心概念 1. ​租约(Lease)​ 一个租约是一个有时间限制的“授权”,绑定到键值对上。每个租约有一个唯一的ID(64位整数),通过etcdctl或客户端API创建。创建租约时需指定TTL(Time-To-Live),即租约的有效期(单位:秒)。客户端需定期向etcd发送续约(KeepAl…...

运筹说 第134期 | 矩阵对策的解法

上一期我们了解了矩阵对策的基本理论&#xff0c;包含矩阵对策的纯策略、矩阵对策的混合策略和矩阵对策的基本定理。 接下来小编将为大家介绍矩阵对策的解法&#xff0c;包括图解法、方程组法和线性规划法三种经典方法。 01 图解法 本节首先介绍矩阵对策的图解法&#xff0c;…...

3. 轴指令(omron 机器自动化控制器)——>MC_CamOut

机器自动化控制器——第三章 轴指令 15 MC_CamOut变量▶输入变量▶输出变量▶输入输出变量 功能说明▶时序图▶指令的中止▶重启运动指令▶多重启动运动指令▶异常 MC_CamOut 结束通过输入参数指定的轴的凸轮动作 指令名称FB/FUN图形表现ST表现MC_CamOut解除凸轮动作FBMC_Cam…...

TF32 与 FP32 的区别

TF32&#xff08;Tensor Float 32&#xff09;与FP32&#xff08;单精度浮点数&#xff09;是两种用于深度学习和高性能计算的浮点格式&#xff0c;其核心区别体现在精度、性能优化和应用场景上。以下是两者的详细对比分析&#xff1a; 一、位宽与结构差异 FP32的位宽结构 FP32…...

【大模型】视觉语言模型:Qwen2.5-VL的使用

官方github地址&#xff1a;https://github.com/QwenLM/Qwen2.5-VL 目录 Qwen家族的最新成员&#xff1a;Qwen2.5-VL 主要增强功能 模型架构更新 快速开始 使用Transformers聊天 Docker Qwen家族的最新成员&#xff1a;Qwen2.5-VL 主要增强功能 强大的文档解析功能&am…...

Web前端之UniApp、Taro、ReactNative和Flutter的区别

MENU 前言介绍及公司技术差异使用方法使用场景差异注意事项打包与部署差异框架应用实例结语 前言 在移动应用开发领域&#xff0c;跨平台框架已成为开发者的得力工具。UniApp、Taro、ReactNative和Flutter它们在Android&#xff08;安卓&#xff09;或iOS&#xff08;苹果&…...

测试用例与需求脱节的修复方案

测试用例与需求脱节的问题可通过明确需求定义、加强需求追踪、建立有效沟通机制进行修复。其中&#xff0c;加强需求追踪尤为关键&#xff0c;能确保测试用例与实际需求的精确匹配&#xff0c;避免资源浪费和测试效果不佳。据行业研究&#xff0c;约70%的软件缺陷源于需求管理不…...

【Unity】 鼠标拖动物体移动速度跟不上鼠标,会掉落

错误示范&#xff1a; 一开始把移动的代码写到update里去了&#xff0c;发现物体老是掉(总之移动非常不流畅&#xff0c;体验感很差&#xff09; void Update(){Ray ray Camera.main.ScreenPointToRay(Input.mousePosition);if (Physics.Raycast(ray, out RaycastHit hit, M…...

Ollama及HuggingFace路径环境变量设置

日常经常用到这俩的一些环境变量&#xff0c;特记录下来&#xff0c;如有错误&#xff0c;还请指正。 1. Ollama路径环境变量设置 Ollama 模型路径变量名为OLLAMA_MODELS&#xff0c;设置示例&#xff1a; 变量名示例OLLAMA_MODELS C:\Users\Administrator\.ollama\models D…...

VLAN 高级特性

VLAN Access 类型端口&#xff1a;只能属于 1 个 VLAN&#xff0c;发出数据时只能根据 PVID 剥离一个 VLAN Tag 入方向&#xff1a;针对没有 tag 的数据包打上 PVID 的 tag出方向&#xff1a;将 tag 为本接口 PVID 的数据包去掉 tag&#xff0c;发出数据。&#xff08;只有在与…...

学习中学习的小tips(主要是学习苍穹外卖的一些学习)

目录 架构的细分 使用实体类来接收配置文件中的值 webMvcConfig类&#xff1a; jwt令牌 管理端的拦截器&#xff1a; JwtProperties&#xff1a; JwtTokenAdminInterceptor &#xff1a; 对密码加密操作 Redis&#xff1a; 分页查询 整体思想 为什么动态 SQL 推荐传实体…...

【极速版 -- 大模型入门到进阶】LORA:大模型轻量级微调

文章目录 &#x1f30a; 有没有低成本的方法微调大模型&#xff1f;&#x1f30a; LoRA 的核心思想&#x1f30a; LoRA 的初始化和 r r r 的值设定&#x1f30a; LoRA 实战&#xff1a;LoraConfig参数详解 论文指路&#xff1a;LORA: LOW-RANK ADAPTATION OF LARGE LANGUAGE M…...

3d pose 指标和数据集

目录 3D姿态估计、3维重建指标: 数据集 EHF数据集 SMPL-X 3D姿态估计、3维重建指标: MVE、PMVE 和 p-MPJPE 都是用于评估3D姿态估计、三维重建等任务中预测结果与真实数据之间误差的指标。 MVE (Mean Vertex Error):是指模型重建过程中每个顶点的预测位置与真实位置之间…...

gogs私服详细配置

一.永久挂载方法 通过 /etc/fstab 实现绑定挂载&#xff08;推荐&#xff09; 绑定挂载&#xff08;Bind Mount&#xff09;允许将一个目录挂载到另一个目录&#xff0c;类似于软链接但更底层。 例如&#xff1a;将 /mnt/data 绑定到 /var/www/html&#xff0c;使两者内容同…...

1688商品详情接口:深度解析与应用实践

在电商领域&#xff0c;1688作为中国领先的B2B平台&#xff0c;拥有海量的商品信息。对于开发者、商家和数据分析师来说&#xff0c;获取1688商品的详细信息是实现数据分析、竞品研究、自动化管理和精准营销的重要手段。本文将详细介绍1688商品详情接口的使用方法、技术细节以及…...

线程同步——读写锁

Linux——线程同步 读写锁 目录 一、基本概念 1.1 读写锁的基本概念 1.2 读写锁的优点 1.3 读写锁的实现 1.4 代码实现 一、基本概念 线程同步中的读写锁&#xff08;Read-Write Lock&#xff09;&#xff0c;也常被称为共享-独占锁&#xff08;Shared-Exclusive Lock&a…...

邪性!Anaconda安装避坑细节Windows11

#工作记录 最近不断重置系统和重装Anaconda&#xff0c;配置的要累死&#xff0c;经几十次意料之外的配置状况打击之后&#xff0c;最后发现是要在在Anaconda安装时&#xff0c;一定要选“仅为我安装”这个选项&#xff0c;而不要选“为所有用户安装”这个选项。 选“仅为我安…...

【大模型】激活函数之SwiGLU详解

文章目录 1. Swish基本定义主要特点代码实现 2. GLU (Gated Linear Unit)基本定义主要特点代码实现 3. SwiGLU基本定义主要特点代码实现 参考资料 SWiGLU是大模型常用的激活函数&#xff0c;是2020年谷歌提出的激活函数&#xff0c;它结合了Swish和GLU两者的特点。SwiGLU激活函…...

AOA与TOA混合定位,MATLAB例程,三维空间下的运动轨迹,滤波使用EKF,附下载链接

本文介绍一个MATLAB代码&#xff0c;实现基于 到达角&#xff08;AOA&#xff09; 和 到达时间&#xff08;TOA&#xff09; 的混合定位算法&#xff0c;结合 扩展卡尔曼滤波&#xff08;EKF&#xff09; 对三维运动目标的轨迹进行滤波优化。代码通过模拟动态目标与基站网络&am…...