Darabonba/Core.cs (400 lines of code) (raw):
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Darabonba.RetryPolicy;
using Darabonba.Exceptions;
using Darabonba.Utils;
namespace Darabonba
{
public class Core
{
private static readonly int bufferLength = 1024;
private static readonly int DefaultMaxAttempts = 3;
private static List<string> bodyMethod = new List<string> { "POST", "PUT", "PATCH" };
private static readonly TimeSpan DefaultMinDelay = TimeSpan.FromMilliseconds(100);
private static readonly TimeSpan DefaultMaxDelay = TimeSpan.FromSeconds(120);
internal static string ComposeUrl(Request request)
{
var urlBuilder = new StringBuilder("");
urlBuilder.Append(ConverterUtils.StrToLower(request.Protocol)).Append("://");
urlBuilder.Append(DictUtils.GetDicValue(request.Headers, "host"));
if (request.Port > 0)
{
urlBuilder.Append(":");
urlBuilder.Append(request.Port);
}
urlBuilder.Append(request.Pathname);
if (request.Query != null && request.Query.Count > 0)
{
if (urlBuilder.ToString().Contains("?"))
{
if (!urlBuilder.ToString().EndsWith("&"))
{
urlBuilder.Append("&");
}
}
else
{
urlBuilder.Append("?");
}
foreach (var entry in request.Query)
{
var key = entry.Key;
var val = entry.Value;
if (val == null)
{
continue;
}
urlBuilder.Append(PercentEncode(key));
urlBuilder.Append("=");
urlBuilder.Append(PercentEncode(val));
urlBuilder.Append("&");
}
}
return urlBuilder.ToString().TrimEnd('?').TrimEnd('&');
}
public static Response DoAction(Request request)
{
return DoAction(request, new Dictionary<string, object>());
}
public static Response DoAction(Request request, Dictionary<string, object> runtimeOptions)
{
int timeout;
var url = ComposeUrl(request);
Uri uri = new Uri(url);
HttpRequestMessage req = GetRequestMessage(request, runtimeOptions, out timeout);
try
{
HttpClient httpClient = HttpClientUtils.GetOrAddHttpClient(request.Protocol, uri.Host, uri.Port, runtimeOptions);
HttpResponseMessage response = httpClient.SendAsync(req, new CancellationTokenSource(timeout).Token).Result;
return new Response(response);
}
catch (TaskCanceledException)
{
throw new WebException("operation is timeout");
}
}
public static async Task<Response> DoActionAsync(Request request)
{
return await DoActionAsync(request, new Dictionary<string, object>());
}
public static async Task<Response> DoActionAsync(Request request, Dictionary<string, object> runtimeOptions)
{
int timeout;
var url = ComposeUrl(request);
Uri uri = new Uri(url);
HttpRequestMessage req = GetRequestMessage(request, runtimeOptions, out timeout);
try
{
HttpClient httpClient = HttpClientUtils.GetOrAddHttpClient(request.Protocol, uri.Host, uri.Port, runtimeOptions);
HttpResponseMessage response = await httpClient.SendAsync(req, new CancellationTokenSource(timeout).Token);
return new Response(response);
}
catch (TaskCanceledException)
{
throw new WebException("operation is timeout");
}
}
internal static string GetResponseBody(Response response)
{
using (var ms = new MemoryStream())
{
var buffer = new byte[bufferLength];
var stream = response.Body;
while (true)
{
var length = stream.Read(buffer, 0, bufferLength);
if (length == 0)
{
break;
}
ms.Write(buffer, 0, length);
}
ms.Seek(0, SeekOrigin.Begin);
var bytes = new byte[ms.Length];
ms.Read(bytes, 0, bytes.Length);
stream.Close();
stream.Dispose();
return Encoding.UTF8.GetString(bytes);
}
}
internal static Dictionary<string, string> ConvertHeaders(WebHeaderCollection headers)
{
var result = new Dictionary<string, string>();
for (int i = 0; i < headers.Count; i++)
{
result.Add(ConverterUtils.StrToLower(headers.GetKey(i)), headers.Get(i));
}
return result;
}
internal static Dictionary<string, string> ConvertHeaders(HttpResponseHeaders headers)
{
var result = new Dictionary<string, string>();
var enumerator = headers.GetEnumerator();
foreach (var item in headers)
{
result.Add(ConverterUtils.StrToLower(item.Key), item.Value.First());
}
return result;
}
internal static bool AllowRetry(IDictionary dict, int retryTimes, long now)
{
if (retryTimes == 0)
{
return true;
}
if (!dict.Get("retryable").ToSafeBool(false))
{
return false;
}
int retry;
if (dict == null)
{
return false;
}
Dictionary<string, object> dictObj = dict.Keys.Cast<string>().ToDictionary(key => key, key => dict[key]);
if (!dictObj.ContainsKey("maxAttempts"))
{
return false;
}
else
{
retry = dictObj["maxAttempts"] == null ? 0 : Convert.ToInt32(dictObj["maxAttempts"]);
}
return retry >= retryTimes;
}
public static bool ShouldRetry(RetryOptions options, RetryPolicyContext ctx)
{
if (ctx.RetriesAttempted == 0)
{
return true;
}
if (options == null || options.Retryable == null || options.Retryable == false)
{
return false;
}
if (ctx.Exception is DaraException)
{
var daraException = (DaraException)ctx.Exception;
List<RetryCondition> noRetryConditions = options.NoRetryCondition;
List<RetryCondition> retryConditions = options.RetryCondition;
if (noRetryConditions != null)
{
foreach (var noRetryCondition in noRetryConditions)
{
if (DictUtils.Contains(noRetryCondition.Exception, daraException.Message) ||
DictUtils.Contains(noRetryCondition.ErrorCode, daraException.Code))
{
return false;
}
}
}
if (retryConditions != null)
{
foreach (var retryCondition in retryConditions)
{
if (!DictUtils.Contains(retryCondition.Exception, daraException.Message) &&
!DictUtils.Contains(retryCondition.ErrorCode, daraException.Code))
{
continue;
}
if (ctx.RetriesAttempted > retryCondition.MaxAttempts)
{
return false;
}
return true;
}
}
}
return false;
}
internal static int GetBackoffTime(IDictionary Idict, int retryTimes)
{
int backOffTime = 0;
Dictionary<string, object> dict = Idict.Keys.Cast<string>().ToDictionary(key => key, key => Idict[key]);
if (!dict.ContainsKey("policy") || dict["policy"] == null ||
string.IsNullOrWhiteSpace(dict["policy"].ToString()) || dict["policy"].ToString() == "no")
{
return backOffTime;
}
if (dict.ContainsKey("period") && dict["period"] != null)
{
int.TryParse(dict["period"].ToString(), out backOffTime);
if (backOffTime <= 0)
{
return retryTimes;
}
}
return backOffTime;
}
public static int GetBackoffDelay(RetryOptions options, RetryPolicyContext ctx)
{
if (ctx.RetriesAttempted == null || ctx.RetriesAttempted == 0)
{
return 0;
}
if (ctx.Exception is DaraException)
{
if (options != null)
{
var daraException = (DaraException)ctx.Exception;
var retryConditions = options.RetryCondition;
if (retryConditions != null)
{
foreach (var retryCondition in retryConditions)
{
if (!DictUtils.Contains(retryCondition.Exception, daraException.Message) && !DictUtils.Contains(retryCondition.ErrorCode, daraException.Code))
{
continue;
}
long maxDelay = retryCondition.MaxDelayTimeMillis ?? (long)DefaultMaxDelay.TotalMilliseconds;
if (daraException is DaraResponseException)
{
var responseException = (DaraResponseException)daraException;
if (responseException.RetryAfter != null)
{
return (int)Math.Min(responseException.RetryAfter.Value, maxDelay);
}
}
if (retryCondition.Backoff == null)
{
return (int)DefaultMinDelay.TotalMilliseconds;
}
var delayTimeMillis = retryCondition.Backoff.GetDelayTime(ctx);
long delayTime = delayTimeMillis != null ? (long)delayTimeMillis : (long)DefaultMinDelay.TotalMilliseconds;
return (int)Math.Min(delayTime, maxDelay);
}
}
}
}
return (int)DefaultMinDelay.TotalMilliseconds;
}
public static void Sleep(int backoffTime)
{
Thread.Sleep(backoffTime);
}
public static async Task SleepAsync(int backoffTime)
{
await Task.Delay(backoffTime);
}
internal static bool IsRetryable(Exception e)
{
return e is DaraRetryableException;
}
public static Dictionary<string, object> ToObject(IDictionary dictionary)
{
var result = new Dictionary<string, object>();
foreach (DictionaryEntry entry in dictionary)
{
result[entry.Key.ToString()] = entry.Value; // 将值存储为 object
}
return result;
}
internal static string PercentEncode(string value)
{
if (value == null)
{
return null;
}
var stringBuilder = new StringBuilder();
var text = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~";
var bytes = Encoding.UTF8.GetBytes(value);
foreach (char c in bytes)
{
if (text.IndexOf(c) >= 0)
{
stringBuilder.Append(c);
}
else
{
stringBuilder.Append("%").Append(string.Format(CultureInfo.InvariantCulture, "{0:X2}", (int)c));
}
}
return stringBuilder.ToString();
}
internal static HttpRequestMessage GetRequestMessage(Request request, Dictionary<string, object> runtimeOptions, out int timeout)
{
var url = ComposeUrl(request);
HttpRequestMessage req = new HttpRequestMessage();
req.Method = HttpUtils.GetHttpMethod(request.Method);
req.RequestUri = new Uri(url);
timeout = 100000;
int readTimeout = DictUtils.GetDicValue(runtimeOptions, "readTimeout").ToSafeInt(0);
int connectTimeout = DictUtils.GetDicValue(runtimeOptions, "connectTimeout").ToSafeInt(0);
if (readTimeout != 0 || connectTimeout != 0)
{
timeout = readTimeout + connectTimeout;
}
if (request.Body != null)
{
Stream requestStream = new MemoryStream();
request.Body.Position = 0;
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = request.Body.Read(buffer, 0, buffer.Length)) != 0)
{
requestStream.Write(buffer, 0, bytesRead);
}
requestStream.Position = 0;
StreamContent streamContent = new StreamContent(requestStream);
req.Content = streamContent;
}
foreach (var header in request.Headers)
{
req.Headers.TryAddWithoutValidation(header.Key, header.Value);
if (header.Key.ToLower().StartsWith("content-") && req.Content != null)
{
req.Content.Headers.TryAddWithoutValidation(header.Key, header.Value);
}
}
return req;
}
public static Exception ThrowException(RetryPolicyContext context)
{
if (context.Exception is IOException)
{
return new DaraUnRetryableException(context);
}
return context.Exception;
}
public static object GetDefaultValue(object obj, object defaultVal)
{
if (obj == null)
return defaultVal;
if (obj is string && obj.Equals(""))
return defaultVal;
if (obj is bool && !(bool)obj)
return defaultVal;
if (obj is byte && (byte)obj == 0)
return defaultVal;
if (obj is sbyte && (sbyte)obj == 0)
return defaultVal;
if (obj is short && (short)obj == 0)
return defaultVal;
if (obj is ushort && (ushort)obj == 0)
return defaultVal;
if (obj is int && (int)obj == 0)
return defaultVal;
if (obj is uint && (uint)obj == 0)
return defaultVal;
if (obj is long && (long)obj == 0)
return defaultVal;
if (obj is ulong && (ulong)obj == 0)
return defaultVal;
if (obj is float && (float)obj == 0)
return defaultVal;
if (obj is double && (double)obj == 0)
return defaultVal;
return obj;
}
}
}