Initial Commit
commit
446247a549
|
@ -0,0 +1,46 @@
|
|||
# Autosave files
|
||||
*~
|
||||
|
||||
# build
|
||||
[Oo]bj/
|
||||
[Bb]in/
|
||||
packages/
|
||||
TestResults/
|
||||
|
||||
# globs
|
||||
Makefile.in
|
||||
*.DS_Store
|
||||
*.sln.cache
|
||||
*.suo
|
||||
*.cache
|
||||
*.pidb
|
||||
*.userprefs
|
||||
*.usertasks
|
||||
config.log
|
||||
config.make
|
||||
config.status
|
||||
aclocal.m4
|
||||
install-sh
|
||||
autom4te.cache/
|
||||
*.user
|
||||
*.tar.gz
|
||||
tarballs/
|
||||
test-results/
|
||||
Thumbs.db
|
||||
.vs/
|
||||
|
||||
# Mac bundle stuff
|
||||
*.dmg
|
||||
*.app
|
||||
|
||||
# resharper
|
||||
*_Resharper.*
|
||||
*.Resharper
|
||||
|
||||
# dotCover
|
||||
*.dotCover
|
||||
|
||||
*.log
|
||||
*.log.old
|
||||
.vscode
|
||||
.build
|
|
@ -0,0 +1,71 @@
|
|||
using System.Reflection.Metadata;
|
||||
using ln.http.api.attributes;
|
||||
using ln.http.router;
|
||||
using ln.json.mapping;
|
||||
using ln.type;
|
||||
|
||||
namespace ln.http.api.demo
|
||||
{
|
||||
class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
DemoApi demoApi = new DemoApi();
|
||||
|
||||
SimpleRouter webRouter = new SimpleRouter();
|
||||
|
||||
webRouter.AddSimpleRoute("/api/v1/*", demoApi);
|
||||
|
||||
HTTPServer httpServer = new HTTPServer(new LoggingRouter(webRouter));
|
||||
httpServer.AddEndpoint(new Endpoint(IPv6.ANY, 3434));
|
||||
|
||||
httpServer.Start();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class DemoApi : WebApiController
|
||||
{
|
||||
|
||||
[POST("/helloworld/:n")]
|
||||
[POST("/helloworld")]
|
||||
public string GetHelloWorld([FromContent( SourceName = "m")]int n, [FromParameter( SourceName = "n")]int i = -1)
|
||||
{
|
||||
return string.Format("Hello World. You gave me a n={0} / i={1}!", n, i);
|
||||
}
|
||||
|
||||
|
||||
[GET("/users/:id")]
|
||||
public User GetUser(int id)
|
||||
{
|
||||
return new User();
|
||||
}
|
||||
|
||||
[POST("/users")]
|
||||
public User CreateUser([FromContent]User user)
|
||||
{
|
||||
user.PrimaryGroup += " / or another one";
|
||||
return user;
|
||||
}
|
||||
|
||||
[GET("/users")]
|
||||
public User[] GetUsers()
|
||||
{
|
||||
return new User[]{
|
||||
new User(),
|
||||
new User(),
|
||||
new User()
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class User
|
||||
{
|
||||
public string Username { get; set;} = "niclasundharald";
|
||||
public string PrimaryGroup { get; set; } = "admins";
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ln.http.api\ln.http.api.csproj" />
|
||||
|
||||
<!--PackageReference Include="ln.http" Version="0.2.1-test" /-->
|
||||
<ProjectReference Include="..\..\ln.http\ln.http\ln.http.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,48 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.26124.0
|
||||
MinimumVisualStudioVersion = 15.0.26124.0
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ln.http.api", "ln.http.api\ln.http.api.csproj", "{A8CE1889-2BFC-4EB1-A0E8-52A9CA30C3A2}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ln.http.api.demo", "ln.http.api.demo\ln.http.api.demo.csproj", "{DA9FFEBD-1422-4062-94FC-88AEF2CEC3C4}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{A8CE1889-2BFC-4EB1-A0E8-52A9CA30C3A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A8CE1889-2BFC-4EB1-A0E8-52A9CA30C3A2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A8CE1889-2BFC-4EB1-A0E8-52A9CA30C3A2}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{A8CE1889-2BFC-4EB1-A0E8-52A9CA30C3A2}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{A8CE1889-2BFC-4EB1-A0E8-52A9CA30C3A2}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{A8CE1889-2BFC-4EB1-A0E8-52A9CA30C3A2}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{A8CE1889-2BFC-4EB1-A0E8-52A9CA30C3A2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A8CE1889-2BFC-4EB1-A0E8-52A9CA30C3A2}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A8CE1889-2BFC-4EB1-A0E8-52A9CA30C3A2}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{A8CE1889-2BFC-4EB1-A0E8-52A9CA30C3A2}.Release|x64.Build.0 = Release|Any CPU
|
||||
{A8CE1889-2BFC-4EB1-A0E8-52A9CA30C3A2}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{A8CE1889-2BFC-4EB1-A0E8-52A9CA30C3A2}.Release|x86.Build.0 = Release|Any CPU
|
||||
{DA9FFEBD-1422-4062-94FC-88AEF2CEC3C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{DA9FFEBD-1422-4062-94FC-88AEF2CEC3C4}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{DA9FFEBD-1422-4062-94FC-88AEF2CEC3C4}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{DA9FFEBD-1422-4062-94FC-88AEF2CEC3C4}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{DA9FFEBD-1422-4062-94FC-88AEF2CEC3C4}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{DA9FFEBD-1422-4062-94FC-88AEF2CEC3C4}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{DA9FFEBD-1422-4062-94FC-88AEF2CEC3C4}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{DA9FFEBD-1422-4062-94FC-88AEF2CEC3C4}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{DA9FFEBD-1422-4062-94FC-88AEF2CEC3C4}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{DA9FFEBD-1422-4062-94FC-88AEF2CEC3C4}.Release|x64.Build.0 = Release|Any CPU
|
||||
{DA9FFEBD-1422-4062-94FC-88AEF2CEC3C4}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{DA9FFEBD-1422-4062-94FC-88AEF2CEC3C4}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
|
@ -0,0 +1,10 @@
|
|||
|
||||
|
||||
namespace ln.http.api
|
||||
{
|
||||
|
||||
public enum HttpMethod {
|
||||
HEAD, GET, POST, PUT, PATCH, DELETE, CONNECT, OPTIONS, TRACE
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
using ln.json;
|
||||
|
||||
namespace ln.http.api
|
||||
{
|
||||
|
||||
public static class HttpResponseExtensions
|
||||
{
|
||||
|
||||
public static HttpResponse Content(this HttpResponse response, JSONValue json)
|
||||
{
|
||||
response.SetHeader("Content-type", "application/json");
|
||||
response.ContentWriter.Write(json.ToString());
|
||||
response.ContentWriter.Flush();
|
||||
return response;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,159 @@
|
|||
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using ln.http.api.attributes;
|
||||
using ln.http.exceptions;
|
||||
using ln.http.router;
|
||||
using ln.json;
|
||||
using ln.json.mapping;
|
||||
|
||||
namespace ln.http.api
|
||||
{
|
||||
|
||||
public abstract class WebApiController : SimpleRouter
|
||||
{
|
||||
Dictionary<string,EndpointMethodCache> methodCaches = new Dictionary<string, EndpointMethodCache>();
|
||||
|
||||
public WebApiController()
|
||||
{
|
||||
Initialize();
|
||||
}
|
||||
|
||||
void Initialize()
|
||||
{
|
||||
foreach (MethodInfo methodInfo in GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
|
||||
{
|
||||
foreach (EndpointAttribute endpointAttribute in methodInfo.GetCustomAttributes<EndpointAttribute>())
|
||||
{
|
||||
if (endpointAttribute != null)
|
||||
{
|
||||
EndpointMethodCache methodCache = GetOrCreateMethodCache(endpointAttribute.Route);
|
||||
methodCache.Add(endpointAttribute.Method, methodInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HttpResponse MapRequest(HttpRequest request, MethodInfo methodInfo)
|
||||
{
|
||||
MapArguments(request, methodInfo, out object[] arguments);
|
||||
|
||||
object result = methodInfo.Invoke(this, arguments);
|
||||
|
||||
if (!methodInfo.ReturnType.Equals(typeof(HttpResponse)))
|
||||
{
|
||||
if (!typeof(JSONValue).IsAssignableFrom(methodInfo.ReturnType))
|
||||
{
|
||||
if (!JSONMapper.DefaultMapper.Serialize(result, out JSONValue json))
|
||||
return HttpResponse.InternalServerError().Content("Method result could not be serialized");
|
||||
|
||||
result = json;
|
||||
}
|
||||
return HttpResponse.OK().Content((JSONValue)result);
|
||||
}
|
||||
|
||||
return HttpResponse.OK().Content(result.ToString());
|
||||
}
|
||||
|
||||
void MapArguments(HttpRequest request, MethodInfo methodInfo,out object[] arguments)
|
||||
{
|
||||
ParameterInfo[] parameterInfos = methodInfo.GetParameters();
|
||||
arguments = new object[parameterInfos.Length];
|
||||
|
||||
JSONValue jsonContent = null;
|
||||
|
||||
if (request.GetRequestHeader("content-type", "").Equals("application/json"))
|
||||
{
|
||||
jsonContent = JSONParser.Parse(request.ContentReader.ReadToEnd());
|
||||
}
|
||||
|
||||
for (int n=0;n<arguments.Length;n++)
|
||||
{
|
||||
ParameterInfo parameterInfo = parameterInfos[n];
|
||||
ArgumentSourceAttribute sourceAttribute = parameterInfo.GetCustomAttribute<ArgumentSourceAttribute>() ?? ArgumentSourceAttribute.Default;
|
||||
|
||||
if (!FindArgumentByName(sourceAttribute, request, jsonContent, parameterInfo.Name, out object value))
|
||||
{
|
||||
if (parameterInfo.HasDefaultValue)
|
||||
{
|
||||
arguments[n] = parameterInfo.DefaultValue;
|
||||
} else
|
||||
throw new BadRequestException();
|
||||
} else {
|
||||
try
|
||||
{
|
||||
if (value is JSONValue jsonValue)
|
||||
{
|
||||
if (!JSONMapper.DefaultMapper.Deserialize(jsonValue, parameterInfo.ParameterType, out arguments[n]))
|
||||
throw new BadRequestException();
|
||||
} else
|
||||
{
|
||||
arguments[n] = Convert.ChangeType(value, parameterInfo.ParameterType);
|
||||
}
|
||||
} catch (FormatException)
|
||||
{
|
||||
throw new BadRequestException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool FindArgumentByName(ArgumentSourceAttribute sourceAttribute, HttpRequest request, JSONValue jsonContent, string parameterName, out object value)
|
||||
{
|
||||
parameterName = sourceAttribute.SourceName ?? parameterName;
|
||||
|
||||
if (((sourceAttribute.Source & ArgumentSource.PARAMETER) == ArgumentSource.PARAMETER) && request.ContainsParameter(parameterName))
|
||||
{
|
||||
value = request.GetParameter(parameterName);
|
||||
return true;
|
||||
} else if (((sourceAttribute.Source & ArgumentSource.CONTENT) == ArgumentSource.CONTENT) && (jsonContent is JSONObject jsonObject) && (jsonObject.ContainsKey(parameterName)))
|
||||
{
|
||||
value = jsonObject[parameterName];
|
||||
return true;
|
||||
}
|
||||
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
EndpointMethodCache GetOrCreateMethodCache(string route)
|
||||
{
|
||||
if (!methodCaches.TryGetValue(route, out EndpointMethodCache methodCache))
|
||||
{
|
||||
methodCache = new EndpointMethodCache();
|
||||
methodCaches.Add(route, methodCache);
|
||||
AddSimpleRoute(route, (request) => {
|
||||
MethodInfo methodInfo = methodCache[Enum.Parse<HttpMethod>(request.Method)];
|
||||
return MapRequest(request, methodInfo);
|
||||
});
|
||||
}
|
||||
return methodCache;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
class EndpointMethodCache
|
||||
{
|
||||
Dictionary<HttpMethod,MethodInfo> cache = new Dictionary<HttpMethod, MethodInfo>();
|
||||
public EndpointMethodCache()
|
||||
{
|
||||
}
|
||||
|
||||
public MethodInfo this[HttpMethod httpMethod]
|
||||
{
|
||||
get => cache[httpMethod];
|
||||
set => cache[httpMethod] = value;
|
||||
}
|
||||
|
||||
public void Add(HttpMethod httpMethod, MethodInfo methodInfo) => cache.Add(httpMethod, methodInfo);
|
||||
public void Remove(HttpMethod httpMethod, MethodInfo methodInfo) => cache.Remove(httpMethod);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
|
||||
|
||||
using System;
|
||||
|
||||
namespace ln.http.api.attributes
|
||||
{
|
||||
[Flags]
|
||||
public enum ArgumentSource : int{
|
||||
AUTO = -1,
|
||||
CONTENT = (1<<0),
|
||||
PARAMETER = (1<<1)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
|
||||
using System;
|
||||
|
||||
namespace ln.http.api.attributes
|
||||
{
|
||||
|
||||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
|
||||
public abstract class EndpointAttribute : Attribute
|
||||
{
|
||||
public string Route { get; set; }
|
||||
public HttpMethod Method { get; set; }
|
||||
|
||||
public EndpointAttribute(HttpMethod method, string route)
|
||||
{
|
||||
Method = method;
|
||||
Route = route;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class HEADAttribute : EndpointAttribute
|
||||
{
|
||||
public HEADAttribute(string route) : base(HttpMethod.HEAD, route){}
|
||||
}
|
||||
public class GETAttribute : EndpointAttribute
|
||||
{
|
||||
public GETAttribute(string route) : base(HttpMethod.GET, route){}
|
||||
}
|
||||
public class POSTAttribute : EndpointAttribute
|
||||
{
|
||||
public POSTAttribute(string route) : base(HttpMethod.POST, route){}
|
||||
}
|
||||
public class PUTAttribute : EndpointAttribute
|
||||
{
|
||||
public PUTAttribute(string route) : base(HttpMethod.PUT, route){}
|
||||
}
|
||||
public class PATCHAttribute : EndpointAttribute
|
||||
{
|
||||
public PATCHAttribute(string route) : base(HttpMethod.PATCH, route){}
|
||||
}
|
||||
public class DELETEAttribute : EndpointAttribute
|
||||
{
|
||||
public DELETEAttribute(string route) : base(HttpMethod.DELETE, route){}
|
||||
}
|
||||
public class CONNECTAttribute : EndpointAttribute
|
||||
{
|
||||
public CONNECTAttribute(string route) : base(HttpMethod.CONNECT, route){}
|
||||
}
|
||||
public class OPTIONSAttribute : EndpointAttribute
|
||||
{
|
||||
public OPTIONSAttribute(string route) : base(HttpMethod.OPTIONS, route){}
|
||||
}
|
||||
public class TRACEAttribute : EndpointAttribute
|
||||
{
|
||||
public TRACEAttribute(string route) : base(HttpMethod.TRACE, route){}
|
||||
}
|
||||
|
||||
|
||||
public abstract class ArgumentSourceAttribute : Attribute
|
||||
{
|
||||
public static readonly ArgumentSourceAttribute Default = new DefaultArgumentSourceAttribute();
|
||||
public string SourceName { get; set; }
|
||||
public ArgumentSource Source { get; }
|
||||
|
||||
public ArgumentSourceAttribute(ArgumentSource source)
|
||||
{
|
||||
Source = source;
|
||||
}
|
||||
|
||||
class DefaultArgumentSourceAttribute : ArgumentSourceAttribute
|
||||
{
|
||||
public DefaultArgumentSourceAttribute() :base(ArgumentSource.AUTO) {}
|
||||
}
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Parameter)]
|
||||
public class FromParameterAttribute : ArgumentSourceAttribute
|
||||
{
|
||||
public FromParameterAttribute() :base(ArgumentSource.PARAMETER) {}
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Parameter)]
|
||||
public class FromContentAttribute : ArgumentSourceAttribute
|
||||
{
|
||||
public FromContentAttribute() :base(ArgumentSource.CONTENT) {}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<Version>0.0.1-test</Version>
|
||||
<Authors>Harald Wolff-Thobaben</Authors>
|
||||
<Company>l--n.de</Company>
|
||||
<Description>Framework to create REST like APIs</Description>
|
||||
<Copyright>(c) 2020 Harald Wolff-Thobaben</Copyright>
|
||||
<PackageTags></PackageTags>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!--PackageReference Include="ln.http" Version="0.2.1-test" /-->
|
||||
<PackageReference Include="ln.json" Version="1.0.0" />
|
||||
<ProjectReference Include="..\..\ln.http\ln.http\ln.http.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
Loading…
Reference in New Issue