从零开始实现multipart/form-data数据提交
在HTTP服務應用中進行數據提交一般都使用application/json,application/x-www-form-urlencoded和multipart/form-data這幾種內容格式。這幾種格式的處理復雜度處理起來和前面定義的先后順序一樣由易到難。不過現有工具都提供了完善的功能在提交這些數據的時候都比較方便了;不過要自己手動基礎協議寫起,那multipart/form-data的處理規范還是要相對復雜些。最近在寫webapi管理和性能測試工具(https://github.com/IKende/WebBenchmark)時為了得到更可控的時間線和性能,在實現并沒有用到任何應用組件都是從HTTP基礎協議寫起,在這時介紹一下如何在基礎HTTP協議的基礎上提交multipart/form-data數據.(如果你沒有什么特別的需求還是不要這么干)
multipart/form-data
這種格式一般配合多數據類型提交使用,如常用的數據表單和文件結合。這種格式有著自己的處理規范和application/json和application/x-www-form-urlencoded有著不同。application/json相對來說最簡單整個數據流是json內容格式,而application/x-www-form-urlencoded則是以k-v的方式處理,只是對應的值要做Url編寫。而multipart/form-data則用一個特別的分隔符來處理,這個分隔符分為開始分隔和結束分隔符。
分隔符定義
如果使用multipart/form-data提交數據,那必須在Content-Type的請求頭后面添加; boundary=value這樣一個描述,boundary的值即是每項數據之間的分隔符
mHeaderCached.Append("Content-Type: ").Append(mCases.ContentType); if (multipartFormData)mHeaderCached.Append("; boundary=").Append(boundary); mHeaderCached.Append("\r\n");需要怎樣定義boundary值?其實boundary的定義是沒有特別的要求的,就是一個字符串完全看自己的喜好。但最終處理的時候是要有一個規范。
開始分隔符-- boundary
結束分隔符--boundary--
開始分隔符必須在每項數據之前寫入,簡單來說就是有多少項數據就有多少個開始分隔符了;結束分隔符只有一個,就是在提交內容的尾部添加,說明這個提交的內容在這里結束不需要再往下解釋。大概格式如下:
-- boundary 數據項 -- boundary 數據項 -- boundary 數據項 -- boundary 數據項 --boundary--數據項
multipart/form-data中的每項數據都分別有Header和Body和整個HTTP上層協議差不多。
Content-Disposition: form-data; name="fname"\r\n \r\n value \r\nContent-Disposition是必須的,描述一下這數據的格式來源,在這里都是form-data;后面根據不同數據的情況有著不同的屬性,每個屬性用;分隔的K-V結構。代碼的處理比較簡單:
mMemoryData.WriteLine($"Content-Disposition: form-data; name=\"{item.Name}\"");接下來就是一個空換行然后再寫入值,完整代碼如下:
mMemoryData.WriteLine($"Content-Disposition: form-data; name=\"{item.Name}\""); mMemoryData.WriteLine(""); mTextBodyCached.Clear(); item.GetTemplate().Execute(mTextBodyCached); mMemoryData.Write(mTextBodyCached); mMemoryData.WriteLine("");提交文件
提交文件相對來說比值要處理多一些屬性,主要包括內容類型,文件名等;其實寫起來也不復雜
mMemoryData.WriteLine($"Content-Disposition: form-data; name=\"{item.Name}\"; filename=\"{item.FileName}\""); mMemoryData.WriteLine($"Content-Type: {item.Type}"); mMemoryData.WriteLine(""); var itemBuffer = item.GetBuffer(); mMemoryData.Write(itemBuffer, 0, itemBuffer.Length); mMemoryData.WriteLine("");以上就是multipart/form-data普通值和文件提交時寫的數據格式,下面看一下這個multipart/form-data的完整代碼
for (int i = 0; i < mCases.FormBody.Count; i++) {var item = mCases.FormBody[i];mMemoryData.Write("--");mMemoryData.WriteLine(boundary);if (item.Type == HttpDataType.Bytes){mMemoryData.WriteLine($"Content-Disposition: form-data; name=\"{item.Name}\"; filename=\"{item.FileName}\"");mMemoryData.WriteLine($"Content-Type: {item.Type}");mMemoryData.WriteLine("");var itemBuffer = item.GetBuffer();mMemoryData.Write(itemBuffer, 0, itemBuffer.Length);mMemoryData.WriteLine("");}else{mMemoryData.WriteLine($"Content-Disposition: form-data; name=\"{item.Name}\"");mMemoryData.WriteLine("");mTextBodyCached.Clear();item.GetTemplate().Execute(mTextBodyCached);mMemoryData.Write(mTextBodyCached);mMemoryData.WriteLine("");} } if (mCases.FormBody.Count > 0) {mMemoryData.Write("--");mMemoryData.Write(boundary);mMemoryData.WriteLine("--");mMemoryData.Flush(); }這樣一個完整的multipart/form-data提交基礎協議代碼就處理完成;在webbenchmark的實現有還有application/json和application/x-www-form-urlencoded的處理,相對于multipart/form-data來說這兩個處理就更加簡單了;下面包括:POST,GET,PUT,DELETE和三種數據格式提交的完整代碼函(在BeetleX的pipestream幫助下這些協議的處理還是比較簡單的)
public void Write(PipeStream stream) {string boundary = null;bool multipartFormData = mCases.ContentType == "multipart/form-data";if (multipartFormData)boundary = "----Beetlex.io" + DateTime.Now.ToString("yyyyMMddHHmmss");byte[] bodyData = null;int bodyLength = 0;if (mHeaderCached == null)mHeaderCached = new StringBuilder();mHeaderCached.Clear();if (mMemoryData == null)mMemoryData = new PipeStream();if (mMemoryData.Length > 0)mMemoryData.ReadFree((int)mMemoryData.Length);if (mTextBodyCached == null)mTextBodyCached = new StringBuilder();mTextBodyCached.Clear();mHeaderCached.Append(mCases.Method).Append(" ");mUrlTemplate.Execute(mHeaderCached);for (int i = 0; i < mCases.QueryString.Count; i++){if (i == 0){if (mUrlHasParameter)mHeaderCached.Append("&");elsemHeaderCached.Append("?");}else{mHeaderCached.Append("&");}mHeaderCached.Append(mCases.QueryString[i].Name);mHeaderCached.Append("=");mCases.QueryString[i].GetTemplate().Execute(mHeaderCached, true);}mHeaderCached.Append(" ");mHeaderCached.Append(Protocol).Append("\r\n");foreach (var item in mCases.Header){mHeaderCached.Append(item.Name).Append(": ");item.GetTemplate().Execute(mHeaderCached);mHeaderCached.Append("\r\n");}mHeaderCached.Append("Content-Type: ").Append(mCases.ContentType);if (multipartFormData)mHeaderCached.Append("; boundary=").Append(boundary);mHeaderCached.Append("\r\n");if (multipartFormData){for (int i = 0; i < mCases.FormBody.Count; i++){var item = mCases.FormBody[i];mMemoryData.Write("--");mMemoryData.WriteLine(boundary);if (item.Type == HttpDataType.Bytes){mMemoryData.WriteLine($"Content-Disposition: form-data; name=\"{item.Name}\"; filename=\"{item.FileName}\"");mMemoryData.WriteLine($"Content-Type: {item.Type}");mMemoryData.WriteLine("");var itemBuffer = item.GetBuffer();mMemoryData.Write(itemBuffer, 0, itemBuffer.Length);mMemoryData.WriteLine("");}else{mMemoryData.WriteLine($"Content-Disposition: form-data; name=\"{item.Name}\"");mMemoryData.WriteLine("");mTextBodyCached.Clear();item.GetTemplate().Execute(mTextBodyCached);mMemoryData.Write(mTextBodyCached);mMemoryData.WriteLine("");}}if (mCases.FormBody.Count > 0){mMemoryData.Write("--");mMemoryData.Write(boundary);mMemoryData.WriteLine("--");mMemoryData.Flush();}}else if (mCases.ContentType == "application/json"){if (mJsonBodyTemplate != null){mJsonBodyTemplate.Execute(mTextBodyCached);}}else{for (int i = 0; i < mCases.FormBody.Count; i++){if (i > 0){mTextBodyCached.Append("&");}mTextBodyCached.Append(mCases.FormBody[i].Name).Append("=");mCases.FormBody[i].GetTemplate().Execute(mTextBodyCached, true);}}try{if (multipartFormData){bodyLength = (int)mMemoryData.Length;if (bodyLength > 0){bodyData = System.Buffers.ArrayPool<byte>.Shared.Rent(bodyLength);mMemoryData.Read(bodyData, 0, bodyLength);}}else{if (mTextBodyCached.Length > 0){char[] charbuffer = System.Buffers.ArrayPool<char>.Shared.Rent(mTextBodyCached.Length);try{mTextBodyCached.CopyTo(0, charbuffer, 0, mTextBodyCached.Length);bodyData = System.Buffers.ArrayPool<byte>.Shared.Rent(mTextBodyCached.Length * 6);bodyLength = Encoding.UTF8.GetBytes(charbuffer, 0, mTextBodyCached.Length, bodyData, 0);}finally{System.Buffers.ArrayPool<char>.Shared.Return(charbuffer);}}}mHeaderCached.Append("Content-Length: ").Append(bodyLength).Append("\r\n");mHeaderCached.Append("\r\n");stream.Write(mHeaderCached);if (bodyData != null){stream.Write(bodyData, 0, bodyLength);}}finally{if (bodyData != null)System.Buffers.ArrayPool<byte>.Shared.Return(bodyData);}}總結
以上是生活随笔為你收集整理的从零开始实现multipart/form-data数据提交的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Blazor带我重玩前端(三)
- 下一篇: IBM、甲骨文、CNCF 就谷歌对 Is