Files
Discord.Net/Discord.Net/Helpers/Http.cs

218 lines
7.8 KiB
C#

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.IO;
using System.IO.Compression;
using System.Net;
using System.Net.Cache;
using System.Text;
using System.Threading.Tasks;
namespace Discord.Helpers
{
internal class HttpOptions
{
public string UserAgent, Token;
public CookieContainer Cookies;
public HttpOptions(string userAgent = null)
{
UserAgent = userAgent ?? "DiscordAPI";
Cookies = new CookieContainer(1);
}
}
internal static class Http
{
private static readonly RequestCachePolicy _cachePolicy = new RequestCachePolicy(RequestCacheLevel.NoCacheNoStore);
#if DEBUG
private const bool _isDebug = true;
#else
private const bool _isDebug = false;
#endif
internal static Task<ResponseT> Get<ResponseT>(string path, object data, HttpOptions options)
where ResponseT : class
=> Send<ResponseT>("GET", path, data, options);
internal static Task<string> Get(string path, object data, HttpOptions options)
=> Send("GET", path, data, options);
internal static Task<ResponseT> Get<ResponseT>(string path, HttpOptions options)
where ResponseT : class
=> Send<ResponseT>("GET", path, null, options);
internal static Task<string> Get(string path, HttpOptions options)
=> Send("GET", path, null, options);
internal static Task<ResponseT> Post<ResponseT>(string path, object data, HttpOptions options)
where ResponseT : class
=> Send<ResponseT>("POST", path, data, options);
internal static Task<string> Post(string path, object data, HttpOptions options)
=> Send("POST", path, data, options);
internal static Task<ResponseT> Post<ResponseT>(string path, HttpOptions options)
where ResponseT : class
=> Send<ResponseT>("POST", path, null, options);
internal static Task<string> Post(string path, HttpOptions options)
=> Send("POST", path, null, options);
internal static Task<ResponseT> Put<ResponseT>(string path, object data, HttpOptions options)
where ResponseT : class
=> Send<ResponseT>("PUT", path, data, options);
internal static Task<string> Put(string path, object data, HttpOptions options)
=> Send("PUT", path, data, options);
internal static Task<ResponseT> Put<ResponseT>(string path, HttpOptions options)
where ResponseT : class
=> Send<ResponseT>("PUT", path, null, options);
internal static Task<string> Put(string path, HttpOptions options)
=> Send("PUT", path, null, options);
internal static Task<ResponseT> Patch<ResponseT>(string path, object data, HttpOptions options)
where ResponseT : class
=> Send<ResponseT>("PATCH", path, data, options);
internal static Task<string> Patch(string path, object data, HttpOptions options)
=> Send("PATCH", path, data, options);
internal static Task<ResponseT> Patch<ResponseT>(string path, HttpOptions options)
where ResponseT : class
=> Send<ResponseT>("PATCH", path, null, options);
internal static Task<string> Patch(string path, HttpOptions options)
=> Send("PATCH", path, null, options);
internal static Task<ResponseT> Delete<ResponseT>(string path, object data, HttpOptions options)
where ResponseT : class
=> Send<ResponseT>("DELETE", path, data, options);
internal static Task<string> Delete(string path, object data, HttpOptions options)
=> Send("DELETE", path, data, options);
internal static Task<ResponseT> Delete<ResponseT>(string path, HttpOptions options)
where ResponseT : class
=> Send<ResponseT>("DELETE", path, null, options);
internal static Task<string> Delete(string path, HttpOptions options)
=> Send("DELETE", path, null, options);
internal static async Task<ResponseT> Send<ResponseT>(string method, string path, object data, HttpOptions options)
where ResponseT : class
{
string requestJson = JsonConvert.SerializeObject(data);
string responseJson = await SendRequest("POST", path, requestJson, options, true);
var response = JsonConvert.DeserializeObject<ResponseT>(responseJson);
#if DEBUG
CheckResponse(responseJson, response);
#endif
return response;
}
internal static async Task<string> Send(string method, string path, object data, HttpOptions options)
{
string requestJson = JsonConvert.SerializeObject(data);
string responseJson = await SendRequest("POST", path, requestJson, options, _isDebug);
#if DEBUG
CheckEmptyResponse(responseJson);
#endif
return responseJson;
}
internal static async Task<ResponseT> Send<ResponseT>(string method, string path, HttpOptions options)
where ResponseT : class
{
string responseJson = await SendRequest("POST", path, null, options, true);
var response = JsonConvert.DeserializeObject<ResponseT>(responseJson);
#if DEBUG
CheckResponse(responseJson, response);
#endif
return response;
}
internal static async Task<string> Send(string method, string path, HttpOptions options)
{
string responseJson = await SendRequest("POST", path, null, options, _isDebug);
#if DEBUG
CheckEmptyResponse(responseJson);
#endif
return responseJson;
}
private static async Task<string> SendRequest(string method, string path, string data, HttpOptions options, bool hasResponse)
{
options = options ?? new HttpOptions();
//Create Request
HttpWebRequest request = WebRequest.CreateHttp(path);
request.Accept = "*/*";
request.Headers[HttpRequestHeader.AcceptLanguage] = "en-US;q=0.8";
request.Headers[HttpRequestHeader.AcceptEncoding] = "gzip, deflate";
request.CachePolicy = _cachePolicy;
request.CookieContainer = options.Cookies;
request.Method = method;
request.UserAgent = options.UserAgent;
//Add Payload
if (data != null)
{
byte[] buffer = Encoding.UTF8.GetBytes(data);
using (var payload = await request.GetRequestStreamAsync())
payload.Write(buffer, 0, buffer.Length);
request.ContentType = "application/json";
}
//Get Response
using (var response = (HttpWebResponse)(await request.GetResponseAsync()))
{
if (hasResponse)
{
using (var stream = response.GetResponseStream())
using (var reader = new BinaryReader(stream))
using (var largeBuffer = new MemoryStream())
{
//Read the response in small chunks and add them to a larger buffer.
//ContentLength isn't always provided, so this is safer.
int bytesRead = 0;
byte[] smallBuffer = new byte[4096];
while ((bytesRead = reader.Read(smallBuffer, 0, smallBuffer.Length)) > 0)
largeBuffer.Write(smallBuffer, 0, bytesRead);
//Do we need to decompress?
if (!string.IsNullOrEmpty(response.ContentEncoding))
{
largeBuffer.Position = 0;
using (var decoder = GetDecoder(response.ContentEncoding, largeBuffer))
using (var decodedStream = new MemoryStream())
{
decoder.CopyTo(decodedStream);
return Encoding.UTF8.GetString(decodedStream.GetBuffer(), 0, (int)decodedStream.Length);
}
}
else
return Encoding.UTF8.GetString(largeBuffer.GetBuffer(), 0, (int)largeBuffer.Length);
}
}
else
return null;
}
}
private static Stream GetDecoder(string contentEncoding, MemoryStream encodedStream)
{
switch (contentEncoding)
{
case "gzip":
return new GZipStream(encodedStream, CompressionMode.Decompress, true);
case "deflate":
return new DeflateStream(encodedStream, CompressionMode.Decompress, true);
default:
throw new ArgumentOutOfRangeException("Unknown encoding: " + contentEncoding);
}
}
#if DEBUG
private static void CheckResponse<T>(string json, T obj)
{
/*JToken token = JToken.Parse(json);
JToken token2 = JToken.FromObject(obj);
if (!JToken.DeepEquals(token, token2))
throw new Exception("API check failed: Objects do not match.");*/
}
private static void CheckEmptyResponse(string json)
{
if (!string.IsNullOrEmpty(json))
throw new Exception("API check failed: Response is not empty.");
}
#endif
}
}