<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      C#實(shí)現(xiàn)HTTP服務(wù)器:處理文件上傳---解析MultipartFormDataContent

      完整項(xiàng)目托管地址:https://github.com/sometiny/http

      HTTP還有重要的一塊:文件上傳。
      這篇文章將詳細(xì)講解下,前面實(shí)現(xiàn)了同一個(gè)鏈接處理多個(gè)請求,為了方便,我們獨(dú)立寫了一個(gè)HTTP基類,專門處理HTTP請求。
      https://github.com/sometiny/http/blob/main/src/Http/HttpServerBase.cs
      本類實(shí)現(xiàn)了簡單的路由功能,路由功能后續(xù)可以使用正則或path2regexp去處理,以處理更復(fù)雜的路由請求。
      增加了對靜態(tài)文件的處理,沒匹配到的路由都會(huì)進(jìn)入OnResource邏輯。
      增加了WebRoot和UploadTempDir的設(shè)置,WebRoot目錄下的靜態(tài)文件在HTTP請求時(shí)都會(huì)自動(dòng)加載,不需要單獨(dú)寫路由。
      UploadTempDir用來臨時(shí)保存上傳的文件。

      1、上傳簡介
      上傳文件時(shí),使用Content-Type: multipart/form-data; boundary=[BOUNDARY]標(biāo)頭來告訴服務(wù)器,請求實(shí)體為multipart/form-data編碼。
      服務(wù)器根據(jù)編碼協(xié)議解析multipart/form-data的內(nèi)容即可,其中[BOUNDARY]為一個(gè)請求實(shí)體“塊”的結(jié)束或開始標(biāo)識,用于解析實(shí)體內(nèi)容。
      在瀏覽器中,為form標(biāo)簽增加enctype="multipart/form-data"屬性時(shí),瀏覽器會(huì)自動(dòng)生成對應(yīng)的上傳標(biāo)頭。
      例如下面的標(biāo)頭,為Chrome瀏覽器生成的:

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

      2、上傳時(shí)到底傳送了什么格式的數(shù)據(jù)
      話不多說,直接上代碼,直觀點(diǎn)。
      先從這里https://github.com/sometiny/http/tree/main/bin/Release把web目錄及其內(nèi)容放到你自己的Debug或Release編譯結(jié)果目錄下。

      實(shí)現(xiàn)一個(gè)測試服務(wù)器
      注意,繼承的是HttpServerBase基類,在OnReceivedPost方法顯示下瀏覽器發(fā)送的內(nèi)容。

      public class HttpServer : HttpServerBase
      {
          public HttpServer() : base()
          {
              //設(shè)置Web根目錄
              //方便輸出靜態(tài)文件
              WebRoot = Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "web"));
              UplaodTempDir = AppDomain.CurrentDomain.BaseDirectory + "uploads";
              //注冊一些路由
              RegisterRoute("/", OnIndex);
              RegisterRoute("/post", OnReceivedPost);
          }
          /// <summary>
          /// 首頁路由處理程序,跳轉(zhuǎn)到index.html
          /// </summary>
          /// <param name="request"></param>
          /// <param name="stream"></param>
          private bool OnIndex(HttpRequest request, Stream stream)
          {
              //跳轉(zhuǎn)到頁面
              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數(shù)據(jù)
          /// </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 />");
              
              ///這里輸出下瀏覽器發(fā)送來的請求
              responser.Write(stream, $"<pre>{Encoding.UTF8.GetString( request.RequestBody)}</pre>");
      
      
              responser.End(stream);
      
              return true;
          }
      }

      運(yùn)行服務(wù)器,瀏覽器訪問:http://127.0.0.1:4189/index.html,展示如下一個(gè)表單。

       我們什么都不上傳,直接點(diǎn)提交,看看服務(wù)器收到些什么內(nèi)容。

       

      綠色部分就是我們收到的請求實(shí)體內(nèi)容。

      編碼分析
      實(shí)例中的[BOUNDARY]為:----WebKitFormBoundarydyAItGK9UU5xVhZq
      3、首行:--[BOUNDARY]\r\n 在boundary前面補(bǔ)兩個(gè)中橫線(-)。
      首行讀取完畢后,即開始表單項(xiàng)的讀取。

      4、非文件表單項(xiàng):
      每行一個(gè)標(biāo)頭,直到空行,空行后表單內(nèi)容開始。這里跟Http請求

      Content-Disposition: form-data; name="name"
      
      測試hello world!
      ------WebKitFormBoundarydyAItGK9UU5xVhZq

      說明:

      Content-Disposition: form-data; name="[表單項(xiàng)名稱]"\r\n\r\n[內(nèi)容]\r\n--[BOUNDARY]

      5、文件表單項(xiàng)
      標(biāo)頭的讀取和結(jié)束標(biāo)志跟上面的一致。
      只是Content-Disposition會(huì)多一個(gè)filename屬性,因?yàn)槲覀儧]選擇文件,filename值為空。
      同時(shí),會(huì)提供一個(gè)Content-Type標(biāo)頭來標(biāo)識文件類型。
      不排除序應(yīng)用程序會(huì)提供更多的標(biāo)頭,我們只要讀取到空行,關(guān)心我們需要的標(biāo)頭即可。

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

      說明:

      Content-Disposition: form-data; name="[表單項(xiàng)名稱]"; filename=""\r\nContent-Type: application/octet-stream\r\n\r\n[文件內(nèi)容]\r\n--[BOUNDARY]

      6、如何確定結(jié)束標(biāo)識之后是下一個(gè)表單項(xiàng),還是請求實(shí)體的結(jié)尾。
      讀取到表單內(nèi)容結(jié)束標(biāo)識后,再往前讀取兩個(gè)字節(jié)。
      如果兩個(gè)字節(jié)為\r\n,代表后面還有其他的表單項(xiàng)。
      如果兩個(gè)字節(jié)為--,代表所有表單項(xiàng)已讀取完畢,請求實(shí)體也讀完了。

      7、實(shí)現(xiàn)上傳請求實(shí)體的解析。
      解析使用了兩個(gè)類。
      將請求實(shí)體解析為Form和Files:https://github.com/sometiny/http/blob/main/src/Http/Utils/HttpMultipartFormDataParser.cs
      Multipart數(shù)據(jù)讀取的輔助流:https://github.com/sometiny/http/blob/main/src/Http/Streams/MultipartReadStream.cs
      輔助流里面實(shí)現(xiàn)了核心的數(shù)據(jù)解析,內(nèi)部用到了BoyerMoore字符串查找算法。
      我們主要是講解協(xié)議原理,協(xié)議解析這部分可以不用關(guān)心,我寫的數(shù)據(jù)解析也可能不是很嚴(yán)格(我不會(huì)告訴你,我寫完后就看不懂了)。

      修改我們上面實(shí)現(xiàn)的服務(wù)器中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 輸出解析后的上傳內(nèi)容
          responser.Write(stream, $"<h5>上傳表單數(shù)據(jù):</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 輸出解析前的上傳內(nèi)容,不能同時(shí)與上面代碼塊運(yùn)行
          //responser.Write(stream, $"<pre style=\"font-family:'microsoft yahei',arial; color: green\">{Encoding.UTF8.GetString( request.RequestBody)}</pre>");
          #endregion
      
          responser.End(stream);
      
          return true;
      }

      運(yùn)行服務(wù)器,瀏覽器訪問:http://127.0.0.1:4189/index.html,現(xiàn)在我們選擇幾個(gè)文件,為了方便演示,建議選擇有少量文本的文本文件。
      頭像我選擇了兩個(gè)文件,微信選擇了一個(gè)。

       提交表單。下面可以看到,服務(wù)器正確處理了表單數(shù)據(jù)和三個(gè)文件數(shù)據(jù)。
      FileItem對象保存了表單名,文件名,文件類型和文件的臨時(shí)保存路徑,可以將文件移動(dòng)到應(yīng)用實(shí)際的目錄。

       移除對OnReceivedPost中對輸出解析前的上傳內(nèi)容代碼塊的注釋,并且把輸出解析后的上傳內(nèi)容代碼塊注釋掉,刷新頁面,可以查看原始未解析的數(shù)據(jù)。可以對照我們前面對上傳數(shù)據(jù)的編碼分析查看下。

       

      8、總結(jié)
           1、文件上傳主要是增加了Content-Type的設(shè)置,使服務(wù)器能正確處理上傳的內(nèi)容。
           2、請求實(shí)體的解析部分,因?yàn)椴幌馠ttpRequest一樣有Content-Length來標(biāo)識具體的長度,只能用boundary去分析什么時(shí)候開始解析,什么時(shí)候結(jié)束解析。
           3、對于上傳的請求,請求實(shí)體解析后,ResponseBody就取不到內(nèi)容了,所以要想看到請求的具體內(nèi)容,不能調(diào)用Form或Files方法,因?yàn)檫@兩個(gè)方法一旦調(diào)用,上傳請求就會(huì)被自動(dòng)解析了。

      如果使用第三方去解析HTTP multipart/form-data content的話---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 包管理器直接搜索安裝即可使用。

       

      原文鏈接:https://blog.csdn.net/moasp/article/details/120197381

      posted @ 2025-03-28 16:54  龍騎科技  閱讀(427)  評論(0)    收藏  舉報(bào)
      主站蜘蛛池模板: 精品午夜福利在线观看 | 国产在线一区二区不卡| 久久精产国品一二三产品| 日本午夜精品一区二区三区电影| 亚洲自拍精品视频在线| 影音先锋亚洲成aⅴ人在| 国产在线自拍一区二区三区 | 定襄县| 亚洲综合色一区二区三区| 亚洲中文字幕国产精品| 久久国产成人午夜av影院| 亚洲日韩精品无码一区二区三区| 99久久精品费精品国产一区二| 中文字幕乱码中文乱码毛片 | 毛片在线看免费| yw尤物av无码国产在线观看| 欧美大胆老熟妇乱子伦视频| 大乳丰满人妻中文字幕日本| 国产av一区二区午夜福利| 亚洲最大激情中文字幕| 人妻熟女av一区二区三区| 久久精品熟女亚洲av麻| 喀喇| 日本视频一区二区三区1| 一区二区不卡国产精品| A男人的天堂久久A毛片| 内射囯产旡码丰满少妇| 看全黄大色黄大片视频| 亚洲av专区一区| 女人香蕉久久毛毛片精品| 强奷漂亮雪白丰满少妇av| 黄色A级国产免费大片视频| 中文有无人妻vs无码人妻激烈| 国产AV巨作丝袜秘书| 在线观看AV永久免费| 国产仑乱无码内谢| 综合激情网一区二区三区| 少妇人妻88久久中文字幕| 婷婷六月天在线| 亚洲色成人一区二区三区| 强奷乱码中文字幕|