Saxonite 1.2.11.4. BusinessMgr.
This commit is contained in:
@@ -1,23 +1,31 @@
|
||||
using Sarand.Net.Standard.Base.App;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using Sarand.Net.Standard.Base.App;
|
||||
using Sarand.Net.Standard.Base.Core;
|
||||
using Sarand.Net.Standard.Base.Helpers;
|
||||
using Sarand.Net.Standard.Base.Tasks;
|
||||
using Sarand.Net.Standard.Base.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Reflection.Metadata;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ZstdNet;
|
||||
|
||||
namespace Sarand.Net.Standard.Base.Net
|
||||
{
|
||||
//=======================================================================
|
||||
public class SacsHttpRequestData : SacsTaskData
|
||||
{
|
||||
public SacsHttpRequestData (CancellationToken? cancellationToken) : base(cancellationToken) { }
|
||||
public SacsHttpRequestData(CancellationToken? cancellationToken) : base(cancellationToken) { }
|
||||
/// <summary>URL to send request to.</summary>
|
||||
public String Url { get; set; }
|
||||
/// <summary>
|
||||
@@ -50,6 +58,21 @@ namespace Sarand.Net.Standard.Base.Net
|
||||
public bool ForcePost { get; set; }
|
||||
}
|
||||
|
||||
//=======================================================================
|
||||
public class SacsHttpCookieData
|
||||
{
|
||||
public String HeaderValue { get; set; }
|
||||
public String Name { get; set; }
|
||||
public String Value { get; set; }
|
||||
public DateTime Expires { get; set; }
|
||||
public long MaxAge { get; set; }
|
||||
public String Path { get; set; }
|
||||
public String Domain { get; set; }
|
||||
public bool Secure { get; set; }
|
||||
public bool HttpOnly { get; set; }
|
||||
public Dictionary<string, string> Properties { get; } = new Dictionary<string, string>();
|
||||
}
|
||||
|
||||
//=======================================================================
|
||||
public class SacsHttpResponseData
|
||||
{
|
||||
@@ -59,10 +82,14 @@ namespace Sarand.Net.Standard.Base.Net
|
||||
public Dictionary<String, IEnumerable<String>> ResponseHeaders { get; } = new Dictionary<String, IEnumerable<String>>();
|
||||
/// <summary>Response content headers.</summary>
|
||||
public Dictionary<String, IEnumerable<String>> ContentHeaders { get; } = new Dictionary<String, IEnumerable<String>>();
|
||||
/// <summary>Response cookies from 'Set-Cookie' header.</summary>
|
||||
public Dictionary<String, SacsHttpCookieData> Cookies { get; } = new Dictionary<string, SacsHttpCookieData>();
|
||||
/// <summary>Indicates whether Status Code represents success.</summary>
|
||||
public bool Success { get; set; }
|
||||
/// <summary>HTTP Status Code.</summary>
|
||||
public HttpStatusCode StatusCode { get; set; }
|
||||
/// <summary>HTTP message version. Default: 1.1</summary>
|
||||
public Version Version { get; set; }
|
||||
/// <summary>HTTP Reason Code.</summary>
|
||||
public String ReasonPhrase { get; set; } = "";
|
||||
/// <summary>Response data.</summary>
|
||||
@@ -80,26 +107,28 @@ namespace Sarand.Net.Standard.Base.Net
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
// Data members.
|
||||
private static readonly HttpClient client;
|
||||
private static readonly HttpClient httpClient;
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Static initialisation.
|
||||
static SacsHttpClient ()
|
||||
static SacsHttpClient()
|
||||
{
|
||||
// Configure client.
|
||||
client = new HttpClient();
|
||||
client.Timeout = new TimeSpan(0, 0, RequestTimeoutSecs_Default);
|
||||
httpClient = new HttpClient();
|
||||
httpClient.Timeout = new TimeSpan(0, 0, RequestTimeoutSecs_Default);
|
||||
ProductInfoHeaderValue productInfoHeaderValue = new ProductInfoHeaderValue("Saratoga", "1.2.3");
|
||||
httpClient.DefaultRequestHeaders.UserAgent.Add(productInfoHeaderValue);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Properties.
|
||||
public static HttpClient HttpClient { get { return httpClient; } }
|
||||
public static int RequestTimeoutSecs_Default { get; } = 5;
|
||||
public static String HeaderName_ContentType { get; } = "Content-Type";
|
||||
public static String HeaderName_ContentType { get; } = HeaderNames.ContentType;
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
public static async Task<SacsHttpResponseData> DoRequest (SacsHttpRequestData requestData)
|
||||
public static async Task<SacsHttpResponseData> DoRequest(SacsHttpRequestData requestData)
|
||||
{
|
||||
String fn = "SacsHttpClient.DoRequest";
|
||||
SacsHttpResponseData responseData = new SacsHttpResponseData();
|
||||
requestData.ThrowIfCancellationRequested();
|
||||
|
||||
@@ -109,13 +138,13 @@ namespace Sarand.Net.Standard.Base.Net
|
||||
{
|
||||
postContent = new FormUrlEncodedContent(requestData.FormData);
|
||||
if (!requestData.ContentHeaders.ContainsKey(HeaderName_ContentType))
|
||||
requestData.ContentHeaders.Add(HeaderName_ContentType,"application/x-www-form-urlencoded; charset=utf-8");
|
||||
requestData.ContentHeaders.Add(HeaderName_ContentType, "application/x-www-form-urlencoded; charset=utf-8");
|
||||
}
|
||||
else if (!StringUtil.IsEmpty(requestData.PostData) || requestData.ForcePost)
|
||||
{
|
||||
postContent = new StringContent(StringUtil.EmptyOnNull(requestData.PostData), Encoding.UTF8);
|
||||
if (!requestData.ContentHeaders.ContainsKey(HeaderName_ContentType))
|
||||
requestData.ContentHeaders.Add(HeaderName_ContentType,"text/plain; charset=utf-8");
|
||||
requestData.ContentHeaders.Add(HeaderName_ContentType, "text/plain; charset=utf-8");
|
||||
}
|
||||
requestData.ThrowIfCancellationRequested();
|
||||
|
||||
@@ -137,7 +166,7 @@ namespace Sarand.Net.Standard.Base.Net
|
||||
}
|
||||
|
||||
// Default request headers.
|
||||
if (client.DefaultRequestHeaders.Accept.Count == 0 && request.Headers.Accept.Count == 0)
|
||||
if (httpClient.DefaultRequestHeaders.Accept.Count == 0 && request.Headers.Accept.Count == 0)
|
||||
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("text/*"));
|
||||
|
||||
// Configure request.
|
||||
@@ -157,7 +186,7 @@ namespace Sarand.Net.Standard.Base.Net
|
||||
}
|
||||
// Send request.
|
||||
responseData.RequestStarted = DateTime.Now;
|
||||
response = await client.SendAsync(request, requestData.CancellationToken);
|
||||
response = await httpClient.SendAsync(request, requestData.CancellationToken);
|
||||
responseData.RequestCompleted = DateTime.Now;
|
||||
}
|
||||
else
|
||||
@@ -165,25 +194,61 @@ namespace Sarand.Net.Standard.Base.Net
|
||||
// GET.
|
||||
request.Method = HttpMethod.Get;
|
||||
responseData.RequestStarted = DateTime.Now;
|
||||
response = await client.SendAsync(request, requestData.CancellationToken);
|
||||
response = await httpClient.SendAsync(request, requestData.CancellationToken);
|
||||
responseData.RequestCompleted = DateTime.Now;
|
||||
}
|
||||
|
||||
// Extract resopnse data.
|
||||
// Extract response data.
|
||||
responseData.Version = response.Version;
|
||||
responseData.Success = response.IsSuccessStatusCode;
|
||||
responseData.StatusCode = response.StatusCode;
|
||||
responseData.ReasonPhrase = response.ReasonPhrase;
|
||||
// Extract response headers.
|
||||
if (response.Headers != null)
|
||||
{
|
||||
foreach (var header in response.Headers)
|
||||
responseData.ResponseHeaders[header.Key] = new List<String>(header.Value);
|
||||
}
|
||||
// Extract response content headers.
|
||||
if (response.Content != null && response.Content.Headers != null)
|
||||
{
|
||||
foreach (var header in response.Content.Headers)
|
||||
responseData.ContentHeaders[header.Key] = new List<String>(header.Value);
|
||||
}
|
||||
responseData.Data = await response.Content.ReadAsStringAsync();
|
||||
// Extract response cookies.
|
||||
if (response.Headers.Contains(HeaderNames.SetCookie))
|
||||
{
|
||||
foreach (var cookieHeader in response.Headers.GetValues(HeaderNames.SetCookie))
|
||||
{
|
||||
SacsHttpCookieData cookie = ExtractCookieFromHeader(cookieHeader);
|
||||
if (cookie != null && !StringUtil.IsBlank(cookie.Name))
|
||||
responseData.Cookies.Add(cookie.Name, cookie);
|
||||
}
|
||||
}
|
||||
// Extract response payload.
|
||||
var contentEncodings = responseData.ContentHeaders.GetValueOrDefault(HeaderNames.ContentEncoding, new List<String>());
|
||||
using (var responseStream = await response.Content.ReadAsStreamAsync())
|
||||
{
|
||||
// Check if response needs to be decompressed, as directed by 'content-encoding' header.
|
||||
Stream decompressedStream = null;
|
||||
if (contentEncodings.ContainsCI("gzip"))
|
||||
decompressedStream = new GZipStream(responseStream, CompressionMode.Decompress);
|
||||
else if (contentEncodings.ContainsCI("deflate"))
|
||||
decompressedStream = new DeflateStream(responseStream, CompressionMode.Decompress);
|
||||
else if (contentEncodings.ContainsCI("br"))
|
||||
decompressedStream = new BrotliStream(responseStream, CompressionMode.Decompress);
|
||||
else if (contentEncodings.ContainsCI("zstd"))
|
||||
decompressedStream = new DecompressionStream(responseStream);
|
||||
|
||||
// Read the response payload, decompressed if necessary.
|
||||
using (var streamReader = new StreamReader(decompressedStream != null ? decompressedStream : responseStream))
|
||||
responseData.Data = await streamReader.ReadToEndAsync();
|
||||
|
||||
// Clean-up.
|
||||
if (decompressedStream != null)
|
||||
decompressedStream.Dispose();
|
||||
}
|
||||
// Clean-up.
|
||||
if (response != null) response.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -198,7 +263,7 @@ namespace Sarand.Net.Standard.Base.Net
|
||||
exc = exc.InnerException;
|
||||
}
|
||||
String msg = exc.GetType().Name + ": " + exc.Message;
|
||||
SaLogger.Warn(fn + " " + msg + ExceptionUtil.GetStackEntries(exc, 6).Concat("", "\n\t"));
|
||||
SaLogger.Warn(SysUtil.Fn + " " + msg + ExceptionUtil.GetStackEntries(exc, 6).Concat("", "\n\t"));
|
||||
responseData.ErrorStateMessage = msg;
|
||||
}
|
||||
requestData.ThrowIfCancellationRequested();
|
||||
@@ -208,10 +273,57 @@ namespace Sarand.Net.Standard.Base.Net
|
||||
return responseData;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Cookies.
|
||||
//-------------------------------------------------------------------
|
||||
public static SacsHttpCookieData ExtractCookieFromHeader(String cookieHeader)
|
||||
{
|
||||
SacsHttpCookieData cookie = null;
|
||||
String[] cookieParts = cookieHeader.Split(';');
|
||||
if (cookieParts.Count() > 1)
|
||||
{
|
||||
cookie = new SacsHttpCookieData();
|
||||
cookie.HeaderValue = cookieHeader;
|
||||
// Name and value.
|
||||
String[] cookieNameValue = cookieParts[0].Trim().Split(new char[] { '=' }, 2);
|
||||
cookie.Name = cookieNameValue[0].Trim();
|
||||
cookie.Value = cookieNameValue.Count() > 1 ? cookieNameValue[1].Trim() : "";
|
||||
// Other properties.
|
||||
for (int i = 1; i < cookieParts.Length; i++)
|
||||
{
|
||||
string[] properties = cookieParts[i].Trim().Split(new char[] { '=' }, 2);
|
||||
String propertyName = properties[0].Trim();
|
||||
String propertyValue = properties.Count() > 1 ? properties[1].Trim() : "True";
|
||||
cookie.Properties.Add(propertyName, propertyValue);
|
||||
if (propertyName.EqualsCI("Domain"))
|
||||
cookie.Domain = propertyValue;
|
||||
else if (propertyName.EqualsCI("Path"))
|
||||
cookie.Path = propertyValue;
|
||||
else if (propertyName.EqualsCI("Expires"))
|
||||
{
|
||||
cookie.Expires = DateUtil.ParseExact(propertyValue, "ddd, d MMM yyyy HH:mm:ss GMT");
|
||||
if (!DateUtil.IsValid(cookie.Expires))
|
||||
{
|
||||
DateTime expires;
|
||||
DateTime.TryParse(propertyValue, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out expires);
|
||||
cookie.Expires = expires;
|
||||
}
|
||||
}
|
||||
else if (propertyName.EqualsCI("Max-Age"))
|
||||
cookie.MaxAge = StringUtil.ToLong(propertyValue);
|
||||
else if (propertyName.EqualsCI("Secure"))
|
||||
cookie.Secure = StringUtil.ToBool(propertyValue);
|
||||
else if (propertyName.EqualsCI("HttpOnly"))
|
||||
cookie.HttpOnly = StringUtil.ToBool(propertyValue);
|
||||
}
|
||||
}
|
||||
return cookie;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// R e s p o n s e S t a t u s D e s c r i p t i o n
|
||||
//-------------------------------------------------------------------
|
||||
public static String GetResponseStatusDescription (HttpStatusCode httpStatus)
|
||||
public static String GetResponseStatusDescription(HttpStatusCode httpStatus)
|
||||
{
|
||||
// Ref. https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
|
||||
switch (httpStatus)
|
||||
|
||||
Reference in New Issue
Block a user