HttpClient.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text;
  4. using System.Net;
  5. using COSXML.Network;
  6. using COSXML.Model;
  7. using COSXML.Common;
  8. using COSXML.Auth;
  9. using COSXML.CosException;
  10. using COSXML.Transfer;
  11. using COSXML.Model.Tag;
  12. using COSXML.Log;
  13. using System.IO;
  14. using COSXML.Model.Object;
  15. using COSXML.Model.CI;
  16. using COSXML.Utils;
  17. namespace COSXML.Network
  18. {
  19. /// <summary>
  20. /// input: CosRequest; output: CosResponse
  21. /// </summary>
  22. public sealed class HttpClient
  23. {
  24. private static string TAG = "HttpClient";
  25. private HttpClientConfig config;
  26. private static HttpClient instance;
  27. private Object sync = new Object();
  28. private static Object syncInstance = new Object();
  29. private const int MAX_ACTIVIE_TASKS = 5;
  30. public int MaxRetry { private get; set;} = 3;
  31. private volatile int activieTasks = 0;
  32. public static HttpClient GetInstance()
  33. {
  34. lock (syncInstance)
  35. {
  36. if (instance == null)
  37. {
  38. instance = new HttpClient();
  39. }
  40. }
  41. return instance;
  42. }
  43. public void Init(HttpClientConfig config)
  44. {
  45. if (this.config == null)
  46. {
  47. lock (sync)
  48. {
  49. if (this.config == null)
  50. {
  51. this.config = config;
  52. // init grobal httpwebreqeust
  53. CommandTask.Init(this.config);
  54. }
  55. }
  56. }
  57. }
  58. private HttpClient()
  59. {
  60. }
  61. /// <summary>
  62. /// excute request
  63. /// </summary>
  64. /// <param name="cosRequest"></param>
  65. /// <param name="cosResult"></param>
  66. /// <param name="credentialProvider"></param>
  67. /// <exception cref="COSXML.CosException.CosClientException">CosClientException</exception>
  68. /// <exception cref="COSXML.CosException.CosServerException">CosServerException</exception>
  69. public void Excute(CosRequest cosRequest, CosResult cosResult, QCloudCredentialProvider credentialProvider)
  70. {
  71. //HttpTask httpTask = new HttpTask();
  72. //httpTask.cosRequest = cosRequest;
  73. //httpTask.cosResult = cosResult;
  74. //httpTask.isSchedue = false;
  75. InternalExcute(cosRequest, cosResult, credentialProvider);
  76. }
  77. public void Schedue(CosRequest cosRequest, CosResult cosResult, COSXML.Callback.OnSuccessCallback<CosResult> successCallback, COSXML.Callback.OnFailedCallback failCallback, QCloudCredentialProvider credentialProvider)
  78. {
  79. //HttpTask httpTask = new HttpTask();
  80. //httpTask.cosRequest = cosRequest;
  81. //httpTask.cosResult = cosResult;
  82. //httpTask.isSchedue = true;
  83. //httpTask.successCallback = successCallback;
  84. //httpTask.failCallback = failCallback;
  85. InternalSchedue(cosRequest, cosResult, successCallback, failCallback, credentialProvider);
  86. }
  87. /// <summary>
  88. /// excute request
  89. /// </summary>
  90. /// <param name="cosRequest"></param>
  91. /// <param name="cosResult"></param>
  92. /// <param name="credentialProvider"></param>
  93. /// <exception cref="COSXML.CosException.CosClientException">CosClientException</exception>
  94. /// <exception cref="COSXML.CosException.CosServerException">CosServerException</exception>
  95. public void InternalExcute(CosRequest cosRequest, CosResult cosResult, QCloudCredentialProvider credentialProvider, int retryIndex = 0)
  96. {
  97. try
  98. {
  99. Request request = CreateRequest(cosRequest, credentialProvider);
  100. //extern informations exchange
  101. cosResult.ExternInfo(cosRequest);
  102. Response response;
  103. if (cosRequest is GetObjectRequest)
  104. {
  105. GetObjectRequest getObjectRequest = cosRequest as GetObjectRequest;
  106. response = new CosResponse(cosResult, getObjectRequest.GetSaveFilePath(), getObjectRequest.GetLocalFileOffset(),
  107. getObjectRequest.GetCosProgressCallback());
  108. }
  109. else if (cosRequest is GetSnapshotRequest)
  110. {
  111. GetSnapshotRequest getSnapshotRequest = cosRequest as GetSnapshotRequest;
  112. response = new CosResponse(cosResult, getSnapshotRequest.GetSaveFilePath(), 0, null);
  113. }
  114. else
  115. {
  116. response = new CosResponse(cosResult, null, -1L, null);
  117. }
  118. cosRequest.BindRequest(request);
  119. CommandTask.Excute(request, response, config);
  120. }
  121. catch (CosServerException serverException)
  122. {
  123. // 服务端5xx才重试
  124. if (serverException.statusCode >= 500 && retryIndex < MaxRetry)
  125. {
  126. InternalExcute(cosRequest, cosResult, credentialProvider, retryIndex + 1);
  127. }
  128. else
  129. {
  130. throw;
  131. }
  132. }
  133. catch (CosClientException)
  134. {
  135. // 客户端异常都重试
  136. if (retryIndex < MaxRetry)
  137. {
  138. InternalExcute(cosRequest, cosResult, credentialProvider, retryIndex + 1);
  139. }
  140. else
  141. {
  142. throw;
  143. }
  144. }
  145. catch (Exception ex)
  146. {
  147. // 未知异常也重试
  148. if (retryIndex < MaxRetry)
  149. {
  150. InternalExcute(cosRequest, cosResult, credentialProvider, retryIndex + 1);
  151. }
  152. else
  153. {
  154. throw new CosClientException((int)CosClientError.BadRequest, ex.Message, ex);
  155. }
  156. }
  157. }
  158. // public void Execute(Request request, Response response)
  159. // {
  160. // try
  161. // {
  162. // CommandTask.Excute(request, response, config);
  163. // }
  164. // catch (CosServerException)
  165. // {
  166. // throw;
  167. // }
  168. // catch (CosClientException)
  169. // {
  170. // throw;
  171. // }
  172. // catch (Exception ex)
  173. // {
  174. // throw new CosClientException((int)CosClientError.BadRequest, ex.Message, ex);
  175. // }
  176. // }
  177. public void InternalSchedue(CosRequest cosRequest, CosResult cosResult, COSXML.Callback.OnSuccessCallback<CosResult> successCallback, COSXML.Callback.OnFailedCallback failCallback, QCloudCredentialProvider credentialProvider)
  178. {
  179. try
  180. {
  181. Request request = CreateRequest(cosRequest, credentialProvider);
  182. cosResult.ExternInfo(cosRequest);
  183. Response response;
  184. if (cosRequest is GetObjectRequest)
  185. {
  186. GetObjectRequest getObjectRequest = cosRequest as GetObjectRequest;
  187. response = new CosResponse(cosResult, getObjectRequest.GetSaveFilePath(), getObjectRequest.GetLocalFileOffset(),
  188. getObjectRequest.GetCosProgressCallback(), successCallback, failCallback);
  189. }
  190. else
  191. {
  192. response = new CosResponse(cosResult, null, -1L, null, successCallback, failCallback);
  193. }
  194. cosRequest.BindRequest(request);
  195. CommandTask.Schedue(request, response, config);
  196. }
  197. catch (CosServerException serverException)
  198. {
  199. //throw serverException;
  200. failCallback(null, serverException);
  201. }
  202. catch (CosClientException clientException)
  203. {
  204. //throw clientException;
  205. failCallback(clientException, null);
  206. }
  207. catch (Exception ex)
  208. {
  209. //throw new CosClientException((int)CosClientError.BAD_REQUEST, ex.Message, ex);
  210. failCallback(new CosClientException((int)CosClientError.BadRequest, ex.Message, ex), null);
  211. }
  212. }
  213. private Request CreateRequest(CosRequest cosRequest, QCloudCredentialProvider credentialProvider)
  214. {
  215. cosRequest.CheckParameters();
  216. string requestUrlWithSign = cosRequest.RequestURLWithSign;
  217. Request request = new Request();
  218. request.Method = cosRequest.Method;
  219. if (requestUrlWithSign != null)
  220. {
  221. if (requestUrlWithSign.StartsWith("https"))
  222. {
  223. request.IsHttps = true;
  224. }
  225. else
  226. {
  227. request.IsHttps = false;
  228. }
  229. request.RequestUrlString = requestUrlWithSign;
  230. }
  231. else
  232. {
  233. request.IsHttps = (bool)cosRequest.IsHttps;
  234. request.Url = CreateUrl(cosRequest);
  235. request.Host = cosRequest.GetHost();
  236. }
  237. request.UserAgent = config.UserAgnet;
  238. Dictionary<string, string> headers = cosRequest.GetRequestHeaders();
  239. if (headers != null)
  240. {
  241. foreach (KeyValuePair<string, string> pair in headers)
  242. {
  243. request.AddHeader(pair.Key, pair.Value);
  244. }
  245. }
  246. request.Body = cosRequest.GetRequestBody();
  247. // cacluate md5
  248. if (CheckNeedMd5(request, cosRequest.IsNeedMD5) && request.Body != null)
  249. {
  250. request.AddHeader(CosRequestHeaderKey.CONTENT_MD5, request.Body.GetMD5());
  251. }
  252. // content type header
  253. if (request.Body != null && request.Body.ContentType != null &&
  254. !request.Headers.ContainsKey(CosRequestHeaderKey.CONTENT_TYPE))
  255. {
  256. request.AddHeader(CosRequestHeaderKey.CONTENT_TYPE, request.Body.ContentType);
  257. }
  258. //cacluate sign, and add it.
  259. if (requestUrlWithSign == null)
  260. {
  261. CheckSign(cosRequest.GetSignSourceProvider(), request, credentialProvider);
  262. }
  263. return request;
  264. }
  265. private HttpUrl CreateUrl(CosRequest cosRequest)
  266. {
  267. HttpUrl httpUrl = new HttpUrl();
  268. httpUrl.Scheme = (bool)cosRequest.IsHttps ? "https" : "http";
  269. httpUrl.Host = cosRequest.GetHost();
  270. httpUrl.Path = URLEncodeUtils.EncodePathOfURL(cosRequest.RequestPath);
  271. httpUrl.SetQueryParameters(cosRequest.GetRequestParamters());
  272. return httpUrl;
  273. }
  274. /// <summary>
  275. /// add authorization
  276. /// </summary>
  277. /// <param name="qcloudSignSource">QCloudSignSource</param>
  278. /// <param name="request"></param>
  279. /// <param name="credentialProvider"></param>
  280. private void CheckSign(IQCloudSignSource qcloudSignSource, Request request, QCloudCredentialProvider credentialProvider)
  281. {
  282. // has authorizaiton, notice: using request.Headers, otherwise, error
  283. if (request.Headers.ContainsKey(CosRequestHeaderKey.AUTHORIZAIION))
  284. {
  285. QLog.Debug(TAG, "has add authorizaiton in headers");
  286. return;
  287. }
  288. //has no authorization, but signSourceProvider == null
  289. if (qcloudSignSource == null)
  290. {
  291. QLog.Debug(TAG, "signSourceProvider == null");
  292. return;
  293. }
  294. if (credentialProvider == null)
  295. {
  296. throw new ArgumentNullException("credentialsProvider == null");
  297. }
  298. CosXmlSigner signer = new CosXmlSigner();
  299. signer.Sign(request, qcloudSignSource, credentialProvider.GetQCloudCredentialsCompat(request));
  300. }
  301. private bool CheckNeedMd5(Request request, bool isNeedMd5)
  302. {
  303. bool result = isNeedMd5;
  304. if (request.Headers.ContainsKey(CosRequestHeaderKey.CONTENT_MD5))
  305. {
  306. result = false;
  307. }
  308. return result;
  309. }
  310. /// <summary>
  311. /// cos response
  312. /// 分为两类:
  313. /// 一类下载文件
  314. /// 一类直接读取数据
  315. /// </summary>
  316. private class CosResponse : Response
  317. {
  318. private CosResult cosResult;
  319. private COSXML.Callback.OnSuccessCallback<CosResult> successCallback;
  320. private COSXML.Callback.OnFailedCallback faileCallback;
  321. private const int MAX_BUFFER_SIZE = 4096;
  322. public CosResponse(CosResult cosResult, string saveFilePath, long saveFileOffset, COSXML.Callback.OnProgressCallback downloadProgressCallback)
  323. {
  324. this.cosResult = cosResult;
  325. if (saveFilePath != null)
  326. {
  327. this.Body = new ResponseBody(saveFilePath, saveFileOffset);
  328. this.Body.ProgressCallback = downloadProgressCallback;
  329. }
  330. else
  331. {
  332. this.Body = new ResponseBody();
  333. }
  334. }
  335. public CosResponse(CosResult cosResult, string saveFilePath, long saveFileOffset, COSXML.Callback.OnProgressCallback downloadProgressCallback,
  336. COSXML.Callback.OnSuccessCallback<CosResult> successCallback,
  337. COSXML.Callback.OnFailedCallback failCallback) : this(cosResult, saveFilePath, saveFileOffset, downloadProgressCallback)
  338. {
  339. this.successCallback = successCallback;
  340. this.faileCallback = failCallback;
  341. }
  342. /// <summary>
  343. /// response has been obtain, and parse headers from response
  344. /// </summary>
  345. public override void HandleResponseHeader()
  346. {
  347. cosResult.httpCode = Code;
  348. cosResult.httpMessage = Message;
  349. cosResult.responseHeaders = Headers;
  350. cosResult.InternalParseResponseHeaders();
  351. if (Code >= 300)
  352. {
  353. this.Body.ParseStream = PaserServerError;
  354. }
  355. else
  356. {
  357. this.Body.ParseStream = cosResult.ParseResponseBody;
  358. }
  359. }
  360. public void PaserServerError(Stream inputStream, string contentType, long contentLength)
  361. {
  362. CosServerException cosServerException = new CosServerException(cosResult.httpCode, cosResult.httpMessage);
  363. List<string> values;
  364. Headers.TryGetValue("x-cos-request-id", out values);
  365. cosServerException.requestId = (values != null && values.Count > 0) ? values[0] : null;
  366. Headers.TryGetValue("x-cos-trace-id", out values);
  367. cosServerException.traceId = (values != null && values.Count > 0) ? values[0] : null;
  368. if (inputStream != null)
  369. {
  370. try
  371. {
  372. CosServerError cosServerError = XmlParse.Deserialize<CosServerError>(inputStream);
  373. cosServerException.SetCosServerError(cosServerError);
  374. }
  375. catch (Exception ex)
  376. {
  377. QLog.Debug(TAG, ex.Message);
  378. }
  379. }
  380. throw cosServerException;
  381. }
  382. /// <summary>
  383. /// error
  384. /// </summary>
  385. /// <param name="ex"></param>
  386. public override void OnFinish(bool isSuccess, Exception ex)
  387. {
  388. cosResult.RawContentBodyString = Body.rawContentBodyString;
  389. if (isSuccess && successCallback != null)
  390. {
  391. successCallback(cosResult);
  392. }
  393. else
  394. if (faileCallback != null)
  395. {
  396. if (ex is CosClientException)
  397. {
  398. faileCallback(ex as CosClientException, null);
  399. }
  400. else
  401. if (ex is CosServerException)
  402. {
  403. faileCallback(null, ex as CosServerException);
  404. }
  405. else
  406. {
  407. faileCallback(new CosClientException((int)CosClientError.InternalError, ex.Message, ex), null);
  408. }
  409. }
  410. }
  411. }
  412. }
  413. }