using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using COSXML.Network;
using COSXML.Model;
using COSXML.Common;
using COSXML.Auth;
using COSXML.CosException;
using COSXML.Transfer;
using COSXML.Model.Tag;
using COSXML.Log;
using System.IO;
using COSXML.Model.Object;
using COSXML.Model.CI;
using COSXML.Utils;
namespace COSXML.Network
{
///
/// input: CosRequest; output: CosResponse
///
public sealed class HttpClient
{
private static string TAG = "HttpClient";
private HttpClientConfig config;
private static HttpClient instance;
private Object sync = new Object();
private static Object syncInstance = new Object();
private const int MAX_ACTIVIE_TASKS = 5;
public int MaxRetry { private get; set;} = 3;
private volatile int activieTasks = 0;
public static HttpClient GetInstance()
{
lock (syncInstance)
{
if (instance == null)
{
instance = new HttpClient();
}
}
return instance;
}
public void Init(HttpClientConfig config)
{
if (this.config == null)
{
lock (sync)
{
if (this.config == null)
{
this.config = config;
// init grobal httpwebreqeust
CommandTask.Init(this.config);
}
}
}
}
private HttpClient()
{
}
///
/// excute request
///
///
///
///
/// CosClientException
/// CosServerException
public void Excute(CosRequest cosRequest, CosResult cosResult, QCloudCredentialProvider credentialProvider)
{
//HttpTask httpTask = new HttpTask();
//httpTask.cosRequest = cosRequest;
//httpTask.cosResult = cosResult;
//httpTask.isSchedue = false;
InternalExcute(cosRequest, cosResult, credentialProvider);
}
public void Schedue(CosRequest cosRequest, CosResult cosResult, COSXML.Callback.OnSuccessCallback successCallback, COSXML.Callback.OnFailedCallback failCallback, QCloudCredentialProvider credentialProvider)
{
//HttpTask httpTask = new HttpTask();
//httpTask.cosRequest = cosRequest;
//httpTask.cosResult = cosResult;
//httpTask.isSchedue = true;
//httpTask.successCallback = successCallback;
//httpTask.failCallback = failCallback;
InternalSchedue(cosRequest, cosResult, successCallback, failCallback, credentialProvider);
}
///
/// excute request
///
///
///
///
/// CosClientException
/// CosServerException
public void InternalExcute(CosRequest cosRequest, CosResult cosResult, QCloudCredentialProvider credentialProvider, int retryIndex = 0)
{
try
{
Request request = CreateRequest(cosRequest, credentialProvider);
//extern informations exchange
cosResult.ExternInfo(cosRequest);
Response response;
if (cosRequest is GetObjectRequest)
{
GetObjectRequest getObjectRequest = cosRequest as GetObjectRequest;
response = new CosResponse(cosResult, getObjectRequest.GetSaveFilePath(), getObjectRequest.GetLocalFileOffset(),
getObjectRequest.GetCosProgressCallback());
}
else if (cosRequest is GetSnapshotRequest)
{
GetSnapshotRequest getSnapshotRequest = cosRequest as GetSnapshotRequest;
response = new CosResponse(cosResult, getSnapshotRequest.GetSaveFilePath(), 0, null);
}
else
{
response = new CosResponse(cosResult, null, -1L, null);
}
cosRequest.BindRequest(request);
CommandTask.Excute(request, response, config);
}
catch (CosServerException serverException)
{
// 服务端5xx才重试
if (serverException.statusCode >= 500 && retryIndex < MaxRetry)
{
InternalExcute(cosRequest, cosResult, credentialProvider, retryIndex + 1);
}
else
{
throw;
}
}
catch (CosClientException)
{
// 客户端异常都重试
if (retryIndex < MaxRetry)
{
InternalExcute(cosRequest, cosResult, credentialProvider, retryIndex + 1);
}
else
{
throw;
}
}
catch (Exception ex)
{
// 未知异常也重试
if (retryIndex < MaxRetry)
{
InternalExcute(cosRequest, cosResult, credentialProvider, retryIndex + 1);
}
else
{
throw new CosClientException((int)CosClientError.BadRequest, ex.Message, ex);
}
}
}
// public void Execute(Request request, Response response)
// {
// try
// {
// CommandTask.Excute(request, response, config);
// }
// catch (CosServerException)
// {
// throw;
// }
// catch (CosClientException)
// {
// throw;
// }
// catch (Exception ex)
// {
// throw new CosClientException((int)CosClientError.BadRequest, ex.Message, ex);
// }
// }
public void InternalSchedue(CosRequest cosRequest, CosResult cosResult, COSXML.Callback.OnSuccessCallback successCallback, COSXML.Callback.OnFailedCallback failCallback, QCloudCredentialProvider credentialProvider)
{
try
{
Request request = CreateRequest(cosRequest, credentialProvider);
cosResult.ExternInfo(cosRequest);
Response response;
if (cosRequest is GetObjectRequest)
{
GetObjectRequest getObjectRequest = cosRequest as GetObjectRequest;
response = new CosResponse(cosResult, getObjectRequest.GetSaveFilePath(), getObjectRequest.GetLocalFileOffset(),
getObjectRequest.GetCosProgressCallback(), successCallback, failCallback);
}
else
{
response = new CosResponse(cosResult, null, -1L, null, successCallback, failCallback);
}
cosRequest.BindRequest(request);
CommandTask.Schedue(request, response, config);
}
catch (CosServerException serverException)
{
//throw serverException;
failCallback(null, serverException);
}
catch (CosClientException clientException)
{
//throw clientException;
failCallback(clientException, null);
}
catch (Exception ex)
{
//throw new CosClientException((int)CosClientError.BAD_REQUEST, ex.Message, ex);
failCallback(new CosClientException((int)CosClientError.BadRequest, ex.Message, ex), null);
}
}
private Request CreateRequest(CosRequest cosRequest, QCloudCredentialProvider credentialProvider)
{
cosRequest.CheckParameters();
string requestUrlWithSign = cosRequest.RequestURLWithSign;
Request request = new Request();
request.Method = cosRequest.Method;
if (requestUrlWithSign != null)
{
if (requestUrlWithSign.StartsWith("https"))
{
request.IsHttps = true;
}
else
{
request.IsHttps = false;
}
request.RequestUrlString = requestUrlWithSign;
}
else
{
request.IsHttps = (bool)cosRequest.IsHttps;
request.Url = CreateUrl(cosRequest);
request.Host = cosRequest.GetHost();
}
request.UserAgent = config.UserAgnet;
Dictionary headers = cosRequest.GetRequestHeaders();
if (headers != null)
{
foreach (KeyValuePair pair in headers)
{
request.AddHeader(pair.Key, pair.Value);
}
}
request.Body = cosRequest.GetRequestBody();
// cacluate md5
if (CheckNeedMd5(request, cosRequest.IsNeedMD5) && request.Body != null)
{
request.AddHeader(CosRequestHeaderKey.CONTENT_MD5, request.Body.GetMD5());
}
// content type header
if (request.Body != null && request.Body.ContentType != null &&
!request.Headers.ContainsKey(CosRequestHeaderKey.CONTENT_TYPE))
{
request.AddHeader(CosRequestHeaderKey.CONTENT_TYPE, request.Body.ContentType);
}
//cacluate sign, and add it.
if (requestUrlWithSign == null)
{
CheckSign(cosRequest.GetSignSourceProvider(), request, credentialProvider);
}
return request;
}
private HttpUrl CreateUrl(CosRequest cosRequest)
{
HttpUrl httpUrl = new HttpUrl();
httpUrl.Scheme = (bool)cosRequest.IsHttps ? "https" : "http";
httpUrl.Host = cosRequest.GetHost();
httpUrl.Path = URLEncodeUtils.EncodePathOfURL(cosRequest.RequestPath);
httpUrl.SetQueryParameters(cosRequest.GetRequestParamters());
return httpUrl;
}
///
/// add authorization
///
/// QCloudSignSource
///
///
private void CheckSign(IQCloudSignSource qcloudSignSource, Request request, QCloudCredentialProvider credentialProvider)
{
// has authorizaiton, notice: using request.Headers, otherwise, error
if (request.Headers.ContainsKey(CosRequestHeaderKey.AUTHORIZAIION))
{
QLog.Debug(TAG, "has add authorizaiton in headers");
return;
}
//has no authorization, but signSourceProvider == null
if (qcloudSignSource == null)
{
QLog.Debug(TAG, "signSourceProvider == null");
return;
}
if (credentialProvider == null)
{
throw new ArgumentNullException("credentialsProvider == null");
}
CosXmlSigner signer = new CosXmlSigner();
signer.Sign(request, qcloudSignSource, credentialProvider.GetQCloudCredentialsCompat(request));
}
private bool CheckNeedMd5(Request request, bool isNeedMd5)
{
bool result = isNeedMd5;
if (request.Headers.ContainsKey(CosRequestHeaderKey.CONTENT_MD5))
{
result = false;
}
return result;
}
///
/// cos response
/// 分为两类:
/// 一类下载文件
/// 一类直接读取数据
///
private class CosResponse : Response
{
private CosResult cosResult;
private COSXML.Callback.OnSuccessCallback successCallback;
private COSXML.Callback.OnFailedCallback faileCallback;
private const int MAX_BUFFER_SIZE = 4096;
public CosResponse(CosResult cosResult, string saveFilePath, long saveFileOffset, COSXML.Callback.OnProgressCallback downloadProgressCallback)
{
this.cosResult = cosResult;
if (saveFilePath != null)
{
this.Body = new ResponseBody(saveFilePath, saveFileOffset);
this.Body.ProgressCallback = downloadProgressCallback;
}
else
{
this.Body = new ResponseBody();
}
}
public CosResponse(CosResult cosResult, string saveFilePath, long saveFileOffset, COSXML.Callback.OnProgressCallback downloadProgressCallback,
COSXML.Callback.OnSuccessCallback successCallback,
COSXML.Callback.OnFailedCallback failCallback) : this(cosResult, saveFilePath, saveFileOffset, downloadProgressCallback)
{
this.successCallback = successCallback;
this.faileCallback = failCallback;
}
///
/// response has been obtain, and parse headers from response
///
public override void HandleResponseHeader()
{
cosResult.httpCode = Code;
cosResult.httpMessage = Message;
cosResult.responseHeaders = Headers;
cosResult.InternalParseResponseHeaders();
if (Code >= 300)
{
this.Body.ParseStream = PaserServerError;
}
else
{
this.Body.ParseStream = cosResult.ParseResponseBody;
}
}
public void PaserServerError(Stream inputStream, string contentType, long contentLength)
{
CosServerException cosServerException = new CosServerException(cosResult.httpCode, cosResult.httpMessage);
List values;
Headers.TryGetValue("x-cos-request-id", out values);
cosServerException.requestId = (values != null && values.Count > 0) ? values[0] : null;
Headers.TryGetValue("x-cos-trace-id", out values);
cosServerException.traceId = (values != null && values.Count > 0) ? values[0] : null;
if (inputStream != null)
{
try
{
CosServerError cosServerError = XmlParse.Deserialize(inputStream);
cosServerException.SetCosServerError(cosServerError);
}
catch (Exception ex)
{
QLog.Debug(TAG, ex.Message);
}
}
throw cosServerException;
}
///
/// error
///
///
public override void OnFinish(bool isSuccess, Exception ex)
{
cosResult.RawContentBodyString = Body.rawContentBodyString;
if (isSuccess && successCallback != null)
{
successCallback(cosResult);
}
else
if (faileCallback != null)
{
if (ex is CosClientException)
{
faileCallback(ex as CosClientException, null);
}
else
if (ex is CosServerException)
{
faileCallback(null, ex as CosServerException);
}
else
{
faileCallback(new CosClientException((int)CosClientError.InternalError, ex.Message, ex), null);
}
}
}
}
}
}