using System; using System.Collections.Generic; using System.Text; using System.IO; using COSXML.Common; using COSXML.Log; using COSXML.Utils; /** * Copyright (c) 2018 Tencent Cloud. All rights reserved. * 11/2/2018 4:33:28 PM * bradyxiao */ namespace COSXML.Network { /// /// request body for http request /// public abstract class RequestBody { protected static string TAG = typeof(RequestBody).Name; public const int SEGMENT_SIZE = 64 * 1024;// 64kb 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 abstract string GetMD5(); /// /// 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; contentType = CosRequestHeaderKey.APPLICATION_XML; } public override void OnWrite(Stream outputStream) { 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); } } } catch (Exception ex) { throw ex; } finally { if (outputStream != null) { outputStream.Flush(); outputStream.Close(); outputStream.Dispose(); //QLog.D("XIAO", "stream close"); outputStream = null; } } } public override string GetMD5() { return DigestUtils.GetMd5ToBase64(data); } public override void StartHandleRequestBody(Stream outputStream, EndRequestBody endRequestBody) { 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); } } endRequestBody(null); } catch (Exception ex) { endRequestBody(ex); } finally { if (outputStream != null) { outputStream.Flush(); outputStream.Close(); outputStream.Dispose(); //QLog.D("XIAO", "stream close"); outputStream = null; } } } private void AsyncStreamCallback(IAsyncResult ar) { RequestBodyState requestBodyState = ar.AsyncState as RequestBodyState; Stream outputStream = null; try { outputStream = requestBodyState.outputStream; outputStream.EndWrite(ar); if (progressCallback != null) { UpdateProgress(requestBodyState.complete, contentLength); } if(requestBodyState.complete < contentLength) { long remain = contentLength - requestBodyState.complete; int count = (int)(remain > SEGMENT_SIZE ? SEGMENT_SIZE : remain); int offset = (int)requestBodyState.complete; requestBodyState.complete += count; outputStream.BeginWrite(requestBodyState.buffer, offset, count, AsyncStreamCallback, requestBodyState); } else { if (outputStream != null) outputStream.Close(); //write over requestBodyState.endRequestBody(null); requestBodyState = null; } } catch (Exception ex) { if (outputStream != null) outputStream.Close(); QLog.E(TAG, ex.Message, ex); requestBodyState.endRequestBody(ex); requestBodyState = 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) { FileStream fileStream = null; try { 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);//seek to designated position 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; } catch (Exception ex) { QLog.E(TAG, ex.Message, ex); 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 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.E(TAG, ex.Message, ex); throw; } finally { if (fileStream != null) fileStream.Close(); } } public override void StartHandleRequestBody(Stream outputStream, EndRequestBody endRequestBody) { FileStream fileStream = null; try { 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);//seek to designated position 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; endRequestBody(null); } catch (Exception ex) { QLog.E(TAG, ex.Message, ex); endRequestBody(ex); } 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; } } } private void AsyncStreamCallback(IAsyncResult ar) { RequestBodyState requestBodyState = ar.AsyncState as RequestBodyState; Stream outputStream = null; try { outputStream = requestBodyState.outputStream; outputStream.EndWrite(ar); outputStream.Flush(); if (progressCallback != null) { UpdateProgress(requestBodyState.complete, contentLength); } if (requestBodyState.complete < contentLength) { long remain = contentLength - requestBodyState.complete; int count = fileStream.Read(requestBodyState.buffer, 0, (int)(requestBodyState.buffer.Length > remain ? remain : requestBodyState.buffer.Length)); requestBodyState.complete += count; outputStream.BeginWrite(requestBodyState.buffer, 0, count, AsyncStreamCallback, requestBodyState); } else { if (fileStream != null) fileStream.Close(); if (outputStream != null) outputStream.Close(); //write over requestBodyState.endRequestBody(null); requestBodyState = null; } } catch (Exception ex) { if (fileStream != null) fileStream.Close(); if (outputStream != null) outputStream.Close(); QLog.E(TAG, ex.Message, ex); requestBodyState.endRequestBody(ex); requestBodyState = null; } } } /** * --boundary\r\n * content-type: value\r\n * content-disposition: form-data; name="key"\r\n * content-transfer-encoding: encoding\r\n * \r\n * value\r\n * --boundary-- */ 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; } set { contentLength = value; } } 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) { //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) { byte[] buffer = new byte[SEGMENT_SIZE]; // 64kb 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(); } } catch (Exception) { 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 override string GetMD5() { throw new NotImplementedException(); } public override void StartHandleRequestBody(Stream outputStream, EndRequestBody endRequestBody) { //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) { byte[] buffer = new byte[SEGMENT_SIZE]; // 64kb 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(); } endRequestBody(null); } catch (Exception ex) { endRequestBody(ex); } 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; } } } private void AsyncStreamCallback(IAsyncResult ar) { RequestBodyState requestBodyState = ar.AsyncState as RequestBodyState; Stream outputStream = null; try { outputStream = requestBodyState.outputStream; outputStream.EndWrite(ar); if (progressCallback != null) { UpdateProgress(requestBodyState.complete, realContentLength); } if (requestBodyState.complete < realContentLength) { if (srcPath != null) { long remain = realContentLength - requestBodyState.complete; int count = fileStream.Read(requestBodyState.buffer, 0, (int)(requestBodyState.buffer.Length > remain ? remain : requestBodyState.buffer.Length)); requestBodyState.complete += count; outputStream.BeginWrite(requestBodyState.buffer, 0, count, AsyncStreamCallback, requestBodyState); } else if (data != null) { long remain = realContentLength - requestBodyState.complete; int count = (int)(remain > SEGMENT_SIZE ? SEGMENT_SIZE : remain); int offset = (int)requestBodyState.complete; requestBodyState.complete += count; outputStream.BeginWrite(requestBodyState.buffer, offset, count, AsyncStreamCallback, requestBodyState); } outputStream.Flush(); } else { WriteEndLine(outputStream); outputStream.Flush(); if (fileStream != null) fileStream.Close(); if (outputStream != null) outputStream.Close(); //write over requestBodyState.endRequestBody(null); } } catch (Exception ex) { if (fileStream != null) fileStream.Close(); if (outputStream != null) outputStream.Close(); QLog.E(TAG, ex.Message, ex); requestBodyState.endRequestBody(ex); } } } }