Release 0.0.6
ln.build - build0.waldrennach.l--n.de build job pending Details

master 0.0.6
Harald Wolff 2020-12-28 08:45:24 +01:00
parent eb1c5c75e8
commit 01fccdc2ba
9 changed files with 316 additions and 7 deletions

View File

@ -3,7 +3,7 @@
<ItemGroup>
<ProjectReference Include="..\ln.http.api\ln.http.api.csproj" />
<PackageReference Include="ln.http" Version="0.3-*" />
<PackageReference Include="ln.http" />
<!--ProjectReference Include="..\..\ln.http\ln.http\ln.http.csproj" /-->
</ItemGroup>

View File

@ -8,7 +8,7 @@ namespace ln.http.api
public static HttpResponse Content(this HttpResponse response, JSONValue json)
{
response.SetHeader("Content-type", "application/json");
response.ContentType("application/json");
response.ContentWriter.Write(json.ToString());
response.ContentWriter.Flush();
return response;

View File

@ -0,0 +1,235 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading;
using ln.http.websocket;
using ln.json;
using ln.json.mapping;
using ln.logging;
using ln.type;
namespace ln.http.api
{
public class JSONEventWebSocketResponse : JSONWebSocketResponse, IDisposable
{
static List<JSONEventWebSocketResponse> activeWebSockets = new List<JSONEventWebSocketResponse>();
public static void SendUpdates()
{
if (Monitor.TryEnter(activeWebSockets, 100))
{
try
{
foreach (JSONEventWebSocketResponse websocket in activeWebSockets)
websocket.SendUpdate();
} catch (Exception e)
{
Logging.Log(e);
} finally
{
Monitor.Exit(activeWebSockets);
}
} else {
Logging.Log(LogLevel.WARNING, "JSONEventWebSocketResponse: SendUpdates(): failed to enter critical section");
}
}
public string DefaultTargetName { get; set; }
Dictionary<string, Target> publishedTargets = new Dictionary<string, Target>();
public JSONEventWebSocketResponse()
{
OnWebSocketReceivedJSON += JSONReceived;
OnWebSocketStateChanged += (WebSocketResponse websocket, WebSocketState newState) => {
switch (newState)
{
case WebSocketState.CLOSED:
lock (activeWebSockets)
activeWebSockets.Remove(this);
break;
case WebSocketState.OPEN:
lock (activeWebSockets)
activeWebSockets.Add(this);
break;
}
};
}
public JSONEventWebSocketResponse(string defaultTargetName)
:this()
{
DefaultTargetName = defaultTargetName;
}
public JSONEventWebSocketResponse(object defaultInstance)
:this()
{
AddTarget(null, defaultInstance, defaultInstance.GetType().GetCustomAttribute<ESTargetAttribute>()?.PublishPublicMembers ?? false, out Target target);
DefaultTargetName = target.Name;
}
public JSONEventWebSocketResponse(object defaultInstance, bool publishPublicMembers)
:this()
{
AddTarget(null, defaultInstance, publishPublicMembers, out Target target);
DefaultTargetName = target.Name;
}
public void AddTarget(object instance) => AddTarget(null, instance, instance.GetType().GetCustomAttribute<ESTargetAttribute>()?.PublishPublicMembers ?? false);
public void AddTarget(object instance, bool publishPublicMembers) => AddTarget(null, instance, publishPublicMembers);
public void AddTarget(string targetName, object instance, bool publishPublicMembers) => AddTarget(targetName, instance, publishPublicMembers, out Target target);
public void AddTarget(string targetName, object instance, bool publishPublicMembers, out Target target)
{
target = new Target(targetName, instance, publishPublicMembers);
publishedTargets.Add(target.Name, target);
}
void JSONReceived(JSONWebSocketResponse sender, JSONValue json)
{
if (json is JSONObject jsonMessage)
{
string eventName = jsonMessage["event"].ToNative().ToString();
string targetName = jsonMessage["target"].ToNative().ToString();
JSONValue jsonValue = jsonMessage["value"];
Target target = publishedTargets[targetName ?? DefaultTargetName];
switch (eventName)
{
case "action":
target.Call(jsonValue);
break;
case "set":
target.SetProperty(jsonValue);
break;
}
}
}
public virtual void SendUpdate()
{
JSONObject jsonUpdateValue = new JSONObject();
JSONObject jsonUpdateMessage = new JSONObject()
.Add("event", "update")
.Add("value", jsonUpdateValue);
foreach (Target target in publishedTargets.Values)
jsonUpdateValue.Add(target.Name, target.CreateUpdateValue());
Send(jsonUpdateMessage);
}
public void Dispose()
{
lock (activeWebSockets)
activeWebSockets.Remove(this);
}
public class Target
{
public string Name { get; }
public object Instance { get; }
Dictionary<string,MethodInfo> publishedMethods = new Dictionary<string, MethodInfo>();
Dictionary<string,PropertyInfo> publishedProperties = new Dictionary<string, PropertyInfo>();
Dictionary<string,FieldInfo> publishedFields = new Dictionary<string, FieldInfo>();
public Target(object instance, bool publishPublicMembers) :this(instance.GetType().Name, instance, publishPublicMembers) { }
public Target(object instance) :this(instance.GetType().Name, instance, false) { }
public Target(string targetName, object instance) : this(targetName, instance, false) { }
public Target(string targetName, object instance, bool publishPublicMembers)
{
Instance = instance;
Name = targetName ?? Instance.GetType().Name;
Initialize(publishPublicMembers);
}
void Initialize(bool publishPublicMembers)
{
foreach (MethodInfo methodInfo in Instance.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy))
{
ESMethodAttribute methodAttribute = methodInfo.GetCustomAttribute<ESMethodAttribute>();
if ((methodAttribute != null) || (publishPublicMembers && methodInfo.IsPublic))
publishedMethods.Add(methodAttribute?.Name ?? methodInfo.Name, methodInfo);
}
foreach (FieldInfo fieldInfo in Instance.GetType().GetFields(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy))
{
ESPropertyAttribute propertyAttribute = fieldInfo.GetCustomAttribute<ESPropertyAttribute>();
if ((propertyAttribute != null) || (publishPublicMembers && fieldInfo.IsPublic))
publishedFields.Add(propertyAttribute?.Name ?? fieldInfo.Name, fieldInfo);
}
foreach (PropertyInfo propertyInfo in Instance.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy))
{
ESPropertyAttribute propertyAttribute = propertyInfo.GetCustomAttribute<ESPropertyAttribute>();
if ((propertyAttribute != null) || (publishPublicMembers && propertyInfo.GetMethod.IsPublic))
publishedProperties.Add(propertyAttribute?.Name ?? propertyInfo.Name, propertyInfo);
}
}
public JSONValue CreateUpdateValue()
{
JSONObject jsonUpdate = new JSONObject();
foreach (PropertyInfo propertyInfo in publishedProperties.Values)
if (propertyInfo.CanRead)
jsonUpdate.Add(propertyInfo.Name, JSONMapper.DefaultMapper.ToJson(propertyInfo.GetValue(Instance)));
foreach (FieldInfo fieldInfo in publishedFields.Values)
jsonUpdate.Add(fieldInfo.Name, JSONMapper.DefaultMapper.ToJson(fieldInfo.GetValue(Instance)));
return jsonUpdate;
}
public void Call(JSONValue callValue)
{
string methodName;
MethodInfo methodInfo;
object[] arguments;
if (callValue is JSONString jsonMethodName)
{
methodName = jsonMethodName.Value;
methodInfo = publishedMethods[methodName];
arguments = new object[0];
}
else if (callValue is JSONObject callObject)
{
methodName = callObject["method"].ToNative().ToString();
methodInfo = publishedMethods[methodName];
if (!JSONMapper.DefaultMapper.MapMethodParameters(methodInfo, callObject["arguments"], out arguments))
throw new ArgumentException(nameof(callValue));
}
else
throw new ArgumentOutOfRangeException(nameof(callValue));
methodInfo.Invoke(Instance, arguments);
}
public void Call(string methodName, object[] arguments)
{
MethodInfo methodInfo = publishedMethods[methodName];
methodInfo.Invoke(Instance, arguments);
}
public void SetProperty(JSONValue jsonValue)
{
if (jsonValue is JSONObject setObject)
{
string propertyName = setObject["name"].ToNative().ToString();
if (publishedProperties.TryGetValue(propertyName, out PropertyInfo propertyInfo) && propertyInfo.CanWrite)
propertyInfo.SetValue(Instance, Cast.To(setObject["value"].ToNative(), propertyInfo.PropertyType));
else if (publishedFields.TryGetValue(propertyName, out FieldInfo fieldInfo) && !fieldInfo.IsInitOnly)
fieldInfo.SetValue(Instance, Cast.To(setObject["value"].ToNative(), fieldInfo.FieldType));
}
}
}
}
}

View File

@ -0,0 +1,39 @@
using System;
using ln.http.websocket;
using ln.json;
using ln.logging;
namespace ln.http.api
{
public delegate void WebSocketReceivedJSON(JSONWebSocketResponse sender, JSONValue json);
public class JSONWebSocketResponse : WebSocketResponse
{
public event WebSocketReceivedJSON OnWebSocketReceivedJSON;
public JSONWebSocketResponse(){ }
public override void Received(string textMessage)
{
base.Received(textMessage);
try
{
JSONValue json = JSONParser.Parse(textMessage);
try {
OnWebSocketReceivedJSON?.Invoke(this, json);
} catch (Exception ex)
{
Logging.Log(ex);
}
} catch (Exception e)
{
Logging.Log(LogLevel.ERROR, "JSONWebSocketResponse: received unparsable json message: {0}", textMessage);
Close();
}
}
public void Send(JSONValue json) => Send(json.ToString());
}
}

View File

@ -51,12 +51,14 @@ namespace ln.http.api
return httpResponse;
if (result is JSONValue jsonResult)
return defaultResponseFactory().Content(jsonResult);
return defaultResponseFactory()
.ContentType("application/json")
.Content(jsonResult);
if (JSONMapper.DefaultMapper.Serialize(result, out JSONValue json))
return defaultResponseFactory()
.ContentType("application/json")
.Content(result.ToString())
.Content(json.ToString())
;
return HttpResponse.InternalServerError().Content("Method result could not be serialized");

