Start of change to Json.NET, Rework HttpEndpointController, beta HttpClient Implementation
parent
82499811c6
commit
ceaa7c1685
|
@ -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)
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
using System;
|
||||
|
||||
namespace ln.http;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Parameter)]
|
||||
public class QueryAttribute : Attribute
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public QueryAttribute(){}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
using System;
|
||||
|
||||
namespace ln.http;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Parameter)]
|
||||
public class RouteAttribute : Attribute
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public RouteAttribute(){}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue