Skip to content
Snippets Groups Projects
Commit ce716a98 authored by Sylvain Renault's avatar Sylvain Renault
Browse files

Merge branch 'feature/WAClientWithWebsockets' into 'main'

Finalisation of REST and websocket client functions and subscription methodics.

See merge request !2
parents 68788e54 866c2b4a
Branches Version_2.0.0 main
Tags 2.0.0
1 merge request!2Finalisation of REST and websocket client functions and subscription methodics.
Showing
with 1097 additions and 150 deletions
fileFormatVersion: 2
guid: 01f9889f0a04026429aa8459e181e973
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace ETSI.ARF.OpenAPI.WorldAnalysis
{
/// <summary>
/// Simple class to debug the requests
/// </summary>
public class BaseClient
{
static public bool EnableClientLog = true;
public string lastJsonText;
public long lastPayload;
protected void _prepareRequest(ETSI.ARF.OpenAPI.WorldAnalysis.IHttpClient client, System.Net.Http.HttpRequestMessage request, string url)
{
if (EnableClientLog)
{
Debug.Log("[REST][URL] Send request: " + client.BaseAddress + url);
Debug.Log("[REST][URL] Send request: " + request);
}
}
protected void _processResponse(ETSI.ARF.OpenAPI.WorldAnalysis.IHttpClient client, System.Net.Http.HttpResponseMessage response)
{
lastJsonText = response.Content.ReadAsStringAsync().Result.ToString();
lastPayload = response.Content.Headers.ContentLength.Value;
var status_ = (int)response.StatusCode;
if (EnableClientLog)
{
Debug.Log("[REST][Data] Status: " + status_ + " Response: " + client.BaseAddress + " Len: " + lastPayload + " JSON: " + lastJsonText);
}
}
}
}
\ No newline at end of file
fileFormatVersion: 2
guid: 696362f892252db4ca192c6eccbbfa25
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace ETSI.ARF.OpenAPI.WorldAnalysis
{
public interface IModel
{
public System.Guid Uuid { get; set; } // Bug: SylR: Why is Uuid not capitalized (UUID)???
public string ToJson();
}
// Class to monitor the server
public class Server : IModel
{
public System.Guid Uuid { get; set; }
public string Name { get; set; }
public Server(string name)
{
Uuid = Guid.Empty;
Name = name;
}
public string ToJson() { return Newtonsoft.Json.JsonConvert.SerializeObject(this); }
}
//public class MatrixPose : Pose
//{
// [Newtonsoft.Json.JsonProperty("matrixValue", Required = Newtonsoft.Json.Required.AllowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
// public MatrixPoseValue MatrixValue { get; set; }
//}
//
// Implement here some constructors
//
public partial class Pose : IModel
{
public Pose()
{
Uuid = Guid.NewGuid();
}
public string ToJson() { return Newtonsoft.Json.JsonConvert.SerializeObject(this); }
}
}
\ No newline at end of file
fileFormatVersion: 2
guid: 90e60e9d41d419146b3b6207e4ef556e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
This diff is collapsed.
// The Fraunhofer HHI Unity Framework
// ___________ .__ _____ ___ ___ ___ ___ .___
// \_ _____/___________ __ __ ____ | |__ _____/ ____\___________ / | \ / | \| |
// | __) \_ __ \__ \ | | \/ \| | \ / _ \ __\/ __ \_ __ \ / ~ \/ ~ \ |
// | \ | | \// __ \| | / | \ Y ( <_> ) | \ ___/| | \/ \ Y /\ Y / |
// \___ / |__| (____ /____/|___| /___| /\____/|__| \___ >__| \___|_ / \___|_ /|___|
// \/ \/ \/ \/ \/ \/ \/
// (C) Fraunhofer HHI, 2024
using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using UnityEngine;
namespace ETSI.ARF.OpenAPI.WorldAnalysis
{
public class CancelToken
{
protected CancellationTokenSource tokenSource;
protected CancellationToken ct;
public CancellationToken cancellationToken { get => ct; }
public void Cancel()
{
tokenSource.Cancel();
}
}
public class ResponseObject<T> : CancelToken
{
// Management stuffs
static int ID = 0;
public int transactionId = 0;
public string message = ""; // custom message, type of data...
// Time monitoring
public TimeSpan DeltaTime { get => responseTime - requestTime; }
public DateTime requestTime;
public DateTime responseTime;
// Incoming data
public T result;
public int payload; // size of data
//public string result = ""; // text result
//public object data = null; // custom result
// Callback
public Action<ResponseObject<T>> callback;
public ResponseObject(string msg, Action<ResponseObject<T>> func = null)
{
requestTime = DateTime.Now;
message = msg;
callback = func;
transactionId = ++ID;
tokenSource = new CancellationTokenSource();
ct = tokenSource.Token;
}
}
}
\ No newline at end of file
fileFormatVersion: 2
guid: 82dc58594acebf747bd716fd00fcfb10
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
//
// 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;
}
}
}
\ No newline at end of file
fileFormatVersion: 2
guid: 4aa105d9487dc27408feffefde9e4475
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
namespace ETSI.ARF.OpenAPI.WorldAnalysis
{
/// <summary>
/// Catch the pre/pos request methods from the autogenerated classes
/// </summary>
public partial class WorldAnalysisClient : BaseClient
{
partial void PrepareRequest(ETSI.ARF.OpenAPI.WorldAnalysis.IHttpClient client, System.Net.Http.HttpRequestMessage request, string url)
{
_prepareRequest(client, request, url);
}
partial void PrepareRequest(ETSI.ARF.OpenAPI.WorldAnalysis.IHttpClient client, System.Net.Http.HttpRequestMessage request, System.Text.StringBuilder urlBuilder)
{
// do something...
}
partial void ProcessResponse(ETSI.ARF.OpenAPI.WorldAnalysis.IHttpClient client, System.Net.Http.HttpResponseMessage response)
{
_processResponse(client, response);
}
}
}
\ No newline at end of file
fileFormatVersion: 2
guid: 748d04737c53fc04697ac6888226e399
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: 6f0fd52fd30564a4c838a545fe0d00ec
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
//
// 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: March 2024
//
using System;
using System.Threading.Tasks;
using UnityEngine;
using ETSI.ARF.OpenAPI;
using ETSI.ARF.OpenAPI.WorldAnalysis;
namespace ETSI.ARF.WorldAnalysis.REST
{
public class AdminRequest : RequestBase<string>
{
#region Test methods
static public void CheckServer(WorldAnalysisServer wa)
{
string ping = AdminRequest.PingSync(wa);
string state = AdminRequest.AdminSync(wa);
string ver = AdminRequest.VersionSync(wa);
Debug.Log("[REST] WA Ping: " + ping);
Debug.Log("[REST] WA State: " + state);
Debug.Log("[REST] WA Version: " + ver);
}
#endregion
//
// Wrapper for the endpoints
//
static public string PingSync(WorldAnalysisServer ws)
{
wsServer = ws;
var httpClient = new BasicHTTPClient(ws.URI);
apiClient = new WorldAnalysisClient(httpClient);
string response = apiClient.GetPing();
return response;
}
static public ResponseObject<string> PingAsync(WorldAnalysisServer ws, Action<ResponseObject<string>> func)
{
wsServer = ws;
var httpClient = new UnityWebRequestHttpClient(ws.URI);
apiClient = new WorldAnalysisClient(httpClient);
Debug.Log("[REST] Request Ping...");
ResponseObject<string> ro = new ResponseObject<string>("Request Ping", func);
apiClient.GetPingAsync(ro.cancellationToken).ContinueWith(OnReceiveObject<string>, ro);
return ro;
}
static public string AdminSync(WorldAnalysisServer ws)
{
wsServer = ws;
var httpClient = new BasicHTTPClient(ws.URI);
apiClient = new WorldAnalysisClient(httpClient);
string response = apiClient.GetAdmin();
return response;
}
static public ResponseObject<string> AdminAsync(WorldAnalysisServer ws, Action<ResponseObject<string>> func)
{
wsServer = ws;
var httpClient = new UnityWebRequestHttpClient(ws.URI);
apiClient = new WorldAnalysisClient(httpClient);
Debug.Log("[REST] Request Admin...");
ResponseObject<string> ro = new ResponseObject<string>("Request Admin", func);
apiClient.GetAdminAsync(ro.cancellationToken).ContinueWith(OnReceiveObject<string>, ro);
return ro;
}
static public string VersionSync(WorldAnalysisServer ws)
{
wsServer = ws;
var httpClient = new BasicHTTPClient(ws.URI);
apiClient = new WorldAnalysisClient(httpClient);
string response = apiClient.GetVersion();
return response;
}
static public ResponseObject<string> VersionAsync(WorldAnalysisServer ws, Action<ResponseObject<string>> func)
{
wsServer = ws;
var httpClient = new UnityWebRequestHttpClient(ws.URI);
apiClient = new WorldAnalysisClient(httpClient);
Debug.Log("[REST] Request Version...");
ResponseObject<string> ro = new ResponseObject<string>("Request Version", func);
apiClient.GetVersionAsync(ro.cancellationToken).ContinueWith(OnReceiveObject<string>, ro);
return ro;
}
}
}
fileFormatVersion: 2
guid: f287a535887d14c46859e33386b90b0e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
//
// 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: March 2024
//
using System;
using System.Threading.Tasks;
using System.Collections.Generic;
using UnityEngine;
using ETSI.ARF.OpenAPI;
using ETSI.ARF.OpenAPI.WorldAnalysis;
namespace ETSI.ARF.WorldAnalysis.REST
{
public class RequestBase<T> // where T : Trackable, WorldAnchor, WorldLink
{
static protected WorldAnalysisServer wsServer;
static protected WorldAnalysisClient apiClient;
static protected string token = "ARF_Permission";
// Cache the current list
static public Dictionary<Guid, object> listOfObjects = new Dictionary<Guid, object>();
//
// Helpers
//
static protected void OnReceiveObject<TObj>(Task<TObj> t, object id)
{
if (t.IsCompleted)
{
ResponseObject<TObj> o = (ResponseObject<TObj>)id;
o.responseTime = DateTime.Now;
o.result = t.Result;
Debug.Log($"[REST] Server Response = {o.result.ToString()} (ID={o.transactionId}, Msg={o.message})");
o.callback?.Invoke(o);
}
else Debug.Log("[REST] OpenAPI Timeout!");
}
static protected void OnReceiveListOfObjects<TObj>(Task<List<TObj>> t, object id) where TObj : IModel
{
if (t.IsCompleted)
{
ResponseObject<List<TObj>> o = (ResponseObject<List<TObj>>)id;
o.responseTime = DateTime.Now;
o.result = t.Result;
Debug.Log($"[REST] Server Response = Got {o.result.Count} entrie(s) (ID={o.transactionId}, Msg={o.message})");
listOfObjects.Clear();
foreach (var i in o.result)
{
listOfObjects.Add(i.Uuid, i);
}
o.callback?.Invoke(o);
}
else Debug.Log("[REST] OpenAPI Timeout!");
}
}
}
fileFormatVersion: 2
guid: c6453e5289606a848ae29ddffa0f7288
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
//
// 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: August 2024
//
using ETSI.ARF.WorldAnalysis;
using UnityEngine;
public class WorldAnalysisInfo : MonoBehaviour
{
public WorldAnalysisServer worldAnalysisServer;
//public bool isServerAlive()
//{
// if (worldStorageServer == null) return false;
// return !string.IsNullOrEmpty(ETSI.ARF.WorldStorage.REST.AdminRequest.Ping(worldStorageServer));
//}
//public string GetServerState()
//{
// if (worldStorageServer == null) return "No Server Defined!";
// return ETSI.ARF.WorldStorage.REST.AdminRequest.GetAdminInfo(worldStorageServer);
//}
//public string GetAPIVersion()
//{
// if (worldStorageServer == null) return "Unknown Version!";
// return ETSI.ARF.WorldStorage.REST.AdminRequest.GetVersion(worldStorageServer);
//}
}
fileFormatVersion: 2
guid: 269d5f98c3c478c4983c24ec09d10a70
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
...@@ -151,7 +151,7 @@ public interface WorldAnalysisInterface ...@@ -151,7 +151,7 @@ public interface WorldAnalysisInterface
/// </summary> /// </summary>
/// <param name="subscriptionUUID">id of the subscription</param> /// <param name="subscriptionUUID">id of the subscription</param>
/// /// <returns>The unsubscription has been performed or not</returns> /// /// <returns>The unsubscription has been performed or not</returns>
public InformationSubscriptionResult UnSubscribeToPose(Guid subscriptionUUID); public InformationSubscriptionResult UnsubscribeFromPose(Guid subscriptionUUID);
#endregion #endregion
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment