Archived
1
0
Fork 0
This repository has been archived on 2024-05-21. You can view files and clone it, but cannot push or open issues or pull requests.
maki/Maki/Rest/WebRequest.cs

289 lines
10 KiB
C#

using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading;
namespace Maki.Rest
{
public class WebRequest : IDisposable
{
private const string USER_AGENT = @"DiscordBot (https://github.com/flashwave/maki, 1.0.0.0)";
private const string GENERIC_CONTENT_TYPE = @"application/octet-stream";
private const string JSON_CONTENT_TYPE = @"application/json";
private const string FORM_CONTENT_TYPE = @"multipart/form-data";
private const long BUFFER_SIZE = 8192000;
private static HttpClient HttpClient;
public readonly HttpMethod Method;
public readonly string Url;
public string UserAgent { get; set; } = USER_AGENT;
public string ContentType { get; set; } = GENERIC_CONTENT_TYPE;
public long ContentLength => HttpWebResponse.ContentLength < 1 ? BUFFER_SIZE : HttpWebResponse.ContentLength;
// TODO: make this not static
internal static string Authorisation { get; set; }
private readonly Dictionary<string, string> Headers = new Dictionary<string, string>();
private readonly Dictionary<string, string> Parameters = new Dictionary<string, string>();
private readonly Dictionary<string, byte[]> Files = new Dictionary<string, byte[]>();
private readonly Dictionary<string, string> MimeTypes = new Dictionary<string, string>()
{
{ "png", "image/png" },
{ "jpg", "image/jpeg" },
{ "jpeg", "image/jpeg" },
{ "gif", "image/gif" },
};
private byte[] RawRequestBody = new byte[0];
private HttpWebRequest HttpWebRequest;
private Stream RequestStream;
private HttpWebResponse HttpWebResponse;
private Stream ResponseStream;
private byte[] RawResponseValue;
public byte[] RawResponse
{
get
{
if (RawResponseValue == null)
using (MemoryStream ms = new MemoryStream())
{
byte[] bytes = new byte[4096];
int read = 0;
while ((read = ResponseStream.Read(bytes, 0, bytes.Length)) > 0)
ms.Write(bytes, 0, read);
ms.Seek(0, SeekOrigin.Begin);
RawResponseValue = new byte[ms.Length];
ms.Read(RawResponseValue, 0, RawResponseValue.Length);
}
return RawResponseValue;
}
}
private string ResponseString = string.Empty;
public string Response
{
get
{
if (string.IsNullOrEmpty(ResponseString))
ResponseString = Encoding.UTF8.GetString(RawResponse);
return ResponseString;
}
}
public T ResponseJson<T>() =>
JsonConvert.DeserializeObject<T>(Response);
public short Status =>
(short)HttpWebResponse?.StatusCode;
static WebRequest()
{
CreateHttpClientInstance();
}
private static void CreateHttpClientInstance()
{
HttpClient?.Dispose();
HttpClient = new HttpClient(new HttpClientHandler
{
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
});
HttpClient.DefaultRequestHeaders.UserAgent.ParseAdd(USER_AGENT);
HttpClient.DefaultRequestHeaders.ExpectContinue = true;
HttpClient.Timeout = new TimeSpan(0, 0, 0, 0, Timeout.Infinite);
}
public WebRequest(HttpMethod method, string url)
{
Method = method;
Url = url;
}
public void AddRaw(byte[] bytes) =>
RawRequestBody = bytes;
public void AddRaw(string str) =>
AddRaw(Encoding.UTF8.GetBytes(str));
public void AddJson(object obj)
{
ContentType = JSON_CONTENT_TYPE;
AddRaw(JsonConvert.SerializeObject(obj));
}
public void AddParam(string name, string contents) =>
Parameters.Add(name, contents);
public void AddFile(string name, byte[] bytes) =>
Files.Add(name, bytes);
public void Perform()
{
StringBuilder urlBuilder = new StringBuilder();
if (!Url.StartsWith("http://") && !Url.StartsWith("https://"))
{
urlBuilder.Append(RestEndpoints.BASE_URL);
urlBuilder.Append(RestEndpoints.BASE_PATH);
}
urlBuilder.Append(Url);
if (Method == HttpMethod.GET
|| Method == HttpMethod.DELETE)
if (Parameters.Count > 1)
{
if (!Url.Contains('?'))
urlBuilder.Append(@"?");
foreach (KeyValuePair<string, string> param in Parameters)
urlBuilder.Append($@"{param.Key}={param.Value}&");
}
string url = urlBuilder.ToString().TrimEnd('&');
HttpWebRequest = System.Net.WebRequest.Create(url) as HttpWebRequest;
HttpWebRequest.Method = Method.ToString();
HttpWebRequest.UserAgent = UserAgent;
HttpWebRequest.KeepAlive = true;
HttpWebRequest.ReadWriteTimeout = Timeout.Infinite;
HttpWebRequest.Timeout = Timeout.Infinite;
if (!string.IsNullOrEmpty(Authorisation) && url.StartsWith(RestEndpoints.BASE_URL + RestEndpoints.BASE_PATH))
HttpWebRequest.Headers[HttpRequestHeader.Authorization] = Authorisation;
foreach (KeyValuePair<string, string> header in Headers)
HttpWebRequest.Headers[header.Key] = header.Value;
if (Method == HttpMethod.POST
|| Method == HttpMethod.PUT
|| Method == HttpMethod.PATCH)
{
RequestStream = HttpWebRequest.GetRequestStream();
if (Parameters.Count + Files.Count < 1)
RequestStream.Write(RawRequestBody, 0, RawRequestBody.Length);
else
{
string boundary = $@"-----------------------------{DateTime.Now.Ticks}";
ContentType = $@"{FORM_CONTENT_TYPE}; boundary={boundary}";
if (Parameters.Count >= 1)
{
StringBuilder postBodyBuilder = new StringBuilder();
byte[] postBody = new byte[0];
foreach (KeyValuePair<string, string> param in Parameters)
{
postBodyBuilder.AppendLine($@"--{boundary}");
postBodyBuilder.AppendLine($@"Content-Disposition: form-data; name=""{param.Key}""");
postBodyBuilder.AppendLine();
postBodyBuilder.AppendLine(param.Value);
}
postBody = Encoding.UTF8.GetBytes(postBodyBuilder.ToString());
RequestStream.Write(postBody, 0, postBody.Length);
}
if (Files.Count >= 1)
{
byte[] boundaryBytes = Encoding.UTF8.GetBytes($@"--{boundary}");
byte[] newLineBytes = Encoding.UTF8.GetBytes("\r\n");
foreach (KeyValuePair<string, byte[]> file in Files)
{
string cType = GENERIC_CONTENT_TYPE;
string fileExt = Path.GetExtension(file.Key).ToLower().TrimStart('.');
if (MimeTypes.ContainsKey(fileExt))
cType = MimeTypes[fileExt];
byte[] cDisposBytes = Encoding.UTF8.GetBytes($@"Content-Disposition: form-data; name=""{file.Key}""; filename=""{file.Key}""");
byte[] cTypeBytes = Encoding.UTF8.GetBytes($@"Content-Type: {cType}");
// Boundary + newline
RequestStream.Write(boundaryBytes, 0, boundaryBytes.Length);
RequestStream.Write(newLineBytes, 0, newLineBytes.Length);
// Disposition header + newline
RequestStream.Write(cDisposBytes, 0, cDisposBytes.Length);
RequestStream.Write(newLineBytes, 0, newLineBytes.Length);
// Type header + newline
RequestStream.Write(cTypeBytes, 0, cTypeBytes.Length);
RequestStream.Write(newLineBytes, 0, newLineBytes.Length);
// newline + contents + newline
RequestStream.Write(newLineBytes, 0, newLineBytes.Length);
RequestStream.Write(file.Value, 0, file.Value.Length);
RequestStream.Write(newLineBytes, 0, newLineBytes.Length);
}
}
byte[] closingBound = Encoding.UTF8.GetBytes($@"--{boundary}--");
RequestStream.Write(closingBound, 0, closingBound.Length);
}
}
HttpWebRequest.ContentType = ContentType;
try
{
HttpWebResponse = HttpWebRequest.GetResponse() as HttpWebResponse;
} catch (WebException ex)
{
HttpWebResponse = ex.Response as HttpWebResponse;
}
ResponseStream = HttpWebResponse.GetResponseStream();
}
#region IDisposable
private bool IsDisposed = false;
/// <summary>
/// Disconnects and releases all unmanaged objects
/// </summary>
private void Dispose(bool disposing)
{
if (IsDisposed)
return;
IsDisposed = true;
RequestStream?.Dispose();
HttpWebRequest?.Abort();
ResponseStream?.Dispose();
HttpWebResponse?.Close();
if (disposing)
GC.SuppressFinalize(this);
}
~WebRequest()
=> Dispose(false);
public void Dispose()
=> Dispose(true);
#endregion
}
}