From 90aa69a5c007f4a3e69aa158d3a1069e7675173a Mon Sep 17 00:00:00 2001 From: Adam McCoy Date: Mon, 1 Feb 2021 09:51:53 +1100 Subject: [PATCH 1/4] Use standard pattern to allow derived classes to raise events --- source/Octopus.Client/OctopusAsyncClient.cs | 16 ++++++++++++---- source/Octopus.Client/OctopusClient.cs | 16 ++++++++++++---- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/source/Octopus.Client/OctopusAsyncClient.cs b/source/Octopus.Client/OctopusAsyncClient.cs index 096e01ba0..43e9f43c0 100644 --- a/source/Octopus.Client/OctopusAsyncClient.cs +++ b/source/Octopus.Client/OctopusAsyncClient.cs @@ -526,9 +526,9 @@ protected virtual async Task> DispatchRequest } } - SendingOctopusRequest?.Invoke(request); + OnSendingOctopusRequest(request); - BeforeSendingHttpRequest?.Invoke(message); + OnBeforeSendingHttpRequest(message); if (request.RequestResource != null) message.Content = GetContent(request); @@ -542,7 +542,7 @@ protected virtual async Task> DispatchRequest { using (var response = await client.SendAsync(message, completionOption).ConfigureAwait(false)) { - AfterReceivedHttpResponse?.Invoke(response); + OnAfterReceivedHttpResponse(response); if (!response.IsSuccessStatusCode) throw await OctopusExceptionFactory.CreateException(response).ConfigureAwait(false); @@ -554,7 +554,7 @@ protected virtual async Task> DispatchRequest var locationHeader = response.Headers.Location?.OriginalString; var octopusResponse = new OctopusResponse(request, response.StatusCode, locationHeader, resource); - ReceivedOctopusResponse?.Invoke(octopusResponse); + OnReceivedOctopusResponse(octopusResponse); return octopusResponse; } @@ -566,6 +566,14 @@ protected virtual async Task> DispatchRequest } } + protected virtual void OnSendingOctopusRequest(OctopusRequest request) => SendingOctopusRequest?.Invoke(request); + + protected virtual void OnBeforeSendingHttpRequest(HttpRequestMessage request) => BeforeSendingHttpRequest?.Invoke(request); + + protected virtual void OnAfterReceivedHttpResponse(HttpResponseMessage response) => AfterReceivedHttpResponse?.Invoke(response); + + protected virtual void OnReceivedOctopusResponse(OctopusResponse response) => ReceivedOctopusResponse?.Invoke(response); + private HttpContent GetContent(OctopusRequest request) { var requestStreamContent = request.RequestResource as Stream; diff --git a/source/Octopus.Client/OctopusClient.cs b/source/Octopus.Client/OctopusClient.cs index 05cbe02fa..4b6d41816 100644 --- a/source/Octopus.Client/OctopusClient.cs +++ b/source/Octopus.Client/OctopusClient.cs @@ -439,9 +439,9 @@ protected virtual OctopusResponse DispatchRequest DispatchRequest DispatchRequest(request, webResponse.StatusCode, locationHeader, resource); - ReceivedOctopusResponse?.Invoke(octopusResponse); + OnReceivedOctopusResponse(octopusResponse); return octopusResponse; } @@ -577,6 +577,14 @@ protected virtual OctopusResponse DispatchRequest BeforeSendingHttpRequest?.Invoke(request); + + protected virtual void OnAfterReceivingHttpResponse(WebResponse response) => AfterReceivingHttpResponse?.Invoke(response); + + protected virtual void OnSendingOctopusRequest(OctopusRequest request) => SendingOctopusRequest?.Invoke(request); + + protected virtual void OnReceivedOctopusResponse(OctopusResponse response) => ReceivedOctopusResponse?.Invoke(response); + /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// From 95ef0f58ce7b6b6230d908fbb6dc06725b0e4ebf Mon Sep 17 00:00:00 2001 From: Adam McCoy Date: Mon, 1 Feb 2021 09:53:19 +1100 Subject: [PATCH 2/4] Use HttpClient in sync client --- ...AreaShouldNotRegress..NETCore.approved.txt | 8 +- ...houldNotRegress..NETFramework.approved.txt | 8 +- source/Octopus.Client/IHttpOctopusClient.cs | 6 +- source/Octopus.Client/Octopus.Client.csproj | 1 + source/Octopus.Client/OctopusClient.cs | 267 ++++++++---------- 5 files changed, 134 insertions(+), 156 deletions(-) diff --git a/source/Octopus.Client.Tests/PublicSurfaceAreaFixture.ThePublicSurfaceAreaShouldNotRegress..NETCore.approved.txt b/source/Octopus.Client.Tests/PublicSurfaceAreaFixture.ThePublicSurfaceAreaShouldNotRegress..NETCore.approved.txt index 64e9885ae..4d8bb9eca 100644 --- a/source/Octopus.Client.Tests/PublicSurfaceAreaFixture.ThePublicSurfaceAreaShouldNotRegress..NETCore.approved.txt +++ b/source/Octopus.Client.Tests/PublicSurfaceAreaFixture.ThePublicSurfaceAreaShouldNotRegress..NETCore.approved.txt @@ -12,8 +12,8 @@ Octopus.Client Octopus.Client.IOctopusClient IDisposable { - event Action AfterReceivingHttpResponse - event Action BeforeSendingHttpRequest + event Action AfterReceivingHttpResponse + event Action BeforeSendingHttpRequest } interface ILinkResolver { @@ -373,8 +373,8 @@ Octopus.Client Octopus.Client.IOctopusClient IDisposable { - event Action AfterReceivingHttpResponse - event Action BeforeSendingHttpRequest + event Action AfterReceivingHttpResponse + event Action BeforeSendingHttpRequest event Action ReceivedOctopusResponse event Action SendingOctopusRequest .ctor(Octopus.Client.OctopusServerEndpoint) diff --git a/source/Octopus.Client.Tests/PublicSurfaceAreaFixture.ThePublicSurfaceAreaShouldNotRegress..NETFramework.approved.txt b/source/Octopus.Client.Tests/PublicSurfaceAreaFixture.ThePublicSurfaceAreaShouldNotRegress..NETFramework.approved.txt index 17c08aeaf..44ecc9ce6 100644 --- a/source/Octopus.Client.Tests/PublicSurfaceAreaFixture.ThePublicSurfaceAreaShouldNotRegress..NETFramework.approved.txt +++ b/source/Octopus.Client.Tests/PublicSurfaceAreaFixture.ThePublicSurfaceAreaShouldNotRegress..NETFramework.approved.txt @@ -12,8 +12,8 @@ Octopus.Client Octopus.Client.IOctopusClient IDisposable { - event Action AfterReceivingHttpResponse - event Action BeforeSendingHttpRequest + event Action AfterReceivingHttpResponse + event Action BeforeSendingHttpRequest } interface ILinkResolver { @@ -373,8 +373,8 @@ Octopus.Client Octopus.Client.IOctopusClient IDisposable { - event Action AfterReceivingHttpResponse - event Action BeforeSendingHttpRequest + event Action AfterReceivingHttpResponse + event Action BeforeSendingHttpRequest event Action ReceivedOctopusResponse event Action SendingOctopusRequest .ctor(Octopus.Client.OctopusServerEndpoint) diff --git a/source/Octopus.Client/IHttpOctopusClient.cs b/source/Octopus.Client/IHttpOctopusClient.cs index 734c2e0e1..9f4781480 100644 --- a/source/Octopus.Client/IHttpOctopusClient.cs +++ b/source/Octopus.Client/IHttpOctopusClient.cs @@ -1,5 +1,5 @@ using System; -using System.Net; +using System.Net.Http; namespace Octopus.Client { @@ -11,11 +11,11 @@ public interface IHttpOctopusClient : IOctopusClient /// /// Occurs when a request is about to be sent. /// - event Action BeforeSendingHttpRequest; + event Action BeforeSendingHttpRequest; /// /// Occurs when a response has been received. /// - event Action AfterReceivingHttpResponse; + event Action AfterReceivingHttpResponse; } } diff --git a/source/Octopus.Client/Octopus.Client.csproj b/source/Octopus.Client/Octopus.Client.csproj index 3fc817d41..1f9b9c0bf 100644 --- a/source/Octopus.Client/Octopus.Client.csproj +++ b/source/Octopus.Client/Octopus.Client.csproj @@ -11,6 +11,7 @@ This package contains the client library for the HTTP API in Octopus. Octopus.Client.nuspec + latest net452;netstandard2.0 diff --git a/source/Octopus.Client/OctopusClient.cs b/source/Octopus.Client/OctopusClient.cs index 4b6d41816..31070e8a0 100644 --- a/source/Octopus.Client/OctopusClient.cs +++ b/source/Octopus.Client/OctopusClient.cs @@ -8,6 +8,9 @@ using Octopus.Client.Serialization; using System.Collections.Generic; using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading.Tasks; using Octopus.Client.Logging; namespace Octopus.Client @@ -20,10 +23,10 @@ public class OctopusClient : IHttpOctopusClient private static readonly ILog Logger = LogProvider.For(); readonly OctopusServerEndpoint serverEndpoint; + readonly JsonSerializerSettings defaultJsonSerializerSettings = JsonSerialization.GetDefaultSerializerSettings(); + readonly HttpClient client; readonly CookieContainer cookieContainer = new CookieContainer(); readonly Uri cookieOriginUri; - readonly JsonSerializerSettings defaultJsonSerializerSettings = JsonSerialization.GetDefaultSerializerSettings(); - readonly OctopusCustomHeaders octopusCustomHeaders; private string antiforgeryCookieName = null; /// @@ -38,7 +41,21 @@ internal OctopusClient(OctopusServerEndpoint serverEndpoint, string requestingTo { this.serverEndpoint = serverEndpoint; cookieOriginUri = BuildCookieUri(serverEndpoint); - octopusCustomHeaders = new OctopusCustomHeaders(requestingTool); + var handler = new HttpClientHandler() + { + CookieContainer = cookieContainer, + Credentials = serverEndpoint.Credentials ?? CredentialCache.DefaultCredentials + }; + if (serverEndpoint.Proxy != null) + handler.Proxy = serverEndpoint.Proxy; + + client = new HttpClient(handler, true) + { + Timeout = TimeSpan.FromMilliseconds(ApiConstants.DefaultClientRequestTimeout) + }; + client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + client.DefaultRequestHeaders.Add(ApiConstants.ApiKeyHttpHeaderName, serverEndpoint.ApiKey); + client.DefaultRequestHeaders.Add("User-Agent", new OctopusCustomHeaders(requestingTool).UserAgent); Repository = new OctopusRepository(this); } @@ -86,12 +103,12 @@ public void SignOut() /// /// Occurs when a request is about to be sent. /// - public event Action BeforeSendingHttpRequest; + public event Action BeforeSendingHttpRequest; /// /// Occurs when a response has been received. /// - public event Action AfterReceivingHttpResponse; + public event Action AfterReceivingHttpResponse; /// /// Occurs when a request is about to be sent. @@ -401,31 +418,16 @@ public Uri QualifyUri(string path, object parameters = null) protected virtual OctopusResponse DispatchRequest(OctopusRequest request, bool readResponse) { - var webRequest = (HttpWebRequest)WebRequest.Create(request.Uri); - if (serverEndpoint.Proxy != null) + using var requestMessage = new HttpRequestMessage { - webRequest.Proxy = serverEndpoint.Proxy; - } - webRequest.CookieContainer = cookieContainer; - webRequest.Accept = "application/json"; - webRequest.ContentType = "application/json"; - webRequest.ReadWriteTimeout = ApiConstants.DefaultClientRequestTimeout; - webRequest.Timeout = ApiConstants.DefaultClientRequestTimeout; - webRequest.Credentials = serverEndpoint.Credentials ?? CredentialCache.DefaultNetworkCredentials; - webRequest.Method = request.Method; - webRequest.Headers[ApiConstants.ApiKeyHttpHeaderName] = serverEndpoint.ApiKey; - webRequest.UserAgent = octopusCustomHeaders.UserAgent; - - if (webRequest.Method == "PUT") - { - webRequest.Headers["X-HTTP-Method-Override"] = "PUT"; - webRequest.Method = "POST"; - } + RequestUri = request.Uri, + Method = new HttpMethod(request.Method) + }; - if (webRequest.Method == "DELETE") + if (request.Method == "PUT" || request.Method == "DELETE") { - webRequest.Headers["X-HTTP-Method-Override"] = "DELETE"; - webRequest.Method = "POST"; + requestMessage.Method = HttpMethod.Post; + requestMessage.Headers.Add("X-HTTP-Method-Override", request.Method); } if (!string.IsNullOrEmpty(antiforgeryCookieName)) @@ -435,7 +437,7 @@ protected virtual OctopusResponse DispatchRequest string.Equals(c.Name, antiforgeryCookieName)); if (antiforgeryCookie != null) { - webRequest.Headers[ApiConstants.AntiforgeryTokenHttpHeaderName] = antiforgeryCookie.Value; + requestMessage.Headers.Add(ApiConstants.AntiforgeryTokenHttpHeaderName, antiforgeryCookie.Value); } } @@ -443,143 +445,118 @@ protected virtual OctopusResponse DispatchRequest(content, defaultJsonSerializerSettings); - } - catch (Exception ex) - { - throw new OctopusDeserializationException((int)webResponse.StatusCode, "Unable to process response from server: " + ex.Message + ". Response content: " + (content.Length > 100 ? content.Substring(0, 100) : content), ex); - } - } - } - } - } + if (!response.IsSuccessStatusCode) + throw OctopusExceptionFactory.CreateException(response).GetAwaiter().GetResult(); - var locationHeader = webResponse.Headers.Get("Location"); - var octopusResponse = new OctopusResponse(request, webResponse.StatusCode, locationHeader, resource); + var resource = readResponse + ? ReadResponse(response) + : default; + + var locationHeader = response.Headers.Location?.OriginalString; + var octopusResponse = new OctopusResponse(request, response.StatusCode, locationHeader, resource); OnReceivedOctopusResponse(octopusResponse); return octopusResponse; } - catch (WebException wex) + catch (TaskCanceledException) { - if (wex.Response != null) - { - throw OctopusExceptionFactory.CreateException(wex, (HttpWebResponse)wex.Response); - } + throw new TimeoutException($"Timeout getting response from {request.Uri}, client timeout is set to {client.Timeout}"); + } + } - throw; + private T ReadResponse(HttpResponseMessage response) + { + return default(T) switch + { + Stream => GetStream(response.Content), + byte[] => GetByteArray(response.Content), + string => GetString(response.Content), + _ => GetJson(response.Content) + }; + + static T GetStream(HttpContent content) + { + var stream = new MemoryStream(); + content.CopyToAsync(stream).GetAwaiter().GetResult(); + stream.Seek(0, SeekOrigin.Begin); + return (T)(object)stream; } - finally + + static T GetByteArray(HttpContent content) + { + return (T)(object)content.ReadAsByteArrayAsync().GetAwaiter().GetResult(); + } + + static T GetString(HttpContent content) { - if (webResponse != null) + var str = content.ReadAsStringAsync().GetAwaiter().GetResult(); + return (T)(object)str; + } + + T GetJson(HttpContent content) + { + var str = content.ReadAsStringAsync().GetAwaiter().GetResult(); + try { - try - { - webResponse.Close(); - } - // ReSharper disable once EmptyGeneralCatchClause - catch - { - } + return JsonConvert.DeserializeObject(str, defaultJsonSerializerSettings); } + catch (Exception ex) + { + throw new OctopusDeserializationException((int)response.StatusCode, + $"Unable to process response from server: {ex.Message}. Response content: {(str.Length > 1000 ? str.Substring(0, 1000) : str)}", ex); + } + } + } + + private HttpContent GetHttpContent(OctopusRequest request) + { + return request.RequestResource switch + { + Stream requestStream => GetStreamContent(requestStream), + FileUpload fileUpload => GetFileUploadContent(fileUpload), + _ => GetJsonContent(request.RequestResource) + }; + + static HttpContent GetStreamContent(Stream stream) + { + var content = new StreamContent(stream); + content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); + return content; + } + + static HttpContent GetFileUploadContent(FileUpload fileUpload) + { + var formContent = new MultipartFormDataContent(); + var streamContent = new StreamContent(fileUpload.Contents); + streamContent.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); + formContent.Add(streamContent, "file", fileUpload.FileName); + return formContent; + } + + HttpContent GetJsonContent(object resource) + { + var text = JsonConvert.SerializeObject(resource, defaultJsonSerializerSettings); + return new StringContent(text, Encoding.UTF8, "application/json"); } } - protected virtual void OnBeforeSendingHttpRequest(WebRequest request) => BeforeSendingHttpRequest?.Invoke(request); + protected virtual void OnBeforeSendingHttpRequest(HttpRequestMessage request) => BeforeSendingHttpRequest?.Invoke(request); - protected virtual void OnAfterReceivingHttpResponse(WebResponse response) => AfterReceivingHttpResponse?.Invoke(response); + protected virtual void OnAfterReceivingHttpResponse(HttpResponseMessage response) => AfterReceivingHttpResponse?.Invoke(response); protected virtual void OnSendingOctopusRequest(OctopusRequest request) => SendingOctopusRequest?.Invoke(request); From 73fbe0872b8c564dcfc965c0ea6b139bbd3312c1 Mon Sep 17 00:00:00 2001 From: Adam McCoy Date: Mon, 1 Feb 2021 10:19:37 +1100 Subject: [PATCH 3/4] Remove use of preview feature in 3.1 SDK --- source/Octopus.Client/OctopusClient.cs | 40 +++++++++----------------- 1 file changed, 13 insertions(+), 27 deletions(-) diff --git a/source/Octopus.Client/OctopusClient.cs b/source/Octopus.Client/OctopusClient.cs index 31070e8a0..c3b3e22f7 100644 --- a/source/Octopus.Client/OctopusClient.cs +++ b/source/Octopus.Client/OctopusClient.cs @@ -480,15 +480,9 @@ protected virtual OctopusResponse DispatchRequest(HttpResponseMessage response) { - return default(T) switch - { - Stream => GetStream(response.Content), - byte[] => GetByteArray(response.Content), - string => GetString(response.Content), - _ => GetJson(response.Content) - }; + var content = response.Content; - static T GetStream(HttpContent content) + if (typeof(T) == typeof(Stream)) { var stream = new MemoryStream(); content.CopyToAsync(stream).GetAwaiter().GetResult(); @@ -496,29 +490,21 @@ static T GetStream(HttpContent content) return (T)(object)stream; } - static T GetByteArray(HttpContent content) - { - return (T)(object)content.ReadAsByteArrayAsync().GetAwaiter().GetResult(); - } + if (typeof(T) == typeof(byte[])) + return (T) (object) content.ReadAsByteArrayAsync().GetAwaiter().GetResult(); - static T GetString(HttpContent content) - { - var str = content.ReadAsStringAsync().GetAwaiter().GetResult(); + var str = content.ReadAsStringAsync().GetAwaiter().GetResult(); + if (typeof(T) == typeof(string)) return (T)(object)str; - } - T GetJson(HttpContent content) + try { - var str = content.ReadAsStringAsync().GetAwaiter().GetResult(); - try - { - return JsonConvert.DeserializeObject(str, defaultJsonSerializerSettings); - } - catch (Exception ex) - { - throw new OctopusDeserializationException((int)response.StatusCode, - $"Unable to process response from server: {ex.Message}. Response content: {(str.Length > 1000 ? str.Substring(0, 1000) : str)}", ex); - } + return JsonConvert.DeserializeObject(str, defaultJsonSerializerSettings); + } + catch (Exception ex) + { + throw new OctopusDeserializationException((int)response.StatusCode, + $"Unable to process response from server: {ex.Message}. Response content: {(str.Length > 1000 ? str.Substring(0, 1000) : str)}", ex); } } From c1734fc1610dc87a1d1541a873a5aa009579c8fa Mon Sep 17 00:00:00 2001 From: Adam McCoy Date: Mon, 1 Feb 2021 10:57:00 +1100 Subject: [PATCH 4/4] Update signing timestamp params --- build.cake | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build.cake b/build.cake index 54218de7d..4ebe73ece 100644 --- a/build.cake +++ b/build.cake @@ -187,7 +187,8 @@ private void SignBinaries(string path) Sign(files, new SignToolSignSettings { ToolPath = MakeAbsolute(File("./certificates/signtool.exe")), - TimeStampUri = new Uri("http://timestamp.globalsign.com/scripts/timestamp.dll"), + TimeStampUri = new Uri("http://rfc3161timestamp.globalsign.com/advanced"), + TimeStampDigestAlgorithm = SignToolDigestAlgorithm.Sha256, CertPath = signingCertificatePath, Password = signingCertificatePassword });