// // ARF - Augmented Reality Framework (ETSI ISG ARF) // // Copyright 2024 ETSI // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Last change: September 2024 // // Depends on UniTask to support cancellation token and GetAwaiter: https://github.com/Cysharp/UniTask // Otherwise, the code can be adapted using https://gist.github.com/krzys-h/9062552e33dd7bd7fe4a6c12db109a1a using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Threading; using System.Threading.Tasks; //using Cysharp.Threading.Tasks; using UnityEngine; using UnityEngine.Networking; namespace ETSI.ARF.OpenAPI.WorldAnalysis { public interface IHttpClient { public Uri BaseAddress { get; set; } public HttpRequestHeaders DefaultRequestHeaders { get; } public Task<HttpResponseMessage> SendAsync(HttpRequestMessage message, HttpCompletionOption option, CancellationToken token); public void Dispose(); } public class BasicHTTPClient : IHttpClient { public BasicHTTPClient() { } public BasicHTTPClient(string baseUri) { BaseAddress = new Uri(baseUri); _httpClient.BaseAddress = BaseAddress; } public BasicHTTPClient(Uri baseUri) { BaseAddress = baseUri; _httpClient.BaseAddress = BaseAddress; } public Uri BaseAddress { get; set; } public HttpRequestHeaders DefaultRequestHeaders => _httpClient.DefaultRequestHeaders; private readonly HttpClient _httpClient = new HttpClient(); public async Task<HttpResponseMessage> SendAsync(HttpRequestMessage message, HttpCompletionOption option, CancellationToken token) { return await _httpClient.SendAsync(message, option, token); } public void Dispose() { _httpClient.Dispose(); DefaultRequestHeaders.Clear(); BaseAddress = null; } } public class UnityWebRequestHttpClient : IHttpClient { public UnityWebRequestHttpClient() { } public UnityWebRequestHttpClient(string baseUri) { BaseAddress = new Uri(baseUri); } public UnityWebRequestHttpClient(Uri baseUri) { BaseAddress = baseUri; } public Uri BaseAddress { get; set; } public HttpRequestHeaders DefaultRequestHeaders => _httpClient.DefaultRequestHeaders; private readonly HttpClient _httpClient = new HttpClient(); private void AppendARFHeaders(HttpRequestMessage message, UnityWebRequest webRequest) { // Add some ARF headers foreach (var item in message.Headers) { try { List<string> li = item.Value as List<string>; if (item.Key == "token") webRequest.SetRequestHeader(item.Key, li[0].ToString()); // add it if (item.Key == "sessionID") webRequest.SetRequestHeader(item.Key, li[0].ToString()); // add it } catch { } // ignore it } } public async Task<HttpResponseMessage> SendAsync(HttpRequestMessage message, HttpCompletionOption option, CancellationToken token) { var content = await (message.Content?.ReadAsStringAsync() ?? Task.FromResult("")); var webRequest = GetUnityWebRequest(message.Method.Method, message.RequestUri, content); AppendHeaders(webRequest); // Add the ARF API headers AppendARFHeaders(message, webRequest); Debug.Log("[HTTP] Request " + webRequest.uri.ToString()); try { //SylR webRequest.SendWebRequest(); while (!webRequest.isDone) { if (token.IsCancellationRequested) { Debug.Log($"Task '{ message.RequestUri }' cancelled"); token.ThrowIfCancellationRequested(); } await Task.Yield(); } //await webRequest // .SendWebRequest() // .WithCancellation(cancellationToken: token); } catch (Exception) { webRequest.Dispose(); throw; } Debug.Log("[HTTP] Result: " + webRequest.result.ToString()); var responseMessage = CreateHttpResponseMessage(webRequest); webRequest.Dispose(); Debug.Log("[HTTP] Response len: " + responseMessage.Content.Headers.ContentLength); return responseMessage; } public void Dispose() { _httpClient.Dispose(); DefaultRequestHeaders.Clear(); BaseAddress = null; } private UnityWebRequest GetUnityWebRequest(string method, Uri endpoint, string content = "") { var requestUri = BaseAddress.AbsoluteUri + endpoint; var webRequest = UnityWebRequest.Get(requestUri); webRequest.method = method; webRequest.disposeUploadHandlerOnDispose = true; webRequest.disposeDownloadHandlerOnDispose = true; if (!string.IsNullOrEmpty(content)) { var data = new System.Text.UTF8Encoding().GetBytes(content); webRequest.uploadHandler = new UploadHandlerRaw(data); webRequest.SetRequestHeader("Content-Type", "application/json"); //webRequest.SetRequestHeader("Content-Type", "image/jpeg"); } return webRequest; } private void AppendHeaders(UnityWebRequest webRequest) { using var enumerator = DefaultRequestHeaders.GetEnumerator(); while (enumerator.MoveNext()) { var (key, value) = enumerator.Current; webRequest.SetRequestHeader(key, value.First()); } } private HttpResponseMessage CreateHttpResponseMessage(UnityWebRequest webRequest) { var responseContent = webRequest.downloadHandler?.text; var response = new HttpResponseMessage(); response.Content = new StringContent(responseContent); response.StatusCode = (HttpStatusCode)webRequest.responseCode; Dictionary<string, string> headers = webRequest.GetResponseHeaders(); if (headers != null) { Debug.Log("[HTTP] Header: " + headers.Count.ToString()); foreach (var h in headers) { switch (h.Key.ToLower().Trim()) { case "content-type": { var trimmed = h.Value.ToLower().Split(";").FirstOrDefault(); response.Content.Headers.ContentType = new MediaTypeHeaderValue(trimmed); break; } case "content-length": response.Content.Headers.ContentLength = long.Parse(h.Value); break; default: if (h.Value == "gzip") { // bug??? } else response.Headers.Add(h.Key, h.Value); break; } } } return response; } } }