diff --git a/HTTP_Adapter/AdapterActions/Pull.cs b/HTTP_Adapter/AdapterActions/Pull.cs index 76f3034..7efce7c 100644 --- a/HTTP_Adapter/AdapterActions/Pull.cs +++ b/HTTP_Adapter/AdapterActions/Pull.cs @@ -29,6 +29,7 @@ using BH.oM.Base; using BH.oM.Data.Requests; using BH.oM.Adapters.HTTP; +using System; namespace BH.Adapter.HTTP { @@ -45,31 +46,41 @@ public override IEnumerable Pull(IRequest request, return Pull(request as dynamic, actionConfig); } - /***************************************************/ /**** Public Methods ****/ /***************************************************/ public IEnumerable Pull(GetRequest request, ActionConfig actionConfig) { - string response = Compute.MakeRequest(request); + if (!(actionConfig is HttpPullConfig config)) + config = new HttpPullConfig(); - if (response == null) - return new List(); + Uri requestUri = ConstructUri(request.BaseUrl, request.Parameters); - // check if the response is a valid json - if (response.StartsWith("{") || response.StartsWith("[")) - return new List() { Engine.Serialiser.Convert.FromJson(response) }; + HttpRequestMessage requestMessage = Create.ConstructHttpRequestMessage(HttpMethod.Get, requestUri, request.Headers); - else - return new List() { response }; + using (HttpResponseMessage response = m_httpClient.SendAsync(requestMessage).ConfigureAwait(false).GetAwaiter().GetResult()) + { + //assert success + if (!response.IsSuccessStatusCode) + { + Engine.Base.Compute.RecordError($"GET request failed with code {response.StatusCode}: {response.ReasonPhrase}"); + return new List(); + } + + if (config.ForceDeserialiseAsBHoM || response.Content.Headers.ContentType.MediaType == "application/bhom") + return Compute.DeserialiseAsBHoMAsync(response).ConfigureAwait(false).GetAwaiter().GetResult(); + + string responseString = response.Content.ReadAsStringAsync().ConfigureAwait(false).GetAwaiter().GetResult(); + return new List() { responseString }; + } } /***************************************************/ - public IEnumerable Pull(BatchRequest requests, HTTPConfig config) + public IEnumerable Pull(BatchRequest requests, ActionConfig actionConfig) { - if (config == null) + if (!(actionConfig is HTTPConfig config)) config = new HTTPConfig(); string[] response = new string[requests.Requests.Count]; @@ -77,7 +88,7 @@ public IEnumerable Pull(BatchRequest requests, HTTPConfig config) using (HttpClient client = new HttpClient() { Timeout = config.Timeout }) { List getRequests = requests.Requests.OfType().ToList(); - response = Task.WhenAll(getRequests.Select(x => Compute.MakeRequestAsync(x, client))).GetAwaiter().GetResult(); + response = Task.WhenAll(getRequests.Select(x => Engine.Adapters.HTTP.Compute.MakeRequestAsync(x, client))).GetAwaiter().GetResult(); client.CancelPendingRequests(); client.Dispose(); } diff --git a/HTTP_Adapter/AdapterActions/Push.cs b/HTTP_Adapter/AdapterActions/Push.cs index b2f4555..a2b5c04 100644 --- a/HTTP_Adapter/AdapterActions/Push.cs +++ b/HTTP_Adapter/AdapterActions/Push.cs @@ -20,10 +20,20 @@ * along with this code. If not, see . */ -using BH.oM.Base; -using System.Collections.Generic; using BH.Engine.Reflection; +using BH.Engine.Serialiser; using BH.oM.Adapter; +using BH.oM.Adapters.HTTP; +using BH.oM.Base; +using BH.oM.Data.Requests; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; namespace BH.Adapter.HTTP { @@ -33,20 +43,38 @@ public partial class HTTPAdapter /**** Override Methods ****/ /***************************************************/ - public override List Push(IEnumerable objects, - string tag = "", - PushType pushType = PushType.AdapterDefault, - ActionConfig actionConfig = null) + public override List Push(IEnumerable objects, string tag = "", PushType pushType = PushType.AdapterDefault, ActionConfig actionConfig = null) { - Engine.Base.Compute.RecordError("POST request not implemented."); - return new List(); - } - } -} - + if (!(actionConfig is HttpPushConfig pushConfig)) + { + BH.Engine.Base.Compute.RecordWarning("Provided ActionConfig was not an HttpPushConfig, running with default push config arguments:\nRequestURI=\"\"\nForcePostAsList=false\nDeserialiseAsBHoM=true"); + pushConfig = new HttpPushConfig(); + } + Uri requestUri = ConstructUri(pushConfig.RequestURL, pushConfig.Parameters); + HttpRequestMessage requestMessage = Create.ConstructHttpRequestMessage(HttpMethod.Post, requestUri, pushConfig.Headers); + if (objects.Count() == 1 & !pushConfig.ForcePostAsList) + Compute.AddBHoMContent(ref requestMessage, objects.Single()); + else + Compute.AddBHoMContent(ref requestMessage, objects); + using (HttpResponseMessage response = m_httpClient.SendAsync(requestMessage).ConfigureAwait(false).GetAwaiter().GetResult()) + { + //assert success + if (!response.IsSuccessStatusCode) + { + Engine.Base.Compute.RecordError($"POST request failed with code {response.StatusCode}: {response.ReasonPhrase}"); + return new List(); + } + if (pushConfig.ForceDeserialiseAsBHoM || response.Content.Headers.ContentType.MediaType == "application/bhom") + return Compute.DeserialiseAsBHoMAsync(response).ConfigureAwait(false).GetAwaiter().GetResult(); + string responseString = response.Content.ReadAsStringAsync().ConfigureAwait(false).GetAwaiter().GetResult(); + return new List() { responseString }; + } + } + } +} \ No newline at end of file diff --git a/HTTP_Adapter/Compute/AddBHoMContent.cs b/HTTP_Adapter/Compute/AddBHoMContent.cs new file mode 100644 index 0000000..dff9d05 --- /dev/null +++ b/HTTP_Adapter/Compute/AddBHoMContent.cs @@ -0,0 +1,39 @@ +/* + * This file is part of the Buildings and Habitats object Model (BHoM) + * Copyright (c) 2015 - 2026, the respective contributors. All rights reserved. + * + * Each contributor holds copyright over their respective contributions. + * The project versioning (Git) records all such contribution source information. + * + * + * The BHoM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, or + * (at your option) any later version. + * + * The BHoM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + */ + +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Text; + +namespace BH.Adapter.HTTP +{ + public static partial class Compute + { + public static void AddBHoMContent(ref HttpRequestMessage message, object value) + { + string json = BH.Engine.Serialiser.Convert.ToJson(value); + StringContent content = new StringContent(json, Encoding.UTF8, "application/bhom"); + message.Content = content; + } + } +} diff --git a/HTTP_Adapter/Compute/DeserialiseAsBHoMAsync.cs b/HTTP_Adapter/Compute/DeserialiseAsBHoMAsync.cs new file mode 100644 index 0000000..000a84e --- /dev/null +++ b/HTTP_Adapter/Compute/DeserialiseAsBHoMAsync.cs @@ -0,0 +1,45 @@ +/* + * This file is part of the Buildings and Habitats object Model (BHoM) + * Copyright (c) 2015 - 2026, the respective contributors. All rights reserved. + * + * Each contributor holds copyright over their respective contributions. + * The project versioning (Git) records all such contribution source information. + * + * + * The BHoM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, or + * (at your option) any later version. + * + * The BHoM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; + +namespace BH.Adapter.HTTP +{ + public static partial class Compute + { + public static async Task> DeserialiseAsBHoMAsync(HttpResponseMessage response) + { + string jsonResponse = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + object obj = BH.Engine.Serialiser.Convert.FromJson(jsonResponse); + + if (obj is IEnumerable objects1) + return objects1.ToList(); + + return new List { obj }; + } + } +} diff --git a/HTTP_Adapter/Create/HttpRequestMessage.cs b/HTTP_Adapter/Create/HttpRequestMessage.cs new file mode 100644 index 0000000..a5c39d9 --- /dev/null +++ b/HTTP_Adapter/Create/HttpRequestMessage.cs @@ -0,0 +1,43 @@ +/* + * This file is part of the Buildings and Habitats object Model (BHoM) + * Copyright (c) 2015 - 2026, the respective contributors. All rights reserved. + * + * Each contributor holds copyright over their respective contributions. + * The project versioning (Git) records all such contribution source information. + * + * + * The BHoM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, or + * (at your option) any later version. + * + * The BHoM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + */ + +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Text; + +namespace BH.Adapter.HTTP +{ + public static partial class Create + { + public static HttpRequestMessage ConstructHttpRequestMessage(HttpMethod method, Uri uri, Dictionary headers = null) + { + var message = new HttpRequestMessage(method, uri); + + if (headers != null) + foreach (var kvp in headers) + message.Headers.Add(kvp.Key, kvp.Value.ToString()); + + return message; + } + } +} diff --git a/HTTP_Adapter/HttpAdapter.cs b/HTTP_Adapter/HttpAdapter.cs index 06aca47..3a96673 100644 --- a/HTTP_Adapter/HttpAdapter.cs +++ b/HTTP_Adapter/HttpAdapter.cs @@ -21,7 +21,10 @@ */ using BH.oM.Adapters.HTTP; +using System; +using System.Collections.Generic; using System.Net; +using System.Net.Http; namespace BH.Adapter.HTTP { @@ -33,6 +36,7 @@ public partial class HTTPAdapter : BHoMAdapter public static string AdapterID = "HTTPAdapter"; + public HttpClient HttpClient => m_httpClient; //allows custom requests in code if necessary (e.g. need to POST with some custom non-bhom content) /***************************************************/ /**** Constructors ****/ @@ -43,10 +47,53 @@ public HTTPAdapter() // Use TLS 1.2 and TLS 1.3 (if available). Deprecated protocols not supported any longer: SSL3, TLS 1.0, TLS 1.1. // TLS 1.2 is widely supported and secure. TLS 1.3 may not be available in all .NET Framework versions. System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | (SecurityProtocolType)3072; // 3072 = Tls13 + m_httpClient = new HttpClient(); } + /***************************************************/ + public HTTPAdapter(string baseAddress) + { + // Use TLS 1.2 and TLS 1.3 (if available). Deprecated protocols not supported any longer: SSL3, TLS 1.0, TLS 1.1. + // TLS 1.2 is widely supported and secure. TLS 1.3 may not be available in all .NET Framework versions. + System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | (SecurityProtocolType)3072; // 3072 = Tls13 + m_httpClient = new HttpClient() { BaseAddress = new Uri(baseAddress) }; + } + + /***************************************************/ + + public HTTPAdapter(HttpClient httpClient) + { + // Use TLS 1.2 and TLS 1.3 (if available). Deprecated protocols not supported any longer: SSL3, TLS 1.0, TLS 1.1. + // TLS 1.2 is widely supported and secure. TLS 1.3 may not be available in all .NET Framework versions. + System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | (SecurityProtocolType)3072; // 3072 = Tls13 + m_httpClient = httpClient; + } + + /***************************************************/ + /**** Private Methods ****/ + /***************************************************/ + + private Uri ConstructUri(string absoluteOrRelativeUrl, Dictionary parameters) + { + Uri baseUri; + + if (m_httpClient.BaseAddress != null) + baseUri = new Uri(m_httpClient.BaseAddress, absoluteOrRelativeUrl); + else + baseUri = new Uri(absoluteOrRelativeUrl); + + string queryParameters = Engine.Adapters.HTTP.Convert.ToUrlString(parameters); + + UriBuilder builder = new UriBuilder(baseUri) { Query = queryParameters }; + + return builder.Uri; + } + + /***************************************************/ + /**** Private Fields ****/ /***************************************************/ + private readonly HttpClient m_httpClient; } } diff --git a/HTTP_oM/HttpPullConfig.cs b/HTTP_oM/HttpPullConfig.cs new file mode 100644 index 0000000..65f1d3a --- /dev/null +++ b/HTTP_oM/HttpPullConfig.cs @@ -0,0 +1,36 @@ +/* + * This file is part of the Buildings and Habitats object Model (BHoM) + * Copyright (c) 2015 - 2026, the respective contributors. All rights reserved. + * + * Each contributor holds copyright over their respective contributions. + * The project versioning (Git) records all such contribution source information. + * + * + * The BHoM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, or + * (at your option) any later version. + * + * The BHoM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + */ + +using BH.oM.Adapter; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Text; + +namespace BH.oM.Adapters.HTTP +{ + public class HttpPullConfig: ActionConfig + { + [Description("If the response from the GET request is expected to be a BHoM object but doesn't have content type of 'application/bhom', this forces the adapter to deserialise the result as BHoM formatted json. Otherwise the adapter returns the response content as a string.")] + public virtual bool ForceDeserialiseAsBHoM { get; set; } = true; + } +} diff --git a/HTTP_oM/HttpPushConfig.cs b/HTTP_oM/HttpPushConfig.cs new file mode 100644 index 0000000..58409df --- /dev/null +++ b/HTTP_oM/HttpPushConfig.cs @@ -0,0 +1,48 @@ +/* + * This file is part of the Buildings and Habitats object Model (BHoM) + * Copyright (c) 2015 - 2026, the respective contributors. All rights reserved. + * + * Each contributor holds copyright over their respective contributions. + * The project versioning (Git) records all such contribution source information. + * + * + * The BHoM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, or + * (at your option) any later version. + * + * The BHoM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + */ + +using BH.oM.Adapter; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Text; + +namespace BH.oM.Adapters.HTTP +{ + public class HttpPushConfig : ActionConfig + { + [Description("The uri provided is appended to the base URI set in the adapter. If the base uri is empty, this must be the full URL for the requested resource.")] + public virtual string RequestURL { get; set; } = ""; + + [Description("If only one object is provided to the Push, this forces the adapter to push it as a json array instead of a single object. By default the adapter will push single objects alone instead of as a list.")] + public virtual bool ForcePostAsList { get; set; } = false; + + [Description("If the response from the POST request is expected to be a BHoM object but doesn't have content type of 'application/bhom', this forces the adapter to deserialise the result as BHoM formatted json. Otherwise the adapter returns the response content as a string.")] + public virtual bool ForceDeserialiseAsBHoM { get; set; } = false; + + [Description("Custom request headers to send with the POST request.")] + public virtual Dictionary Headers { get; set; } = new Dictionary(); + + [Description("Custom parameters to provide to the POST request.")] + public virtual Dictionary Parameters { get; set; } = new Dictionary(); + } +}