246 lines
7.9 KiB
C#
246 lines
7.9 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using ln.logging;
|
|
using ln.types.threads;
|
|
using ln.http.listener;
|
|
using ln.http.connections;
|
|
using ln.types.net;
|
|
using System.Globalization;
|
|
using ln.http.exceptions;
|
|
using System.Threading;
|
|
using ln.types;
|
|
using ln.http.router;
|
|
|
|
|
|
namespace ln.http
|
|
{
|
|
public class HTTPServer
|
|
{
|
|
public static int backlog = 5;
|
|
public static int defaultPort = 8080;
|
|
public static bool exclusivePortListener = false;
|
|
|
|
public IHttpRouter Router { get; set; }
|
|
|
|
public bool IsRunning => !shutdown && (threadPool.CurrentPoolSize > 0);
|
|
public Logger Logger { get; set; }
|
|
|
|
bool shutdown = false;
|
|
|
|
List<Listener> listeners = new List<Listener>();
|
|
public Listener[] Listeners => listeners.ToArray();
|
|
|
|
DynamicPool threadPool;
|
|
public DynamicPool ThreadPool => threadPool;
|
|
|
|
HashSet<Connection> currentConnections = new HashSet<Connection>();
|
|
public IEnumerable<Connection> CurrentConnections => currentConnections;
|
|
|
|
public HTTPServer()
|
|
{
|
|
Logger = Logger.Default;
|
|
threadPool = new DynamicPool(1024);
|
|
}
|
|
public HTTPServer(IHttpRouter router)
|
|
: this()
|
|
{
|
|
Router = router;
|
|
}
|
|
public HTTPServer(Listener listener, IHttpRouter router)
|
|
: this(router)
|
|
{
|
|
AddListener(listener);
|
|
}
|
|
public HTTPServer(Endpoint endpoint, IHttpRouter router)
|
|
: this(new HttpListener(endpoint), router) { }
|
|
|
|
public void AddListener(Listener listener)
|
|
{
|
|
listeners.Add(listener);
|
|
if (IsRunning)
|
|
StartListener(listener);
|
|
}
|
|
|
|
public void StartListener(Listener listener)
|
|
{
|
|
listener.Open();
|
|
|
|
threadPool.Enqueue(
|
|
() => listener.AcceptMany(
|
|
(connection) => threadPool.Enqueue(
|
|
() => this.HandleConnection(connection)
|
|
)
|
|
)
|
|
);
|
|
}
|
|
|
|
public void StopListener(Listener listener)
|
|
{
|
|
listener.Close();
|
|
}
|
|
|
|
|
|
public void AddEndpoint(Endpoint endpoint)
|
|
{
|
|
AddListener(new HttpListener(endpoint));
|
|
}
|
|
|
|
public void Start()
|
|
{
|
|
threadPool.Start();
|
|
|
|
foreach (Listener listener in listeners)
|
|
StartListener(listener);
|
|
}
|
|
|
|
public void Stop()
|
|
{
|
|
lock (this)
|
|
{
|
|
this.shutdown = true;
|
|
}
|
|
foreach (Listener listener in listeners)
|
|
StopListener(listener);
|
|
|
|
for (int n = 0; n < 150; n++)
|
|
{
|
|
lock (currentConnections)
|
|
{
|
|
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();
|
|
}
|
|
}
|
|
|
|
threadPool.Stop(true);
|
|
}
|
|
|
|
private void HandleConnection(Connection connection)
|
|
{
|
|
lock (this.currentConnections)
|
|
currentConnections.Add(connection);
|
|
|
|
try
|
|
{
|
|
HttpRequest httpRequest = null;
|
|
bool keepAlive = true;
|
|
try
|
|
{
|
|
do
|
|
{
|
|
httpRequest = connection.ReadRequest(this);
|
|
if (httpRequest == null)
|
|
break;
|
|
|
|
HttpResponse response;
|
|
try
|
|
{
|
|
response = Router.Route(new HttpRoutingContext(httpRequest),httpRequest);
|
|
}
|
|
catch (HttpException httpExc)
|
|
{
|
|
response = new HttpResponse(httpRequest);
|
|
response.StatusCode = httpExc.StatusCode;
|
|
response.StatusMessage = httpExc.Message;
|
|
response.ContentWriter.WriteLine(httpExc.Message);
|
|
}
|
|
|
|
if (response != null)
|
|
{
|
|
keepAlive = httpRequest.GetRequestHeader("connection", "keep-alive").Equals("keep-alive") && response.GetHeader("connection", "keep-alive").Equals("keep-alive");
|
|
response.SetHeader("connection", keepAlive ? "keep-alive" : "close");
|
|
|
|
connection.SendResponse(response);
|
|
}
|
|
else
|
|
{
|
|
keepAlive = false;
|
|
}
|
|
|
|
response?.ContentStream?.Dispose();
|
|
|
|
} while (keepAlive);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Logging.Log(e);
|
|
if (httpRequest != null)
|
|
{
|
|
HttpResponse httpResponse = new HttpResponse(httpRequest);
|
|
httpResponse.StatusCode = 500;
|
|
httpResponse.ContentWriter.WriteLine("500 Internal Server Error");
|
|
httpResponse.ContentWriter.Flush();
|
|
|
|
connection.SendResponse(httpResponse);
|
|
}
|
|
}
|
|
|
|
HttpRequest.ClearCurrent();
|
|
connection.GetStream().Close();
|
|
} finally
|
|
{
|
|
lock (currentConnections)
|
|
currentConnections.Remove(connection);
|
|
}
|
|
}
|
|
|
|
public void Log(DateTime startTime,double duration,HttpRequest httpRequest,HttpResponse httpResponse)
|
|
{
|
|
Logger.Log(LogLevel.INFO, "{0} {1} {2} {3}",startTime.ToString("yyyyMMdd-HH:mm:ss"),duration.ToString(CultureInfo.InvariantCulture),httpRequest.Hostname,httpRequest.RequestURL);
|
|
}
|
|
|
|
|
|
|
|
public static void StartSimpleServer(string[] arguments)
|
|
{
|
|
ArgumentContainer argumentContainer = new ArgumentContainer(new Argument[]
|
|
{
|
|
new Argument('p',"port",8080),
|
|
new Argument('l',"listen","127.0.0.1"),
|
|
new Argument('c', "catch",null)
|
|
});
|
|
|
|
argumentContainer.Parse(arguments);
|
|
|
|
SimpleRouter router = new SimpleRouter();
|
|
router.AddSimpleRoute("/*", new RouterTarget((request) =>
|
|
{
|
|
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 argumentContainer.AdditionalArguments)
|
|
{
|
|
StaticRouter staticRouter = new StaticRouter(path);
|
|
staticRouter.AddIndex("index.html");
|
|
staticRouter.AddIndex("index.htm");
|
|
router.AddSimpleRoute("/*", staticRouter);
|
|
}
|
|
|
|
if (argumentContainer['c'].IsSet)
|
|
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),argumentContainer['p'].IntegerValue),
|
|
new LoggingRouter(router));
|
|
server.Start();
|
|
}
|
|
|
|
}
|
|
}
|