You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

UnstableRestClient.cs 6.6 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. using Newtonsoft.Json;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Globalization;
  5. using System.IO;
  6. using System.Linq;
  7. using System.Net;
  8. using System.Net.Http;
  9. using System.Text;
  10. using System.Threading;
  11. using System.Threading.Tasks;
  12. namespace Discord.Net.Rest
  13. {
  14. internal sealed class UnstableRestClient : IRestClient, IDisposable
  15. {
  16. private const double FailureRate = 0.10; //10%
  17. private const int HR_SECURECHANNELFAILED = -2146233079;
  18. private readonly HttpClient _client;
  19. private readonly string _baseUrl;
  20. private readonly JsonSerializer _errorDeserializer;
  21. private readonly Random _rand;
  22. private CancellationToken _cancelToken;
  23. private bool _isDisposed;
  24. public DefaultRestClient(string baseUrl)
  25. {
  26. _baseUrl = baseUrl;
  27. _client = new HttpClient(new HttpClientHandler
  28. {
  29. AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,
  30. UseCookies = false,
  31. UseProxy = false
  32. });
  33. SetHeader("accept-encoding", "gzip, deflate");
  34. _cancelToken = CancellationToken.None;
  35. _errorDeserializer = new JsonSerializer();
  36. _rand = new Random();
  37. }
  38. private void Dispose(bool disposing)
  39. {
  40. if (!_isDisposed)
  41. {
  42. if (disposing)
  43. _client.Dispose();
  44. _isDisposed = true;
  45. }
  46. }
  47. public void Dispose()
  48. {
  49. Dispose(true);
  50. }
  51. public void SetHeader(string key, string value)
  52. {
  53. _client.DefaultRequestHeaders.Remove(key);
  54. if (value != null)
  55. _client.DefaultRequestHeaders.Add(key, value);
  56. }
  57. public void SetCancelToken(CancellationToken cancelToken)
  58. {
  59. _cancelToken = cancelToken;
  60. }
  61. public async Task<RestResponse> SendAsync(string method, string endpoint, CancellationToken cancelToken, bool headerOnly)
  62. {
  63. string uri = Path.Combine(_baseUrl, endpoint);
  64. using (var restRequest = new HttpRequestMessage(GetMethod(method), uri))
  65. return await SendInternalAsync(restRequest, cancelToken, headerOnly).ConfigureAwait(false);
  66. }
  67. public async Task<RestResponse> SendAsync(string method, string endpoint, string json, CancellationToken cancelToken, bool headerOnly)
  68. {
  69. string uri = Path.Combine(_baseUrl, endpoint);
  70. using (var restRequest = new HttpRequestMessage(GetMethod(method), uri))
  71. {
  72. restRequest.Content = new StringContent(json, Encoding.UTF8, "application/json");
  73. return await SendInternalAsync(restRequest, cancelToken, headerOnly).ConfigureAwait(false);
  74. }
  75. }
  76. public async Task<RestResponse> SendAsync(string method, string endpoint, IReadOnlyDictionary<string, object> multipartParams, CancellationToken cancelToken, bool headerOnly)
  77. {
  78. string uri = Path.Combine(_baseUrl, endpoint);
  79. using (var restRequest = new HttpRequestMessage(GetMethod(method), uri))
  80. {
  81. var content = new MultipartFormDataContent("Upload----" + DateTime.Now.ToString(CultureInfo.InvariantCulture));
  82. if (multipartParams != null)
  83. {
  84. foreach (var p in multipartParams)
  85. {
  86. switch (p.Value)
  87. {
  88. case string stringValue: { content.Add(new StringContent(stringValue), p.Key); continue; }
  89. case byte[] byteArrayValue: { content.Add(new ByteArrayContent(byteArrayValue), p.Key); continue; }
  90. case Stream streamValue: { content.Add(new StreamContent(streamValue), p.Key); continue; }
  91. case MultipartFile fileValue:
  92. {
  93. var stream = fileValue.Stream;
  94. if (!stream.CanSeek)
  95. {
  96. var memoryStream = new MemoryStream();
  97. await stream.CopyToAsync(memoryStream).ConfigureAwait(false);
  98. memoryStream.Position = 0;
  99. stream = memoryStream;
  100. }
  101. content.Add(new StreamContent(stream), p.Key, fileValue.Filename);
  102. continue;
  103. }
  104. default: throw new InvalidOperationException($"Unsupported param type \"{p.Value.GetType().Name}\"");
  105. }
  106. }
  107. }
  108. restRequest.Content = content;
  109. return await SendInternalAsync(restRequest, cancelToken, headerOnly).ConfigureAwait(false);
  110. }
  111. }
  112. private async Task<RestResponse> SendInternalAsync(HttpRequestMessage request, CancellationToken cancelToken, bool headerOnly)
  113. {
  114. if (!UnstableCheck())
  115. throw new TimeoutException();
  116. cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_cancelToken, cancelToken).Token;
  117. HttpResponseMessage response = await _client.SendAsync(request, cancelToken).ConfigureAwait(false);
  118. var headers = response.Headers.ToDictionary(x => x.Key, x => x.Value.FirstOrDefault(), StringComparer.OrdinalIgnoreCase);
  119. var stream = !headerOnly ? await response.Content.ReadAsStreamAsync().ConfigureAwait(false) : null;
  120. return new RestResponse(response.StatusCode, headers, stream);
  121. }
  122. private static readonly HttpMethod _patch = new HttpMethod("PATCH");
  123. private HttpMethod GetMethod(string method)
  124. {
  125. switch (method)
  126. {
  127. case "DELETE": return HttpMethod.Delete;
  128. case "GET": return HttpMethod.Get;
  129. case "PATCH": return _patch;
  130. case "POST": return HttpMethod.Post;
  131. case "PUT": return HttpMethod.Put;
  132. default: throw new ArgumentOutOfRangeException(nameof(method), $"Unknown HttpMethod: {method}");
  133. }
  134. }
  135. private bool UnstableCheck()
  136. {
  137. return _rand.NextDouble() > FailureRate;
  138. }
  139. }
  140. }