using System; using System.Collections.Generic; using System.Text; using System.IO; using COSXML.Common; using COSXML.Log; using COSXML.Utils; namespace COSXML.Network { /// /// request body for http request /// public abstract class RequestBody { protected static string TAG = typeof(RequestBody).Name; // 64kb public const int SEGMENT_SIZE = 64 * 1024; protected long contentLength; protected string contentType; protected Callback.OnProgressCallback progressCallback; /// /// body length /// public virtual long ContentLength { get { return contentLength; } set { contentLength = value; } } /// /// body mime type /// public virtual string ContentType { get { return contentType; } set { contentType = value; } } /// /// calculation content md5 /// /// public virtual string GetMD5() { throw new NotImplementedException(); } /// /// Synchronization method: write data to outputStream /// /// output stream for writing data public abstract void OnWrite(Stream outputStream); /// /// Asynchronous method: handle request body /// /// /// public abstract void StartHandleRequestBody(Stream outputStream, EndRequestBody endRequestBody); public Callback.OnProgressCallback ProgressCallback { get { return progressCallback; } set { progressCallback = value; } } /// /// notify progress is complete! /// internal void OnNotifyGetResponse() { if (progressCallback != null && contentLength >= 0) { progressCallback(contentLength, contentLength); } } /// /// calculation progress /// /// /// protected void UpdateProgress(long complete, long total) { if (total == 0) { progressCallback(0, 0); } else if (complete < total) { progressCallback(complete, total); } else { progressCallback(total - 1, total); } } } public class RequestBodyState { public byte[] buffer; public long complete; public Stream outputStream; public EndRequestBody endRequestBody; } public delegate void EndRequestBody(Exception exception); public class ByteRequestBody : RequestBody { private readonly byte[] data; //private RequestBodyState requestBodyState; public ByteRequestBody(byte[] data) { this.data = data; contentLength = data.Length; } public override void OnWrite(Stream outputStream) { StartHandleRequestBody(outputStream); } public override string GetMD5() { return DigestUtils.GetMd5ToBase64(data); } public override void StartHandleRequestBody(Stream outputStream, EndRequestBody endRequestBody = null) { try { int completed = 0; while (completed + SEGMENT_SIZE < contentLength) { outputStream.Write(data, completed, SEGMENT_SIZE); outputStream.Flush(); completed += SEGMENT_SIZE; if (progressCallback != null) { UpdateProgress(completed, contentLength); } } if (completed < contentLength) { //包括本身 //包括本身 outputStream.Write(data, completed, (int)(contentLength - completed)); outputStream.Flush(); if (progressCallback != null) { UpdateProgress(contentLength, contentLength); } } if (endRequestBody != null) { endRequestBody(null); } } catch (Exception ex) { if (endRequestBody != null) { endRequestBody(ex); } else { throw; } } finally { if (outputStream != null) { outputStream.Flush(); outputStream.Close(); outputStream.Dispose(); //QLog.D("XIAO", "stream close"); outputStream = null; } } } } public class FileRequestBody : RequestBody { private readonly string srcPath; private readonly long fileOffset; //private RequestBodyState requestBodyState; private FileStream fileStream; public FileRequestBody(string srcPath, long fileOffset, long sendContentSize) { this.srcPath = srcPath; this.fileOffset = fileOffset; contentLength = sendContentSize; } public override void OnWrite(Stream outputStream) { StartHandleRequestBody(outputStream); } public override string GetMD5() { try { fileStream = new FileStream(srcPath, FileMode.Open, FileAccess.Read); fileStream.Seek(fileOffset, SeekOrigin.Begin); return DigestUtils.GetMd5ToBase64(fileStream, contentLength); } catch (Exception ex) { QLog.Error(TAG, ex.Message, ex); throw; } finally { if (fileStream != null) { fileStream.Close(); } } } public override void StartHandleRequestBody(Stream outputStream, EndRequestBody endRequestBody = null) { FileStream fileStream = null; try { byte[] buffer = new byte[SEGMENT_SIZE]; int bytesRead = 0; long completed = bytesRead; fileStream = new FileStream(srcPath, FileMode.Open, FileAccess.Read); //seek to designated position //seek to designated position fileStream.Seek(fileOffset, SeekOrigin.Begin); long remain = contentLength - completed; if (remain > 0) { while ((bytesRead = fileStream.Read(buffer, 0, (int)(buffer.Length > remain ? remain : buffer.Length))) != 0) { outputStream.Write(buffer, 0, bytesRead); outputStream.Flush(); completed += bytesRead; if (progressCallback != null) { UpdateProgress(completed, contentLength); } remain = contentLength - completed; if (remain == 0) { break; } } } else { if (progressCallback != null) { UpdateProgress(completed, contentLength); } } buffer = null; if (endRequestBody != null) { endRequestBody(null); } } catch (Exception ex) { if (endRequestBody != null) { endRequestBody(ex); } else { throw; } } finally { if (fileStream != null) { fileStream.Close(); fileStream.Dispose(); //QLog.D("XIAO", "stream close"); fileStream = null; } if (outputStream != null) { outputStream.Flush(); outputStream.Close(); outputStream.Dispose(); //QLog.D("XIAO", "stream close"); outputStream = null; } } } } public class StreamRequestBody : RequestBody { private readonly long fileOffset; //private RequestBodyState requestBodyState; private Stream stream; public StreamRequestBody(Stream stream, long fileOffset, long sendContentSize) { this.stream = stream; this.fileOffset = fileOffset; contentLength = sendContentSize; } public override void OnWrite(Stream outputStream) { StartHandleRequestBody(outputStream); } public override string GetMD5() { try { stream.Seek(fileOffset, SeekOrigin.Begin); return DigestUtils.GetMd5ToBase64(stream, contentLength); } catch (Exception ex) { QLog.Error(TAG, ex.Message, ex); throw; } } public override void StartHandleRequestBody(Stream outputStream, EndRequestBody endRequestBody = null) { try { byte[] buffer = new byte[SEGMENT_SIZE]; int bytesRead = 0; long completed = bytesRead; //seek to designated position //seek to designated position stream.Seek(fileOffset, SeekOrigin.Begin); long remain = contentLength - completed; if (remain > 0) { while ((bytesRead = stream.Read(buffer, 0, (int)(buffer.Length > remain ? remain : buffer.Length))) != 0) { outputStream.Write(buffer, 0, bytesRead); outputStream.Flush(); completed += bytesRead; if (progressCallback != null) { UpdateProgress(completed, contentLength); } remain = contentLength - completed; if (remain == 0) { break; } } } else { if (progressCallback != null) { UpdateProgress(completed, contentLength); } } buffer = null; if (endRequestBody != null) { endRequestBody(null); } } catch (Exception ex) { if (endRequestBody != null) { endRequestBody(ex); } else { throw; } } finally { if (outputStream != null) { outputStream.Flush(); outputStream.Close(); outputStream.Dispose(); //QLog.D("XIAO", "stream close"); outputStream = null; } } } } public class FileStreamRequestBody : RequestBody { private readonly long fileOffset; //private RequestBodyState requestBodyState; private FileStream fileStream; public FileStreamRequestBody(FileStream fileStream, long fileOffset, long sendContentSize) { this.fileStream = fileStream; this.fileOffset = fileOffset; contentLength = sendContentSize; } public override void OnWrite(Stream outputStream) { StartHandleRequestBody(outputStream); } public override string GetMD5() { try { fileStream.Seek(fileOffset, SeekOrigin.Begin); return DigestUtils.GetMd5ToBase64(fileStream, contentLength); } catch (Exception ex) { QLog.Error(TAG, ex.Message, ex); throw; } } public override void StartHandleRequestBody(Stream outputStream, EndRequestBody endRequestBody = null) { try { byte[] buffer = new byte[SEGMENT_SIZE]; int bytesRead = 0; long completed = bytesRead; //seek to designated position //seek to designated position fileStream.Seek(fileOffset, SeekOrigin.Begin); long remain = contentLength - completed; if (remain > 0) { while ((bytesRead = fileStream.Read(buffer, 0, (int)(buffer.Length > remain ? remain : buffer.Length))) != 0) { outputStream.Write(buffer, 0, bytesRead); outputStream.Flush(); completed += bytesRead; if (progressCallback != null) { UpdateProgress(completed, contentLength); } remain = contentLength - completed; if (remain == 0) { break; } } } else { if (progressCallback != null) { UpdateProgress(completed, contentLength); } } buffer = null; if (endRequestBody != null) { endRequestBody(null); } } catch (Exception ex) { if (endRequestBody != null) { endRequestBody(ex); } else { throw; } } finally { if (outputStream != null) { outputStream.Flush(); outputStream.Close(); outputStream.Dispose(); //QLog.D("XIAO", "stream close"); outputStream = null; } } } } public class MultipartRequestBody : RequestBody { private readonly string DASHDASH = "--"; public static string BOUNDARY = "314159265358979323------------"; private readonly string CRLF = "\r\n"; private readonly string CONTENT_DISPOSITION = "Content-Disposition: form-data; "; private Dictionary parameters; private string name; private string fileName; private byte[] data; private string srcPath; private long fileOffset; private Stream fileStream; private long realContentLength; private RequestBodyState requestBodyState; public MultipartRequestBody() { contentType = "multipart/form-data; boundary=" + BOUNDARY; parameters = new Dictionary(); contentLength = -1L; } public void AddParamters(Dictionary parameters) { if (parameters != null) { foreach (KeyValuePair pair in parameters) { this.parameters.Add(pair.Key, pair.Value); } } } public void AddParameter(string key, string value) { if (key != null) { parameters.Add(key, value); } } public void AddData(byte[] data, string name, string fileName) { this.data = data; this.name = name; this.fileName = fileName; this.realContentLength = data.Length; } public void AddData(string srcPath, long fileOffset, long sendContentSize, string name, string fileName) { this.srcPath = srcPath; this.fileOffset = fileOffset; this.name = name; this.fileName = fileName; realContentLength = sendContentSize; } //计算长度 public override long ContentLength { get { ComputerContentLength(); return base.ContentLength; } } private void ComputerContentLength() { if (contentLength != -1) { return; } contentLength = 0; if (parameters != null && parameters.Count > 0) { StringBuilder parametersBuilder = new StringBuilder(); foreach (KeyValuePair pair in parameters) { parametersBuilder.Append(DASHDASH).Append(BOUNDARY).Append(CRLF); parametersBuilder.Append(CONTENT_DISPOSITION).Append("name=\"").Append(pair.Key).Append("\"").Append(CRLF); parametersBuilder.Append(CRLF); parametersBuilder.Append(pair.Value).Append(CRLF); } string content = parametersBuilder.ToString(); byte[] data = Encoding.UTF8.GetBytes(content); contentLength += data.Length; } if (name != null) { StringBuilder parametersBuilder = new StringBuilder(); parametersBuilder.Append(DASHDASH).Append(BOUNDARY).Append(CRLF); parametersBuilder.Append(CONTENT_DISPOSITION).Append("name=\"").Append(name).Append("\""); if (!String.IsNullOrEmpty(fileName)) { parametersBuilder.Append("; filename=").Append("\"").Append(fileName).Append("\""); } parametersBuilder.Append(CRLF); parametersBuilder.Append("Content-Type: ").Append("application/octet-stream").Append(CRLF); parametersBuilder.Append(CRLF); string content = parametersBuilder.ToString(); byte[] data = Encoding.UTF8.GetBytes(content); contentLength += data.Length; } contentLength += realContentLength; string endLine = CRLF + DASHDASH + BOUNDARY + DASHDASH + CRLF; byte[] endData = Encoding.UTF8.GetBytes(endLine); contentLength += endData.Length; } private void WriteParameters(Stream outputStream) { if (parameters != null && parameters.Count > 0) { StringBuilder parametersBuilder = new StringBuilder(); foreach (KeyValuePair pair in parameters) { parametersBuilder.Append(DASHDASH).Append(BOUNDARY).Append(CRLF); parametersBuilder.Append(CONTENT_DISPOSITION).Append("name=\"").Append(pair.Key).Append("\"").Append(CRLF); parametersBuilder.Append(CRLF); parametersBuilder.Append(pair.Value).Append(CRLF); } string content = parametersBuilder.ToString(); byte[] data = Encoding.UTF8.GetBytes(content); outputStream.Write(data, 0, data.Length); } } private void WriteFileParameters(Stream outputStream) { StringBuilder parametersBuilder = new StringBuilder(); parametersBuilder.Append(DASHDASH).Append(BOUNDARY).Append(CRLF); parametersBuilder.Append(CONTENT_DISPOSITION).Append("name=\"").Append(name).Append("\""); if (!String.IsNullOrEmpty(fileName)) { parametersBuilder.Append("; filename=").Append("\"").Append(fileName).Append("\""); } parametersBuilder.Append(CRLF); parametersBuilder.Append("Content-Type: ").Append("application/octet-stream").Append(CRLF); parametersBuilder.Append(CRLF); string content = parametersBuilder.ToString(); byte[] data = Encoding.UTF8.GetBytes(content); outputStream.Write(data, 0, data.Length); } private void WriteEndLine(Stream outputStream) { string endLine = CRLF + DASHDASH + BOUNDARY + DASHDASH + CRLF; byte[] data = Encoding.UTF8.GetBytes(endLine); outputStream.Write(data, 0, data.Length); } public override void OnWrite(Stream outputStream) { StartHandleRequestBody(outputStream); } public override void StartHandleRequestBody(Stream outputStream, EndRequestBody endRequestBody = null) { //write paramters WriteParameters(outputStream); //写入content-disposition: form-data; name = "file"; filename = "xxx"\r\n WriteFileParameters(outputStream); outputStream.Flush(); //wrtie content: file or bintary try { if (data != null) { int completed = 0; while (completed + SEGMENT_SIZE < realContentLength) { outputStream.Write(data, completed, SEGMENT_SIZE); outputStream.Flush(); completed += SEGMENT_SIZE; if (progressCallback != null) { UpdateProgress(completed, realContentLength); } } if (completed < realContentLength) { //包括本身 //包括本身 outputStream.Write(data, completed, (int)(realContentLength - completed)); if (progressCallback != null) { UpdateProgress(realContentLength, realContentLength); } } WriteEndLine(outputStream); outputStream.Flush(); } else if (srcPath != null) { // 64kb // 64kb byte[] buffer = new byte[SEGMENT_SIZE]; int bytesRead = 0; long completed = bytesRead; fileStream = new FileStream(srcPath, FileMode.Open, FileAccess.Read); fileStream.Seek(fileOffset, SeekOrigin.Begin); long remain = realContentLength - completed; if (remain > 0) { while ((bytesRead = fileStream.Read(buffer, 0, (int)(buffer.Length > remain ? remain : buffer.Length))) != 0) { outputStream.Write(buffer, 0, bytesRead); outputStream.Flush(); completed += bytesRead; if (progressCallback != null) { UpdateProgress(completed, realContentLength); } remain = realContentLength - completed; if (remain == 0) { break; } } } else { if (progressCallback != null) { completed += bytesRead; UpdateProgress(completed, realContentLength); } } WriteEndLine(outputStream); outputStream.Flush(); } if (endRequestBody != null) { endRequestBody(null); } } catch (Exception ex) { if (endRequestBody != null) { endRequestBody(ex); } else { throw; } } finally { if (fileStream != null) { fileStream.Close(); fileStream.Dispose(); fileStream = null; } if (outputStream != null) { outputStream.Flush(); outputStream.Close(); outputStream.Dispose(); outputStream = null; } } } } }