View File

@ -0,0 +1,10 @@
using System;
namespace ln.http.api
{
public class ESMethodAttribute : Attribute
{
public string Name { get; set; }
}
}

View File

@ -0,0 +1,11 @@
using System;
namespace ln.http.api
{
public class ESPropertyAttribute : Attribute
{
public string Name { get; set; }
public bool ReadOnly { get; set; }
}
}

View File

@ -0,0 +1,12 @@
using System;
using System.Reflection;
namespace ln.http.api
{
public class ESTargetAttribute : Attribute
{
public bool PublishPublicMembers { get; set; }
}
}

View File

@ -4,7 +4,7 @@
<TargetFramework>netcoreapp3.1</TargetFramework>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Version>0.0.4</Version>
<Version>0.0.6</Version>
<Authors>Harald Wolff-Thobaben</Authors>
<Company>l--n.de</Company>
<Description>Framework to create REST like APIs</Description>
@ -13,8 +13,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ln.http" Version="0.3" />
<PackageReference Include="ln.json" Version="1.0" />
<PackageReference Include="ln.http" Version="0.4.1" />
<PackageReference Include="ln.json" Version="1.0.7" />
<PackageReference Include="ln.type" Version="0.1.6" />
<!--ProjectReference Include="..\..\ln.http\ln.http\ln.http.csproj" /-->
</ItemGroup>