Added HttpListener.cs, HttpsListener.cs, etc. , changed structure to increase compatibility with DI frameworks
parent
caf1ba201f
commit
a26c4e33b5
|
@ -1,7 +1,5 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
using ln.http;
|
||||
using ln.http.listener;
|
||||
using System.Threading;
|
||||
using ln.bootstrap;
|
||||
using ln.http.router;
|
||||
|
||||
namespace ln.http.service
|
||||
|
@ -10,13 +8,7 @@ namespace ln.http.service
|
|||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
StaticRouter staticRouter = new StaticRouter(".");
|
||||
HTTPServer httpServer = new HTTPServer(new HttpListener(8888), new LoggingRouter(staticRouter.Route).Route);
|
||||
|
||||
httpServer.Start();
|
||||
|
||||
lock (httpServer)
|
||||
Monitor.Wait(httpServer);
|
||||
Bootstrap.Start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"ln.http.HTTPServer, ln.http": {
|
||||
"services": [
|
||||
],
|
||||
"properties": {
|
||||
}
|
||||
},
|
||||
"ln.http.HttpListener, ln.http": {
|
||||
"services": [
|
||||
|
||||
],
|
||||
"properties": {
|
||||
"DefaultPort": 8180
|
||||
}
|
||||
},
|
||||
"ln.http.HttpsListener, ln.http": {
|
||||
"services": [
|
||||
|
||||
],
|
||||
"properties": {
|
||||
"DefaultPort": 8443
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,4 +10,14 @@
|
|||
<ProjectReference Include="../ln.http/ln.http.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ln.bootstrap" Version="1.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="bootstrap.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -21,18 +21,18 @@ namespace ln.http.tests
|
|||
if (server != null)
|
||||
return;
|
||||
|
||||
HttpRouter testRouter = new HttpRouter();
|
||||
server = new HTTPServer();
|
||||
|
||||
HttpRouter testRouter = new HttpRouter(server);
|
||||
testRouter.Map(HttpMethod.ANY, "/controller/*", HttpRoutePriority.NORMAL, new TestApiController().Route);
|
||||
|
||||
StaticRouter staticRouter = new StaticRouter(AppContext.BaseDirectory);
|
||||
testRouter.Map(HttpMethod.ANY, "/static/*", staticRouter.Route);
|
||||
|
||||
HttpListener.DefaultPort = 0;
|
||||
HttpListener httpListener = new HttpListener(server);
|
||||
|
||||
server = new HTTPServer(testRouter.Route);
|
||||
server.AddEndpoint(new Endpoint(IPv6.ANY,0));
|
||||
|
||||
server.Start();
|
||||
|
||||
testPort = server.Listeners[0].LocalEndpoint.Port;
|
||||
testPort = httpListener.LocalEndpoint.Port;
|
||||
TestContext.Error.WriteLine("Using Port {0}", testPort);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,259 +1,136 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using ln.logging;
|
||||
using ln.threading;
|
||||
using ln.http.listener;
|
||||
using ln.http.connections;
|
||||
using ln.http.exceptions;
|
||||
using System.Threading;
|
||||
using ln.type;
|
||||
using ln.http.router;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using ln.protocols.helper;
|
||||
|
||||
namespace ln.http
|
||||
{
|
||||
public class HTTPServer
|
||||
{
|
||||
public static int backlog = 5;
|
||||
public static int defaultPort = 8080;
|
||||
public static bool exclusivePortListener = false;
|
||||
|
||||
public static bool DefaultRouteAuthentication { get; set; } = false;
|
||||
|
||||
public HttpRouterDelegate Router { get; set; }
|
||||
|
||||
public bool IsRunning => !shutdown;
|
||||
public Logger Logger { get; set; }
|
||||
private HashSet<HttpRouterDelegate> _routerDelegates = new HashSet<HttpRouterDelegate>();
|
||||
public IEnumerable<HttpRouterDelegate> Routers => _routerDelegates;
|
||||
|
||||
bool shutdown = false;
|
||||
public TextWriter LoggingWriter { get; set; }
|
||||
|
||||
List<Listener> listeners = new List<Listener>();
|
||||
public Listener[] Listeners => listeners.ToArray();
|
||||
|
||||
HashSet<Connection> currentConnections = new HashSet<Connection>();
|
||||
public IEnumerable<Connection> CurrentConnections => currentConnections;
|
||||
|
||||
public HTTPServer()
|
||||
public HTTPServer() : this(Console.Out)
|
||||
{
|
||||
Logger = Logger.Default;
|
||||
}
|
||||
public HTTPServer(TextWriter loggingWriter)
|
||||
{
|
||||
LoggingWriter = loggingWriter;
|
||||
}
|
||||
public HTTPServer(HttpRouterDelegate router)
|
||||
: this()
|
||||
{
|
||||
Router = router;
|
||||
}
|
||||
public HTTPServer(Listener listener, HttpRouterDelegate router)
|
||||
: this(router)
|
||||
{
|
||||
AddListener(listener);
|
||||
}
|
||||
public HTTPServer(Endpoint endpoint, HttpRouterDelegate router)
|
||||
: this(new HttpListener(endpoint), router) { }
|
||||
|
||||
public void AddListener(Listener listener)
|
||||
{
|
||||
listeners.Add(listener);
|
||||
if (IsRunning)
|
||||
StartListener(listener);
|
||||
AddRouter(router);
|
||||
}
|
||||
|
||||
public void StartListener(Listener listener)
|
||||
public void AddRouter(HttpRouter httpRouter) => AddRouter(httpRouter.Route);
|
||||
public void AddRouter(HttpRouterDelegate routerDelegate) => _routerDelegates.Add(routerDelegate);
|
||||
|
||||
public void RemoveRouter(HttpRouter httpRouter) => RemoveRouter(httpRouter.Route);
|
||||
public void RemoveRouter(HttpRouterDelegate routerDelegate) => _routerDelegates.Remove(routerDelegate);
|
||||
|
||||
public void Connection(HttpConnection httpConnection)
|
||||
{
|
||||
if (listener.IsOpen)
|
||||
return;
|
||||
|
||||
listener.Open();
|
||||
|
||||
DynamicThreadPool.DefaultPool.Enqueue(
|
||||
() => listener.AcceptMany(
|
||||
(connection) => this.HandleConnection(connection))
|
||||
);
|
||||
}
|
||||
|
||||
public void StopListener(Listener listener)
|
||||
{
|
||||
listener.Close();
|
||||
}
|
||||
|
||||
|
||||
public void AddEndpoint(Endpoint endpoint)
|
||||
{
|
||||
AddListener(new HttpListener(endpoint));
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
foreach (Listener listener in listeners)
|
||||
if (!listener.IsOpen) StartListener(listener);
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
lock (this)
|
||||
try
|
||||
{
|
||||
this.shutdown = true;
|
||||
}
|
||||
foreach (Listener listener in listeners)
|
||||
StopListener(listener);
|
||||
|
||||
for (int n = 0; n < 150; n++)
|
||||
{
|
||||
lock (currentConnections)
|
||||
bool keepalive = false;
|
||||
do
|
||||
{
|
||||
if (currentConnections.Count == 0)
|
||||
break;
|
||||
if ((n % 20) == 0)
|
||||
{
|
||||
Logging.Log(LogLevel.INFO, "HTTPServer: still waiting for {0} connections to close", currentConnections.Count);
|
||||
}
|
||||
}
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
|
||||
lock (currentConnections)
|
||||
{
|
||||
foreach (Connection connection in currentConnections)
|
||||
{
|
||||
connection.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleConnection(Connection connection, bool dontClose = false)
|
||||
{
|
||||
lock (this.currentConnections)
|
||||
currentConnections.Add(connection);
|
||||
|
||||
bool keepalive = false;
|
||||
|
||||
do
|
||||
{
|
||||
using (HttpRequest httpRequest = connection.ReadRequest(this))
|
||||
{
|
||||
if (httpRequest == null)
|
||||
break;
|
||||
|
||||
HttpContext httpContext = new HttpContext()
|
||||
{ Request = httpRequest, RoutableUri = httpRequest.RequestUri.AbsolutePath };
|
||||
DateTime start = DateTime.Now;
|
||||
|
||||
try
|
||||
using (HttpRequest httpRequest = ReadRequest(httpConnection))
|
||||
{
|
||||
if (!Router(httpContext) && httpContext.Response is null)
|
||||
httpContext.Response = HttpResponse.NotFound();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Logging.Log(exception);
|
||||
if ((exception is HttpException httpException) && (httpException.HttpResponse != null))
|
||||
httpContext.Response = httpException.HttpResponse;
|
||||
else
|
||||
httpContext.Response = HttpResponse.InternalServerError()
|
||||
.Content(String.Format("An internal error occured ({0})", exception.ToString()));
|
||||
}
|
||||
if (httpRequest == null)
|
||||
break;
|
||||
|
||||
try
|
||||
{
|
||||
httpContext.Response.WriteTo(connection.GetStream());
|
||||
httpContext.Response?.ContentStream?.Dispose();
|
||||
}
|
||||
catch (IOException ioexception)
|
||||
{
|
||||
break;
|
||||
}
|
||||
HttpContext httpContext = new HttpContext()
|
||||
{ Request = httpRequest, RoutableUri = httpRequest.RequestUri.AbsolutePath };
|
||||
|
||||
keepalive = httpContext.Response.GetHeader("connection", "keep-alive").Equals("keep-alive") && httpRequest
|
||||
.GetRequestHeader("connection",
|
||||
httpRequest.Protocol.Equals("HTTP/1.1") ? "keep-alive" : "close").Contains("keep-alive",
|
||||
StringComparison.InvariantCultureIgnoreCase);
|
||||
}
|
||||
} while (keepalive && false);
|
||||
|
||||
lock (this.currentConnections)
|
||||
currentConnections.Remove(connection);
|
||||
connection.Close();
|
||||
|
||||
// new threading.Promise<HttpRequest>((resolve, reject)=>{
|
||||
// resolve(connection.ReadRequest(this));
|
||||
// })
|
||||
// .Then((httpRequest)=>
|
||||
// {
|
||||
// if (httpRequest == null)
|
||||
// return null;
|
||||
// return Router.Route(new HttpRoutingContext(httpRequest),httpRequest);
|
||||
// })
|
||||
// // .Then((httpResponse)=>{
|
||||
// // if (httpResponse == null)
|
||||
// // throw new Exception("no response returned");
|
||||
// // return httpResponse;
|
||||
// // })
|
||||
// .Catch((exception)=>{
|
||||
// Logging.Log(exception);
|
||||
// if ((exception is HttpException httpException) && (httpException.HttpResponse != null))
|
||||
// return httpException.HttpResponse;
|
||||
// return HttpResponse
|
||||
// .InternalServerError()
|
||||
// .Content(String.Format("An internal error occured ({0})", exception.ToString()));
|
||||
// })
|
||||
// .Then((httpResponse)=>
|
||||
// {
|
||||
// if (httpResponse == null)
|
||||
// return false;
|
||||
//
|
||||
// httpResponse.SendResponse(connection.GetStream());
|
||||
// httpResponse?.ContentStream?.Dispose();
|
||||
// return httpResponse.GetHeader("connection", "keep-alive").Equals("keep-alive");
|
||||
// })
|
||||
// .Then((keepalive)=>{
|
||||
// if (keepalive)
|
||||
// HandleConnection(connection, true);
|
||||
// })
|
||||
// .Finally(()=>{
|
||||
// lock (this.currentConnections)
|
||||
// currentConnections.Remove(connection);
|
||||
// connection.Close();
|
||||
// })
|
||||
// ;
|
||||
try
|
||||
{
|
||||
foreach (var routerDelegate in _routerDelegates)
|
||||
{
|
||||
if (!routerDelegate(httpContext) && httpContext.Response is not null)
|
||||
break;
|
||||
}
|
||||
|
||||
if (httpContext.Response is null)
|
||||
httpContext.Response = HttpResponse.NotFound();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Logging.Log(exception);
|
||||
if ((exception is HttpException httpException) && (httpException.HttpResponse != null))
|
||||
httpContext.Response = httpException.HttpResponse;
|
||||
else
|
||||
httpContext.Response = HttpResponse.InternalServerError()
|
||||
.Content(String.Format("An internal error occured ({0})", exception.ToString()));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
httpContext.Response.WriteTo(httpConnection.ClientStream);
|
||||
httpContext.Response?.ContentStream?.Dispose();
|
||||
}
|
||||
catch (IOException ioexception)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
DateTime end = DateTime.Now;
|
||||
TimeSpan duration = end - start;
|
||||
|
||||
LoggingWriter.WriteLine("{0} {1} {2} {3} {4} {5} {6}",
|
||||
start,
|
||||
end,
|
||||
duration,
|
||||
httpContext.Response?.StatusCode.ToString() ?? "-",
|
||||
httpContext.AuthenticatedPrincipal?.ToString() ?? "-",
|
||||
httpContext.Request.Method,
|
||||
httpContext.Request.RequestUri
|
||||
);
|
||||
|
||||
keepalive = httpContext.Response.GetHeader("connection", "keep-alive").Equals("keep-alive") &&
|
||||
httpRequest
|
||||
.GetRequestHeader("connection",
|
||||
httpRequest.Protocol.Equals("HTTP/1.1") ? "keep-alive" : "close").Contains(
|
||||
"keep-alive",
|
||||
StringComparison.InvariantCultureIgnoreCase);
|
||||
}
|
||||
} while (keepalive);
|
||||
}
|
||||
finally
|
||||
{
|
||||
httpConnection.ClientStream.Close();
|
||||
httpConnection.ClientStream.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
public static void StartSimpleServer(string[] arguments)
|
||||
private HttpRequest ReadRequest(HttpConnection httpConnection)
|
||||
{
|
||||
ArgumentContainer argumentContainer = new ArgumentContainer(new Argument[]
|
||||
try
|
||||
{
|
||||
new Argument('p',"port",8080),
|
||||
new Argument('l',"listen","127.0.0.1"),
|
||||
new Argument('c', "catch",null)
|
||||
});
|
||||
|
||||
argumentContainer.Parse(ref arguments);
|
||||
|
||||
HttpRouter router = new HttpRouter();
|
||||
router.AddSimpleRoute("/*", new RouterTarget((request) =>
|
||||
if (HttpLikeProtocolReader.ReadRequest(httpConnection.ClientStream, Encoding.UTF8, out Request request))
|
||||
return new HttpRequest(this, request);
|
||||
return null;
|
||||
} catch (IOException)
|
||||
{
|
||||
HttpResponse response = new HttpResponse(request);
|
||||
response.StatusCode = 404;
|
||||
response.SetHeader("content-type", "text/plain");
|
||||
response.ContentWriter.WriteLine("404 Not Found");
|
||||
response.ContentWriter.Flush();
|
||||
return response;
|
||||
}), -100);
|
||||
|
||||
foreach (String path in arguments)
|
||||
return null;
|
||||
} catch (ConnectionClosedException)
|
||||
{
|
||||
StaticRouter staticRouter = new StaticRouter(path);
|
||||
staticRouter.AddIndex("index.html");
|
||||
staticRouter.AddIndex("index.htm");
|
||||
router.AddSimpleRoute("/*", staticRouter);
|
||||
return null;
|
||||
} catch (Exception e)
|
||||
{
|
||||
Logging.Log(e);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (argumentContainer['c'].Value != null)
|
||||
router.AddSimpleRoute("/*", new RouterTarget((request) => router.Route(new HttpRoutingContext(request, argumentContainer['c'].Value), request)), 0);
|
||||
|
||||
HTTPServer server = new HTTPServer(new Endpoint(IPv6.Parse(argumentContainer['l'].Value),int.Parse(argumentContainer['p'].Value)),
|
||||
new LoggingRouter(router));
|
||||
server.Start();
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
using System.IO;
|
||||
using System.Net;
|
||||
|
||||
namespace ln.http
|
||||
{
|
||||
public class HttpConnection
|
||||
{
|
||||
public Stream ClientStream { get; }
|
||||
public IPEndPoint RemoteEndPoint { get; }
|
||||
public IPEndPoint LocalEndPoint { get; }
|
||||
|
||||
public bool IsEncrypted { get; }
|
||||
|
||||
public HttpConnection(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, Stream clientStream)
|
||||
:this(localEndPoint, remoteEndPoint, clientStream, false)
|
||||
{}
|
||||
|
||||
public HttpConnection(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, Stream clientStream, bool isEncrypted)
|
||||
{
|
||||
ClientStream = clientStream;
|
||||
LocalEndPoint = localEndPoint;
|
||||
RemoteEndPoint = remoteEndPoint;
|
||||
IsEncrypted = isEncrypted;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
|
||||
namespace ln.http
|
||||
{
|
||||
public class HttpListener : IDisposable
|
||||
{
|
||||
public static int DefaultPort = 80;
|
||||
|
||||
|
||||
private IPEndPoint _localEndPoint;
|
||||
private Socket _socket;
|
||||
private HTTPServer _httpServer;
|
||||
|
||||
public IPEndPoint LocalEndpoint => _localEndPoint;
|
||||
|
||||
public HttpListener(HTTPServer httpServer) : this(httpServer, DefaultPort)
|
||||
{
|
||||
}
|
||||
|
||||
public HttpListener(HTTPServer httpServer, int port)
|
||||
{
|
||||
_httpServer = httpServer;
|
||||
_localEndPoint = new IPEndPoint(IPAddress.IPv6Any, port);
|
||||
|
||||
Initialize();
|
||||
}
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
_socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
|
||||
_socket.ExclusiveAddressUse = false;
|
||||
_socket.Bind(_localEndPoint);
|
||||
_localEndPoint = (IPEndPoint)_socket.LocalEndPoint;
|
||||
_socket.Listen();
|
||||
|
||||
ThreadPool.QueueUserWorkItem((state )=> ListenerThread());
|
||||
}
|
||||
|
||||
private void ListenerThread()
|
||||
{
|
||||
while (_socket?.IsBound ?? false)
|
||||
{
|
||||
try
|
||||
{
|
||||
Socket clientSocket = _socket.Accept();
|
||||
_httpServer.Connection(
|
||||
new HttpConnection(
|
||||
_localEndPoint,
|
||||
(IPEndPoint)clientSocket.RemoteEndPoint,
|
||||
new NetworkStream(clientSocket)
|
||||
)
|
||||
);
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_socket?.Close();
|
||||
_socket?.Dispose();
|
||||
_socket = null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,19 +13,27 @@ namespace ln.http
|
|||
|
||||
public enum HttpRoutePriority : int { HIGHEST = 0, HIGH = 1, NORMAL = 2, LOW = 3, LOWEST = 4 }
|
||||
|
||||
public class HttpRouter
|
||||
public class HttpRouter : IDisposable
|
||||
{
|
||||
public event HttpFilterDelegate HttpFilters;
|
||||
public event HttpAuthenticationDelegate AuthenticationDelegates;
|
||||
|
||||
private List<HttpMapping>[] _mappings = new List<HttpMapping>[5];
|
||||
|
||||
private HTTPServer _httpServer;
|
||||
|
||||
public HttpRouter()
|
||||
{
|
||||
for (int n = 0; n < 5; n++)
|
||||
_mappings[n] = new List<HttpMapping>();
|
||||
}
|
||||
|
||||
public HttpRouter(HTTPServer httpServer)
|
||||
: this()
|
||||
{
|
||||
_httpServer = httpServer;
|
||||
httpServer.AddRouter(this);
|
||||
}
|
||||
|
||||
public HttpMapping Map(HttpMethod httpMethod, string uri, HttpRouterDelegate routerDelegate) =>
|
||||
Map(httpMethod, uri, HttpRoutePriority.NORMAL, routerDelegate);
|
||||
|
||||
|
@ -165,107 +173,9 @@ namespace ln.http
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
List<SimpleRoute> routes = new List<SimpleRoute>();
|
||||
public SimpleRoute[] Routes => routes.ToArray();
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
public void AddSimpleRoute(string simpleRoute, Func<HttpRoutingContext, HttpRequest, HttpResponse> target) => AddSimpleRoute(simpleRoute, new RouterTarget(target));
|
||||
public void AddSimpleRoute(string simpleRoute, Func<string, HttpRequest, HttpResponse> target) => AddSimpleRoute(simpleRoute, new RouterTarget(target));
|
||||
public void AddSimpleRoute(string simpleRoute, Func<HttpRequest, HttpResponse> target) => AddSimpleRoute(simpleRoute, new RouterTarget(target));
|
||||
public void AddSimpleRoute(string simpleRoute, IHttpRouter target) => AddSimpleRoute(simpleRoute, target, simpleRoute.Split('/').Length);
|
||||
public void AddSimpleRoute(string simpleRoute, IHttpRouter target, int priority)
|
||||
public void Dispose()
|
||||
{
|
||||
string[] parts = simpleRoute.Split(new char[] { '/' });
|
||||
string[] reparts = parts.Select((part) =>
|
||||
{
|
||||
if (part.StartsWith(":", StringComparison.InvariantCulture))
|
||||
if (part.EndsWith("*", StringComparison.InvariantCulture))
|
||||
return string.Format("(?<{0}>[^/]+)(?<_>/.*)?", part.Substring(1, part.Length - 2));
|
||||
else
|
||||
return string.Format("(?<{0}>[^/]+)", part.Substring(1));
|
||||
else if (part.Equals("*"))
|
||||
return string.Format("(?<_>.*)");
|
||||
else
|
||||
return string.Format("{0}", part);
|
||||
}).ToArray();
|
||||
|
||||
string reroute = string.Format("{0}\\/?$", string.Join("/", reparts));
|
||||
|
||||
AddRoute(reroute, target, priority);
|
||||
_httpServer?.RemoveRouter(this);
|
||||
}
|
||||
|
||||
public void AddRoute(String route, IHttpRouter target) => AddRoute(route, target, 0);
|
||||
public void AddRoute(String route, IHttpRouter target,int priority)
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
routes.Add(new SimpleRoute(route, target, priority));
|
||||
routes.Sort((SimpleRoute a, SimpleRoute b) => b.Priority - a.Priority);
|
||||
}
|
||||
}
|
||||
public void Remove(SimpleRoute simpleRoute) => routes.Remove(simpleRoute);
|
||||
|
||||
public HttpResponse Route(HttpRoutingContext routingContext, HttpRequest httpRequest)
|
||||
{
|
||||
HttpResponse httpResponse;
|
||||
|
||||
if (OnRoute != null)
|
||||
{
|
||||
foreach (RouterFilterDelegate filterDelegate in OnRoute.GetInvocationList())
|
||||
{
|
||||
if (filterDelegate(this, ref routingContext, httpRequest, out httpResponse))
|
||||
return httpResponse;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (SimpleRoute simpleRoute in routes.ToArray())
|
||||
{
|
||||
Match match = simpleRoute.Route.Match(routingContext.Path);
|
||||
if (match.Success)
|
||||
{
|
||||
string residual = "";
|
||||
|
||||
foreach (Group group in match.Groups)
|
||||
{
|
||||
httpRequest?.SetParameter(group.Name, group.Value);
|
||||
if (group.Name.Equals("_"))
|
||||
if (group.Value.StartsWith("/", StringComparison.InvariantCulture))
|
||||
residual = group.Value;
|
||||
else
|
||||
residual = "/" + group.Value;
|
||||
}
|
||||
|
||||
httpResponse = simpleRoute.Target.Route(routingContext.Routed(residual), httpRequest);
|
||||
if (httpResponse != null)
|
||||
return httpResponse;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public class SimpleRoute
|
||||
{
|
||||
public int Priority { get; }
|
||||
|
||||
public Regex Route { get; }
|
||||
public IHttpRouter Target { get; }
|
||||
|
||||
public SimpleRoute(string regex, IHttpRouter target) : this(regex, target, 0) { }
|
||||
public SimpleRoute(string regex, IHttpRouter target,int priority)
|
||||
{
|
||||
Route = new Regex(regex);
|
||||
Target = target;
|
||||
Priority = priority;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,140 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Security;
|
||||
using System.Net.Sockets;
|
||||
using System.Security.Cryptography;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Threading;
|
||||
|
||||
namespace ln.http
|
||||
{
|
||||
public class HttpsListener : IDisposable
|
||||
{
|
||||
public static int DefaultPort = 443;
|
||||
public string CertificateStore = Path.Combine(AppContext.BaseDirectory, "certs");
|
||||
|
||||
|
||||
private IPEndPoint _localEndPoint;
|
||||
private Socket _socket;
|
||||
private HTTPServer _httpServer;
|
||||
|
||||
private X509Certificate _defaultCertificate;
|
||||
private Dictionary<string, X509Certificate> _certificateCache = new Dictionary<string, X509Certificate>();
|
||||
|
||||
public HttpsListener(HTTPServer httpServer) : this(httpServer, DefaultPort)
|
||||
{
|
||||
}
|
||||
|
||||
public HttpsListener(HTTPServer httpServer, int port)
|
||||
{
|
||||
_httpServer = httpServer;
|
||||
_localEndPoint = new IPEndPoint(IPAddress.IPv6Any, port);
|
||||
|
||||
Initialize();
|
||||
}
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
_socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
|
||||
_socket.ExclusiveAddressUse = false;
|
||||
_socket.Bind(_localEndPoint);
|
||||
_socket.Listen();
|
||||
|
||||
if (File.Exists("localhost.crt"))
|
||||
_defaultCertificate = X509Certificate.CreateFromCertFile("localhost.crt");
|
||||
else
|
||||
_defaultCertificate = buildSelfSignedServerCertificate();
|
||||
|
||||
ThreadPool.QueueUserWorkItem((state )=> ListenerThread());
|
||||
}
|
||||
|
||||
private void ListenerThread()
|
||||
{
|
||||
while (_socket?.IsBound ?? false)
|
||||
{
|
||||
try
|
||||
{
|
||||
Socket clientSocket = _socket.Accept();
|
||||
InitializeTLS(clientSocket);
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeTLS(Socket clientSocket)
|
||||
{
|
||||
SslStream sslStream = new SslStream(new NetworkStream(clientSocket), false, null, CertificateSelectionCallback);
|
||||
sslStream.AuthenticateAsServer(_defaultCertificate, false, false);
|
||||
|
||||
_httpServer.Connection(
|
||||
new HttpConnection(
|
||||
_localEndPoint,
|
||||
(IPEndPoint)clientSocket.RemoteEndPoint,
|
||||
sslStream
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
private X509Certificate CertificateSelectionCallback(object sender, string targethost,
|
||||
X509CertificateCollection localcertificates, X509Certificate? remotecertificate, string[] acceptableissuers)
|
||||
{
|
||||
Console.Error.WriteLine("Certificate Selection for: {0}", targethost);
|
||||
if (_certificateCache.TryGetValue(targethost, out X509Certificate localCertificate))
|
||||
{
|
||||
return localCertificate;
|
||||
}
|
||||
else if (File.Exists(Path.Combine(CertificateStore ?? ".", String.Format("{0}.crt", targethost))))
|
||||
{
|
||||
localCertificate = X509Certificate.CreateFromCertFile(
|
||||
Path.Combine(CertificateStore ?? ".", String.Format("{0}.crt", targethost))
|
||||
);
|
||||
_certificateCache.Add(targethost, localCertificate);
|
||||
return localCertificate;
|
||||
}
|
||||
|
||||
return _defaultCertificate;
|
||||
}
|
||||
|
||||
private X509Certificate2 buildSelfSignedServerCertificate()
|
||||
{
|
||||
SubjectAlternativeNameBuilder sanBuilder = new SubjectAlternativeNameBuilder();
|
||||
sanBuilder.AddIpAddress(IPAddress.Loopback);
|
||||
sanBuilder.AddIpAddress(IPAddress.IPv6Loopback);
|
||||
sanBuilder.AddDnsName("localhost");
|
||||
sanBuilder.AddDnsName(Environment.MachineName);
|
||||
|
||||
X500DistinguishedName distinguishedName = new X500DistinguishedName($"CN=localhost");
|
||||
|
||||
using (RSA rsa = RSA.Create(4096))
|
||||
{
|
||||
var request = new CertificateRequest(distinguishedName, rsa, HashAlgorithmName.SHA256,RSASignaturePadding.Pkcs1);
|
||||
request.CertificateExtensions.Add(
|
||||
new X509KeyUsageExtension(X509KeyUsageFlags.DataEncipherment | X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.DigitalSignature , false));
|
||||
request.CertificateExtensions.Add(
|
||||
new X509EnhancedKeyUsageExtension(
|
||||
new OidCollection { new Oid("1.3.6.1.5.5.7.3.1") }, false));
|
||||
request.CertificateExtensions.Add(sanBuilder.Build());
|
||||
|
||||
var certificate= request.CreateSelfSigned(new DateTimeOffset(DateTime.UtcNow.AddDays(-1)), new DateTimeOffset(DateTime.UtcNow.AddDays(3650)));
|
||||
//certificate.FriendlyName = "localhost";
|
||||
using (FileStream fs = new FileStream("localhost.crt", FileMode.Create, FileAccess.Write))
|
||||
fs.Write(certificate.Export(X509ContentType.Pfx));
|
||||
|
||||
return certificate;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_socket?.Close();
|
||||
_socket?.Dispose();
|
||||
_socket = null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
// /**
|
||||
// * File: Connection.cs
|
||||
// * Author: haraldwolff
|
||||
// *
|
||||
// * This file and it's content is copyrighted by the Author and / or copyright holder.
|
||||
// * Any use wihtout proper permission is illegal and may lead to legal actions.
|
||||
// *
|
||||
// *
|
||||
// **/
|
||||
using System;
|
||||
using ln.type;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using ln.logging;
|
||||
using ln.http.listener;
|
||||
using ln.http.exceptions;
|
||||
using ln.protocols.helper;
|
||||
|
||||
namespace ln.http.connections
|
||||
{
|
||||
public abstract class Connection : IDisposable
|
||||
{
|
||||
public Listener Listener { get; private set; }
|
||||
|
||||
public abstract IPv6 RemoteHost { get; }
|
||||
public abstract int RemotePort { get; }
|
||||
|
||||
public abstract Stream GetStream();
|
||||
|
||||
public Connection(Listener listener)
|
||||
{
|
||||
Listener = listener;
|
||||
}
|
||||
|
||||
public virtual HttpRequest ReadRequest(HTTPServer httpServer)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (HttpLikeProtocolReader.ReadRequest(GetStream(), Encoding.UTF8, out Request request))
|
||||
return new HttpRequest(httpServer, request);
|
||||
return null;
|
||||
} catch (IOException)
|
||||
{
|
||||
return null;
|
||||
} catch (ConnectionClosedException)
|
||||
{
|
||||
return null;
|
||||
} catch (Exception e)
|
||||
{
|
||||
Logging.Log(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract void Close();
|
||||
public virtual void Dispose()
|
||||
{
|
||||
Close();
|
||||
Listener = null;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
// /**
|
||||
// * File: HttpConnection.cs
|
||||
// * Author: haraldwolff
|
||||
// *
|
||||
// * This file and it's content is copyrighted by the Author and / or copyright holder.
|
||||
// * Any use wihtout proper permission is illegal and may lead to legal actions.
|
||||
// *
|
||||
// *
|
||||
// **/
|
||||
using System;
|
||||
using System.IO;
|
||||
using ln.type;
|
||||
using System.Net.Sockets;
|
||||
using ln.http.listener;
|
||||
|
||||
namespace ln.http.connections
|
||||
{
|
||||
public class HttpConnection : Connection
|
||||
{
|
||||
public TcpClient TcpClient { get; }
|
||||
public Endpoint RemoteEndpoint { get; }
|
||||
|
||||
public HttpConnection(Listener listener, TcpClient tcpClient)
|
||||
:base(listener)
|
||||
{
|
||||
TcpClient = tcpClient;
|
||||
RemoteEndpoint = new Endpoint(TcpClient.Client.RemoteEndPoint);
|
||||
}
|
||||
|
||||
public override IPv6 RemoteHost => RemoteEndpoint.Address;
|
||||
public override int RemotePort => RemoteEndpoint.Port;
|
||||
|
||||
public override Stream GetStream() => TcpClient.GetStream();
|
||||
|
||||
public override void Close()
|
||||
{
|
||||
TcpClient.Close();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
// /**
|
||||
// * File: HttpsConnection.cs
|
||||
// * Author: haraldwolff
|
||||
// *
|
||||
// * This file and it's content is copyrighted by the Author and / or copyright holder.
|
||||
// * Any use wihtout proper permission is illegal and may lead to legal actions.
|
||||
// *
|
||||
// *
|
||||
// **/
|
||||
using System;
|
||||
using System.IO;
|
||||
using ln.type;
|
||||
using System.Net.Security;
|
||||
using ln.http.listener;
|
||||
|
||||
namespace ln.http.connections
|
||||
{
|
||||
public class HttpsConnection : Connection
|
||||
{
|
||||
Connection Connection { get; }
|
||||
SslStream sslStream { get; }
|
||||
|
||||
public override IPv6 RemoteHost => Connection.RemoteHost;
|
||||
public override int RemotePort => Connection.RemotePort;
|
||||
|
||||
public HttpsConnection(Listener listener,Connection connection,LocalCertificateSelectionCallback localCertificateSelectionCallback)
|
||||
:base(listener)
|
||||
{
|
||||
Connection = connection;
|
||||
sslStream = new SslStream(connection.GetStream(),false, null, localCertificateSelectionCallback);
|
||||
sslStream.AuthenticateAsServer(new System.Security.Cryptography.X509Certificates.X509Certificate());
|
||||
}
|
||||
|
||||
public override HttpRequest ReadRequest(HTTPServer server)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override Stream GetStream() => sslStream;
|
||||
|
||||
public override void Close()
|
||||
{
|
||||
sslStream.Close();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
// /**
|
||||
// * File: HttpListener.cs
|
||||
// * Author: haraldwolff
|
||||
// *
|
||||
// * This file and it's content is copyrighted by the Author and / or copyright holder.
|
||||
// * Any use wihtout proper permission is illegal and may lead to legal actions.
|
||||
// *
|
||||
// *
|
||||
// **/
|
||||
using System.Net.Sockets;
|
||||
using ln.type;
|
||||
using ln.http.connections;
|
||||
|
||||
namespace ln.http.listener
|
||||
{
|
||||
public class HttpListener : Listener
|
||||
{
|
||||
|
||||
protected TcpListener tcpListener;
|
||||
|
||||
public HttpListener(int port) :this(IPv6.ANY,port){}
|
||||
public HttpListener(Endpoint endpoint) : this(endpoint.Address, endpoint.Port) {}
|
||||
public HttpListener(IPv6 listen, int port)
|
||||
: base(listen, port)
|
||||
{
|
||||
}
|
||||
|
||||
public override Endpoint LocalEndpoint => new Endpoint(tcpListener.LocalEndpoint);
|
||||
|
||||
public override Connection Accept()
|
||||
{
|
||||
return new HttpConnection(this,tcpListener.AcceptTcpClient());
|
||||
}
|
||||
|
||||
public override bool IsOpen => (tcpListener != null);
|
||||
|
||||
public override void Open()
|
||||
{
|
||||
tcpListener = new TcpListener(Listen, Port);
|
||||
tcpListener.Start();
|
||||
}
|
||||
public override void Close()
|
||||
{
|
||||
if (tcpListener != null)
|
||||
{
|
||||
tcpListener.Stop();
|
||||
tcpListener = null;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
if (IsOpen)
|
||||
Close();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
// /**
|
||||
// * File: HttpsListener.cs
|
||||
// * Author: haraldwolff
|
||||
// *
|
||||
// * This file and it's content is copyrighted by the Author and / or copyright holder.
|
||||
// * Any use wihtout proper permission is illegal and may lead to legal actions.
|
||||
// *
|
||||
// *
|
||||
// **/
|
||||
using ln.type;
|
||||
using ln.http.connections;
|
||||
using ln.http.cert;
|
||||
namespace ln.http.listener
|
||||
{
|
||||
public class HttpsListener : HttpListener
|
||||
{
|
||||
public HttpsListener(int port) : this(IPv6.ANY, port) { }
|
||||
public HttpsListener(IPv6 listen, int port) : base(listen, port) { }
|
||||
|
||||
public CertContainer CertContainer { get; set; } = new CertContainer();
|
||||
|
||||
public override Connection Accept()
|
||||
{
|
||||
return new HttpsConnection(this, base.Accept(),CertContainer.SelectCertificate);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
// /**
|
||||
// * File: Listener.cs
|
||||
// * Author: haraldwolff
|
||||
// *
|
||||
// * This file and it's content is copyrighted by the Author and / or copyright holder.
|
||||
// * Any use wihtout proper permission is illegal and may lead to legal actions.
|
||||
// *
|
||||
// *
|
||||
// **/
|
||||
using System;
|
||||
using System.Net.Sockets;
|
||||
using ln.http.connections;
|
||||
using ln.logging;
|
||||
using ln.type;
|
||||
namespace ln.http.listener
|
||||
{
|
||||
public abstract class Listener : IDisposable
|
||||
{
|
||||
public IPv6 Listen { get; }
|
||||
public int Port { get; }
|
||||
|
||||
public virtual Endpoint LocalEndpoint => new Endpoint(Listen, Port);
|
||||
|
||||
public abstract bool IsOpen { get; }
|
||||
|
||||
protected Listener(IPv6 listen, int port)
|
||||
{
|
||||
Listen = listen;
|
||||
Port = port;
|
||||
}
|
||||
|
||||
public virtual void AcceptMany(Action<Connection> handler)
|
||||
{
|
||||
while (IsOpen)
|
||||
{
|
||||
try
|
||||
{
|
||||
handler(Accept());
|
||||
}
|
||||
catch (SocketException soe)
|
||||
{
|
||||
if (IsOpen)
|
||||
Logging.Log(soe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public abstract void Open();
|
||||
public abstract void Close();
|
||||
|
||||
public abstract Connection Accept();
|
||||
|
||||
public abstract void Dispose();
|
||||
}
|
||||
}
|
|
@ -10,7 +10,7 @@
|
|||
<Copyright>(c) 2020 Harald Wolff-Thobaben</Copyright>
|
||||
<PackageTags>http server</PackageTags>
|
||||
<LangVersion>9</LangVersion>
|
||||
<PackageVersion>0.5.2</PackageVersion>
|
||||
<PackageVersion>0.6.0</PackageVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -14,19 +14,42 @@ using ln.http.mime;
|
|||
|
||||
namespace ln.http.router
|
||||
{
|
||||
public class StaticRouter
|
||||
public class StaticRouter : IDisposable
|
||||
{
|
||||
public String RootPath { get; }
|
||||
private string _rootPath;
|
||||
public String RootPath
|
||||
{
|
||||
get => _rootPath;
|
||||
private set
|
||||
{
|
||||
_rootPath = Path.GetFullPath(value);
|
||||
}
|
||||
}
|
||||
|
||||
List<string> indexNames = new List<string>();
|
||||
public String[] IndexNames => indexNames.ToArray();
|
||||
|
||||
private HTTPServer _httpServer;
|
||||
|
||||
|
||||
public StaticRouter(HTTPServer httpServer)
|
||||
{
|
||||
_httpServer = httpServer;
|
||||
httpServer.AddRouter(this.Route);
|
||||
}
|
||||
|
||||
public StaticRouter(HTTPServer httpServer, string path)
|
||||
:this(path)
|
||||
{
|
||||
_httpServer = httpServer;
|
||||
httpServer.AddRouter(this.Route);
|
||||
}
|
||||
public StaticRouter(string path)
|
||||
{
|
||||
if (!Directory.Exists(path))
|
||||
throw new FileNotFoundException();
|
||||
|
||||
RootPath = Path.GetFullPath(path);
|
||||
RootPath = path;
|
||||
|
||||
AddIndex("index.html");
|
||||
AddIndex("index.htm");
|
||||
|
@ -63,6 +86,12 @@ namespace ln.http.router
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_httpServer?.RemoveRouter(this.Route);
|
||||
_httpServer = null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using ln.logging;
|
||||
using ln.http.exceptions;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using ln.type;
|
||||
using ln.http.connections;
|
||||
|
||||
namespace ln.http.websocket
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue