可參考前一篇文章的教學:[JAVA] Simulator to POST multipart/form-data using HttpURLConnection
其實整個POST流程只要了解用任何語言都很好實作:)
class MultiPartFormOutputStream
{
private Stream dataOutputStream;
private StringBuilder sbRequBody = new StringBuilder();
// unique random value
private string boundary = null;
// each Parameter line separate
private string CRLF = "\r\n";
// optional prefix char
private string PREFIX = "--";
public MultiPartFormOutputStream(Stream outputSteam, string boundary)
{
this.boundary = boundary;
this.dataOutputStream = outputSteam;
}
public void writeFormFieldData() {
}
public void writeFormFileData(string fieldName,string fileName,string miniType, byte[] filedata){
/*
--boundary\r\n
Content-Disposition: form-data; name="<fieldname>"; filename="<filename>"\r\n
Content-Type: <mime-type>\r\n
\r\n
<file-data>\r\n
*/
this.sbRequBody.Append(this.PREFIX);
this.sbRequBody.Append(this.boundary);
this.sbRequBody.Append(this.CRLF);
this.sbRequBody.Append("Content-Disposition: form-data; name=\"");
this.sbRequBody.Append(fieldName);
this.sbRequBody.Append("\"; filename=\"");
this.sbRequBody.Append(fileName);
this.sbRequBody.Append("\"");
this.sbRequBody.Append(this.CRLF);
if (miniType != null)
{
this.sbRequBody.Append("Content-Type:");
this.sbRequBody.Append(miniType);
this.sbRequBody.Append(this.CRLF);
this.sbRequBody.Append(this.CRLF);
}
// file data
string fileStr = Encoding.UTF8.GetString(filedata);
this.sbRequBody.Append(fileStr);
this.sbRequBody.Append(this.CRLF);
}
public void close() {
// important!! write final boundary
this.sbRequBody.Append(this.PREFIX);
this.sbRequBody.Append(this.boundary);
this.sbRequBody.Append(this.PREFIX);
this.sbRequBody.Append(this.CRLF);
String requestBody = this.sbRequBody.ToString();
#region show console
#if DEBUG
//Console.WriteLine("--multipart/form-data request body--");
//Console.WriteLine(requestBody);
#endif
#endregion
byte[] sendRequBody = Encoding.UTF8.GetBytes(requestBody);
this.dataOutputStream.Write(sendRequBody, 0, sendRequBody.Length);
//this.dataOutputStream.Flush();
//this.dataOutputStream.Close();
this.sbRequBody.Clear();
}
}
改良的寫法,直接以Stream.write逐步寫入串流,不使用Stringbuilder串接所有Post Data,寫入的Buffer會將它切割成指定的block size(請設定fileBlockSize變數),避免傳送太大的buffer而造成timeout。
class MultiPartFormOutputStream
{
public delegate void uploadProgressDelegate(int percent);
public event uploadProgressDelegate uploadProgress;
//request stream
private Stream postStream;
//post header parameter data to stream
private StringBuilder sbRequBody = new StringBuilder();
// unique random value
private string boundary = null;
// each Parameter line separate
private string CRLF = "\r\n";
// optional prefix char
private string PREFIX = "--";
private int fileChunk = 4096;
public MultiPartFormOutputStream(Stream outputSteam, string boundary)
{
this.boundary = boundary;
this.postStream = outputSteam;
}
public void writeFormFieldData(string fieldName,string fieldValue) {
#region debug info
#if DEBUG
Console.WriteLine("-------------ThreadName:{0} writeFormFieldData start-------------", Thread.CurrentThread.Name);
#endif
#endregion
/*
--boundary/r/n
Content-Disposition: form-data; name=""/r/n
/r/n
/r/n
*/
this.sbRequBody.Append(this.PREFIX);
this.sbRequBody.Append(this.boundary);
this.sbRequBody.Append(this.CRLF);
this.sbRequBody.Append("Content-Disposition: form-data; name=\"");
this.sbRequBody.Append(fieldName);
this.sbRequBody.Append("\"");
this.sbRequBody.Append(this.CRLF);
this.sbRequBody.Append(this.CRLF);
this.sbRequBody.Append(fieldValue);
this.sbRequBody.Append(this.CRLF);
#region debug info
#if DEBUG
Console.WriteLine(this.sbRequBody.ToString(), Thread.CurrentThread.Name);
#endif
#endregion
byte[] postFieldBoundary = Encoding.UTF8.GetBytes(this.sbRequBody.ToString());
this.postStream.Write(postFieldBoundary, 0, postFieldBoundary.Length);
this.sbRequBody.Clear();
#region debug info
#if DEBUG
Console.WriteLine("-------------ThreadName:{0} writeFormFieldData end-------------", Thread.CurrentThread.Name);
#endif
#endregion
}
public void writeFromFileData(string fieldName, string fileName, string mimeType, String path)
{
#region debug info
#if DEBUG
Console.WriteLine("-------------ThreadName:{0} writeFormFileData(path) start-------------", Thread.CurrentThread.Name);
#endif
#endregion
#region pre header
/*
--boundary\r\n
Content-Disposition: form-data; name=""; filename=""\r\n
Content-Type: \r\n
\r\n
\r\n
*/
this.sbRequBody.Append(this.PREFIX);
this.sbRequBody.Append(this.boundary);
this.sbRequBody.Append(this.CRLF);
this.sbRequBody.Append("Content-Disposition: form-data; name=\"");
this.sbRequBody.Append(fieldName);
this.sbRequBody.Append("\"; filename=\"");
this.sbRequBody.Append(fileName);
this.sbRequBody.Append("\"");
this.sbRequBody.Append(this.CRLF);
if (mimeType != null)
{
this.sbRequBody.Append("Content-Type:");
this.sbRequBody.Append(mimeType);
this.sbRequBody.Append(this.CRLF);
this.sbRequBody.Append(this.CRLF);
}
#region debug info
#if DEBUG
Console.WriteLine(this.sbRequBody.ToString(), Thread.CurrentThread.Name);
#endif
#endregion
byte[] postFileBoundary = Encoding.UTF8.GetBytes(this.sbRequBody.ToString());
this.postStream.Write(postFileBoundary, 0, postFileBoundary.Length);
this.sbRequBody.Clear();
#endregion
#region chunk
//chunk buffer
byte[] filebuffer = new byte[this.fileChunk];
long offset = 0;
int readByteCount = 0;
int percent = 0;
long fileStreamLen = 0;
#region stream write
FileStream oFile = null;
try
{
#region read chunk
oFile = new FileStream(path, FileMode.Open);
fileStreamLen = oFile.Length;
do
{
readByteCount = oFile.Read(filebuffer, 0, this.fileChunk);
//send current buffer
this.postStream.Write(filebuffer, 0, readByteCount);
#region progress bar
if (this.uploadProgress != null && fileStreamLen > 0)
{
//current offset
offset += readByteCount;
var currentPercent = (int)(((double)offset) / fileStreamLen * 100);
Console.WriteLine("Current = " + currentPercent);
Console.WriteLine(currentPercent + "% File Position:{0}", oFile.Position);
//pass current upload percent
this.uploadProgress(currentPercent);
}
#endregion
} while (readByteCount > 0);
oFile.Flush();
oFile.Close();
oFile.Dispose();
#endregion
}
catch (Exception ex)
{
#if DEBUG
Console.WriteLine(ex.Message);
#endif
}
#endregion
#endregion
#region end header
//this.sbRequBody.Append(this.CRLF);
#region debug info
#if DEBUG
Console.WriteLine(this.CRLF, Thread.CurrentThread.Name);
#endif
#endregion
byte[] CRLFByte = Encoding.UTF8.GetBytes(this.CRLF);
this.postStream.Write(CRLFByte, 0, CRLFByte.Length);
#endregion
#if DEBUG
Console.WriteLine("-------------ThreadName:{0} writeFormFileData(path) end-------------", Thread.CurrentThread.Name);
#endif
}
public void writeFormFileData(string fieldName,string fileName,string miniType, Stream filedata)
{
#region debug info
#if DEBUG
Console.WriteLine("-------------ThreadName:{0} writeFormFileData start-------------", Thread.CurrentThread.Name);
#endif
#endregion
#region pre header
/*
--boundary\r\n
Content-Disposition: form-data; name=""; filename=""\r\n
Content-Type: \r\n
\r\n
\r\n
*/
this.sbRequBody.Append(this.PREFIX);
this.sbRequBody.Append(this.boundary);
this.sbRequBody.Append(this.CRLF);
this.sbRequBody.Append("Content-Disposition: form-data; name=\"");
this.sbRequBody.Append(fieldName);
this.sbRequBody.Append("\"; filename=\"");
this.sbRequBody.Append(fileName);
this.sbRequBody.Append("\"");
this.sbRequBody.Append(this.CRLF);
if (miniType != null)
{
this.sbRequBody.Append("Content-Type:");
this.sbRequBody.Append(miniType);
this.sbRequBody.Append(this.CRLF);
this.sbRequBody.Append(this.CRLF);
}
byte[] postFileBoundary = Encoding.UTF8.GetBytes(this.sbRequBody.ToString());
this.postStream.Write(postFileBoundary, 0, postFileBoundary.Length);
this.sbRequBody.Clear();
#endregion
#region chunk
//chunk buffer
byte[] filebuffer = new byte[this.fileChunk];
int offset = 0;
int readByteCount = 0;
int percent = 0;
long fileStreamLen = filedata.Length;
long filePosition = 0;
#region debug info
#if DEBUG
Console.WriteLine("---ThreadName:{0},filedata.Position:{1}---", Thread.CurrentThread.Name,filedata.Position);
#endif
#endregion
//reset filedata position
//如果讓每個執行緒共享同一個file stream時一定要重設
filedata.Position = 0;
#region debug info
#if DEBUG
Console.WriteLine("---ThreadName:{0},Reset filedata.Position:{1}---", Thread.CurrentThread.Name, filedata.Position);
#endif
#endregion
#region stream write
do
{
#region debug info
#if DEBUG
//Console.WriteLine("ThreadName:{0},Pre filedata.Position:{1}", Thread.CurrentThread.Name, filedata.Position);
#endif
#endregion
//reset
filedata.Position = filePosition;
//read buffer
readByteCount = filedata.Read(filebuffer, 0, this.fileChunk);
//send current buffer
this.postStream.Write(filebuffer, 0, readByteCount);
#region progress bar
if (this.uploadProgress != null)
{
//current offset
offset += readByteCount;
filePosition = offset;
var currentPercent = (int)(((double)offset) / fileStreamLen * 100);
if (currentPercent == percent)
continue;
percent = currentPercent;
#region debug info
#if DEBUG
Console.WriteLine("ThreadName:{0}, filedata.Position:{1}", Thread.CurrentThread.Name, filedata.Position);
#endif
#endregion
//pass current upload percent
this.uploadProgress(percent);
}
#endregion
} while (readByteCount > 0);
#endregion
#endregion
#region end header
//this.sbRequBody.Append(this.CRLF);
byte[] CRLFByte = Encoding.UTF8.GetBytes(this.CRLF);
this.postStream.Write(CRLFByte, 0, CRLFByte.Length);
#endregion
Console.WriteLine("-------------ThreadName:{0} writeFormFileData end-------------", Thread.CurrentThread.Name);
}
public void close()
{
#if DEBUG
Console.WriteLine("-------------ThreadName:{0} close start-------------", Thread.CurrentThread.Name);
#endif
#region important!! write final boundary
this.sbRequBody.Append(this.PREFIX);
this.sbRequBody.Append(this.boundary);
this.sbRequBody.Append(this.PREFIX);
this.sbRequBody.Append(this.CRLF);
#region debug info
#if DEBUG
Console.WriteLine(this.sbRequBody.ToString(), Thread.CurrentThread.Name);
#endif
#endregion
byte[] endBoundary = Encoding.UTF8.GetBytes(this.sbRequBody.ToString());
this.postStream.Write(endBoundary, 0, endBoundary.Length);
#endregion
this.sbRequBody.Clear();
this.postStream.Flush();
this.postStream.Close();
#if DEBUG
Console.WriteLine("-------------ThreadName:{0} close end-------------", Thread.CurrentThread.Name);
#endif
}
}
實作RESTClient,預設的HttpWebRequest是無法傳大檔的,需設定 request.AllowWriteStreamBuffering = false;,但傳太大的檔案容易造成Server的timeout。可以設request.Timeout = -1,但需自已避免無限等待的錯誤。
public delegate void uploadProgressDelegate(int percent);
public event uploadProgressDelegate uploadProgress;
private NameValueCollection fieldNameValueCollection = new NameValueCollection();
private List<MultiPartFieldFileItem> fileItems = new List<MultiPartFieldFileItem>();
public RESTClient()
{
}
public void AddFormFieldItem(string fieldName,string fieldValue) {
this.fieldNameValueCollection.Add(fieldName, fieldValue);
}
public void AddFormFileItem(MultiPartFieldFileItem fileItem) {
this.fileItems.Add(fileItem);
}
public Hashtable POSTMultiPartFormData(string url) {
Hashtable jsonObj = null;
string boundary = new Random(DateTime.Now.Millisecond).Next().ToString();
#if DEBUG
Console.WriteLine("POSTMultiPartFormData request url:" + url);
Console.WriteLine("boundary:" + boundary);
#endif
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.SendChunked = true;
request.AllowWriteStreamBuffering = false;
request.Method = "POST";
request.ContentType = string.Format("multipart/form-data;boundary={0}", boundary);
request.KeepAlive = true;
Stream stream = null;
try
{
stream = request.GetRequestStream();
}
catch
{
stream.Close();
request.Abort();
}
//initial Multipart-from stream
MultiPartFormOutputStream multipartStream = new MultiPartFormOutputStream(stream, boundary);
multipartStream.uploadProgress += new MultiPartFormOutputStream.uploadProgressDelegate(multipartStream_uploadProgress);
//field data parameter
foreach (string fieldName in this.fieldNameValueCollection.AllKeys) {
string fieldValue = this.fieldNameValueCollection.Get(fieldName);
multipartStream.writeFormFieldData(fieldName,fieldValue);
}
this.fieldNameValueCollection.Clear();
//file data parameter
int fileItemsCount = this.fileItems.Count ;
if (fileItemsCount > 0) {
for(int i=0;i<fileItemsCount;i++){
MultiPartFieldFileItem file = (MultiPartFieldFileItem)this.fileItems[i];
//request.ContentLength = file.Filedata.Length;
if (file.FileFrom == FileFrom.Stream)
multipartStream.writeFormFileData(file.FieldName, file.Filename, file.MIMEType, file.Filedata);
else
multipartStream.writeFromFileData(file.FieldName, file.Filename, file.MIMEType, file.Path);
}
}
this.fileItems.Clear();
//important!!
multipartStream.send();
WebResponse response = request.GetResponse();
//Console.WriteLine("Status Description: " + (int) ((HttpWebResponse)response).StatusCode);
stream = response.GetResponseStream();
StreamReader reader = new StreamReader(stream);
jsonObj = (Hashtable)JSON.JsonDecode(reader.ReadToEnd()); //responsed data decoded as JSON object
reader.Close();
stream.Close();
response.Close();
request.Abort();
return jsonObj;
}
public void multipartStream_uploadProgress(int percent) {
if(this.uploadProgress!=null){
this.uploadProgress(percent);
}
}
Reference:Upload files with HTTPWebrequest (multipart/form-data)
沒有留言:
張貼留言
留個話吧:)