From 30aeef0a23265e1eacf41b10412bd703ead21342 Mon Sep 17 00:00:00 2001 From: Tom Kingstone Date: Tue, 8 Sep 2026 16:14:59 +0100 Subject: [PATCH 1/3] refactor pull and push methods (hopefully other toolkits will be able to use the adapter and GET methods in the same way as before) --- HTTP_Adapter/AdapterActions/Pull.cs | 33 ++++++++---- HTTP_Adapter/AdapterActions/Push.cs | 52 ++++++++++++++----- HTTP_Adapter/Compute/AddBHoMContent.cs | 17 ++++++ .../Compute/DeserialiseAsBHoMAsync.cs | 23 ++++++++ HTTP_Adapter/Create/HttpRequestMessage.cs | 21 ++++++++ HTTP_Adapter/HttpAdapter.cs | 47 +++++++++++++++++ HTTP_oM/HttpPullConfig.cs | 14 +++++ HTTP_oM/HttpPushConfig.cs | 26 ++++++++++ 8 files changed, 210 insertions(+), 23 deletions(-) create mode 100644 HTTP_Adapter/Compute/AddBHoMContent.cs create mode 100644 HTTP_Adapter/Compute/DeserialiseAsBHoMAsync.cs create mode 100644 HTTP_Adapter/Create/HttpRequestMessage.cs create mode 100644 HTTP_oM/HttpPullConfig.cs create mode 100644 HTTP_oM/HttpPushConfig.cs diff --git a/HTTP_Adapter/AdapterActions/Pull.cs b/HTTP_Adapter/AdapterActions/Pull.cs index 76f3034..659f41a 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,24 +46,34 @@ public override IEnumerable Pull(IRequest request, return Pull(request as dynamic, actionConfig); } - /***************************************************/ /**** Public Methods ****/ /***************************************************/ - public IEnumerable Pull(GetRequest request, ActionConfig actionConfig) + public IEnumerable Pull(GetRequest request, HttpPullConfig actionConfig) { - string response = Compute.MakeRequest(request); + if (actionConfig == null) + actionConfig = 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 (actionConfig.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 }; + } } /***************************************************/ @@ -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..6a94b97 --- /dev/null +++ b/HTTP_Adapter/Compute/AddBHoMContent.cs @@ -0,0 +1,17 @@ +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..86dabe5 --- /dev/null +++ b/HTTP_Adapter/Compute/DeserialiseAsBHoMAsync.cs @@ -0,0 +1,23 @@ +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..5ae1548 --- /dev/null +++ b/HTTP_Adapter/Create/HttpRequestMessage.cs @@ -0,0 +1,21 @@ +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..8fa3095 --- /dev/null +++ b/HTTP_oM/HttpPullConfig.cs @@ -0,0 +1,14 @@ +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..8f197ff --- /dev/null +++ b/HTTP_oM/HttpPushConfig.cs @@ -0,0 +1,26 @@ +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(); + } +} From 8b14c497f22093699a65fb463c9d4fe861906c32 Mon Sep 17 00:00:00 2001 From: Tom Kingstone Date: Thu, 15 Oct 2026 11:19:46 +0100 Subject: [PATCH 2/3] fix old bug that made BatchRequest not actually reachable. --- HTTP_Adapter/AdapterActions/Pull.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/HTTP_Adapter/AdapterActions/Pull.cs b/HTTP_Adapter/AdapterActions/Pull.cs index 659f41a..7efce7c 100644 --- a/HTTP_Adapter/AdapterActions/Pull.cs +++ b/HTTP_Adapter/AdapterActions/Pull.cs @@ -50,10 +50,10 @@ public override IEnumerable Pull(IRequest request, /**** Public Methods ****/ /***************************************************/ - public IEnumerable Pull(GetRequest request, HttpPullConfig actionConfig) + public IEnumerable Pull(GetRequest request, ActionConfig actionConfig) { - if (actionConfig == null) - actionConfig = new HttpPullConfig(); + if (!(actionConfig is HttpPullConfig config)) + config = new HttpPullConfig(); Uri requestUri = ConstructUri(request.BaseUrl, request.Parameters); @@ -68,7 +68,7 @@ public IEnumerable Pull(GetRequest request, HttpPullConfig actionConfig) return new List(); } - if (actionConfig.ForceDeserialiseAsBHoM || response.Content.Headers.ContentType.MediaType == "application/bhom") + 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(); @@ -78,9 +78,9 @@ public IEnumerable Pull(GetRequest request, HttpPullConfig actionConfig) /***************************************************/ - 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]; From bdd9d0f4fec40d92e386cb35dda82596c38ec8eb Mon Sep 17 00:00:00 2001 From: Tom Kingstone Date: Thu, 15 Oct 2026 13:55:03 +0100 Subject: [PATCH 3/3] compliance --- HTTP_Adapter/Compute/AddBHoMContent.cs | 24 ++++++++++++++++++- .../Compute/DeserialiseAsBHoMAsync.cs | 24 ++++++++++++++++++- HTTP_Adapter/Create/HttpRequestMessage.cs | 24 ++++++++++++++++++- HTTP_oM/HttpPullConfig.cs | 24 ++++++++++++++++++- HTTP_oM/HttpPushConfig.cs | 24 ++++++++++++++++++- 5 files changed, 115 insertions(+), 5 deletions(-) diff --git a/HTTP_Adapter/Compute/AddBHoMContent.cs b/HTTP_Adapter/Compute/AddBHoMContent.cs index 6a94b97..dff9d05 100644 --- a/HTTP_Adapter/Compute/AddBHoMContent.cs +++ b/HTTP_Adapter/Compute/AddBHoMContent.cs @@ -1,4 +1,26 @@ -using System; +/* + * 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; diff --git a/HTTP_Adapter/Compute/DeserialiseAsBHoMAsync.cs b/HTTP_Adapter/Compute/DeserialiseAsBHoMAsync.cs index 86dabe5..000a84e 100644 --- a/HTTP_Adapter/Compute/DeserialiseAsBHoMAsync.cs +++ b/HTTP_Adapter/Compute/DeserialiseAsBHoMAsync.cs @@ -1,4 +1,26 @@ -using System; +/* + * 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; diff --git a/HTTP_Adapter/Create/HttpRequestMessage.cs b/HTTP_Adapter/Create/HttpRequestMessage.cs index 5ae1548..a5c39d9 100644 --- a/HTTP_Adapter/Create/HttpRequestMessage.cs +++ b/HTTP_Adapter/Create/HttpRequestMessage.cs @@ -1,4 +1,26 @@ -using System; +/* + * 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; diff --git a/HTTP_oM/HttpPullConfig.cs b/HTTP_oM/HttpPullConfig.cs index 8fa3095..65f1d3a 100644 --- a/HTTP_oM/HttpPullConfig.cs +++ b/HTTP_oM/HttpPullConfig.cs @@ -1,4 +1,26 @@ -using BH.oM.Adapter; +/* + * 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; diff --git a/HTTP_oM/HttpPushConfig.cs b/HTTP_oM/HttpPushConfig.cs index 8f197ff..58409df 100644 --- a/HTTP_oM/HttpPushConfig.cs +++ b/HTTP_oM/HttpPushConfig.cs @@ -1,4 +1,26 @@ -using BH.oM.Adapter; +/* + * 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;