【C#編程最佳實踐 二十】如何發送帶有重試機制的Http請求

最近在做的一個功能是通過ESB調用http的client來發送Http請求,學習了相關的調用方式,如何讓請求帶有重試機制的發送呢?

HttpClient初始化

在整個調用過程中,我們使用到了委託方法的方式,在外層的委託里加入了重試機制以及線程的休眠機制。然後委託調用的方法又分爲POST和Get,同時我還使用了返回結果泛型類的方式來定義響應情況,包括成功還是失敗的響應狀態碼。

    /// <summary>
    /// 用於訪問Rest接口(訪問站點的請求)
    /// </summary>
    public class HttpClientHelper
    {
        #region 日誌及單例

        protected static readonly LogWrapper Logger = new LogWrapper();

        #endregion 日誌及單例

        #region 委託方式進行重試調用

        public static TResult ExecuteFunc<TResult>(Func<TResult> target, int retryCount = 5, int current = 1, int sleepMilliseconds = 0)
        {
            try
            {
                return target.Invoke();
            }
            catch (Exception)
            {
                //超過重試次數後拋出異常
                if (retryCount - current <= 0)
                {
                    throw;
                }
                if (sleepMilliseconds > 0)
                {
                    Thread.Sleep(sleepMilliseconds);
                }
            }
            //遞歸調用直至超出重試次數後拋出異常
            return ExecuteFunc(target, retryCount, current + 1, sleepMilliseconds);
        }

        #endregion 委託方式進行重試調用

        #region POST及GET請求方法

        /// <summary>
        /// 通過post請求數據
        /// </summary>
        /// <typeparam name="TResult"></typeparam>
        /// <typeparam name="TData"></typeparam>
        /// <param name="url"></param>
        /// <param name="data"></param>
        /// <returns></returns>
        public static ApiResult<TResult> Post<TResult, TData>(string url, TData data)
        {
            //獲取請求數據
            var value = data == null ? string.Empty : JsonConvert.SerializeObject(data);
            //封裝有關個別HTTP請求的所有HTTP特定的信息(上下文信息)
            var content = new StringContent(value, Encoding.UTF8);
            //設置請求頭的上下文類型
            content.Headers.ContentType = new MediaTypeHeaderValue("application/json");

            //發送Post異步請求信息
            using (var client = new HttpClient())
            {
                //發送異步請求
                var result = client.PostAsync(url, content).Result;
                //獲取請求返回的結果數據並將其序列化爲字符串
                var response = result.Content.ReadAsStringAsync().Result;
                if (result.StatusCode != System.Net.HttpStatusCode.OK)
                    throw new HttpRequestException($"調用接口:{url}報錯,StatusCode:{result.StatusCode},Msg:{response}");
                //將返回結果反序列化爲指定Model
                return JsonConvert.DeserializeObject<ApiResult<TResult>>(response);
            }
        }

        /// <summary>
        /// 通過Get方式請求數據
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="url"></param>
        /// <returns></returns>
        public static ApiResult<T> Get<T>(string url)
        {
            //發送Get異步請求信息
            using (var client = new HttpClient())
            {
                //發送異步請求
                var result = client.GetAsync(url).Result;
                //獲取請求返回的結果數據並將其序列化爲字符串
                var response = result.Content.ReadAsStringAsync().Result;
                if (result.StatusCode != System.Net.HttpStatusCode.OK)
                    throw new HttpRequestException($"調用接口:{url}報錯,StatusCode:{result.StatusCode},Msg:{response}");
                //將返回結果反序列化爲指定Model
                return JsonConvert.DeserializeObject<ApiResult<T>>(response);
            }
        }

        #endregion POST及GET請求方法
    }

    /// <summary>
    /// 返回結果類
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class ApiResult<T>
    {
        private static readonly string SUCCESS = "200";
        private static readonly string FAIL = "500";
        public string Code { get; set; }
        public string Message { get; set; }
        public T Data { get; set; }
        public int Total { get; set; }

        /// <summary>
        /// 訪問成功
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        public static ApiResult<T> Success(T data)
        {
            return new ApiResult<T>()
            {
                Data = data,
                Code = SUCCESS
            };
        }

        /// <summary>
        /// 訪問失敗
        /// </summary>
        /// <param name="message"></param>
        /// <returns></returns>
        public static ApiResult<T> Fail(string message)
        {
            return new ApiResult<T>()
            {
                Code = FAIL,
                Message = message
            };
        }

接口調用

在定義好了HttpClient之後,我們就可以通過接口調用的方式來啓動對站點的訪問了,這部分因爲我們的環境不同,所以域名需要不同,所以域名就需要通過配置來讀取:

 //封裝url請求Model,用於填充到POST請求的body裏
var urlModel = new UrlModel()
 {
    tenantId = tenantId,
    appName = appName,
    api_key = ApiKey
  };      
var url = $"{HOST}/tmlapi/tmlRequest?&tenantId={tenantId}&appName={appName}&api_key={ApiKey}";
//開啓http客戶端發送Post請求
var result = HttpClientHelper.ExecuteFunc<ApiResult<string>>(() => HttpClientHelper.Post<string, UrlModel>(url, urlModel));          

在這個過程中我們設計好body裏要放置的POST請求所需參數並將其放置到一個model裏,然後依據配置讀取域名並拼接好url,把應該放置到query裏的參數拼接到url上,連同model一起傳入HttpClient方法中。需要特別注意的是如果是POST請求,那麼定義在body裏的請求參數要放到一個model裏傳進去,而定義爲query的請求參數必須拼接到url上傳入,如果定義爲query,再放到body裏來是會導致缺少參數請求不通的。
在這裏插入圖片描述
總而言之,需要調用HttpClient的時候,一定要封裝一層方法,然後做重試機制的處理,畢竟是訪問站點。還要注意參數的傳遞形式!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章