如果寫的內容有誤,請不吝嗇的給於小弟一些指導,對於一般使用者,在看完以下內容後跟不上的朋友,請多花時間了解。
HTTP/1.1協議 中規定,HTTP request 由三個部分組成:狀態行、請求頭、消息主體。
<method> <request-uri> <http-version> <request-header> <message-body>詳見 HTTP/1.1協議 第五章內容。
協議規定POST提交的數據放在消息主體中傳輸,消息主體有多種編碼方式,請求頭中的字段Content-type標明了消息主體使用的編碼方式,常見的媒體格式類型如下:
- text/html:HTML格式
- text/plain:純文本格式
- text/xml:XML格式
- image/gif:gif圖片格式
- image/jpeg:jpg圖片格式
- image/png:png圖片格式
- application/xhtml+xml:XHTML格式
- application/xml:XML數據格式
- application/atom+xml:Atom XML聚合格式
- application/json:JSON數據格式
- application/pdf:pdf格式
- application/msword:Word文檔格式
- application/octet-stream:二進制流數據(如常見的文件下載)
- application/x-www-form-urlencoded:預設encType,form表單數據被編碼為格式發送到伺服器(表單預設的傳送數據的格式)
- multipart/form-data:在表單中進行檔上傳時,就需要使用該格式
答案是「enctype」,它規定了表單數據編碼的內容類型,引用 HTML 4.01規範的Form章節 一段文字:
enctype = content-type [CI] This attribute specifies the content type used to submit the form to the server (when the value of method is "post"). The default value for this attribute is "application/x-www-form-urlencoded". The value "multipart/form-data" should be used in combination with the INPUT element, type="file".以上enctype其實就是Content-type,它也是Form中的屬性之一,用來表式傳送參數到伺服器時,消息主體所使用的編碼類型。
<form enctype="value">
筆者將4個常用的Content-type類型補充說明:
類型 | 描述 |
---|---|
application/x-www-form-urlencoded | 在發送前編碼所有字元(預設)。 |
multipart/form-data | 不對字元編碼。,在使用包含檔上傳控制項的表單時,必須使用該值。 |
application/json | AJAX中預設JSON數據格式。 |
text/plain | 空格轉換為 "+" 加號,但不對特殊字元編碼。 |
application/x-www-form-urlencoded說明:
以台灣證券交易所的個股日收盤價及月平均價網頁為例。
查詢股票代碼的表單原始程式碼。
透過 Fiddler 查看傳送的HTTP request內容。
從表單原始程式碼中,並未見到「enctype」屬性設定,在POST傳送參數時,Content-type以「application/x-www-form-urlencoded」作為預設,將消息主體以URL編碼方式轉換。
multipart/form-data 說明:
在Imgur免費圖片空間上傳一張圖片為例。
透過 Fiddler 查看傳送的HTTP request內容。
在HTTP request內容,會看到在 「multipart/form-data」後面用boundary字樣緊接一個字串,該字串是隨機產生,也可指定字串內容,主要是用來區分消息主體內容不重複。每部分以「 --boundary」開始,再接文字內容或檔案內容,如果傳輸的是文件,還要包含文件名和文件類型資訊。消息主體最後以「--boundary--」標示結束。關於「multipart/form-data」的詳細定義,可至 rfc1867 與 rfc7578 查看。
補充說明:
- 有些網站如上傳的檔案內容很大,會將檔案資料分割成好幾段,藉由「multipart/form-data」方式上傳檔案。
- 使用「multipart/form-data」要注意的是,由於未提供任何編碼動作,所以須將消息主體內容進行UTF8的轉換,不然將無法成功傳送內容。
原因:
- Line Notify並未提供任何傳送圖片的原始程式碼參考範例,以上述介紹 「multipart/form-data 」的觀念,可用來傳送文字與檔案,再加上 Line Notify API 有支援 「multipart/form-data」,故以 「multipart/form-data」作為本機圖片傳送至Line Notify操作。
- 筆者先行介紹傳送檔案的觀念,大家對文章後面的內容能快速上手。
在開啟Fiddler 後,於命令提示字元下輸入以下指令傳送圖片,再觀察Fiddler的變化。
curl -k -x http://127.0.0.1:8888 -X POST https://notify-api.line.me/api/notify -H "Authorization: Bearer dV36PcwrvXQWvySCiGcmxb5ESlbu3seOrCMEquy8am2" -F "message=中文123456789" -F "imageFile=@C:\Users\Amin\Desktop\curl\123.jpg"
搭配Fiddler的HTTP request內容,轉成程式來操作就可以完成傳送本機圖片。
以下就以 Excel VBA 作為傳送圖片的範例程式碼,請搭配以下HTTP request內容圖片:
''' WinApi function that maps a UTF-16 (wide character) string to a new character string Private Declare Function WideCharToMultiByte Lib "kernel32" ( _ ByVal CodePage As Long, _ ByVal dwFlags As Long, _ ByVal lpWideCharStr As Long, _ ByVal cchWideChar As Long, _ ByVal lpMultiByteStr As Long, _ ByVal cbMultiByte As Long, _ ByVal lpDefaultChar As Long, _ ByVal lpUsedDefaultChar As Long) As Long ' CodePage constant for UTF-8 Private Const CP_UTF8 = 65001 ''' Return byte array with VBA "Unicode" string encoded in UTF-8 Public Function Utf8BytesFromString(strInput As String) As Byte() Dim nBytes As Long Dim abBuffer() As Byte ' Get length in bytes *including* terminating null nBytes = WideCharToMultiByte(CP_UTF8, 0&, ByVal StrPtr(strInput), -1, vbNull, 0&, 0&, 0&) ' We don't want the terminating null in our byte array, so ask for `nBytes-1` bytes ReDim abBuffer(nBytes - 2) ' NB ReDim with one less byte than you need nBytes = WideCharToMultiByte(CP_UTF8, 0&, ByVal StrPtr(strInput), -1, ByVal VarPtr(abBuffer(0)), nBytes - 1, 0&, 0&) Utf8BytesFromString = abBuffer End Function Sub Line傳讀圖與訊息() Dim URL As String Dim sToken As String Dim sFilepath As String Dim nFile As Integer Dim baBuffer() As Byte Dim ssPostData1 As String Dim ssPostData2 As String Dim ssPostData3 As String Dim Messagelength As Integer Dim arr1() As Byte Dim arr2() As Byte Dim arr3() As Byte Dim arr4() As Byte Const STR_BOUNDARY As String = "------------------------058b4eeb7d99b4f6" '設定 BOUNDARY sToken = "你的Token" URL = "https://notify-api.line.me/api/notify" sFilepath = "C:\Users\Amin\Desktop\line\123.jpg" sFileName = GetFilenameFromPath(sFilepath) '第一段 ssPostData1 = "--" & STR_BOUNDARY & vbCrLf & _ "Content-Disposition: form-data; name=" & """message""" & vbCrLf & vbCrLf arr1 = StrConv(ssPostData1, vbFromUnicode) '第二段 訊息段 arr2 = Utf8BytesFromString("小天使圖傳送!!!LaLaLa~~~") '第三段 ssPostData2 = vbCrLf & _ "--" & STR_BOUNDARY & vbCrLf & _ "Content-Disposition: form-data; name=" & """imageFile""" & "; filename=" & sFileName & vbCrLf & _ "Content-Type: image/jpeg" & vbCrLf & vbCrLf arr3 = StrConv(ssPostData2, vbFromUnicode) '第四段,讀出圖檔,圖檔段 nFile = FreeFile Open sFilepath For Binary Access Read As nFile If LOF(nFile) > 0 Then ReDim baBuffer(0 To LOF(nFile) - 1) As Byte Get nFile, , baBuffer imagear = baBuffer End If Close nFile '第五段,尾巴段 arr4 = StrConv(vbCrLf & "--" & STR_BOUNDARY & "--" & vbCrLf, vbFromUnicode) Dim arraytotal As Long Dim sendarray() As Byte arraytotal = UBound(arr1) + UBound(arr2) + UBound(arr3) + UBound(imagear) + UBound(arr4) + 4 ReDim sendarray(arraytotal) '組合全部資料' '將 http 的 POST Data 轉換成 Binary array '第一段內容填入 For i = 0 To UBound(arr1) sendarray(i) = arr1(i) Next '第二段內容填入 For i = 0 To UBound(arr2) sendarray(UBound(arr1) + i + 1) = arr2(i) Next '第三段內容填入 For i = 0 To UBound(arr3) sendarray(UBound(arr1) + UBound(arr2) + i + 2) = arr3(i) Next '第四段內容填入 圖檔段內容 轉換成 Binary array For i = 0 To UBound(imagear) sendarray(UBound(arr1) + UBound(arr2) + UBound(arr3) + i + 3) = imagear(i) Next '組合 http POST 內容的尾端部分。 For i = 0 To UBound(arr4) sendarray(UBound(arr1) + UBound(arr2) + UBound(arr3) + UBound(imagear) + i + 4) = arr4(i) Next With CreateObject("Microsoft.XMLHTTP") '傳送request .Open "POST", URL, 0 '設定Header內容 .SetRequestHeader "Content-Type", "multipart/form-data; boundary=" & STR_BOUNDARY .SetRequestHeader "Authorization", "Bearer " & sToken '傳送request到server .send sndar(sendarray) '顯示傳輸是否成功 Debug.Print .responseText End With End Sub Public Function sndar(sendarray As Variant) As Byte() '陣列轉換 sndar = sendarray End Function Public Function GetFilenameFromPath(ByVal strPath As String) As String '以遞迴方式,從完整路徑中取得檔名 If Right$(strPath, 1) <> "\" And Len(strPath) > 0 Then GetFilenameFromPath = GetFilenameFromPath(Left$(strPath, Len(strPath) - 1)) + Right$(strPath, 1) End If End Function Function URL_Encode(ByVal strOrg As String) As String With CreateObject("ScriptControl") .Language = "JScript" URL_Encode = .CodeObject.encodeURI(strOrg) End With End Function
透過 Fiddler 查看傳送的HTTP request內容。
執行結果:
參考資料:
- AJAX简介
- 使用XMLHttpRequest
- Microsoft.XMLHTTP基本用法
- 四種常見的POST 提交數據方式
- 理解HTTP之Content-Type
- HTTP 表单编码 enctype
- HTTP 表单编码 enctype
- HTTP request : Content-Type
- Http Header裡的Content-Type
- POST提交數據的常見編碼方式
- [VB6] Using WinInet to post binary file
- POST 提交數據的4種編碼方式以及PHP 接收方式
- Http請求中Content-Type講解以及在Spring MVC中的應用
- Ajax方式提交表單的常見編碼類型總結
- 表單提交時編碼類型enctype詳解
- 網頁用 enctype="multipart/form-data" 傳送(POST)時,用Request.Form("FieldName")讀值
- JS中使用FormData上传文件、图片的方法