From b191f17ff051be03d45ccf7c887269c78c7daa36 Mon Sep 17 00:00:00 2001 From: Shawn LaMountain Date: Thu, 17 Feb 2022 14:15:02 -0500 Subject: [PATCH 1/2] Initial commit --- .gitattributes | 63 +++ .gitignore | 363 ++++++++++++++++++ README.md | 6 + ...hunderDesign.Net-PCL.HttpClientService.sln | 25 ++ .../DataObjects/ResponseContentData.cs | 25 ++ .../DataObjects/ResponseData.cs | 46 +++ .../CookieContainerChangedEventArgs.cs | 18 + .../CookieContainerChangedEventHandler.cs | 6 + .../Extentions/CookieContainerExtention.cs | 105 +++++ .../Http/HttpClientAutoRedirect.cs | 88 +++++ .../HttpClientService.cs | 51 +++ .../Interfaces/IHttpClientAutoRedirect.cs | 11 + .../Interfaces/IHttpClientService.cs | 11 + ...derDesign.Net-PCL.HttpClientService.csproj | 13 + 14 files changed, 831 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 README.md create mode 100644 src/ThunderDesign.Net-PCL.HttpClientService.sln create mode 100644 src/ThunderDesign.Net-PCL.HttpClientService/DataObjects/ResponseContentData.cs create mode 100644 src/ThunderDesign.Net-PCL.HttpClientService/DataObjects/ResponseData.cs create mode 100644 src/ThunderDesign.Net-PCL.HttpClientService/EventArgs/CookieContainerChangedEventArgs.cs create mode 100644 src/ThunderDesign.Net-PCL.HttpClientService/EventHandlers/CookieContainerChangedEventHandler.cs create mode 100644 src/ThunderDesign.Net-PCL.HttpClientService/Extentions/CookieContainerExtention.cs create mode 100644 src/ThunderDesign.Net-PCL.HttpClientService/Http/HttpClientAutoRedirect.cs create mode 100644 src/ThunderDesign.Net-PCL.HttpClientService/HttpClientService.cs create mode 100644 src/ThunderDesign.Net-PCL.HttpClientService/Interfaces/IHttpClientAutoRedirect.cs create mode 100644 src/ThunderDesign.Net-PCL.HttpClientService/Interfaces/IHttpClientService.cs create mode 100644 src/ThunderDesign.Net-PCL.HttpClientService/ThunderDesign.Net-PCL.HttpClientService.csproj diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1ff0c42 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9491a2f --- /dev/null +++ b/.gitignore @@ -0,0 +1,363 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Oo]ut/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..9652c07 --- /dev/null +++ b/README.md @@ -0,0 +1,6 @@ +# ThunderDesign.Net-PCL.HttpClientService +[![CI](https://github.com/ThunderDesign/ThunderDesign.Net-PCL.HttpClientService/actions/workflows/CI.yml/badge.svg)](https://github.com/ThunderDesign/ThunderDesign.Net-PCL.HttpClientService/actions/workflows/CI.yml) +[![CD](https://github.com/ThunderDesign/ThunderDesign.Net-PCL.HttpClientService/actions/workflows/CD.yml/badge.svg)](https://github.com/ThunderDesign/ThunderDesign.Net-PCL.HttpClientService/actions/workflows/CD.yml) +[![Nuget](https://img.shields.io/nuget/v/ThunderDesign.Net-PCL.HttpClientService)](https://www.nuget.org/packages/ThunderDesign.Net-PCL.HttpClientService) + +Enhances AutoRedirect and Cookies from the standard 'System.Net.Http.HttpClient' diff --git a/src/ThunderDesign.Net-PCL.HttpClientService.sln b/src/ThunderDesign.Net-PCL.HttpClientService.sln new file mode 100644 index 0000000..8540f80 --- /dev/null +++ b/src/ThunderDesign.Net-PCL.HttpClientService.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.1.32127.271 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ThunderDesign.Net-PCL.HttpClientService", "ThunderDesign.Net-PCL.HttpClientService\ThunderDesign.Net-PCL.HttpClientService.csproj", "{70AF5F93-1A88-4DDE-8073-E9B49F56C3E9}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {70AF5F93-1A88-4DDE-8073-E9B49F56C3E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {70AF5F93-1A88-4DDE-8073-E9B49F56C3E9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {70AF5F93-1A88-4DDE-8073-E9B49F56C3E9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {70AF5F93-1A88-4DDE-8073-E9B49F56C3E9}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {4660B02E-1A64-4DC4-89D0-2833EEFAA728} + EndGlobalSection +EndGlobal diff --git a/src/ThunderDesign.Net-PCL.HttpClientService/DataObjects/ResponseContentData.cs b/src/ThunderDesign.Net-PCL.HttpClientService/DataObjects/ResponseContentData.cs new file mode 100644 index 0000000..681a595 --- /dev/null +++ b/src/ThunderDesign.Net-PCL.HttpClientService/DataObjects/ResponseContentData.cs @@ -0,0 +1,25 @@ +using System.Net.Http; +using ThunderDesign.Net.Threading.Extentions; + +namespace ThunderDesign.Net.HttpClientService.DataObjects +{ + public class ResponseContentData : ResponseData + { + #region constructors + public ResponseContentData() : base() { } + public ResponseContentData(HttpResponseMessage httpResponseMessage) : base(httpResponseMessage) { } + #endregion + + #region properties + public string Content + { + get { return this.GetProperty(ref _Content, _Locker); } + set { this.SetProperty(ref _Content, value, _Locker); } + } + #endregion + + #region variables + private string _Content = ""; + #endregion + } +} diff --git a/src/ThunderDesign.Net-PCL.HttpClientService/DataObjects/ResponseData.cs b/src/ThunderDesign.Net-PCL.HttpClientService/DataObjects/ResponseData.cs new file mode 100644 index 0000000..5406866 --- /dev/null +++ b/src/ThunderDesign.Net-PCL.HttpClientService/DataObjects/ResponseData.cs @@ -0,0 +1,46 @@ +using System.Net; +using System.Net.Http; +using ThunderDesign.Net.Threading.Extentions; +using ThunderDesign.Net.Threading.Objects; + +namespace ThunderDesign.Net.HttpClientService.DataObjects +{ + public class ResponseData : ThreadObject + { + #region constructors + public ResponseData() : base() { } + public ResponseData(HttpResponseMessage httpResponseMessage) : base() + { + _IsSuccessStatusCode = httpResponseMessage.IsSuccessStatusCode; + _ReasonPhrase = httpResponseMessage.ReasonPhrase; + _StatusCode = httpResponseMessage.StatusCode; + } + #endregion + + #region properties + public bool IsSuccessStatusCode + { + get { return this.GetProperty(ref _IsSuccessStatusCode, _Locker); } + set { this.SetProperty(ref _IsSuccessStatusCode, value, _Locker); } + } + + public string ReasonPhrase + { + get { return this.GetProperty(ref _ReasonPhrase, _Locker); } + set { this.SetProperty(ref _ReasonPhrase, value, _Locker); } + } + + public HttpStatusCode StatusCode + { + get { return this.GetProperty(ref _StatusCode, _Locker); } + set { this.SetProperty(ref _StatusCode, value, _Locker); } + } + #endregion + + #region variables + private bool _IsSuccessStatusCode; + private string _ReasonPhrase = ""; + private HttpStatusCode _StatusCode; + #endregion + } +} diff --git a/src/ThunderDesign.Net-PCL.HttpClientService/EventArgs/CookieContainerChangedEventArgs.cs b/src/ThunderDesign.Net-PCL.HttpClientService/EventArgs/CookieContainerChangedEventArgs.cs new file mode 100644 index 0000000..a1e185f --- /dev/null +++ b/src/ThunderDesign.Net-PCL.HttpClientService/EventArgs/CookieContainerChangedEventArgs.cs @@ -0,0 +1,18 @@ +using System.Net; + +namespace ThunderDesign.Net.HttpClientService.EventArgs +{ + public class CookieContainerChangedEventArgs : System.EventArgs + { + #region constructor + public CookieContainerChangedEventArgs(CookieCollection cookieCollection) + { + CookieCollection = cookieCollection; + } + #endregion + + #region properties + public CookieCollection CookieCollection { get; private set; } + #endregion + } +} diff --git a/src/ThunderDesign.Net-PCL.HttpClientService/EventHandlers/CookieContainerChangedEventHandler.cs b/src/ThunderDesign.Net-PCL.HttpClientService/EventHandlers/CookieContainerChangedEventHandler.cs new file mode 100644 index 0000000..f0f6726 --- /dev/null +++ b/src/ThunderDesign.Net-PCL.HttpClientService/EventHandlers/CookieContainerChangedEventHandler.cs @@ -0,0 +1,6 @@ +using ThunderDesign.Net.HttpClientService.EventArgs; + +namespace ThunderDesign.Net.HttpClientService.EventHandlers +{ + public delegate void CookieContainerChangedEventHandler(object sender, CookieContainerChangedEventArgs e); +} diff --git a/src/ThunderDesign.Net-PCL.HttpClientService/Extentions/CookieContainerExtention.cs b/src/ThunderDesign.Net-PCL.HttpClientService/Extentions/CookieContainerExtention.cs new file mode 100644 index 0000000..715f60b --- /dev/null +++ b/src/ThunderDesign.Net-PCL.HttpClientService/Extentions/CookieContainerExtention.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Http.Headers; +using ThunderDesign.Net.HttpClientService.EventArgs; +using ThunderDesign.Net.HttpClientService.EventHandlers; + +namespace ThunderDesign.Net.HttpClientService.Extentions +{ + public static class CookieContainerExtention + { + #region methods + static public void SetCookies(this CookieContainer cookieContainer, HttpHeaders httpHeaders, string defaultDomain, CookieContainerChangedEventHandler? handler = null) + { + foreach (var header in httpHeaders) + { + if (!header.Key.Equals("set-cookie", StringComparison.OrdinalIgnoreCase)) + continue; + + foreach (var value in header.Value) + { + var cookieCollection = ParseCookieString(value, defaultDomain); + + cookieContainer.Add(cookieCollection); + handler?.Invoke(cookieContainer, new CookieContainerChangedEventArgs(cookieCollection)); + } + } + } + + static private CookieCollection ParseCookieString(string cookieString, string defaultDomain) + { + bool secure = false; + bool httpOnly = false; + + string domainFromCookie = String.Empty; + string path = String.Empty; + string expiresString = String.Empty; + + Dictionary cookiesValues = new Dictionary(); + + var cookieValuePairsStrings = cookieString.Split(';'); + foreach (string cookieValuePairString in cookieValuePairsStrings) + { + var pairArr = cookieValuePairString.Split('='); + int pairArrLength = pairArr.Length; + for (int i = 0; i < pairArrLength; i++) + { + pairArr[i] = pairArr[i].Trim(); + } + string propertyName = pairArr[0]; + if (pairArrLength == 1) + { + if (string.IsNullOrEmpty(propertyName)) + continue; + if (propertyName.Equals("httponly", StringComparison.OrdinalIgnoreCase)) + httpOnly = true; + else if (propertyName.Equals("secure", StringComparison.OrdinalIgnoreCase)) + secure = true; + else + throw new InvalidOperationException(string.Format("Unknown cookie property \"{0}\". All cookie is \"{1}\"", propertyName, cookieString)); + continue; + } + + string propertyValue = pairArr[1]; + if (propertyName.Equals("expires", StringComparison.OrdinalIgnoreCase)) + expiresString = propertyValue; + else if (propertyName.Equals("domain", StringComparison.OrdinalIgnoreCase)) + domainFromCookie = propertyValue; + else if (propertyName.Equals("path", StringComparison.OrdinalIgnoreCase)) + path = propertyValue; + else + cookiesValues.Add(propertyName, propertyValue); + } + + DateTime expiresDateTime; + if (expiresString != null) + { + expiresDateTime = DateTime.Parse(expiresString); + } + else + { + expiresDateTime = DateTime.MinValue; + } + if (string.IsNullOrEmpty(domainFromCookie)) + { + domainFromCookie = defaultDomain; + } + + CookieCollection cookieCollection = new CookieCollection(); + foreach (var pair in cookiesValues) + { + Cookie cookie = new Cookie(pair.Key, pair.Value, path, domainFromCookie) + { + Secure = secure, + HttpOnly = httpOnly, + Expires = expiresDateTime + }; + + cookieCollection.Add(cookie); + } + return cookieCollection; + } + #endregion + } +} diff --git a/src/ThunderDesign.Net-PCL.HttpClientService/Http/HttpClientAutoRedirect.cs b/src/ThunderDesign.Net-PCL.HttpClientService/Http/HttpClientAutoRedirect.cs new file mode 100644 index 0000000..65a0408 --- /dev/null +++ b/src/ThunderDesign.Net-PCL.HttpClientService/Http/HttpClientAutoRedirect.cs @@ -0,0 +1,88 @@ +using System; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using ThunderDesign.Net.HttpClientService.EventHandlers; +using ThunderDesign.Net.HttpClientService.Extentions; +using ThunderDesign.Net.HttpClientService.Interfaces; + +namespace ThunderDesign.Net.HttpClientService.Http +{ + public class HttpClientAutoRedirect : HttpClient, IHttpClientAutoRedirect + { + #region constructors + public HttpClientAutoRedirect() : this(new HttpClientHandler()) + { + } + + public HttpClientAutoRedirect(HttpMessageHandler handler) : this(handler, true) + { + } + + public HttpClientAutoRedirect(HttpMessageHandler handler, bool disposeHandler) : base(handler, disposeHandler) + { + if (handler is HttpClientHandler) + _HttpClientHandler = handler as HttpClientHandler; + } + #endregion + + #region event handlers + public event CookieContainerChangedEventHandler? CookieContainerChangedEvent; + #endregion + + #region methods + public new Task SendAsync(HttpRequestMessage request) + { + return SendAsync(request, defaultCompletionOption, CancellationToken.None); + } + + public override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + return SendAsync(request, defaultCompletionOption, cancellationToken); + } + + public new Task SendAsync(HttpRequestMessage request, HttpCompletionOption completionOption) + { + return SendAsync(request, completionOption, CancellationToken.None); + } + + public new Task SendAsync(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationToken cancellationToken) + { + Task task = base.SendAsync(request, completionOption, cancellationToken); + task.Wait(cancellationToken); + HttpResponseMessage responseMessage = task.Result; + + if ((_HttpClientHandler?.UseCookies ?? false) && responseMessage.Headers.Contains("set-cookie")) + { + _HttpClientHandler?.CookieContainer.SetCookies(responseMessage.Headers, responseMessage.RequestMessage.RequestUri.Host, CookieContainerChangedEvent); + } + + if ((_HttpClientHandler?.AllowAutoRedirect ?? false) && responseMessage.StatusCode == HttpStatusCode.Redirect && responseMessage.Headers.Contains("location")) + { + if (responseMessage.Headers.TryGetValues("location", out var location)) + { + using HttpRequestMessage requestMessage = new HttpRequestMessage { Method = HttpMethod.Get, RequestUri = new Uri(location.Single()) }; + try + { + return SendAsync(requestMessage, cancellationToken); + } + finally + { + responseMessage.Dispose(); + } + } + return task; + } + else + return task; + } + #endregion + + #region variables + private const HttpCompletionOption defaultCompletionOption = HttpCompletionOption.ResponseContentRead; + private readonly HttpClientHandler? _HttpClientHandler; + #endregion + } +} diff --git a/src/ThunderDesign.Net-PCL.HttpClientService/HttpClientService.cs b/src/ThunderDesign.Net-PCL.HttpClientService/HttpClientService.cs new file mode 100644 index 0000000..4f3992c --- /dev/null +++ b/src/ThunderDesign.Net-PCL.HttpClientService/HttpClientService.cs @@ -0,0 +1,51 @@ +using System.Net.Http; +using ThunderDesign.Net.HttpClientService.Http; +using ThunderDesign.Net.Threading.Objects; + +namespace HttpClientService.Core +{ + public class HttpClientService : ThreadObject + { + #region constructors + public HttpClientService() : this(DefaultHttpClientHandler) + { + } + + public HttpClientService(HttpMessageHandler? httpClientHandler) + { + HttpClient = httpClientHandler != null ? + new HttpClientAutoRedirect(httpClientHandler) : new HttpClientAutoRedirect(); + } + #endregion + + #region properties + public static HttpMessageHandler? DefaultHttpClientHandler + { + get { lock (_Locker) { return _DefaultHttpClientHandler; } } + set { lock (_Locker) { _DefaultHttpClientHandler = value; } } + } + + public static HttpClientService Instance + { + get + { + lock (_Locker) + { + return _Instance ??= new HttpClientService(); + } + } + } + + public HttpClientAutoRedirect HttpClient + { + get; + private set; + } + #endregion + + #region variables + private static HttpClientService? _Instance = null; + private static HttpMessageHandler? _DefaultHttpClientHandler = null; + #endregion + } +} diff --git a/src/ThunderDesign.Net-PCL.HttpClientService/Interfaces/IHttpClientAutoRedirect.cs b/src/ThunderDesign.Net-PCL.HttpClientService/Interfaces/IHttpClientAutoRedirect.cs new file mode 100644 index 0000000..86556d9 --- /dev/null +++ b/src/ThunderDesign.Net-PCL.HttpClientService/Interfaces/IHttpClientAutoRedirect.cs @@ -0,0 +1,11 @@ +using ThunderDesign.Net.HttpClientService.EventHandlers; + +namespace ThunderDesign.Net.HttpClientService.Interfaces +{ + public interface IHttpClientAutoRedirect + { + #region event handlers + event CookieContainerChangedEventHandler CookieContainerChangedEvent; + #endregion + } +} diff --git a/src/ThunderDesign.Net-PCL.HttpClientService/Interfaces/IHttpClientService.cs b/src/ThunderDesign.Net-PCL.HttpClientService/Interfaces/IHttpClientService.cs new file mode 100644 index 0000000..4d29df1 --- /dev/null +++ b/src/ThunderDesign.Net-PCL.HttpClientService/Interfaces/IHttpClientService.cs @@ -0,0 +1,11 @@ +using System.Net.Http; + +namespace ThunderDesign.Net.HttpClientService.Interfaces +{ + public interface IHttpClientService + { + #region properties + HttpMessageHandler HttpClientHandler { get; } + #endregion + } +} diff --git a/src/ThunderDesign.Net-PCL.HttpClientService/ThunderDesign.Net-PCL.HttpClientService.csproj b/src/ThunderDesign.Net-PCL.HttpClientService/ThunderDesign.Net-PCL.HttpClientService.csproj new file mode 100644 index 0000000..4872b88 --- /dev/null +++ b/src/ThunderDesign.Net-PCL.HttpClientService/ThunderDesign.Net-PCL.HttpClientService.csproj @@ -0,0 +1,13 @@ + + + + netstandard2.1 + ThunderDesign.Net_PCL.HttpClientService + enable + + + + + + + From fb7e8ae25a95891d0f1f0c1dd2cd8e1f492e6045 Mon Sep 17 00:00:00 2001 From: Shawn LaMountain Date: Thu, 17 Feb 2022 14:27:49 -0500 Subject: [PATCH 2/2] Adding CI/CD --- .github/workflows/CD.yml | 57 ++++++++++++++++++++++++++++++++++++++++ .github/workflows/CI.yml | 39 +++++++++++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 .github/workflows/CD.yml create mode 100644 .github/workflows/CI.yml diff --git a/.github/workflows/CD.yml b/.github/workflows/CD.yml new file mode 100644 index 0000000..ac92ea4 --- /dev/null +++ b/.github/workflows/CD.yml @@ -0,0 +1,57 @@ +name: CD + +on: + release: + types: [published] + +env: + TITLE: "A wrapper around 'System.Net.Http.HttpClient'" + DESCRIPTION: "A wrapper around 'System.Net.Http.HttpClient' enhancing AutoRedirect and Cookies. Can be used in all application types." + TAGS: "thunderdesign httpclient threading net" + #FILE_NAME: ex: "ThunderDesign.Net-PCL.HttpClientService" + FILE_NAME: "${{ github.event.repository.name }}" + #REPOSITORY_NAME: ex: "ThunderDesign.Net-PCL.HttpClientService" + REPOSITORY_NAME: ${{ github.event.repository.name }} + #REPOSITORY_OWNER: ex: "ThunderDesign" + REPOSITORY_OWNER: ${{ github.repository_owner }} + #GITHUB_URL: ex: "https://github.com/ThunderDesign" + GITHUB_URL: ${{ github.server_url }}/${{ github.repository_owner }} + #REPOSITORY_URL: ex: "https://github.com/ThunderDesign/ThunderDesign.Net-PCL.HttpClientService" + REPOSITORY_URL: ${{ github.server_url }}/${{ github.repository_owner }}/${{ github.event.repository.name }} + + PACKAGE_OUTPUT_DIRECTORY: ${{ github.workspace }}\output + +jobs: + pack: + + runs-on: [windows-latest] + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup .NET 3.0 + uses: actions/setup-dotnet@v1 + with: + dotnet-version: 3.0.x + + - name: Setup MSBuild + uses: microsoft/setup-msbuild@v1.1 + + - name: Setup NuGet + uses: NuGet/setup-nuget@v1.0.5 + + - name: Restore NuGet packages.sln + run: nuget restore ./src/${{ env.FILE_NAME}}.sln + + - name: Create NuGet Package + run: msbuild ./src/${{ env.FILE_NAME}}.sln -t:pack /p:VersionPrefix=${{ github.event.release.tag_name }} /p:Configuration=Release /p:Title="${{ env.TITLE }}" /p:Description="${{ env.DESCRIPTION }}" /p:PackageTags="${{ env.TAGS }}" /p:Authors=ThunderDesign /p:PackageProjectUrl=${{ env.GITHUB_URL }} /p:PackageLicenseExpression=MIT /p:RepositoryType=git /p:RepositoryUrl=${{ env.REPOSITORY_URL }} /p:PackageReleaseNotes="See ${{ env.REPOSITORY_URL }}/releases/tag/${{ github.event.release.tag_name }}" /p:PackageOutputPath=${{ env.PACKAGE_OUTPUT_DIRECTORY}} + + - name: Archive NuGet Package + uses: actions/upload-artifact@v2.3.1 + with: + name: Package_${{ env.FILE_NAME}}.${{ github.event.release.tag_name }} + path: ${{ env.PACKAGE_OUTPUT_DIRECTORY}}\${{ env.FILE_NAME}}.${{ github.event.release.tag_name }}.nupkg + + - name: Publish NuGet Package + run: nuget push ${{ env.PACKAGE_OUTPUT_DIRECTORY}}\${{ env.FILE_NAME}}.${{ github.event.release.tag_name }}.nupkg -Source https://api.nuget.org/v3/index.json -ApiKey ${{ secrets.NUGET_API_KEY }} diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml new file mode 100644 index 0000000..3ba05a1 --- /dev/null +++ b/.github/workflows/CI.yml @@ -0,0 +1,39 @@ +name: CI + +on: + workflow_dispatch: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +env: + #FILE_NAME: ex: "ThunderDesign.Net-PCL.HttpClientService" + FILE_NAME: "${{ github.event.repository.name }}" + +jobs: + build: + + runs-on: [windows-latest] + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup .NET 3.0 + uses: actions/setup-dotnet@v1 + with: + dotnet-version: 3.0.x + + - name: Setup MSBuild + uses: microsoft/setup-msbuild@v1.1 + + - name: Setup NuGet + uses: NuGet/setup-nuget@v1.0.5 + + - name: Restore NuGet packages + run: nuget restore ./src/${{ env.FILE_NAME}}.sln + + - name: Build Solution + run: msbuild ./src/${{ env.FILE_NAME}}.sln /p:Configuration=Release +