Start of change to Json.NET, Rework HttpEndpointController, beta HttpClient Implementation

master
Harald Wolff 2024-04-26 14:49:44 +02:00
parent 82499811c6
commit ceaa7c1685
13 changed files with 271 additions and 88 deletions

View File

@ -7,6 +7,8 @@ using System.Text.RegularExpressions;
using ln.json;
using ln.json.mapping;
using ln.type;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace ln.http
{
@ -61,6 +63,8 @@ namespace ln.http
private HttpArgumentSourceAttribute[] _argumentSourceAttributes;
private Type _returnType;
private RequiresAttribute[] _requiresAttributes;
public MappedEndpoint(HttpEndpointController endpointController, MapAttribute mapAttribute, MethodInfo methodInfo)
{
EndpointController = endpointController;
@ -71,10 +75,89 @@ namespace ln.http
_returnType = MethodInfo.ReturnType;
_argumentSourceAttributes = _parameterInfos
.Select((pi) => pi.GetCustomAttribute<HttpArgumentSourceAttribute>()).ToArray();
_requiresAttributes = methodInfo.GetCustomAttributes<RequiresAttribute>().ToArray();
}
public abstract bool Route(HttpRequestContext httpContext, string routePath);
public bool Route(HttpRequestContext httpContext, string routePath)
{
if (_requiresAttributes.Length > 0){
if (httpContext.AuthenticatedPrincipal is HttpPrincipal)
{
foreach (var requiresAttribute in _requiresAttributes)
{
if (requiresAttribute.Roles.All((role) =>
httpContext.AuthenticatedPrincipal.Roles.Contains(role)))
{
return Route2(httpContext, routePath);
}
}
}
httpContext.Response = HttpResponse.Unauthorized();
return true;
}
return Route2(httpContext, routePath);
}
public abstract bool Route2(HttpRequestContext httpContext, string routePath);
bool TryFindParameterValue(HttpRequestContext context, ParameterInfo parameterInfo, out object value)
{
if (parameterInfo.GetCustomAttribute<JsonBodyAttribute>() is JsonBodyAttribute jsonBodyAttribute)
{
if (jsonBodyAttribute.Property is null)
{
value = context.Request.Json(parameterInfo.ParameterType);
return true;
}
else if (context.Request.Json().TryGetValue(jsonBodyAttribute.Property, out JToken property))
{
value = property.ToObject(parameterInfo.ParameterType);
return true;
}
}
if ((parameterInfo.GetCustomAttribute<RouteAttribute>() is RouteAttribute routeAttribute) &&
context.TryGetParameter(routeAttribute.Name ?? parameterInfo.Name, out string pvalue))
{
value = pvalue;
return true;
}
if ((parameterInfo.GetCustomAttribute<QueryAttribute>() is QueryAttribute queryAttribute) &&
context.Request.Query.TryGetValue(queryAttribute.Name ?? parameterInfo.Name, out string svalue))
{
value = svalue;
return true;
}
if (parameterInfo.ParameterType.Equals(typeof(Stream)))
{
value = context.Request.ContentStream;
return true;
}
if (parameterInfo.ParameterType.Equals(typeof(HttpRequestContext)))
{
value = context;
return true;
}
if (parameterInfo.ParameterType.Equals(typeof(HttpRequest)))
{
value = context.Request;
return true;
}
if (parameterInfo.ParameterType.Equals(typeof(HttpPrincipal)))
{
value = context.AuthenticatedPrincipal;
return true;
}
value = null;
return false;
}
bool TryApplyParameters(HttpRequestContext requestContext, out object[] parameters)
{
parameters = new object[_parameterInfos.Length];
@ -83,78 +166,26 @@ namespace ln.http
{
ParameterInfo parameterInfo = _parameterInfos[n];
if (parameterInfo.ParameterType.Equals(typeof(HttpRequestContext)))
parameters[n] = requestContext;
else if (parameterInfo.ParameterType.Equals(typeof(HttpRequest)))
parameters[n] = requestContext.Request;
else if (parameterInfo.ParameterType.Equals(typeof(HttpPrincipal)))
parameters[n] = requestContext.AuthenticatedPrincipal;
else if (TryFindArgumentByName(
requestContext,
_argumentSourceAttributes[n]?.ArgumentSource ?? HttpArgumentSource.AUTO,
_argumentSourceAttributes[n]?.ArgumentName ?? _parameterInfos[n].Name,
out string parameterValue
))
if (!TryFindParameterValue(requestContext, parameterInfo, out parameters[n]))
parameters[n] = parameterInfo.DefaultValue;
if ((parameters[n] != null) &&
(!parameterInfo.ParameterType.IsAssignableFrom(parameters[n].GetType())))
{
if (!Cast.To(parameterValue, _parameterInfos[n].ParameterType, out parameters[n]))
if (!Cast.To(parameters[n], _parameterInfos[n].ParameterType, out parameters[n]))
{
parameters[n] = TypeDescriptor
.GetConverter(parameterInfo.ParameterType)
.ConvertFromInvariantString(parameterValue);
.ConvertFrom(parameters[n]);
}
}
else if ((_argumentSourceAttributes[n].ArgumentSource == HttpArgumentSource.CONTENT) && (requestContext.Request.Headers.Contains("content-length")))
{
if (_parameterInfos[n].ParameterType.Equals(typeof(Stream)))
{
parameters[n] = requestContext.Request.ContentStream;
} else if (_parameterInfos[n].ParameterType.Equals(typeof(TextReader)))
{
parameters[n] = new StreamReader(requestContext.Request.ContentStream);
} else if (_parameterInfos[n].ParameterType.Equals(typeof(string)))
{
using (var r = new StreamReader(requestContext.Request.ContentStream))
parameters[n] = r.ReadToEnd();
} else if (requestContext.Request.Headers.TryGetValue("Content-Type", out string contentType) &&
contentType.Equals("application/json"))
{
using (TextReader reader = new StreamReader(requestContext.Request.ContentStream))
{
JSONValue jsonValue = JSONParser.Parse(reader);
if (typeof(JSONValue).IsAssignableFrom(_parameterInfos[n].ParameterType))
parameters[n] = jsonValue;
else
parameters[n] = JSONMapper.DefaultMapper.FromJson(jsonValue,
_parameterInfos[n].ParameterType);
}
}
}
else if (_parameterInfos[n].HasDefaultValue)
{
parameters[n] = _parameterInfos[n].DefaultValue;
}
else
if ((parameters[n] == null) && (parameterInfo.ParameterType.IsValueType))
return false;
}
return true;
}
bool TryFindArgumentByName(HttpRequestContext httpContext, HttpArgumentSource argumentSource, string parameterName, out string parameterValue)
{
if (((argumentSource & HttpArgumentSource.PARAMETER) == HttpArgumentSource.PARAMETER) && httpContext.TryGetParameter(parameterName, out parameterValue))
return true;
else if (((argumentSource & HttpArgumentSource.HEADER) == HttpArgumentSource.HEADER) &&
(httpContext.Request.Headers.TryGetValue(parameterName, out parameterValue)))
return true;
else if (((argumentSource & HttpArgumentSource.QUERY) == HttpArgumentSource.QUERY) &&
(httpContext.Request.Query.TryGetValue(parameterName, out parameterValue)))
return true;
parameterValue = null;
return false;
}
private object InvokeMethod(HttpRequestContext httpContext)
{
@ -181,7 +212,7 @@ namespace ln.http
{
}
public override bool Route(HttpRequestContext httpContext, string routePath)
public override bool Route2(HttpRequestContext httpContext, string routePath)
{
object returnedValue = InvokeMethod(httpContext);
@ -204,7 +235,7 @@ namespace ln.http
{
}
public override bool Route(HttpRequestContext httpContext, string routePath)
public override bool Route2(HttpRequestContext httpContext, string routePath)
{
object returnedValue = InvokeMethod(httpContext);
if (returnedValue is HttpResponse httpResponse)

View File

@ -2,9 +2,13 @@
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using ln.http.content;
using ln.json;
using ln.type;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace ln.http
{
@ -154,6 +158,28 @@ namespace ln.http
}
}
private string? _bodyText;
public string Text
{
get
{
if (_bodyText is null)
_bodyText = Encoding.UTF8.GetString(ContentStream.ReadToEnd());
return _bodyText;
}
}
private JObject? _jobject;
public JObject Json()
{
if (_jobject is null)
_jobject = JObject.Load(new JsonTextReader(new StringReader(Text)));
return _jobject;
}
public T Json<T>() => JsonConvert.DeserializeObject<T>(Text);
public object Json(Type nativeType) => JsonConvert.DeserializeObject(Text, nativeType);
public void Dispose() => ContentStream?.Dispose();
}

View File

@ -2,6 +2,7 @@
using System.IO;
using System.Collections.Generic;
using ln.json;
using Newtonsoft.Json;
namespace ln.http
{
@ -133,6 +134,12 @@ namespace ln.http
return this;
}
public HttpResponse Json<T>(T content)
{
HttpContent = new StringContent(JsonConvert.SerializeObject(content), "application/json");
return this;
}
public void Dispose()
{
HttpContent?.Dispose();

View File

@ -46,7 +46,7 @@ namespace ln.http
=> Map(new HttpRoute(httpMethod, route, routerDelegate));
public bool RouteRequest(HttpRequestContext httpRequestContext, string routePath)
public virtual bool RouteRequest(HttpRequestContext httpRequestContext, string routePath)
{
HttpFilters?.Invoke(httpRequestContext);

View File

@ -0,0 +1,14 @@
using System;
namespace ln.http;
[AttributeUsage(AttributeTargets.Parameter)]
public class JsonBodyAttribute : Attribute
{
public string Property { get; set; }
public JsonBodyAttribute(){}
public JsonBodyAttribute(string property)
{
Property = property;
}
}

View File

@ -2,6 +2,7 @@ using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using ln.http.exceptions;
using ln.threading;
@ -47,25 +48,36 @@ public class Listener : IDisposable
Socket.Listen();
Socket.ReceiveTimeout = 10000;
DynamicThreadPool.DefaultPool.Enqueue(ConnectionHandler);
DynamicThreadPool.DefaultPool.Enqueue(ListenerLoop);
}
private void ListenerLoop()
{
while (Socket.IsBound)
{
Socket clientSocket = Socket.Accept();
ThreadPool.QueueUserWorkItem((state => ConnectionHandler(clientSocket)));
// DynamicThreadPool.DefaultPool.Enqueue(()=>ConnectionHandler(clientSocket));
}
}
private void ConnectionHandler()
private void ConnectionHandler(Socket clientSocket)
{
Socket clientSocket = Socket.Accept();
if (Socket.IsBound)
DynamicThreadPool.DefaultPool.Enqueue(ConnectionHandler);
clientSocket.ReceiveTimeout = 10000;
// clientSocket.ReceiveTimeout = 10000;
try
{
using (NetworkStream networkStream = new NetworkStream(clientSocket))
using (NetworkStream networkStream = new NetworkStream(clientSocket))
Accepted(clientSocket, networkStream);
}
catch (SocketException se)
{
}
catch (IOException e)
{
}
finally
{
clientSocket.Close();
clientSocket.Dispose();
}
}

View File

@ -0,0 +1,11 @@
using System;
namespace ln.http;
[AttributeUsage(AttributeTargets.Parameter)]
public class QueryAttribute : Attribute
{
public string Name { get; set; }
public QueryAttribute(){}
}

View File

@ -0,0 +1,13 @@
using System;
namespace ln.http;
public class RequiresAttribute : Attribute
{
public string[] Roles { get; set; }
public RequiresAttribute(params string[] roles)
{
Roles = roles;
}
}

View File

@ -0,0 +1,12 @@
using System;
namespace ln.http;
[AttributeUsage(AttributeTargets.Parameter)]
public class RouteAttribute : Attribute
{
public string Name { get; set; }
public RouteAttribute(){}
}

View File

@ -5,7 +5,6 @@ using System.Net.Security;
using System.Net.Sockets;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
namespace ln.http;
@ -27,20 +26,33 @@ public class TlsListener : Listener
{
_certificateStore = certificateStore;
DefaultCertificate = defaultCertificate;
if (DefaultCertificate is null)
DefaultCertificate = buildSelfSignedServerCertificate();
{
if (_certificateStore.TryGetCertificate("localhost", out defaultCertificate))
DefaultCertificate = defaultCertificate;
}
}
protected override void Accepted(Socket connectedSocket, Stream connectionStream)
{
SslStream sslStream = new SslStream(connectionStream, false, null, CertificateSelectionCallback);
sslStream.AuthenticateAsServer(DefaultCertificate);
try
{
sslStream.AuthenticateAsServer(DefaultCertificate);
// ToDo: Check for correct ALPN protocol identifier
base.Accepted(connectedSocket, sslStream);
// ToDo: Check for correct ALPN protocol identifier
base.Accepted(connectedSocket, sslStream);
}
catch (Exception e)
{
}
finally
{
sslStream?.Dispose();
}
}

View File

@ -1,17 +1,16 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Runtime.InteropServices.JavaScript;
using System.Text;
using ln.http.content;
using ln.http.exceptions;
using ln.logging;
using ln.patterns;
using ln.type;
using Newtonsoft.Json;
namespace ln.http.client
{
public class HttpClient
@ -184,6 +183,33 @@ namespace ln.http.client
public Optional<HttpClientResponse> Post(string uri, IEnumerable<Header> headers, Stream body) => Request(HttpMethod.POST, new Uri(uri), headers, body);
public Optional<HttpClientResponse> Post(string uri, Newtonsoft.Json.JsonToken json) => Post(uri, null, json);
public Optional<HttpClientResponse> Post(string uri, IEnumerable<Header> headers,
Newtonsoft.Json.JsonToken json)
{
using (MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(json.ToString())))
return Request(HttpMethod.POST, new Uri(uri), headers, stream);
}
public Optional<HttpClientResponse> Post(string uri, IEnumerable<Header> headers, string body)
{
using (MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(body)))
return Request(HttpMethod.POST, new Uri(uri), headers, stream);
}
public Optional<HttpClientResponse> Post<T>(string uri, IEnumerable<Header> headers, T body) =>
Post(new Uri(uri), headers, body);
public Optional<HttpClientResponse> Post<T>(Uri uri, IEnumerable<Header> headers, T body)
{
HeaderContainer headerContainer = new HeaderContainer(headers);
if (!headerContainer.Contains("Content-Type"))
headerContainer.Add("Content-Type", "application/json");
using (MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(body))))
return Request(HttpMethod.POST, uri, headerContainer, stream);
}
public static bool ReadResponseLine(Stream stream, out HttpVersion httpVersion, out HttpStatusCode statusCode,
out string reason)
{

View File

@ -1,6 +1,9 @@
using System;
using System.IO;
using System.Text;
using ln.http.content;
using ln.type;
using Newtonsoft.Json;
namespace ln.http.client
{
@ -30,6 +33,20 @@ namespace ln.http.client
_contentStream = contentStream;
}
private string? _bodyText;
public string Text
{
get
{
if (_bodyText is null)
_bodyText = Encoding.UTF8.GetString(ContentStream.ReadToEnd());
return _bodyText;
}
}
public T Json<T>() => JsonConvert.DeserializeObject<T>(Text);
public override string ToString() => String.Format("{0} {1}", (int)StatusCode, StatusCode);
}
}

View File

@ -9,7 +9,7 @@
<Copyright>(c) 2020 Harald Wolff-Thobaben</Copyright>
<PackageTags>http server</PackageTags>
<LangVersion>default</LangVersion>
<PackageVersion>0.9.9</PackageVersion>
<PackageVersion>0.9.10-preview0</PackageVersion>
<AssemblyVersion>0.6.2.0</AssemblyVersion>
<TargetFramework>net7.0</TargetFramework>
</PropertyGroup>
@ -20,7 +20,9 @@
<PackageReference Include="ln.mime" Version="1.1.1" />
<PackageReference Include="ln.patterns" Version="0.1.0-preview7" />
<PackageReference Include="ln.threading" Version="0.2.2" />
<PackageReference Include="ln.type" Version="0.1.9" />
<PackageReference Include="ln.type" Version="0.1.10-preview0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Pandoc" Version="3.1.0" />
</ItemGroup>
</Project>