透過 http 做 file upload 通常都是走 POST 比較多,但是最近遇到用 PUT 做 upload 的,需要自己生 multipart/form-data 的資料比較麻煩一點
整個 scenario 是這樣: 在同一個 session 裏先做 http post 做帳號密碼的檢查,然後取得 authentication key,然後再將該 key 放在下一個 put request header中,同時上傳檔案。
以C#來說,需要注意的就是要有 CookieContainer(),要自己組 multipart/form-data 的 header,還有就是資料形態要注意是 application/octet-stream
HttpWebRequest hwr;
CookieContainer mcc = new CookieContainer(); //create a CookieContainer, to keep session cookies
hwr = (HttpWebRequest)HttpWebRequest.Create("http://YOUR_URL/api/login");
hwr.Method = "POST";
var data = Encoding.ASCII.GetBytes("username=" + email + "&password=" + pwd);
hwr.ContentType = "application/x-www-form-urlencoded"; //post request should set ContentType as application/x-www-form-urlencoded
hwr.ContentLength = data.Length;
hwr.CookieContainer = mcc;
using (var stream = hwr.GetRequestStream())
{
stream.Write(data, 0, data.Length);
}
string str_result;
using (var httpResponse = (HttpWebResponse)hwr.GetResponse())
{
using (var streamReader = new StreamReader(httpResponse.GetResponseStream()))
{
var result = streamReader.ReadToEnd();
str_result = result.ToString();
}
}
dynamic tmp = JsonConvert.DeserializeObject(str_result); // in my example, request result is in JSON format, so I use JsonConvert
hwr = (HttpWebRequest)HttpWebRequest.Create("http://YOUR_URL/api/put/my");
hwr.CookieContainer = mcc;
hwr.Method = "PUT";
string str_boundary = "---WebKitFormBoundarySkAQdHysJKel8YBM"; // create the boundary
hwr.ContentType = "multipart/form-data; boundary=" + str_boundary; // set http header ContentType as multipart/form-data, and specify the boundary
hwr.Headers.Add("Authorization", Convert.ToString(tmp.authHeader)); // set Authorization in header
hwr.Headers.Add("Cache-Control", "no-cache");
hwr.Headers.Add("Accept-Encoding", "gzip, deflate, sdch"); //set accept encoding
hwr.Headers.Add("Accept-Language", "zh-TW,zh;q=0.8,en-US;q=0.6,en;q=0.4");
hwr.Accept = "*/*";
hwr.KeepAlive = true;
hwr.AllowAutoRedirect = false;
hwr.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate; // set auto decompress
using (Stream memStream = new System.IO.MemoryStream())
{
// NOTE: you should add "--" before the boundary, this is HTTP specification. Besides, end boundary needs "--" at the end.
var boundarybytes = System.Text.Encoding.ASCII.GetBytes("--" + str_boundary + "\r\n");
var endBoundaryBytes = System.Text.Encoding.ASCII.GetBytes("\r\n--" + str_boundary + "--");
// Content-Disposition is form-data, and the content-type is application/octet-stream
string headerTemplate = "Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"\r\n" +
"Content-Type: application/octet-stream\r\n\r\n";
string files = @"c:\myput.dat";
memStream.Write(boundarybytes, 0, boundarybytes.Length);
var header = string.Format(headerTemplate, "putDatas", "myput.dat");
var headerbytes = System.Text.Encoding.ASCII.GetBytes(header);
memStream.Write(headerbytes, 0, headerbytes.Length);
using (var fileStream = new FileStream(files, FileMode.Open, FileAccess.Read))
{
var buffer = new byte[1024];
var bytesRead = 0;
while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) != 0)
{
memStream.Write(buffer, 0, bytesRead);
}
}
memStream.Write(endBoundaryBytes, 0, endBoundaryBytes.Length);
hwr.ContentLength = memStream.Length;
using (var requestStream = hwr.GetRequestStream())
{
memStream.Position = 0;
byte[] tempBuffer = new byte[memStream.Length];
memStream.Read(tempBuffer, 0, tempBuffer.Length);
memStream.Close();
requestStream.Write(tempBuffer, 0, tempBuffer.Length);
}
string result_2;
var response = hwr.GetResponse();
using (Stream stream2 = response.GetResponseStream())
{
byte[] tmp_b = new byte[10000]; // if you do not set auto decompress, the returned data is compressed
stream2.Read(tmp_b, 0, 10000);
result_2 = System.Text.Encoding.UTF8.GetString(tmp_b).Replace("\0", "");
}
hwr.Abort();
response.Close();
response.Dispose();
memStream.Close();
}
以 python 來說,其實也差不多,我用了 requests 跟 MultipartEncoder,要不然會麻煩許多。
import requests
import json
import os
from requests_toolbelt.multipart.encoder import MultipartEncoder
filepath = 'C:\\myput.dat'
with open(filepath, mode='rb') as fh:
filedata = fh.read()
length = os.path.getsize(filepath)
s = requests.session()
response = s.post('http://YOUR_URL/api/login',
json={"username":"user@email.com", "password":"pwd"}
)
print(response.content)
print("\r\n")
res_json = json.loads(response.content.decode())
print(res_json['authHeader'])
multipartdata = MultipartEncoder(
fields = {
'putDatas': ('myput.dat', open('C:\\myput.dat', 'rb'), 'application/octet-stream')
},
boundary = "---011000010111000001101001")
url = "http://YOUR_URL/api/put/my"
headers = {
'Content-Type': "multipart/form-data; boundary=---011000010111000001101001",
'Authorization': res_json['authHeader'],
'Cache-Control': "no-cache",
'Connection': 'keep-alive',
'Accept': '*/*',
'Accept-Encoding': 'gzip, deflate, sdch',
'Accept-Language': 'zh-TW,zh;q=0.8,en-US;q=0.6,en;q=0.4',
'postman-token': "14552a5c-1e22-7a74-4437-8e82124100ea"
}
response = s.put(url, data=multipartdata, headers=headers)
print(response.content)
print(response.headers)
1 thought on “Create HTTP PUT request to upload a file (c# & python)”