可參考前一篇文章的教學:[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)
沒有留言:
張貼留言
留個話吧:)