414 lines
15 KiB
C#
414 lines
15 KiB
C#
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.Utils;
|
||
/**
|
||
* Copyright (c) 2018 Tencent Cloud. All rights reserved.
|
||
* 11/6/2018 8:52:29 PM
|
||
* bradyxiao
|
||
*/
|
||
namespace COSXML.Network
|
||
{
|
||
/// <summary>
|
||
/// input: CosRequest; output: CosResponse
|
||
/// </summary>
|
||
public sealed class HttpClient
|
||
{
|
||
private static string TAG = "HttpClient";
|
||
|
||
private static HttpClientConfig config;
|
||
private static QCloudCredentialProvider credentialsProvider;
|
||
private static HttpClient instance;
|
||
private static Object sync = new Object();
|
||
private static Object syncInstance = new Object();
|
||
private const int MAX_ACTIVIE_TASKS = 5;
|
||
private volatile int activieTasks = 0;
|
||
|
||
public static HttpClient GetInstance()
|
||
{
|
||
lock (syncInstance)
|
||
{
|
||
if (instance == null)
|
||
{
|
||
instance = new HttpClient();
|
||
}
|
||
}
|
||
return instance;
|
||
}
|
||
|
||
public static void Init(HttpClientConfig config, QCloudCredentialProvider credentialsProvider)
|
||
{
|
||
lock (sync)
|
||
{
|
||
if (config == null)
|
||
{
|
||
throw new CosClientException((int)CosClientError.INVALID_ARGUMENT, "HttpClientConfig = null");
|
||
}
|
||
HttpClient.config = config;
|
||
HttpClient.credentialsProvider = credentialsProvider;
|
||
// init grobal httpwebreqeust
|
||
CommandTask.Init(HttpClient.config);
|
||
}
|
||
|
||
}
|
||
|
||
private HttpClient()
|
||
{
|
||
if (config == null)
|
||
{
|
||
throw new CosClientException((int)CosClientError.INTERNA_LERROR, "need to call Init(HttpClientConfig, QCloudCredentialProvider) before");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// excute request
|
||
/// </summary>
|
||
/// <param name="cosRequest"></param>
|
||
/// <param name="cosResult"></param>
|
||
/// <exception cref="COSXML.CosException.CosClientException">CosClientException</exception>
|
||
/// <exception cref="COSXML.CosException.CosServerException">CosServerException</exception>
|
||
public void Excute(CosRequest cosRequest, CosResult cosResult)
|
||
{
|
||
//HttpTask httpTask = new HttpTask();
|
||
//httpTask.cosRequest = cosRequest;
|
||
//httpTask.cosResult = cosResult;
|
||
//httpTask.isSchedue = false;
|
||
InternalExcute(cosRequest, cosResult);
|
||
}
|
||
|
||
|
||
public void Schedue(CosRequest cosRequest, CosResult cosResult, COSXML.Callback.OnSuccessCallback<CosResult> successCallback,
|
||
COSXML.Callback.OnFailedCallback failCallback)
|
||
{
|
||
//HttpTask httpTask = new HttpTask();
|
||
//httpTask.cosRequest = cosRequest;
|
||
//httpTask.cosResult = cosResult;
|
||
//httpTask.isSchedue = true;
|
||
//httpTask.successCallback = successCallback;
|
||
//httpTask.failCallback = failCallback;
|
||
InternalSchedue(cosRequest, cosResult, successCallback, failCallback);
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// excute request
|
||
/// </summary>
|
||
/// <param name="cosRequest"></param>
|
||
/// <param name="cosResult"></param>
|
||
/// <exception cref="COSXML.CosException.CosClientException">CosClientException</exception>
|
||
/// <exception cref="COSXML.CosException.CosServerException">CosServerException</exception>
|
||
public void InternalExcute(CosRequest cosRequest, CosResult cosResult)
|
||
{
|
||
try
|
||
{
|
||
Request request = CreateRequest(cosRequest);
|
||
//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
|
||
{
|
||
response = new CosResponse(cosResult, null, -1L, null);
|
||
}
|
||
|
||
cosRequest.BindRequest(request);
|
||
CommandTask.Excute(request, response, config);
|
||
}
|
||
catch (CosServerException)
|
||
{
|
||
throw ;
|
||
}
|
||
catch (CosClientException)
|
||
{
|
||
throw ;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
throw new CosClientException((int)CosClientError.BAD_REQUEST, ex.Message, ex);
|
||
}
|
||
|
||
}
|
||
|
||
public void excute(Request request, Response response)
|
||
{
|
||
try
|
||
{
|
||
CommandTask.Excute(request, response, config);
|
||
}
|
||
catch (CosServerException)
|
||
{
|
||
throw ;
|
||
}
|
||
catch (CosClientException)
|
||
{
|
||
throw ;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
throw new CosClientException((int)CosClientError.BAD_REQUEST, ex.Message, ex);
|
||
}
|
||
}
|
||
|
||
public void InternalSchedue(CosRequest cosRequest, CosResult cosResult, COSXML.Callback.OnSuccessCallback<CosResult> successCallback,
|
||
COSXML.Callback.OnFailedCallback failCallback)
|
||
{
|
||
try
|
||
{
|
||
Request request = CreateRequest(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.BAD_REQUEST, ex.Message, ex), null);
|
||
}
|
||
}
|
||
|
||
private Request CreateRequest(CosRequest cosRequest)
|
||
{
|
||
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<string, string> headers = cosRequest.GetRequestHeaders();
|
||
if (headers != null)
|
||
{
|
||
foreach (KeyValuePair<string, string> 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)
|
||
{
|
||
cosRequest.GetSignSourceProvider().setCosHost(cosRequest.GetCOSHost());
|
||
CheckSign(cosRequest.GetSignSourceProvider(), request);
|
||
}
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// add authorization
|
||
/// </summary>
|
||
/// <param name="qcloudSignSource">QCloudSignSource</param>
|
||
/// <param name="request"></param>
|
||
private void CheckSign(QCloudSignSource qcloudSignSource, Request request)
|
||
{
|
||
// has authorizaiton, notice: using request.Headers, otherwise, error
|
||
if (request.Headers.ContainsKey(CosRequestHeaderKey.AUTHORIZAIION))
|
||
{
|
||
QLog.D(TAG, "has add authorizaiton in headers");
|
||
return;
|
||
}
|
||
|
||
//has no authorization, but signSourceProvider == null
|
||
if (qcloudSignSource == null)
|
||
{
|
||
QLog.D(TAG, "signSourceProvider == null");
|
||
return;
|
||
}
|
||
|
||
if (credentialsProvider == null) throw new ArgumentNullException("credentialsProvider == null");
|
||
|
||
CosXmlSigner signer = new CosXmlSigner();
|
||
signer.Sign(request, qcloudSignSource, credentialsProvider.GetQCloudCredentials());
|
||
}
|
||
|
||
private bool CheckNeedMd5(Request request, bool isNeedMd5)
|
||
{
|
||
bool result = isNeedMd5;
|
||
if (request.Headers.ContainsKey(CosRequestHeaderKey.CONTENT_MD5))
|
||
{
|
||
result = false;
|
||
}
|
||
return result;
|
||
}
|
||
|
||
/// <summary>
|
||
/// cos response
|
||
/// 分为两类:
|
||
/// 一类下载文件
|
||
/// 一类直接读取数据
|
||
/// </summary>
|
||
private class CosResponse : Response
|
||
{
|
||
private CosResult cosResult;
|
||
private COSXML.Callback.OnSuccessCallback<CosResult> 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<CosResult> successCallback,
|
||
COSXML.Callback.OnFailedCallback failCallback):this(cosResult, saveFilePath, saveFileOffset, downloadProgressCallback)
|
||
{
|
||
this.successCallback = successCallback;
|
||
this.faileCallback = failCallback;
|
||
}
|
||
|
||
/// <summary>
|
||
/// response has been obtain, and parse headers from response
|
||
/// </summary>
|
||
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<string> 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)
|
||
{
|
||
CosServerError cosServerError = new CosServerError();
|
||
try
|
||
{
|
||
XmlParse.ParseCosError(inputStream, cosServerError);
|
||
if (cosServerException.requestId != null) cosServerException.requestId = cosServerError.requestId;
|
||
if (cosServerException.traceId != null) cosServerException.traceId = cosServerError.traceId;
|
||
cosServerException.resource = cosServerError.resource;
|
||
cosServerException.errorCode = cosServerError.code;
|
||
cosServerException.errorMessage = cosServerError.message;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
QLog.D(TAG, ex.Message);
|
||
|
||
}
|
||
}
|
||
throw cosServerException;
|
||
}
|
||
|
||
/// <summary>
|
||
/// error
|
||
/// </summary>
|
||
/// <param name="ex"></param>
|
||
public override void OnFinish(bool isSuccess, Exception ex)
|
||
{
|
||
if (isSuccess) successCallback(cosResult);
|
||
else
|
||
{
|
||
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.INTERNA_LERROR, ex.Message, ex), null);
|
||
}
|
||
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
}
|