v0.9.5 last fix HttpContent not setting content value
parent
b169066235
commit
e7ed29d78d
|
@ -0,0 +1,19 @@
|
|||
using System.Security.Cryptography.X509Certificates;
|
||||
using ln.collections;
|
||||
|
||||
namespace ln.http;
|
||||
|
||||
public class CertificateStore
|
||||
{
|
||||
private Cache<string, X509Certificate> _cache = new Cache<string, X509Certificate>();
|
||||
|
||||
public CertificateStore()
|
||||
{
|
||||
}
|
||||
|
||||
public void AddCertificate(X509Certificate certificate) => _cache.Add(certificate.Subject, certificate);
|
||||
public bool TryGetCertificate(string hostname, out X509Certificate certificate) =>
|
||||
_cache.TryGetValue(hostname, out certificate);
|
||||
|
||||
public void RemoveCertificate(string hostname) => _cache.Remove(hostname);
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
// /**
|
||||
// * File: FileSystemRouter.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.IO;
|
||||
using ln.http.mime;
|
||||
using ln.http.route;
|
||||
using ln.mime;
|
||||
|
||||
namespace ln.http
|
||||
{
|
||||
public class FileRouter : HttpRoute
|
||||
{
|
||||
public string FileName { get; }
|
||||
public string ContentType { get; }
|
||||
|
||||
public FileRouter(string filename)
|
||||
:this(null, filename)
|
||||
{
|
||||
}
|
||||
|
||||
public FileRouter(string filename, MimeTypes mimeTypes)
|
||||
:this(null, filename)
|
||||
{
|
||||
ContentType = mimeTypes.GetMimeTypeByExtension(Path.GetExtension(FileName)).ToString();
|
||||
}
|
||||
public FileRouter(string filename, string contentType)
|
||||
:base(HttpMethod.GET, null)
|
||||
{
|
||||
ContentType = contentType;
|
||||
|
||||
if (!File.Exists(filename))
|
||||
throw new FileNotFoundException();
|
||||
|
||||
FileName = filename;
|
||||
_routerDelegate = RouteToFile;
|
||||
}
|
||||
|
||||
public bool RouteToFile(HttpRequestContext requestContext, string routePath)
|
||||
{
|
||||
requestContext.Response =
|
||||
HttpResponse
|
||||
.OK()
|
||||
.Content(new FileStream(FileName, FileMode.Open, FileAccess.Read))
|
||||
.ContentType(ContentType);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -10,11 +10,12 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using ln.http.mime;
|
||||
using ln.http.route;
|
||||
using ln.mime;
|
||||
|
||||
namespace ln.http
|
||||
{
|
||||
public class FileSystemRouter : IDisposable
|
||||
public class FileSystemRouter : HttpRoute
|
||||
{
|
||||
private string _rootPath;
|
||||
public String RootPath
|
||||
|
@ -28,75 +29,89 @@ namespace ln.http
|
|||
|
||||
List<string> indexNames = new List<string>();
|
||||
public String[] IndexNames => indexNames.ToArray();
|
||||
|
||||
private HttpServer _httpServer;
|
||||
|
||||
private HttpRouter _parentRouter;
|
||||
private HttpRouter.HttpMapping _parentMapping;
|
||||
private MimeTypes _mimeTypes;
|
||||
|
||||
public FileSystemRouter(HttpServer httpServer, string path)
|
||||
public FileSystemRouter(HttpRouter parentRouter, string mapPath, MimeTypes mimeTypes, string fileSystemPath)
|
||||
:base(HttpMethod.GET, mapPath)
|
||||
{
|
||||
_httpServer = httpServer;
|
||||
httpServer?.AddRouter(this.Route);
|
||||
_parentRouter = parentRouter;
|
||||
_parentRouter?.Map(this);
|
||||
|
||||
_mimeTypes = mimeTypes;
|
||||
|
||||
if (!Directory.Exists(path))
|
||||
if (!Directory.Exists(fileSystemPath))
|
||||
throw new FileNotFoundException();
|
||||
|
||||
RootPath = path;
|
||||
RootPath = fileSystemPath;
|
||||
Console.Error.WriteLine("FileSystemRouter created ({0})", RootPath);
|
||||
|
||||
AddIndex("index.html");
|
||||
AddIndex("index.htm");
|
||||
|
||||
_routerDelegate = RouteToFileSystem;
|
||||
}
|
||||
public FileSystemRouter(string path)
|
||||
:this(null, path)
|
||||
|
||||
public FileSystemRouter(string mapPath, MimeTypes mimeTypes, string fileSystemPath)
|
||||
:this(null, mapPath, mimeTypes, fileSystemPath){}
|
||||
|
||||
public FileSystemRouter(string mapPath, string fileSystemPath)
|
||||
:this(null, mapPath, null, fileSystemPath){}
|
||||
|
||||
public FileSystemRouter(MimeTypes mimeTypes, string fileSystemPath)
|
||||
:this(null, null, mimeTypes, fileSystemPath)
|
||||
{
|
||||
}
|
||||
|
||||
public FileSystemRouter(HttpRouter parentRouter, string mappingPath, string path)
|
||||
:this(null, path)
|
||||
public FileSystemRouter(string fileSystemPath)
|
||||
:this(null, null, null, fileSystemPath)
|
||||
{
|
||||
_parentRouter = parentRouter;
|
||||
_parentMapping = _parentRouter.Map(HttpMethod.ANY, mappingPath, this.Route);
|
||||
}
|
||||
|
||||
|
||||
public void AddIndex(string indexName) => indexNames.Add(indexName);
|
||||
public void RemoveIndex(string indexName) => indexNames.Remove(indexName);
|
||||
|
||||
public bool Route(HttpContext httpContext)
|
||||
public bool RouteToFileSystem(HttpRequestContext requestContext, string routePath)
|
||||
{
|
||||
string finalPath = httpContext.RoutableUri.Length > 0 ? Path.Combine(RootPath, httpContext.RoutableUri.Substring(1)) : ".";
|
||||
string filename = ChooseFile(routePath);
|
||||
|
||||
if (filename is null)
|
||||
return false;
|
||||
|
||||
requestContext.Response =
|
||||
HttpResponse
|
||||
.OK()
|
||||
.Content(new FileStream(filename, FileMode.Open, FileAccess.Read));
|
||||
|
||||
if (_mimeTypes?.TryGetMimeTypeByExtension(Path.GetExtension(filename), out MimeType mimeType) ?? false)
|
||||
requestContext.Response.ContentType(mimeType.ToString());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
string ChooseFile(string path)
|
||||
{
|
||||
string finalPath = Path.Join(_rootPath, path);
|
||||
|
||||
if (File.Exists(finalPath))
|
||||
return finalPath;
|
||||
|
||||
if (Directory.Exists(finalPath))
|
||||
{
|
||||
foreach (string indexName in indexNames)
|
||||
{
|
||||
string indexFileName = Path.Combine(finalPath, indexName);
|
||||
if (File.Exists(indexFileName))
|
||||
{
|
||||
finalPath = indexFileName;
|
||||
break;
|
||||
}
|
||||
return indexFileName;
|
||||
}
|
||||
}
|
||||
|
||||
if (File.Exists(finalPath))
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
httpContext.Response = new HttpResponse(httpContext.Request, new FileStream(finalPath, FileMode.Open, FileAccess.Read));
|
||||
httpContext.Response.SetHeader("content-type", MimeTypeMap.GetMimeType(Path.GetExtension(finalPath)));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_httpServer?.RemoveRouter(this.Route);
|
||||
_httpServer = null;
|
||||
_parentRouter?.RemoveHttpMapping(_parentMapping);
|
||||
_parentMapping = null;
|
||||
_parentRouter?.UnMap(this);
|
||||
_parentRouter = null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,255 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace ln.http
|
||||
{
|
||||
public class HeaderContainer : IEnumerable<Header>
|
||||
{
|
||||
private Dictionary<string, Header> _headers = new Dictionary<string, Header>();
|
||||
|
||||
public HeaderContainer()
|
||||
{
|
||||
}
|
||||
|
||||
public HeaderContainer(Stream stream)
|
||||
{
|
||||
Read(stream);
|
||||
}
|
||||
|
||||
public string this[string headerName]
|
||||
{
|
||||
get => Get(headerName);
|
||||
set => Set(headerName, value);
|
||||
}
|
||||
|
||||
public bool TryGetHeader(string headerName, out Header header) =>
|
||||
_headers.TryGetValue(headerName.ToLower(), out header);
|
||||
|
||||
public bool TryGetValue(string headerName, out string headerValue)
|
||||
{
|
||||
if (_headers.TryGetValue(headerName.ToLower(), out Header header))
|
||||
{
|
||||
headerValue = header.Value;
|
||||
return true;
|
||||
}
|
||||
|
||||
headerValue = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool Contains(string headerName) => _headers.ContainsKey(headerName.ToLower());
|
||||
|
||||
public void Add(string headerName, string headerValue)
|
||||
{
|
||||
string lowerHeaderName = headerName.ToLower();
|
||||
if (_headers.TryGetValue(lowerHeaderName, out Header header))
|
||||
header.Value += " " + headerValue;
|
||||
else
|
||||
_headers.Add(lowerHeaderName, new Header(headerName, headerValue));
|
||||
}
|
||||
|
||||
public void Remove(string headerName) => _headers.Remove(headerName.ToLower());
|
||||
public void Clear() => _headers.Clear();
|
||||
|
||||
public void Set(string headerName, String headerValue)
|
||||
{
|
||||
string lowerHeaderName = headerName.ToLower();
|
||||
if (!_headers.TryGetValue(lowerHeaderName, out Header header))
|
||||
{
|
||||
header = new Header(headerName, "");
|
||||
_headers.Add(lowerHeaderName, header);
|
||||
}
|
||||
|
||||
header.Value = headerValue;
|
||||
}
|
||||
|
||||
public string Get(string headerName) => Get(headerName, null);
|
||||
public string Get(string headerName, string defaultValue)
|
||||
{
|
||||
if (TryGetHeader(headerName, out Header header))
|
||||
return header.Value;
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public int GetInteger(string headerName)
|
||||
{
|
||||
return int.Parse(Get(headerName));
|
||||
}
|
||||
|
||||
public int GetInteger(string headerName, int defaultValue)
|
||||
{
|
||||
if (TryGetValue(headerName, out string headerValue))
|
||||
return int.Parse(headerValue);
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public bool TryGetInteger(string headerName, out int value)
|
||||
{
|
||||
value = 0;
|
||||
return TryGetValue(headerName, out string headerValue) && int.TryParse(headerValue, out value);
|
||||
}
|
||||
|
||||
public float GetFloat(string headerName)
|
||||
{
|
||||
return float.Parse(Get(headerName));
|
||||
}
|
||||
|
||||
public float GetFloat(string headerName, float defaultValue)
|
||||
{
|
||||
if (TryGetValue(headerName, out string headerValue))
|
||||
return float.Parse(headerValue);
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public bool TryGetFloat(string headerName, out float value)
|
||||
{
|
||||
value = 0;
|
||||
return TryGetValue(headerName, out string headerValue) && float.TryParse(headerValue, out value);
|
||||
}
|
||||
|
||||
|
||||
public double GetDouble(string headerName)
|
||||
{
|
||||
return double.Parse(Get(headerName));
|
||||
}
|
||||
|
||||
public double GetDouble(string headerName, double defaultValue)
|
||||
{
|
||||
if (TryGetValue(headerName, out string headerValue))
|
||||
return double.Parse(headerValue);
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public bool TryGetDouble(string headerName, out double value)
|
||||
{
|
||||
value = 0;
|
||||
return TryGetValue(headerName, out string headerValue) && double.TryParse(headerValue, out value);
|
||||
}
|
||||
|
||||
public IEnumerator<Header> GetEnumerator() => _headers.Values.GetEnumerator();
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
foreach (var header in _headers)
|
||||
sb.AppendFormat("{0}: {1}\r\n", header.Value.Name, header.Value.Value);
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public void Read(Stream stream)
|
||||
{
|
||||
string headerName = null;
|
||||
string headerLine;
|
||||
while (!String.Empty.Equals(headerLine = HttpConnection.ReadLine(stream)))
|
||||
{
|
||||
int colon = headerLine.IndexOf(':', StringComparison.InvariantCulture);
|
||||
if (char.IsWhiteSpace(headerLine[0]))
|
||||
{
|
||||
if (headerName is string)
|
||||
Add(headerName, " " + headerLine.Substring(1).Trim());
|
||||
else
|
||||
throw new FormatException("expected header name");
|
||||
}
|
||||
else if (colon > 0)
|
||||
{
|
||||
headerName = headerLine.Substring(0, colon).Trim();
|
||||
Add(headerName, headerLine.Substring(colon + 1).Trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void CopyTo(Stream stream)
|
||||
{
|
||||
foreach (var header in _headers.Values)
|
||||
{
|
||||
byte[] bytes = Encoding.ASCII.GetBytes($"{header.Name}: {header.Value}\r\n");
|
||||
stream.Write(bytes, 0, bytes.Length);
|
||||
}
|
||||
stream.Write(new byte[] { 0x0d, 0x0a }, 0, 2);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class Header
|
||||
{
|
||||
private string _name;
|
||||
private Dictionary<string, string> _parameters;
|
||||
|
||||
public string Name
|
||||
{
|
||||
get => _name;
|
||||
set
|
||||
{
|
||||
_name = value;
|
||||
UpperName = value.ToUpper();
|
||||
}
|
||||
}
|
||||
|
||||
public string UpperName { get; private set; }
|
||||
public string Value { get; set; }
|
||||
|
||||
|
||||
public Header(string headerName)
|
||||
{
|
||||
Name = headerName;
|
||||
Value = String.Empty;
|
||||
}
|
||||
|
||||
public Header(string headerName, string headerValue)
|
||||
{
|
||||
Name = headerName;
|
||||
Value = headerValue;
|
||||
}
|
||||
|
||||
public bool TryGetParameter(string parameterName, out string parameterValue)
|
||||
{
|
||||
if (_parameters is null)
|
||||
ParseParameters();
|
||||
|
||||
return _parameters.TryGetValue(parameterName, out parameterValue);
|
||||
}
|
||||
public bool ContainsParameter(string parameterName)
|
||||
{
|
||||
if (_parameters is null)
|
||||
ParseParameters();
|
||||
|
||||
return _parameters.ContainsKey(parameterName);
|
||||
}
|
||||
|
||||
public Header ParseParameters()
|
||||
{
|
||||
_parameters = new Dictionary<string, string>();
|
||||
|
||||
int idxSemicolon = Value.IndexOf(';');
|
||||
if (idxSemicolon != -1)
|
||||
{
|
||||
string[] ptoks = Value.Split(';');
|
||||
|
||||
foreach (string ptok in ptoks)
|
||||
{
|
||||
int idxEqual = ptok.IndexOf('=');
|
||||
string pn, pv;
|
||||
if (idxEqual != -1)
|
||||
{
|
||||
pn = ptok.Substring(0, idxEqual).Trim();
|
||||
pv = ptok.Substring(idxEqual + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
pn = ptok.Trim();
|
||||
pv = "";
|
||||
}
|
||||
|
||||
_parameters.Add(pn, pv);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
|
||||
namespace ln.http;
|
||||
|
||||
public class Http1XConnection : HttpConnection
|
||||
{
|
||||
bool _keepAlive;
|
||||
|
||||
public Http1XConnection(Listener listener, IPEndPoint remoteEndpoint, Stream connectionStream, string method, Uri uri, HttpVersion httpVersion)
|
||||
: base(listener, remoteEndpoint, connectionStream, method, uri, httpVersion)
|
||||
{
|
||||
if (httpVersion == HttpVersion.HTTP11)
|
||||
_keepAlive = true;
|
||||
}
|
||||
|
||||
public override void Run()
|
||||
{
|
||||
string _method = Method;
|
||||
Uri _requestUri = RequestUri;
|
||||
HttpVersion _httpVersion = HttpVersion;
|
||||
HeaderContainer headerContainer = new HeaderContainer();
|
||||
|
||||
while (ConnectionStream.CanRead && ConnectionStream.CanWrite)
|
||||
{
|
||||
headerContainer.Clear();
|
||||
headerContainer.Read(ConnectionStream);
|
||||
|
||||
if (
|
||||
headerContainer.TryGetHeader("connection", out Header connectionHeader)
|
||||
)
|
||||
{
|
||||
switch (connectionHeader.Value)
|
||||
{
|
||||
case "close":
|
||||
_keepAlive = false;
|
||||
break;
|
||||
case "keep-alive":
|
||||
_keepAlive = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
HttpRequestStream requestStream = null;
|
||||
if (headerContainer.TryGetInteger("content-length", out int contentLength))
|
||||
requestStream = new HttpRequestStream(ConnectionStream, contentLength);
|
||||
|
||||
HttpRequestContext requestContext = new HttpRequestContext(Listener, this, ConnectionStream, new HttpRequest(_method, _requestUri, _httpVersion, false, headerContainer, requestStream));
|
||||
Listener.Dispatch(requestContext);
|
||||
|
||||
if (!ConnectionStream.CanWrite && !ConnectionStream.CanRead)
|
||||
break;
|
||||
|
||||
SendResponse(requestContext);
|
||||
ConnectionStream.Flush();
|
||||
|
||||
string responseConnectionHeader = requestContext.Response.GetHeader("connection");
|
||||
_keepAlive = responseConnectionHeader switch
|
||||
{
|
||||
"close" => false,
|
||||
"keep-alive" => true,
|
||||
_ => _keepAlive
|
||||
};
|
||||
|
||||
requestContext.Dispose();
|
||||
|
||||
if (!_keepAlive)
|
||||
break;
|
||||
|
||||
if (!HttpConnection.ReadRequestLine(ConnectionStream, out _method, out _requestUri, out _httpVersion))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override void SendResponse(HttpRequestContext requestContext)
|
||||
{
|
||||
foreach (var httpCookie in requestContext.Response.Cookies)
|
||||
requestContext.Response.Headers.Add("Set-Cookie", httpCookie.ToString());
|
||||
|
||||
string statusLine = $"{HttpVersionSupport.ToString(requestContext.Request.HttpVersion)} {(int)requestContext.Response.HttpStatusCode} {requestContext.Response.HttpStatusCode.ToString()}\r\n";
|
||||
byte[] statusLineBytes = Encoding.ASCII.GetBytes(statusLine);
|
||||
requestContext.ConnectionStream.Write(statusLineBytes, 0, statusLineBytes.Length);
|
||||
if (!requestContext.Response.Headers.Contains("content-type"))
|
||||
requestContext.Response.Headers.Set("content-type", requestContext.Response.HttpContent?.ContentType);
|
||||
requestContext.Response.Headers.Set("content-length", requestContext.Response.HttpContent?.Length.ToString() ?? "0");
|
||||
requestContext.Response.Headers.CopyTo(requestContext.ConnectionStream);
|
||||
requestContext.Response.HttpContent?.CopyTo(requestContext.ConnectionStream);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
|
||||
namespace ln.http;
|
||||
|
||||
public class Http2Connection : HttpConnection
|
||||
{
|
||||
public Http2Connection(Listener listener, IPEndPoint remoteEndpoint, Stream connectionStream, string method, Uri uri, HttpVersion httpVersion)
|
||||
: base(listener, remoteEndpoint, connectionStream, method, uri, httpVersion)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void Run()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void SendResponse(HttpRequestContext requestContext)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
|
@ -1,26 +1,105 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
|
||||
namespace ln.http
|
||||
{
|
||||
public class HttpConnection
|
||||
public abstract class HttpConnection
|
||||
{
|
||||
public Stream ClientStream { get; }
|
||||
public IPEndPoint RemoteEndPoint { get; }
|
||||
public IPEndPoint LocalEndPoint { get; }
|
||||
|
||||
public bool IsEncrypted { get; }
|
||||
public Listener Listener { get; }
|
||||
public IPEndPoint LocalEndpoint => Listener.LocalEndpoint;
|
||||
public IPEndPoint RemoteEndpoint { get; }
|
||||
public Stream ConnectionStream { get; }
|
||||
public string Method { get; }
|
||||
public Uri RequestUri { get; }
|
||||
public HttpVersion HttpVersion { get; }
|
||||
|
||||
public HttpConnection(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, Stream clientStream)
|
||||
:this(localEndPoint, remoteEndPoint, clientStream, false)
|
||||
{}
|
||||
|
||||
public HttpConnection(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, Stream clientStream, bool isEncrypted)
|
||||
public HttpConnection(Listener listener, IPEndPoint remoteEndpoint, Stream connectionStream, string method,
|
||||
Uri uri, HttpVersion httpVersion)
|
||||
{
|
||||
ClientStream = clientStream;
|
||||
LocalEndPoint = localEndPoint;
|
||||
RemoteEndPoint = remoteEndPoint;
|
||||
IsEncrypted = isEncrypted;
|
||||
Listener = listener;
|
||||
RemoteEndpoint = remoteEndpoint;
|
||||
ConnectionStream = connectionStream;
|
||||
|
||||
Method = method;
|
||||
RequestUri = uri;
|
||||
HttpVersion = httpVersion;
|
||||
}
|
||||
|
||||
public abstract void Run();
|
||||
|
||||
public static string ReadLine(Stream stream) => ReadLine(stream, Encoding.ASCII);
|
||||
|
||||
public static string ReadLine(Stream stream, Encoding encoding)
|
||||
{
|
||||
if (!stream.CanRead)
|
||||
return null;
|
||||
|
||||
byte[] line = new byte[1024];
|
||||
int n = 0;
|
||||
int ch;
|
||||
while ((ch = stream.ReadByte()) != -1)
|
||||
{
|
||||
switch (ch)
|
||||
{
|
||||
case '\r':
|
||||
break;
|
||||
case '\n':
|
||||
return encoding.GetString(line, 0, n);
|
||||
default:
|
||||
line[n++] = (byte)ch;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (n > 0)
|
||||
return encoding.GetString(line, 0, n);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static bool ReadRequestLine(Stream stream, out string method, out Uri requestUri,
|
||||
out HttpVersion httpVersion)
|
||||
{
|
||||
string requestLine = ReadLine(stream);
|
||||
if (requestLine is null)
|
||||
{
|
||||
method = null;
|
||||
requestUri = null;
|
||||
httpVersion = HttpVersion.None;
|
||||
return false;
|
||||
}
|
||||
|
||||
int idxSpace1 = requestLine.IndexOf(' ');
|
||||
int idxSpace2 = requestLine.IndexOf(' ', idxSpace1 + 1);
|
||||
|
||||
if ((idxSpace1 > 0) && (idxSpace2 > 0))
|
||||
{
|
||||
method = requestLine.Substring(0, idxSpace1);
|
||||
requestUri = new Uri(requestLine.Substring(idxSpace1 + 1, (idxSpace2 - idxSpace1 - 1)));
|
||||
string protocol = requestLine.Substring(idxSpace2 + 1);
|
||||
httpVersion = HttpVersion.None;
|
||||
|
||||
if ("PRI".Equals(method) && Http2PrefaceUri.Equals(requestUri) && "HTTP/2.0".Equals(protocol))
|
||||
httpVersion = HttpVersion.HTTP2;
|
||||
else if ("HTTP/1.0".Equals(protocol))
|
||||
httpVersion = HttpVersion.HTTP10;
|
||||
else if ("HTTP/1.1".Equals(protocol))
|
||||
httpVersion = HttpVersion.HTTP11;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
method = null;
|
||||
requestUri = null;
|
||||
httpVersion = HttpVersion.None;
|
||||
return false;
|
||||
}
|
||||
|
||||
public abstract void SendResponse(HttpRequestContext requestContext);
|
||||
|
||||
static Uri Http2PrefaceUri = new Uri("*");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
using System;
|
||||
|
||||
namespace ln.http;
|
||||
|
||||
[Flags]
|
||||
public enum HttpConnectionFlags
|
||||
{
|
||||
TLS,
|
||||
}
|
|
@ -0,0 +1,166 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using ln.json;
|
||||
using ln.mime;
|
||||
|
||||
namespace ln.http;
|
||||
|
||||
public abstract class HttpContent : IDisposable
|
||||
{
|
||||
public HttpContent(string contentType)
|
||||
{
|
||||
ContentType = contentType;
|
||||
}
|
||||
|
||||
public string ContentType { get; }
|
||||
public abstract long Length { get; }
|
||||
public abstract void CopyTo(Stream targetStream);
|
||||
|
||||
public virtual void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class StringContent : HttpContent
|
||||
{
|
||||
public string Text { get; set; } = String.Empty;
|
||||
public Encoding Encoding { get; set; }
|
||||
|
||||
public StringContent(string text, string contentType)
|
||||
:base(contentType)
|
||||
{
|
||||
Encoding = Encoding.UTF8;
|
||||
Text = text;
|
||||
}
|
||||
public StringContent(string text)
|
||||
:this(text, "text/plain; charset=utf-8")
|
||||
{
|
||||
}
|
||||
|
||||
public override long Length => Encoding.GetBytes(Text ).Length;
|
||||
public override void CopyTo(Stream targetStream)
|
||||
{
|
||||
byte[] bytes = Encoding.GetBytes(Text);
|
||||
targetStream.Write(bytes, 0, bytes.Length);
|
||||
}
|
||||
}
|
||||
|
||||
public class FileContent : HttpContent
|
||||
{
|
||||
public string FileName { get; set; }
|
||||
|
||||
public FileContent(string filename, string contentType)
|
||||
: base(contentType)
|
||||
{
|
||||
FileName = filename;
|
||||
}
|
||||
public FileContent(string filename)
|
||||
:this(filename, new MimeTypes().GetMimeTypeByExtension(Path.GetExtension(filename)).ToString())
|
||||
{
|
||||
}
|
||||
|
||||
public override long Length => new FileInfo(FileName).Length;
|
||||
public override void CopyTo(Stream targetStream)
|
||||
{
|
||||
using FileStream fs = new FileStream(FileName, FileMode.Open, FileAccess.Read);
|
||||
fs.CopyTo(targetStream);
|
||||
}
|
||||
}
|
||||
|
||||
public class JsonContent : HttpContent
|
||||
{
|
||||
public JSONValue Value { get; set; }
|
||||
|
||||
private byte[] _bytes;
|
||||
|
||||
public JsonContent(JSONValue json)
|
||||
:base("application/json")
|
||||
{
|
||||
Value = json;
|
||||
}
|
||||
|
||||
public override long Length
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_bytes is null)
|
||||
Serialize();
|
||||
|
||||
return _bytes.Length;
|
||||
}
|
||||
}
|
||||
|
||||
public override void CopyTo(Stream targetStream)
|
||||
{
|
||||
if (_bytes is null)
|
||||
Serialize();
|
||||
|
||||
targetStream.Write(_bytes, 0, _bytes.Length);
|
||||
}
|
||||
|
||||
public void Serialize()
|
||||
{
|
||||
string jsonText = Value.ToString();
|
||||
_bytes = Encoding.UTF8.GetBytes(jsonText);
|
||||
}
|
||||
}
|
||||
|
||||
public class StreamContent : HttpContent
|
||||
{
|
||||
public Stream Stream { get; protected set; }
|
||||
public bool DisposeStream { get; }
|
||||
|
||||
public StreamContent(Stream stream)
|
||||
: this(stream, true, "application/octet-stream")
|
||||
{
|
||||
}
|
||||
|
||||
public StreamContent(Stream stream, bool disposeStream)
|
||||
:this(stream, disposeStream, "application/octet-stream")
|
||||
{}
|
||||
|
||||
public StreamContent(Stream stream, string contentType)
|
||||
:this(stream, true, contentType)
|
||||
{
|
||||
}
|
||||
public StreamContent(Stream stream, bool disposeStream, string contentType)
|
||||
:base(contentType)
|
||||
{
|
||||
Stream = stream;
|
||||
DisposeStream = disposeStream;
|
||||
}
|
||||
|
||||
public override long Length => Stream.CanSeek ? Stream.Length - Stream.Position : Stream.Length;
|
||||
public override void CopyTo(Stream targetStream) => Stream.CopyTo(targetStream);
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
if (DisposeStream)
|
||||
Stream.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public class StreamedContent : HttpContent
|
||||
{
|
||||
public MemoryStream ContentStream { get; }
|
||||
|
||||
public StreamedContent(string contentType)
|
||||
:base(contentType)
|
||||
{
|
||||
ContentStream = new MemoryStream();
|
||||
}
|
||||
|
||||
public override long Length => ContentStream.Length;
|
||||
|
||||
public override void CopyTo(Stream targetStream)
|
||||
{
|
||||
ContentStream.Position = 0;
|
||||
ContentStream.CopyTo(targetStream);
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
ContentStream?.Dispose();
|
||||
}
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
using System;
|
||||
using ln.http.exceptions;
|
||||
using ln.http.router;
|
||||
|
||||
namespace ln.http
|
||||
{
|
||||
public class HttpContext
|
||||
{
|
||||
public HttpServer HttpServer { get; }
|
||||
public HttpContext SourceContext { get; }
|
||||
public HttpException HttpException { get; }
|
||||
public HttpRequest Request { get; set; }
|
||||
public HttpResponse Response { get; set; }
|
||||
public HttpPrincipal AuthenticatedPrincipal { get; private set; }
|
||||
|
||||
public HttpContext(HttpServer httpServer)
|
||||
{
|
||||
HttpServer = httpServer;
|
||||
}
|
||||
|
||||
public HttpContext(HttpServer httpServer, HttpRequest httpRequest) : this(httpServer)
|
||||
{
|
||||
Request = httpRequest;
|
||||
RoutableUri = httpRequest.RequestUri.AbsolutePath;
|
||||
}
|
||||
|
||||
public HttpContext(HttpContext sourceContext, HttpException httpException)
|
||||
{
|
||||
SourceContext = sourceContext;
|
||||
HttpException = httpException;
|
||||
|
||||
HttpServer = sourceContext.HttpServer;
|
||||
RoutableUri = String.Format("/_err/{0:3}.html", (int)httpException.HttpStatusCode);
|
||||
AuthenticatedPrincipal = SourceContext.AuthenticatedPrincipal;
|
||||
}
|
||||
|
||||
public string RoutableUri { get; set; }
|
||||
|
||||
public bool Authenticate()
|
||||
{
|
||||
foreach (var authenticationSource in HttpServer.AuthenticationSources)
|
||||
{
|
||||
if (authenticationSource.AuthenticatePrincipal(this, out HttpPrincipal httpPrincipal))
|
||||
{
|
||||
AuthenticatedPrincipal = httpPrincipal;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public void DeAuthenticate() => AuthenticatedPrincipal = null;
|
||||
}
|
||||
}
|
|
@ -3,18 +3,31 @@ using System.ComponentModel;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using ln.json;
|
||||
using ln.json.mapping;
|
||||
using ln.protocols.helper;
|
||||
using ln.type;
|
||||
|
||||
namespace ln.http
|
||||
{
|
||||
public abstract class HttpEndpointController : HttpRouter
|
||||
public abstract class HttpEndpointController : HttpRouter, IDisposable
|
||||
{
|
||||
public HttpEndpointController()
|
||||
private HttpRouter _httpRouter;
|
||||
|
||||
public HttpEndpointController():this(null){}
|
||||
public HttpEndpointController(HttpRouter httpRouter)
|
||||
{
|
||||
_httpRouter = httpRouter;
|
||||
MapAttribute mapAttribute = GetType().GetCustomAttribute<MapAttribute>();
|
||||
if (mapAttribute is not null)
|
||||
{
|
||||
HttpMethod = mapAttribute.Method != HttpMethod.NONE ? mapAttribute.Method : HttpMethod.ANY;
|
||||
Route = mapAttribute.Path is not null ? new Regex(mapAttribute.Path) : null;
|
||||
}
|
||||
|
||||
Initialize();
|
||||
|
||||
_httpRouter?.Map(this);
|
||||
}
|
||||
|
||||
void Initialize()
|
||||
|
@ -60,9 +73,9 @@ namespace ln.http
|
|||
.Select((pi) => pi.GetCustomAttribute<HttpArgumentSourceAttribute>()).ToArray();
|
||||
}
|
||||
|
||||
public abstract bool Route(HttpContext httpContext);
|
||||
public abstract bool Route(HttpRequestContext httpContext, string routePath);
|
||||
|
||||
bool TryApplyParameters(HttpContext httpContext, out object[] parameters)
|
||||
bool TryApplyParameters(HttpRequestContext requestContext, out object[] parameters)
|
||||
{
|
||||
parameters = new object[_parameterInfos.Length];
|
||||
|
||||
|
@ -70,14 +83,14 @@ namespace ln.http
|
|||
{
|
||||
ParameterInfo parameterInfo = _parameterInfos[n];
|
||||
|
||||
if (parameterInfo.ParameterType.Equals(typeof(HttpContext)))
|
||||
parameters[n] = httpContext;
|
||||
if (parameterInfo.ParameterType.Equals(typeof(HttpRequestContext)))
|
||||
parameters[n] = requestContext;
|
||||
else if (parameterInfo.ParameterType.Equals(typeof(HttpRequest)))
|
||||
parameters[n] = httpContext.Request;
|
||||
parameters[n] = requestContext.Request;
|
||||
else if (parameterInfo.ParameterType.Equals(typeof(HttpPrincipal)))
|
||||
parameters[n] = httpContext.AuthenticatedPrincipal;
|
||||
parameters[n] = requestContext.AuthenticatedPrincipal;
|
||||
else if (TryFindArgumentByName(
|
||||
httpContext,
|
||||
requestContext,
|
||||
_argumentSourceAttributes[n]?.ArgumentSource ?? HttpArgumentSource.AUTO,
|
||||
_argumentSourceAttributes[n]?.ArgumentName ?? _parameterInfos[n].Name,
|
||||
out string parameterValue
|
||||
|
@ -94,14 +107,14 @@ namespace ln.http
|
|||
{
|
||||
if (_parameterInfos[n].ParameterType.Equals(typeof(Stream)))
|
||||
{
|
||||
parameters[n] = httpContext.Request.ContentStream;
|
||||
parameters[n] = requestContext.Request.ContentStream;
|
||||
} else if (_parameterInfos[n].ParameterType.Equals(typeof(TextReader)))
|
||||
{
|
||||
parameters[n] = new StreamReader(httpContext.Request.ContentStream);
|
||||
} else if (httpContext.Request.Headers.TryGetValue("Content-Type", out string contentType) &&
|
||||
parameters[n] = new StreamReader(requestContext.Request.ContentStream);
|
||||
} else if (requestContext.Request.Headers.TryGetValue("Content-Type", out string contentType) &&
|
||||
contentType.Equals("application/json"))
|
||||
{
|
||||
using (TextReader reader = httpContext.Request.ContentStream.TextReader())
|
||||
using (TextReader reader = new StreamReader(requestContext.Request.ContentStream))
|
||||
{
|
||||
JSONValue jsonValue = JSONParser.Parse(reader);
|
||||
|
||||
|
@ -124,9 +137,9 @@ namespace ln.http
|
|||
return true;
|
||||
}
|
||||
|
||||
bool TryFindArgumentByName(HttpContext httpContext, HttpArgumentSource argumentSource, string parameterName, out string parameterValue)
|
||||
bool TryFindArgumentByName(HttpRequestContext httpContext, HttpArgumentSource argumentSource, string parameterName, out string parameterValue)
|
||||
{
|
||||
if (((argumentSource & HttpArgumentSource.PARAMETER) == HttpArgumentSource.PARAMETER) && httpContext.Request.TryGetParameter(parameterName, out 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)))
|
||||
|
@ -139,7 +152,7 @@ namespace ln.http
|
|||
return false;
|
||||
}
|
||||
|
||||
private object InvokeMethod(HttpContext httpContext)
|
||||
private object InvokeMethod(HttpRequestContext httpContext)
|
||||
{
|
||||
object[] parameters = null;
|
||||
try
|
||||
|
@ -156,8 +169,6 @@ namespace ln.http
|
|||
DisposeParameters(parameters);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public class NonVoidEndpoint : MappedEndpoint
|
||||
{
|
||||
|
@ -166,7 +177,7 @@ namespace ln.http
|
|||
{
|
||||
}
|
||||
|
||||
public override bool Route(HttpContext httpContext)
|
||||
public override bool Route(HttpRequestContext httpContext, string routePath)
|
||||
{
|
||||
object returnedValue = InvokeMethod(httpContext);
|
||||
|
||||
|
@ -189,7 +200,7 @@ namespace ln.http
|
|||
{
|
||||
}
|
||||
|
||||
public override bool Route(HttpContext httpContext)
|
||||
public override bool Route(HttpRequestContext httpContext, string routePath)
|
||||
{
|
||||
object returnedValue = InvokeMethod(httpContext);
|
||||
if (returnedValue is HttpResponse httpResponse)
|
||||
|
@ -213,6 +224,11 @@ namespace ln.http
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_httpRouter?.Dispose();
|
||||
base.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,49 +2,70 @@
|
|||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using ln.protocols.helper;
|
||||
using ln.rtp;
|
||||
using System.Text.RegularExpressions;
|
||||
using ln.json;
|
||||
|
||||
namespace ln.http
|
||||
{ public class HttpRequest : IDisposable
|
||||
{
|
||||
public class HttpRequest : IDisposable
|
||||
{
|
||||
public Request BaseRequest { get; }
|
||||
public HttpServer Server { get; }
|
||||
|
||||
public String Protocol => BaseRequest.Protocol;
|
||||
public HttpMethod Method { get; }
|
||||
|
||||
public IPAddress ClientAddress { get; }
|
||||
public String Host { get; }
|
||||
public int Port { get; }
|
||||
public bool TLS { get; }
|
||||
|
||||
public HeaderContainer Headers => BaseRequest.Headers;
|
||||
public string RequestMethodName { get; }
|
||||
public Uri RequestUri { get; private set; }
|
||||
public HttpVersion HttpVersion { get; }
|
||||
public HeaderContainer Headers { get; }
|
||||
|
||||
public HttpMethod Method { get; private set; }
|
||||
public String Host { get; private set; }
|
||||
public int Port { get; private set; }
|
||||
public bool ViaTLS { get; private set; }
|
||||
|
||||
public QueryStringParameters Query { get; private set; }
|
||||
|
||||
public Stream ContentStream => BaseRequest.ContentStream;
|
||||
public Stream ConnectionStream => BaseRequest.ContentStream.BaseStream;
|
||||
|
||||
public Uri BaseUri { get; }
|
||||
public Uri RequestUri { get; }
|
||||
public Uri BaseUri { get; private set; }
|
||||
|
||||
|
||||
Dictionary<String, String> requestCookies;
|
||||
Dictionary<string, String> requestParameters;
|
||||
|
||||
|
||||
public HttpRequest(HttpServer httpServer, Request baseRequest)
|
||||
public HttpRequestStream ContentStream { get; }
|
||||
|
||||
public HttpRequest(string requestMethodName, Uri requestUri, HttpVersion httpVersion, bool viaTLS, HeaderContainer headerContainer, HttpRequestStream requestStream)
|
||||
{
|
||||
Server = httpServer;
|
||||
BaseRequest = baseRequest;
|
||||
RequestMethodName = requestMethodName;
|
||||
RequestUri = requestUri;
|
||||
HttpVersion = httpVersion;
|
||||
Headers = headerContainer;
|
||||
ViaTLS = viaTLS;
|
||||
ContentStream = requestStream;
|
||||
|
||||
if (Enum.TryParse(BaseRequest.Method, out HttpMethod httpMethod))
|
||||
Initialize();
|
||||
}
|
||||
|
||||
//private static Regex reHostPort = new Regex("(?<host>(\\w[^:]*|\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|\\[\\]]+))(:(?<port>\\d+))?");
|
||||
|
||||
void Initialize()
|
||||
{
|
||||
if (Enum.TryParse(RequestMethodName, out HttpMethod httpMethod))
|
||||
Method = httpMethod;
|
||||
else
|
||||
Method = HttpMethod.NONE;
|
||||
|
||||
Headers.TryGetValue("Host", out string host);
|
||||
if (Headers.TryGetValue("Host", out string host))
|
||||
{
|
||||
int colon = host.LastIndexOf(':');
|
||||
if (colon != -1)
|
||||
{
|
||||
Host = host.Substring(0, colon).Trim();
|
||||
Port = int.Parse(host.Substring(colon + 1).Trim());
|
||||
}
|
||||
else
|
||||
{
|
||||
Host = host.Trim();
|
||||
}
|
||||
} else
|
||||
{
|
||||
Host = RequestUri.Host;
|
||||
Port = RequestUri.Port;
|
||||
}
|
||||
|
||||
/*
|
||||
if (Headers.TryGetValue("X-Forwarded-Host", out string forwardedHost))
|
||||
host = forwardedHost;
|
||||
if (Headers.TryGetValue("X-Forwarded-For", out string forwardedFor))
|
||||
|
@ -56,31 +77,17 @@ namespace ln.http
|
|||
{
|
||||
// ToDo: Implement parser
|
||||
}
|
||||
|
||||
int colon = host.IndexOf(':');
|
||||
if (colon != -1)
|
||||
{
|
||||
Host = host.Substring(0, colon).Trim();
|
||||
Port = int.Parse(host.Substring(colon + 1).Trim());
|
||||
}
|
||||
else
|
||||
{
|
||||
Host = host.Trim();
|
||||
}
|
||||
|
||||
BaseUri = new UriBuilder(TLS ? "https:" : "http:", Host, Port).Uri;
|
||||
RequestUri = new Uri(BaseUri, BaseRequest.RequestUri);
|
||||
*/
|
||||
BaseUri = new UriBuilder(ViaTLS ? "https:" : "http:", Host, Port).Uri;
|
||||
RequestUri = new Uri(BaseUri, RequestUri);
|
||||
Query = new QueryStringParameters(RequestUri.Query);
|
||||
|
||||
requestParameters = new Dictionary<string, string>();
|
||||
|
||||
requestCookies = new Dictionary<string, string>();
|
||||
|
||||
if (Headers.TryGetValue("Cookie", out string cookies))
|
||||
SetupCookies(cookies);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
private void SetupCookies(string cookies)
|
||||
{
|
||||
foreach (String cookie in cookies.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
|
||||
|
@ -98,11 +105,8 @@ namespace ln.http
|
|||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
//return string.Format("[HttpRequest: RemoteEndpoint={0}, Hostname={1} Port={2} URI={4}, Method={4}, RequestURL={5}, Protocol={6} Query={7}]", RemoteEndpoint, URI, Method, RequestURL, Protocol, Hostname, Port,Query);
|
||||
return base.ToString();
|
||||
}
|
||||
public override string ToString() =>
|
||||
$"[HttpRequest: Host={Host} Port={Port} HttpVersion={HttpVersion} Method={Method} URI={RequestUri} Query={Query}]";
|
||||
|
||||
public String GetRequestHeader(String name)
|
||||
{
|
||||
|
@ -122,36 +126,8 @@ namespace ln.http
|
|||
|
||||
public bool TryGetCookie(string cookieName, out string cookieValue) =>
|
||||
requestCookies.TryGetValue(cookieName, out cookieValue);
|
||||
|
||||
public bool ContainsParameter(string parameterName) => requestParameters.ContainsKey(parameterName);
|
||||
public String GetParameter(String parameterName) => GetParameter(parameterName, null);
|
||||
|
||||
public String GetParameter(String parameterName, String defaultValue)
|
||||
{
|
||||
if (!requestParameters.TryGetValue(parameterName, out string value))
|
||||
value = defaultValue;
|
||||
return value;
|
||||
}
|
||||
|
||||
public bool TryGetParameter(String parameterName, out string parameterValue) =>
|
||||
requestParameters.TryGetValue(parameterName, out parameterValue);
|
||||
public void SetParameter(String parameterName, String parameterValue) =>
|
||||
requestParameters[parameterName] = parameterValue;
|
||||
|
||||
public IEnumerable<string> ParameterNames => requestParameters.Keys;
|
||||
|
||||
public HttpResponse Redirect(string location, params object[] p) => Redirect(303, location, p);
|
||||
|
||||
public HttpResponse Redirect(int status, string location, params object[] p)
|
||||
{
|
||||
location = string.Format(location, p);
|
||||
|
||||
HttpResponse response = new HttpResponse(this);
|
||||
response.StatusCode = status;
|
||||
response.SetHeader("location", location);
|
||||
return response;
|
||||
|
||||
}
|
||||
|
||||
public HttpResponse Redirect(string location, params object[] p) => Redirect(HttpStatusCode.SeeOther, location, p);
|
||||
|
||||
public HttpResponse Redirect(HttpStatusCode statusCode, string location, params object[] p)
|
||||
{
|
||||
|
@ -163,9 +139,21 @@ namespace ln.http
|
|||
return response;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
public JSONValue ToJsonValue()
|
||||
{
|
||||
ContentStream?.Dispose();
|
||||
long p = ContentStream.Position;
|
||||
try
|
||||
{
|
||||
using (TextReader reader = new StreamReader(ContentStream))
|
||||
return JSONParser.Parse(reader);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ContentStream.Position = p;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void Dispose() => ContentStream?.Dispose();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using ln.collections;
|
||||
|
||||
namespace ln.http;
|
||||
|
||||
public sealed class HttpRequestContext : IDisposable
|
||||
{
|
||||
public Listener Listener { get; }
|
||||
public IPEndPoint LocalEndpoint => Listener.LocalEndpoint;
|
||||
public HttpConnection HttpConnection { get; }
|
||||
public Stream ConnectionStream { get; }
|
||||
public HttpRequest Request { get; protected set; }
|
||||
public HttpResponse Response { get; set; }
|
||||
public HttpRequestContext ParentContext { get; }
|
||||
public HttpPrincipal AuthenticatedPrincipal { get; set; }
|
||||
|
||||
|
||||
Dictionary<string, String> _parameters = new Dictionary<string, string>();
|
||||
|
||||
public HttpRequestContext(HttpRequestContext parentContext, HttpRequest request)
|
||||
: this(parentContext.Listener, parentContext.HttpConnection, parentContext.ConnectionStream, request)
|
||||
{
|
||||
ParentContext = parentContext;
|
||||
}
|
||||
|
||||
public HttpRequestContext(Listener listener, HttpConnection httpConnection, Stream connectionStream, HttpRequest request)
|
||||
{
|
||||
Listener = listener;
|
||||
Request = request;
|
||||
HttpConnection = httpConnection;
|
||||
ConnectionStream = connectionStream;
|
||||
}
|
||||
|
||||
protected HttpRequestContext(Listener listener, HttpConnection httpConnection, Stream connectionStream)
|
||||
{
|
||||
Listener = listener;
|
||||
HttpConnection = httpConnection;
|
||||
ConnectionStream = connectionStream;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Request?.Dispose();
|
||||
Response?.Dispose();
|
||||
}
|
||||
|
||||
|
||||
public bool ContainsParameter(string parameterName) => _parameters.ContainsKey(parameterName);
|
||||
public String GetParameter(String parameterName) => GetParameter(parameterName, null);
|
||||
|
||||
public String GetParameter(String parameterName, String defaultValue)
|
||||
{
|
||||
if (!_parameters.TryGetValue(parameterName, out string value))
|
||||
value = defaultValue;
|
||||
return value;
|
||||
}
|
||||
|
||||
public bool TryGetParameter(String parameterName, out string parameterValue) =>
|
||||
_parameters.TryGetValue(parameterName, out parameterValue);
|
||||
public void SetParameter(String parameterName, String parameterValue) =>
|
||||
_parameters[parameterName] = parameterValue;
|
||||
|
||||
public IEnumerable<string> ParameterNames => _parameters.Keys;
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace ln.http;
|
||||
|
||||
public class HttpRequestStream : Stream
|
||||
{
|
||||
public int MemoryLimit { get; set; } = 1024 * 1024 * 10;
|
||||
|
||||
private Stream _stream;
|
||||
private string _tempFileName;
|
||||
|
||||
public HttpRequestStream(Stream connectionStream, int length)
|
||||
{
|
||||
byte[] transferBuffer;
|
||||
if (length <= MemoryLimit)
|
||||
{
|
||||
transferBuffer = new byte[length];
|
||||
if (connectionStream.Read(transferBuffer, 0, transferBuffer.Length) != length)
|
||||
throw new IOException();
|
||||
|
||||
_stream = new MemoryStream(transferBuffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
_tempFileName = Path.GetTempFileName();
|
||||
_stream = new FileStream(_tempFileName, FileMode.Open, FileAccess.ReadWrite);
|
||||
|
||||
transferBuffer = new byte[MemoryLimit];
|
||||
while (length > 0)
|
||||
{
|
||||
int size = length > transferBuffer.Length ? transferBuffer.Length : length;
|
||||
int nread = connectionStream.Read(transferBuffer, 0, size);
|
||||
_stream.Write(transferBuffer, 0, nread);
|
||||
length -= nread;
|
||||
}
|
||||
|
||||
_stream.Position = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count) => _stream.Read(buffer, offset, count);
|
||||
public override long Seek(long offset, SeekOrigin origin) => _stream.Seek(offset, origin);
|
||||
public override void SetLength(long value) => throw new System.NotImplementedException();
|
||||
public override void Write(byte[] buffer, int offset, int count) => throw new System.NotImplementedException();
|
||||
public override void Flush() => throw new System.NotImplementedException();
|
||||
|
||||
public override bool CanRead { get; } = true;
|
||||
public override bool CanSeek { get; } = true;
|
||||
public override bool CanWrite { get; } = false;
|
||||
public override long Length => _stream.Length;
|
||||
public override long Position { get => _stream.Position; set => _stream.Position = value; }
|
||||
|
||||
public override int Read(Span<byte> buffer) => _stream.Read(buffer);
|
||||
public override void Close() => _stream.Close();
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
_stream.Dispose();
|
||||
if (_tempFileName is not null)
|
||||
File.Delete(_tempFileName);
|
||||
}
|
||||
}
|
|
@ -1,117 +1,52 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using ln.json;
|
||||
using ln.json.mapping;
|
||||
|
||||
namespace ln.http
|
||||
{
|
||||
public class HttpResponse
|
||||
public class HttpResponse : IDisposable
|
||||
{
|
||||
public Stream ContentStream { get; private set; }
|
||||
public TextWriter ContentWriter { get; private set; }
|
||||
public bool HasCustomContentStream { get; private set; }
|
||||
List<HttpCookie> _cookies = new List<HttpCookie>();
|
||||
|
||||
Dictionary<string, List<String>> headers = new Dictionary<string, List<string>>();
|
||||
List<HttpCookie> cookies = new List<HttpCookie>();
|
||||
|
||||
public HttpResponse() : this(HttpStatusCode.OK)
|
||||
{
|
||||
}
|
||||
public HttpResponse(HttpStatusCode statusCode)
|
||||
{
|
||||
HttpStatusCode = statusCode;
|
||||
ContentStream = new MemoryStream();
|
||||
ContentWriter = new StreamWriter(ContentStream);
|
||||
|
||||
SetHeader("content-type", "text/html");
|
||||
Headers = new HeaderContainer();
|
||||
}
|
||||
public HttpResponse() : this(HttpStatusCode.OK)
|
||||
{
|
||||
}
|
||||
|
||||
[Obsolete]
|
||||
public HttpResponse(HttpRequest httpRequest) : this() { }
|
||||
[Obsolete]
|
||||
public HttpResponse(HttpRequest httpRequest,string contentType) : this(contentType) { }
|
||||
public HttpResponse(string contentType)
|
||||
:this()
|
||||
{
|
||||
SetHeader("content-type", contentType);
|
||||
}
|
||||
|
||||
public HeaderContainer Headers { get; }
|
||||
public HttpContent HttpContent { get; set; }
|
||||
|
||||
[Obsolete]
|
||||
public HttpResponse(HttpRequest httpRequest, Stream contentStream) : this(contentStream) { }
|
||||
public HttpResponse(Stream contentStream)
|
||||
{
|
||||
Content(contentStream);
|
||||
public String GetHeader(string name) => Headers.Get(name);
|
||||
public String GetHeader(string name, string defValue) => Headers.Get(name, defValue);
|
||||
|
||||
HttpStatusCode = HttpStatusCode.OK;
|
||||
SetHeader("content-type", "text/html");
|
||||
}
|
||||
public void SetHeader(String name, String value) => Headers.Set(name, value);
|
||||
|
||||
[Obsolete]
|
||||
public HttpResponse(HttpRequest httpRequest, Stream contentStream,string contentType) : this(contentStream, contentType){ }
|
||||
public HttpResponse(Stream contentStream,string contentType)
|
||||
:this(contentStream)
|
||||
{
|
||||
SetHeader("content-type", contentType);
|
||||
}
|
||||
|
||||
public String GetHeader(string name) => GetHeader(name, null);
|
||||
public String GetHeader(string name, string defValue)
|
||||
{
|
||||
return headers.ContainsKey(name) ? String.Join(",", headers[name.ToUpper()]) : defValue;
|
||||
}
|
||||
|
||||
public String[] GetHeaderValues(string name)
|
||||
{
|
||||
return headers[name.ToUpper()].ToArray();
|
||||
}
|
||||
public String[] GetHeaderNames()
|
||||
{
|
||||
return headers.Keys.ToArray();
|
||||
}
|
||||
|
||||
public void SetHeader(String name, String value)
|
||||
{
|
||||
name = name.ToUpper();
|
||||
|
||||
headers[name] = new List<string>();
|
||||
headers[name].Add(value);
|
||||
}
|
||||
public void AddHeader(String name, String value)
|
||||
{
|
||||
name = name.ToUpper();
|
||||
|
||||
if (!headers.ContainsKey(name))
|
||||
headers[name] = new List<string>();
|
||||
|
||||
headers[name].Add(value);
|
||||
}
|
||||
public void RemoveHeader(String name) => headers.Remove(name.ToUpper());
|
||||
public bool ContainsHeader(string headerName) => headers.ContainsKey(headerName.ToUpper());
|
||||
public void AddHeader(String name, String value) => Headers.Add(name, value);
|
||||
public void RemoveHeader(String name) => Headers.Remove(name);
|
||||
public bool ContainsHeader(string headerName) => Headers.Contains(headerName);
|
||||
|
||||
public void AddCookie(string name,string value)
|
||||
{
|
||||
AddCookie(new HttpCookie(name, value));
|
||||
}
|
||||
public void AddCookie(HttpCookie httpCookie) => cookies.Add(httpCookie);
|
||||
public void RemoveCookie(HttpCookie httpCookie) => cookies.Remove(httpCookie);
|
||||
public HttpCookie[] Cookies => cookies.ToArray();
|
||||
public void AddCookie(HttpCookie httpCookie) => _cookies.Add(httpCookie);
|
||||
public void RemoveCookie(HttpCookie httpCookie) => _cookies.Remove(httpCookie);
|
||||
public HttpCookie[] Cookies => _cookies.ToArray();
|
||||
|
||||
public HttpStatusCode HttpStatusCode { get; set; }
|
||||
|
||||
[Obsolete]
|
||||
public int StatusCode
|
||||
{
|
||||
get => (int)HttpStatusCode;
|
||||
set => HttpStatusCode = (HttpStatusCode)value;
|
||||
}
|
||||
[Obsolete]
|
||||
public String StatusMessage
|
||||
{
|
||||
get => HttpStatusCode.ToString();
|
||||
}
|
||||
|
||||
|
||||
public HttpResponse Header(string name,string value)
|
||||
{
|
||||
SetHeader(name, value);
|
||||
|
@ -171,55 +106,36 @@ namespace ln.http
|
|||
|
||||
public HttpResponse Content(Exception exception)
|
||||
{
|
||||
SetHeader("content-type", "text/plain");
|
||||
ContentWriter.WriteLine("{0}", exception);
|
||||
ContentWriter.Flush();
|
||||
|
||||
HttpContent = new StringContent(exception.ToString());
|
||||
return this;
|
||||
}
|
||||
public HttpResponse Content(string text)
|
||||
{
|
||||
ContentWriter.Write(text);
|
||||
ContentWriter.Flush();
|
||||
|
||||
HttpContent = new StringContent(text);
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpResponse Content(JSONValue json) => ContentType("application/json").Content(json.ToString());
|
||||
public HttpResponse Content(JSONValue json)
|
||||
{
|
||||
HttpContent = new JsonContent(json);
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpResponse Content(Stream contentStream)
|
||||
{
|
||||
ContentStream = contentStream;
|
||||
ContentWriter = null;
|
||||
HasCustomContentStream = true;
|
||||
HttpContent = new StreamContent(contentStream);
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual void WriteTo(Stream stream)
|
||||
public HttpResponse Content(HttpContent httpContent)
|
||||
{
|
||||
SetHeader("Content-Length", ContentStream.Length.ToString());
|
||||
HttpContent = httpContent;
|
||||
return this;
|
||||
}
|
||||
|
||||
StreamWriter streamWriter = new StreamWriter(stream) {NewLine = "\r\n"};
|
||||
streamWriter.WriteLine("{0} {1} {2}", "HTTP/1.1", (int)HttpStatusCode, HttpStatusCode.ToString());
|
||||
foreach (String headerName in GetHeaderNames())
|
||||
{
|
||||
streamWriter.WriteLine("{0}: {1}", headerName, GetHeader(headerName));
|
||||
}
|
||||
|
||||
foreach (HttpCookie httpCookie in Cookies)
|
||||
{
|
||||
streamWriter.WriteLine("Set-Cookie: {0}", httpCookie.ToString());
|
||||
}
|
||||
|
||||
streamWriter.WriteLine();
|
||||
streamWriter.Flush();
|
||||
|
||||
ContentStream.Position = 0;
|
||||
ContentStream.CopyTo(stream);
|
||||
ContentStream.Close();
|
||||
ContentStream.Dispose();
|
||||
|
||||
stream.Flush();
|
||||
public void Dispose()
|
||||
{
|
||||
HttpContent?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,108 +2,74 @@
|
|||
using System.Text.RegularExpressions;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using ln.http.route;
|
||||
|
||||
namespace ln.http
|
||||
{
|
||||
public delegate bool HttpRouterDelegate(HttpContext context);
|
||||
public delegate bool HttpAuthorizationDelegate(HttpContext httpContext);
|
||||
public delegate void HttpFilterDelegate(HttpContext httpContext);
|
||||
public delegate bool HttpRouterDelegate(HttpRequestContext context, string routedPath);
|
||||
public delegate bool HttpAuthorizationDelegate(HttpRequestContext httpRequestContext);
|
||||
public delegate void HttpFilterDelegate(HttpRequestContext httpRequestContext);
|
||||
|
||||
|
||||
public enum HttpRoutePriority : int { HIGHEST = 0, HIGH = 1, NORMAL = 2, LOW = 3, LOWEST = 4 }
|
||||
|
||||
public class HttpRouter : IDisposable
|
||||
public class HttpRouter : HttpRoute, IDisposable
|
||||
{
|
||||
public event HttpFilterDelegate HttpFilters;
|
||||
|
||||
private List<HttpMapping>[] _mappings = new List<HttpMapping>[5];
|
||||
private HttpServer _httpServer;
|
||||
public event HttpFilterDelegate HttpFixups;
|
||||
|
||||
private HttpRouter _parentRouter;
|
||||
private HttpMapping _parentMapping;
|
||||
private List<HttpRoute> _routes = new List<HttpRoute>();
|
||||
|
||||
public HttpRouter()
|
||||
:this(HttpMethod.ANY, null)
|
||||
{
|
||||
for (int n = 0; n < 5; n++)
|
||||
_mappings[n] = new List<HttpMapping>();
|
||||
_routerDelegate = RouteRequest;
|
||||
}
|
||||
|
||||
public HttpRouter(HttpServer httpServer)
|
||||
: this()
|
||||
public HttpRouter(HttpMethod httpMethod, string mapPath)
|
||||
: base(httpMethod, mapPath)
|
||||
{
|
||||
_httpServer = httpServer;
|
||||
httpServer.AddRouter(this);
|
||||
}
|
||||
|
||||
public HttpRouter(HttpRouter parentRouter, string mappingPath)
|
||||
|
||||
public HttpRouter(HttpRouter parentRouter, string mapPath)
|
||||
:base(HttpMethod.ANY, mapPath)
|
||||
{
|
||||
_parentRouter = parentRouter;
|
||||
_parentMapping = _parentRouter.Map(HttpMethod.ANY, mappingPath, this.Route);
|
||||
_parentRouter.Map(this);
|
||||
}
|
||||
|
||||
public HttpMapping Map(HttpMethod httpMethod, string uri, HttpRouterDelegate routerDelegate) =>
|
||||
Map(httpMethod, uri, HttpRoutePriority.NORMAL, routerDelegate);
|
||||
|
||||
public HttpMapping Map(HttpMethod httpMethod, string uri, HttpRoutePriority priority, HttpRouterDelegate routerDelegate)
|
||||
{
|
||||
HttpMapping httpMapping = new HttpMapping(httpMethod, RegexFromRoute(uri), routerDelegate);
|
||||
_mappings[(int)priority].Add(httpMapping);
|
||||
return httpMapping;
|
||||
}
|
||||
|
||||
public bool RemoveHttpMapping(HttpMapping httpMapping)
|
||||
{
|
||||
foreach (var mappings in _mappings)
|
||||
{
|
||||
if (mappings.Contains(httpMapping))
|
||||
return mappings.Remove(httpMapping);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool Route(HttpContext httpContext)
|
||||
{
|
||||
string residual = "";
|
||||
|
||||
HttpFilters?.Invoke(httpContext);
|
||||
|
||||
for (int n = 0; n < _mappings.Length; n++)
|
||||
{
|
||||
foreach (HttpMapping httpMapping in _mappings[n])
|
||||
{
|
||||
if ((httpMapping.Method == httpContext.Request?.Method) || (httpMapping.Method == HttpMethod.ANY))
|
||||
{
|
||||
Match match = httpMapping.UriRegex.Match(httpContext.RoutableUri);
|
||||
if (match.Success)
|
||||
{
|
||||
foreach (Group group in match.Groups)
|
||||
{
|
||||
httpContext.Request?.SetParameter(group.Name, group.Value);
|
||||
if (group.Name.Equals("_"))
|
||||
if (group.Value.StartsWith("/", StringComparison.InvariantCulture))
|
||||
residual = group.Value;
|
||||
else
|
||||
residual = "/" + group.Value;
|
||||
}
|
||||
|
||||
string saveRoutableUri = httpContext.RoutableUri;
|
||||
httpContext.RoutableUri = residual;
|
||||
if (httpMapping.Route(httpContext))
|
||||
return true;
|
||||
|
||||
httpContext.RoutableUri = saveRoutableUri;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (httpContext.Response is not null)
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public void Map(HttpRoute httpRoute) => _routes.Add(httpRoute);
|
||||
public void UnMap(HttpRoute httpRoute) => _routes.Remove(httpRoute);
|
||||
|
||||
public void Map(HttpMethod httpMethod, string route, HttpRouterDelegate routerDelegate)
|
||||
=> Map(new HttpRoute(httpMethod, route, routerDelegate));
|
||||
|
||||
private string RegexFromRoute(string route)
|
||||
|
||||
public bool RouteRequest(HttpRequestContext httpRequestContext, string routePath)
|
||||
{
|
||||
HttpFilters?.Invoke(httpRequestContext);
|
||||
|
||||
foreach (var httpRoute in _routes)
|
||||
{
|
||||
if (httpRoute.TryRoute(httpRequestContext, routePath))
|
||||
{
|
||||
HttpFixups?.Invoke(httpRequestContext);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_parentRouter?.Dispose();
|
||||
}
|
||||
|
||||
public override string ToString() => $"[HttpRouter]";
|
||||
|
||||
/*private string RegexFromRoute(string route)
|
||||
{
|
||||
string[] parts = route.Split(new char[] { '/' });
|
||||
string[] reparts = parts.Select((part) =>
|
||||
|
@ -115,84 +81,14 @@ namespace ln.http
|
|||
return string.Format("(?<{0}>[^/]+)", part.Substring(1));
|
||||
else if (part.Equals("*"))
|
||||
return string.Format("(?<_>.*)");
|
||||
else if (part.EndsWith("*", StringComparison.InvariantCulture))
|
||||
return string.Format("{0}[^/]+(?<_>/.*)?", part.Substring(0, part.Length-1));
|
||||
else
|
||||
return string.Format("{0}", part);
|
||||
}).ToArray();
|
||||
|
||||
return string.Format("^{0}/?$", string.Join("/", reparts));
|
||||
}
|
||||
}*/
|
||||
|
||||
public class HttpMapping
|
||||
{
|
||||
public HttpMethod Method { get; set; }
|
||||
public Regex UriRegex { get; set; }
|
||||
public HttpRouterDelegate RouterDelegate { get; set; }
|
||||
|
||||
public bool AuthenticationRequired { get; set; }
|
||||
public HttpAuthorizationDelegate AuthorizationDelegate { get; set; }
|
||||
|
||||
public event HttpFilterDelegate HttpFilters;
|
||||
|
||||
public HttpMapping(HttpMethod httpMethod, string reUri, HttpRouterDelegate routerDelegate)
|
||||
{
|
||||
Method = httpMethod;
|
||||
UriRegex = new Regex(reUri);
|
||||
RouterDelegate = routerDelegate;
|
||||
AuthenticationRequired = HttpServer.DefaultRouteAuthentication;
|
||||
}
|
||||
|
||||
public HttpMapping UseFilter(HttpFilterDelegate filterDelegate)
|
||||
{
|
||||
HttpFilters += filterDelegate;
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpMapping Authenticate()
|
||||
{
|
||||
AuthenticationRequired = true;
|
||||
return this;
|
||||
}
|
||||
public HttpMapping Anonymous()
|
||||
{
|
||||
AuthenticationRequired = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpMapping Authorize(HttpAuthorizationDelegate authorizationDelegate)
|
||||
{
|
||||
AuthorizationDelegate = authorizationDelegate;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public bool Route(HttpContext httpContext)
|
||||
{
|
||||
if (AuthenticationRequired && httpContext.AuthenticatedPrincipal is null)
|
||||
{
|
||||
httpContext.Response = HttpResponse.Unauthorized().Header("WWW-Authenticate", "Basic");
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((AuthorizationDelegate is not null) && (!AuthorizationDelegate(httpContext)))
|
||||
return false;
|
||||
|
||||
HttpFilters?.Invoke(httpContext);
|
||||
|
||||
return RouterDelegate(httpContext);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_httpServer?.RemoveRouter(this);
|
||||
_httpServer = null;
|
||||
|
||||
_parentRouter?.RemoveHttpMapping(_parentMapping);
|
||||
_parentMapping = null;
|
||||
_parentRouter = null;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,13 +3,13 @@ using System.Collections.Generic;
|
|||
using ln.logging;
|
||||
using ln.http.exceptions;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using ln.protocols.helper;
|
||||
|
||||
namespace ln.http
|
||||
{
|
||||
|
||||
/*
|
||||
public class HttpServer
|
||||
{
|
||||
public static bool DefaultRouteAuthentication { get; set; } = false;
|
||||
|
@ -17,19 +17,22 @@ namespace ln.http
|
|||
private HashSet<HttpRouterDelegate> _routerDelegates = new HashSet<HttpRouterDelegate>();
|
||||
public IEnumerable<HttpRouterDelegate> Routers => _routerDelegates;
|
||||
|
||||
public TextWriter LoggingWriter { get; set; }
|
||||
public TextWriter AccessLogWriter { get; set; }
|
||||
private ILogWriter _logWriter;
|
||||
|
||||
|
||||
private HashSet<IHttpAuthenticationSource> _authenticationSources = new HashSet<IHttpAuthenticationSource>();
|
||||
public IEnumerable<IHttpAuthenticationSource> AuthenticationSources => _authenticationSources;
|
||||
|
||||
|
||||
public HttpServer() : this(Console.Out)
|
||||
public HttpServer() : this(Console.Out, new LogWriter(new ConsoleLogSink()))
|
||||
{
|
||||
}
|
||||
public HttpServer(TextWriter loggingWriter)
|
||||
|
||||
public HttpServer(TextWriter accessLogWriter, ILogWriter logWriter)
|
||||
{
|
||||
LoggingWriter = loggingWriter;
|
||||
AccessLogWriter = accessLogWriter;
|
||||
_logWriter = logWriter;
|
||||
}
|
||||
public HttpServer(HttpRouterDelegate router)
|
||||
: this()
|
||||
|
@ -42,10 +45,10 @@ namespace ln.http
|
|||
public void UnregisterAuthenticationSource(IHttpAuthenticationSource authenticationSource) =>
|
||||
_authenticationSources.Remove(authenticationSource);
|
||||
|
||||
public void AddRouter(HttpRouter httpRouter) => AddRouter(httpRouter.Route);
|
||||
public void AddRouter(HttpRouter httpRouter) => AddRouter(httpRouter.RouteRequest);
|
||||
public void AddRouter(HttpRouterDelegate routerDelegate) => _routerDelegates.Add(routerDelegate);
|
||||
|
||||
public void RemoveRouter(HttpRouter httpRouter) => RemoveRouter(httpRouter.Route);
|
||||
public void RemoveRouter(HttpRouter httpRouter) => RemoveRouter(httpRouter.RouteRequest);
|
||||
public void RemoveRouter(HttpRouterDelegate routerDelegate) => _routerDelegates.Remove(routerDelegate);
|
||||
|
||||
public void Connection(HttpConnection httpConnection) =>
|
||||
|
@ -74,7 +77,7 @@ namespace ln.http
|
|||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Logging.Log(exception);
|
||||
_logWriter?.Log(exception);
|
||||
|
||||
if (exception is HttpException httpException)
|
||||
{
|
||||
|
@ -83,8 +86,8 @@ namespace ln.http
|
|||
HttpContext errorContext = new HttpContext(httpContext, httpException);
|
||||
if (!RouteRequest(errorContext))
|
||||
{
|
||||
errorContext.RoutableUri = String.Format("/_err.html",
|
||||
(int)httpException.HttpStatusCode);
|
||||
errorContext.ResetRoutingStack(String.Format("/_err.html",
|
||||
(int)httpException.HttpStatusCode));
|
||||
RouteRequest(errorContext);
|
||||
}
|
||||
httpContext.Response = errorContext.Response;
|
||||
|
@ -118,15 +121,17 @@ namespace ln.http
|
|||
DateTime end = DateTime.Now;
|
||||
TimeSpan duration = end - start;
|
||||
|
||||
LoggingWriter.WriteLine("{0} {1} {2} {3} {4} {5} {6}",
|
||||
AccessLogWriter?.WriteLine("{0} {1} {2} {3} {4} {5} {6} {7}",
|
||||
start,
|
||||
end,
|
||||
duration,
|
||||
httpConnection.RemoteEndPoint?.ToString(),
|
||||
httpContext.Response?.StatusCode.ToString() ?? "-",
|
||||
httpContext.AuthenticatedPrincipal?.ToString() ?? "-",
|
||||
httpContext.Request.Method,
|
||||
httpContext.Request.RequestUri
|
||||
);
|
||||
AccessLogWriter?.Flush();
|
||||
|
||||
keepalive = httpContext.Response.GetHeader("connection", "keep-alive").Equals("keep-alive") &&
|
||||
httpRequest
|
||||
|
@ -170,9 +175,10 @@ namespace ln.http
|
|||
return null;
|
||||
} catch (Exception e)
|
||||
{
|
||||
Logging.Log(e);
|
||||
_logWriter?.Log(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
using System;
|
||||
|
||||
namespace ln.http;
|
||||
|
||||
[Flags]
|
||||
public enum HttpVersion
|
||||
{
|
||||
None = 0,
|
||||
HTTP10 = (1<<0), // HTTP/1.0
|
||||
HTTP11 = (1<<1), // HTTP/1.1
|
||||
HTTP2 = (1<<2), // HTTP/2.0
|
||||
|
||||
ALL = HTTP10 | HTTP11 | HTTP2
|
||||
}
|
||||
|
||||
public static class HttpVersionSupport
|
||||
{
|
||||
|
||||
public static string ToString(HttpVersion httpVersion)
|
||||
{
|
||||
switch (httpVersion)
|
||||
{
|
||||
case HttpVersion.None:
|
||||
return "None";
|
||||
case HttpVersion.ALL:
|
||||
return "ALL";
|
||||
case HttpVersion.HTTP2:
|
||||
return "HTTP/2";
|
||||
case HttpVersion.HTTP10:
|
||||
return "HTTP/1.0";
|
||||
case HttpVersion.HTTP11:
|
||||
return "HTTP/1.1";
|
||||
default:
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,140 +0,0 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,6 +2,6 @@ namespace ln.http
|
|||
{
|
||||
public interface IHttpAuthenticationSource
|
||||
{
|
||||
bool AuthenticatePrincipal(HttpContext httpContext, out HttpPrincipal httpPrincipal);
|
||||
bool AuthenticatePrincipal(HttpRequestContext requestContext, out HttpPrincipal httpPrincipal);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using ln.http.exceptions;
|
||||
using ln.threading;
|
||||
|
||||
namespace ln.http;
|
||||
|
||||
public class Listener : IDisposable
|
||||
{
|
||||
static string _http2_preface = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n";
|
||||
static string _http2_preface1 = "PRI * HTTP/2.0";
|
||||
|
||||
protected Socket Socket;
|
||||
|
||||
public IPEndPoint LocalEndpoint => (IPEndPoint)Socket.LocalEndPoint;
|
||||
public HttpVersion AcceptedHttpVersion { get; set; } = HttpVersion.ALL;
|
||||
|
||||
public HttpRouter HttpRouter { get; set; }
|
||||
|
||||
public Listener(HttpRouter httpRouter)
|
||||
: this(httpRouter, new IPEndPoint(IPAddress.IPv6Any, 0))
|
||||
{
|
||||
}
|
||||
public Listener(HttpRouter httpRouter, int port)
|
||||
: this(httpRouter, new IPEndPoint(IPAddress.IPv6Any, port))
|
||||
{
|
||||
}
|
||||
|
||||
public Listener(HttpRouter httpRouter, IPAddress bind, int port)
|
||||
: this(httpRouter, new IPEndPoint(bind, port))
|
||||
{
|
||||
}
|
||||
|
||||
public Listener(HttpRouter httpRouter, IPEndPoint bind)
|
||||
{
|
||||
HttpRouter = httpRouter;
|
||||
|
||||
Socket = new Socket(bind?.AddressFamily ?? AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp);
|
||||
Socket.ExclusiveAddressUse = false;
|
||||
|
||||
if (bind is null)
|
||||
bind = new IPEndPoint(IPAddress.IPv6Any, 0);
|
||||
|
||||
Socket.Bind(bind);
|
||||
Socket.Listen();
|
||||
Socket.ReceiveTimeout = 10000;
|
||||
|
||||
DynamicThreadPool.DefaultPool.Enqueue(ConnectionHandler);
|
||||
}
|
||||
|
||||
private void ConnectionHandler()
|
||||
{
|
||||
Socket clientSocket = Socket.Accept();
|
||||
if (Socket.IsBound)
|
||||
DynamicThreadPool.DefaultPool.Enqueue(ConnectionHandler);
|
||||
|
||||
clientSocket.ReceiveTimeout = 10000;
|
||||
|
||||
try
|
||||
{
|
||||
using (NetworkStream networkStream = new NetworkStream(clientSocket))
|
||||
Accepted(clientSocket, networkStream);
|
||||
}
|
||||
finally
|
||||
{
|
||||
clientSocket.Close();
|
||||
clientSocket.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void Accepted(Socket connectedSocket, Stream connectionStream)
|
||||
{
|
||||
if (HttpConnection.ReadRequestLine(connectionStream, out string method, out Uri requestUri, out HttpVersion httpVersion))
|
||||
{
|
||||
if ((AcceptedHttpVersion & httpVersion) == HttpVersion.None)
|
||||
return;
|
||||
|
||||
HttpConnection httpConnection;
|
||||
|
||||
switch (httpVersion)
|
||||
{
|
||||
case HttpVersion.HTTP2:
|
||||
httpConnection = new Http2Connection(this, (IPEndPoint) connectedSocket.RemoteEndPoint, connectionStream, method, requestUri, httpVersion);
|
||||
break;
|
||||
case HttpVersion.HTTP10:
|
||||
case HttpVersion.HTTP11:
|
||||
default:
|
||||
httpConnection = new Http1XConnection(this, (IPEndPoint) connectedSocket.RemoteEndPoint, connectionStream, method, requestUri, httpVersion);
|
||||
break;
|
||||
}
|
||||
|
||||
Accepted(httpConnection);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
protected virtual void Accepted(HttpConnection httpConnection)
|
||||
{
|
||||
httpConnection.Run();
|
||||
}
|
||||
|
||||
public void Dispatch(HttpRequestContext requestContext)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!HttpRouter.RouteRequest(requestContext, requestContext.Request.RequestUri.AbsolutePath))
|
||||
throw new NotFoundException();
|
||||
}
|
||||
catch (HttpException httpException)
|
||||
{
|
||||
requestContext.Response = new HttpResponse(httpException.HttpStatusCode);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
requestContext.Response = HttpResponse.InternalServerError().Content(e);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Dispose()
|
||||
{
|
||||
Socket?.Dispose();
|
||||
}
|
||||
|
||||
}
|
|
@ -2,7 +2,7 @@ using System;
|
|||
|
||||
namespace ln.http
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
|
||||
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true)]
|
||||
public class MapAttribute : Attribute
|
||||
{
|
||||
public string Path { get; set; }
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
using System;
|
||||
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 TlsListener : Listener
|
||||
{
|
||||
private CertificateStore _certificateStore;
|
||||
public X509Certificate DefaultCertificate { get; set; }
|
||||
|
||||
public TlsListener(HttpRouter httpRouter, CertificateStore certificateStore)
|
||||
:this(httpRouter, null, certificateStore){}
|
||||
public TlsListener(HttpRouter httpRouter, IPAddress bind, int port, CertificateStore certificateStore)
|
||||
:this(httpRouter, new IPEndPoint(bind, port), certificateStore){}
|
||||
|
||||
public TlsListener(HttpRouter httpRouter, IPEndPoint bind, CertificateStore certificateStore)
|
||||
: this(httpRouter, bind, certificateStore, null)
|
||||
{
|
||||
}
|
||||
public TlsListener(HttpRouter httpRouter, IPEndPoint bind, CertificateStore certificateStore, X509Certificate defaultCertificate) :base(httpRouter, bind)
|
||||
{
|
||||
_certificateStore = certificateStore;
|
||||
DefaultCertificate = defaultCertificate;
|
||||
|
||||
if (DefaultCertificate is null)
|
||||
DefaultCertificate = buildSelfSignedServerCertificate();
|
||||
|
||||
}
|
||||
|
||||
protected override void Accepted(Socket connectedSocket, Stream connectionStream)
|
||||
{
|
||||
SslStream sslStream = new SslStream(connectionStream, false, null, CertificateSelectionCallback);
|
||||
sslStream.AuthenticateAsServer(DefaultCertificate);
|
||||
|
||||
// ToDo: Check for correct ALPN protocol identifier
|
||||
|
||||
base.Accepted(connectedSocket, sslStream);
|
||||
}
|
||||
|
||||
|
||||
private X509Certificate CertificateSelectionCallback(object sender, string targethost,
|
||||
X509CertificateCollection localcertificates, X509Certificate? remotecertificate, string[] acceptableissuers)
|
||||
{
|
||||
if (!_certificateStore.TryGetCertificate(targethost, out X509Certificate certificate))
|
||||
return null;
|
||||
|
||||
return certificate;
|
||||
}
|
||||
|
||||
private X509Certificate2 buildSelfSignedServerCertificate()
|
||||
{
|
||||
SubjectAlternativeNameBuilder sanBuilder = new SubjectAlternativeNameBuilder();
|
||||
sanBuilder.AddIpAddress(LocalEndpoint.Address);
|
||||
sanBuilder.AddDnsName(Environment.MachineName);
|
||||
|
||||
X500DistinguishedName distinguishedName = new X500DistinguishedName($"CN={LocalEndpoint.Address.ToString()}");
|
||||
|
||||
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)));
|
||||
return certificate;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
using ln.http.exceptions;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace ln.http.router
|
||||
{
|
||||
public class VirtualHostRouter : HttpRouter
|
||||
{
|
||||
public HttpRouter DefaultRoute { get; set; }
|
||||
|
||||
Dictionary<string, HttpRouter> virtualHosts = new Dictionary<string, HttpRouter>();
|
||||
|
||||
public VirtualHostRouter()
|
||||
{
|
||||
}
|
||||
public VirtualHostRouter(HttpRouter defaultRoute)
|
||||
{
|
||||
DefaultRoute = defaultRoute;
|
||||
}
|
||||
public VirtualHostRouter(IEnumerable<KeyValuePair<string, HttpRouter>> routes)
|
||||
{
|
||||
foreach (KeyValuePair<string, HttpRouter> route in routes)
|
||||
virtualHosts.Add(route.Key, route.Value);
|
||||
}
|
||||
public VirtualHostRouter(HttpRouter defaultRoute,IEnumerable<KeyValuePair<string, HttpRouter>> routes)
|
||||
:this(routes)
|
||||
{
|
||||
DefaultRoute = defaultRoute;
|
||||
}
|
||||
public VirtualHostRouter(VirtualHostRouter source)
|
||||
: this(source.virtualHosts) { }
|
||||
|
||||
public void Add(string hostname,HttpRouter router) => virtualHosts.Add(hostname, router);
|
||||
public void Remove(string hostname) => virtualHosts.Remove(hostname);
|
||||
public bool Contains(string hostname) => virtualHosts.ContainsKey(hostname);
|
||||
|
||||
public bool TryGetValue(string hostname, out HttpRouter router) => virtualHosts.TryGetValue(hostname, out router);
|
||||
|
||||
public bool Route(HttpRequestContext requestContext, string routePath)
|
||||
{
|
||||
if (virtualHosts.TryGetValue(requestContext.Request.Host, out HttpRouter virtualHost))
|
||||
return virtualHost.RouteRequest(requestContext, routePath);
|
||||
if (DefaultRoute != null)
|
||||
return DefaultRoute.RouteRequest(requestContext, routePath);
|
||||
|
||||
throw new HttpException(HttpStatusCode.Gone, string.Format("Gone. Hostname {0} not found on this server.", requestContext.Request.Host));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,7 +3,6 @@ using ln.type;
|
|||
using System.IO;
|
||||
using System.Net.Sockets;
|
||||
using System.Net.Security;
|
||||
using ln.protocols.helper;
|
||||
|
||||
namespace ln.http.client
|
||||
{
|
||||
|
|
|
@ -9,21 +9,17 @@
|
|||
<Copyright>(c) 2020 Harald Wolff-Thobaben</Copyright>
|
||||
<PackageTags>http server</PackageTags>
|
||||
<LangVersion>default</LangVersion>
|
||||
<PackageVersion>0.6.5</PackageVersion>
|
||||
<PackageVersion>0.9.5</PackageVersion>
|
||||
<AssemblyVersion>0.6.2.0</AssemblyVersion>
|
||||
<TargetFrameworks>net5.0;net6.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ln.collections" Version="0.1" />
|
||||
<PackageReference Include="ln.json" Version="1.0.8-ci" />
|
||||
<PackageReference Include="ln.logging" Version="1.0" />
|
||||
<PackageReference Include="ln.threading" Version="0.2.1" />
|
||||
<PackageReference Include="ln.type" Version="0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\ln.protocols.helper\ln.protocols.helper.csproj" />
|
||||
<PackageReference Include="ln.collections" Version="0.2.2" />
|
||||
<PackageReference Include="ln.json" Version="1.2.1" />
|
||||
<PackageReference Include="ln.mime" Version="1.1.1" />
|
||||
<PackageReference Include="ln.threading" Version="0.2.2" />
|
||||
<PackageReference Include="ln.type" Version="0.1.9" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,63 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace ln.http.route;
|
||||
|
||||
public class HttpRoute
|
||||
{
|
||||
protected HttpRouterDelegate _routerDelegate;
|
||||
|
||||
public HttpMethod HttpMethod { get; protected set; }
|
||||
public Regex Route { get; protected set; }
|
||||
|
||||
public HttpRoute(HttpMethod httpMethod, string route, HttpRouterDelegate routerDelegate)
|
||||
:this(httpMethod, route)
|
||||
{
|
||||
_routerDelegate = routerDelegate;
|
||||
}
|
||||
|
||||
protected HttpRoute(HttpMethod httpMethod, string route)
|
||||
{
|
||||
HttpMethod = httpMethod;
|
||||
Route = route is string ? new Regex(route) : null;
|
||||
}
|
||||
|
||||
public bool TryRoute(HttpRequestContext httpRequestContext, string routePath)
|
||||
{
|
||||
bool matched = false;
|
||||
|
||||
if (
|
||||
(HttpMethod == HttpMethod.ANY) ||
|
||||
(HttpMethod == httpRequestContext.Request.Method)
|
||||
)
|
||||
{
|
||||
if (Route is null)
|
||||
{
|
||||
matched = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Match match = Route.Match(routePath);
|
||||
if (match.Success)
|
||||
{
|
||||
foreach (Group group in match.Groups.Values)
|
||||
if (group.Success)
|
||||
httpRequestContext.SetParameter(group.Name, group.Value);
|
||||
|
||||
routePath = routePath.Substring(match.Length);
|
||||
matched = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (matched)
|
||||
return _routerDelegate(httpRequestContext, routePath) && (httpRequestContext.Response is not null);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override string ToString() => $"[HttpRoute HttpMethod={HttpMethod} Route={Route}]";
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
// /**
|
||||
// * File: FileSystemRouter.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.IO;
|
||||
using ln.http.mime;
|
||||
|
||||
namespace ln.http.router
|
||||
{
|
||||
public class FileRouter
|
||||
{
|
||||
public string FileName { get; set; }
|
||||
|
||||
public FileRouter(string filename)
|
||||
{
|
||||
if (!File.Exists(filename))
|
||||
throw new FileNotFoundException();
|
||||
|
||||
FileName = filename;
|
||||
}
|
||||
|
||||
public bool Route(HttpContext httpContext)
|
||||
{
|
||||
httpContext.Response = new HttpResponse(httpContext.Request, new FileStream(FileName, FileMode.Open));
|
||||
httpContext.Response.SetHeader("content-type", MimeTypeMap.GetMimeType(Path.GetExtension(FileName)));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
// /**
|
||||
// * File: LoggingRouter.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.logging;
|
||||
using System.Diagnostics;
|
||||
namespace ln.http.router
|
||||
{
|
||||
public class LoggingRouter
|
||||
{
|
||||
HttpRouterDelegate Next { get; set; }
|
||||
Logger Logger { get; }
|
||||
|
||||
public LoggingRouter(HttpRouterDelegate next)
|
||||
:this(next, Logger.Default)
|
||||
{}
|
||||
public LoggingRouter(HttpRouterDelegate next,Logger logger)
|
||||
{
|
||||
Next = next;
|
||||
Logger = logger;
|
||||
}
|
||||
|
||||
public bool Route(HttpContext httpContext)
|
||||
{
|
||||
DateTime start = DateTime.Now;
|
||||
bool success = false;
|
||||
|
||||
try
|
||||
{
|
||||
success = Next(httpContext);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
DateTime end = DateTime.Now;
|
||||
TimeSpan duration = end - start;
|
||||
|
||||
Logger.Log(LogLevel.INFO, "{0} {1} {2} {3} {4} {5} {6}",
|
||||
start,
|
||||
end,
|
||||
duration,
|
||||
httpContext.Response?.StatusCode.ToString() ?? "-",
|
||||
httpContext.AuthenticatedPrincipal?.ToString() ?? "-",
|
||||
httpContext.Request.Method,
|
||||
httpContext.Request.RequestUri
|
||||
);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,97 +0,0 @@
|
|||
// /**
|
||||
// * File: FileSystemRouter.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 System.Collections.Generic;
|
||||
using ln.http.mime;
|
||||
|
||||
namespace ln.http.router
|
||||
{
|
||||
public class StaticRouter : IDisposable
|
||||
{
|
||||
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;
|
||||
|
||||
AddIndex("index.html");
|
||||
AddIndex("index.htm");
|
||||
}
|
||||
|
||||
public void AddIndex(string indexName) => indexNames.Add(indexName);
|
||||
public void RemoveIndex(string indexName) => indexNames.Remove(indexName);
|
||||
|
||||
public bool Route(HttpContext httpContext)
|
||||
{
|
||||
string finalPath = httpContext.RoutableUri.Length > 0 ? Path.Combine(RootPath, httpContext.RoutableUri.Substring(1)) : ".";
|
||||
|
||||
if (Directory.Exists(finalPath))
|
||||
{
|
||||
foreach (string indexName in indexNames)
|
||||
{
|
||||
string indexFileName = Path.Combine(finalPath, indexName);
|
||||
if (File.Exists(indexFileName))
|
||||
{
|
||||
finalPath = indexFileName;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (File.Exists(finalPath))
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
httpContext.Response = new HttpResponse(httpContext.Request, new FileStream(finalPath, FileMode.Open, FileAccess.Read));
|
||||
httpContext.Response.SetHeader("content-type", MimeTypeMap.GetMimeType(Path.GetExtension(finalPath)));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_httpServer?.RemoveRouter(this.Route);
|
||||
_httpServer = null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
using ln.http.exceptions;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace ln.http.router
|
||||
{
|
||||
public class VirtualHostRouter
|
||||
{
|
||||
public HttpRouterDelegate DefaultRoute { get; set; }
|
||||
|
||||
Dictionary<string, HttpRouterDelegate> virtualHosts = new Dictionary<string, HttpRouterDelegate>();
|
||||
|
||||
public VirtualHostRouter()
|
||||
{
|
||||
}
|
||||
public VirtualHostRouter(HttpRouterDelegate defaultRoute)
|
||||
{
|
||||
DefaultRoute = defaultRoute;
|
||||
}
|
||||
public VirtualHostRouter(IEnumerable<KeyValuePair<string, HttpRouterDelegate>> routes)
|
||||
{
|
||||
foreach (KeyValuePair<string, HttpRouterDelegate> route in routes)
|
||||
virtualHosts.Add(route.Key, route.Value);
|
||||
}
|
||||
public VirtualHostRouter(HttpRouterDelegate defaultRoute,IEnumerable<KeyValuePair<string, HttpRouterDelegate>> routes)
|
||||
:this(routes)
|
||||
{
|
||||
DefaultRoute = defaultRoute;
|
||||
}
|
||||
public VirtualHostRouter(VirtualHostRouter source)
|
||||
: this(source.virtualHosts) { }
|
||||
|
||||
public void Add(string hostname,HttpRouterDelegate router) => virtualHosts.Add(hostname, router);
|
||||
public void Remove(string hostname) => virtualHosts.Remove(hostname);
|
||||
public bool Contains(string hostname) => virtualHosts.ContainsKey(hostname);
|
||||
|
||||
public bool TryGetValue(string hostname, out HttpRouterDelegate router) => virtualHosts.TryGetValue(hostname, out router);
|
||||
|
||||
public bool Route(HttpContext httpContext)
|
||||
{
|
||||
if (virtualHosts.TryGetValue(httpContext.Request.Host, out HttpRouterDelegate virtualHost))
|
||||
return virtualHost(httpContext);
|
||||
if (DefaultRoute != null)
|
||||
return DefaultRoute(httpContext);
|
||||
|
||||
throw new HttpException(HttpStatusCode.Gone, string.Format("Gone. Hostname {0} not found on this server.", httpContext.Request.Host));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ using ln.http.exceptions;
|
|||
using ln.logging;
|
||||
namespace ln.http.router
|
||||
{
|
||||
/*
|
||||
public class WebsocketRouter
|
||||
{
|
||||
Func<HttpRequest, WebSocket> createWebsocket;
|
||||
|
@ -24,9 +25,10 @@ namespace ln.http.router
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logging.Log(e);
|
||||
Console.Error.WriteLine(e);
|
||||
}
|
||||
throw new DisposeConnectionException();
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
|
|
@ -29,47 +29,61 @@ namespace ln.http.websocket
|
|||
ERROR
|
||||
}
|
||||
|
||||
public delegate void WebSocketEventDelegate(WebSocket sender,WebSocketEventArgs e);
|
||||
public delegate void WebSocketStateChanged(WebSocket webSocket,WebSocketState newState);
|
||||
public delegate void WebSocketTextReceived(WebSocket webSocket, string text);
|
||||
public delegate void WebSocketBytesReceived(WebSocket webSocket, byte[] bytes);
|
||||
|
||||
public abstract class WebSocket
|
||||
public class WebSocket
|
||||
{
|
||||
public HttpServer HTTPServer => HttpRequest.Server;
|
||||
public HttpRequest HttpRequest { get; }
|
||||
public event WebSocketTextReceived TextReceived;
|
||||
public event WebSocketBytesReceived BytesReceived;
|
||||
public event WebSocketStateChanged StateChanged;
|
||||
|
||||
public HttpRequestContext RequestContext { get; }
|
||||
public Stream Stream { get; }
|
||||
|
||||
public WebSocketState State { get; private set; } = WebSocketState.HANDSHAKE;
|
||||
|
||||
public WebSocket(HttpRequest httpRequest)
|
||||
private WebSocketState _state = WebSocketState.HANDSHAKE;
|
||||
public WebSocketState State
|
||||
{
|
||||
HttpRequest = httpRequest;
|
||||
Stream = httpRequest.ConnectionStream;
|
||||
get => _state;
|
||||
private set
|
||||
{
|
||||
_state = value;
|
||||
StateChanged?.Invoke(this, _state);
|
||||
}
|
||||
}
|
||||
|
||||
if ((!httpRequest.GetRequestHeader("upgrade", "").Contains("websocket")) && (!httpRequest.GetRequestHeader("connection", "").Contains("Upgrade")))
|
||||
public WebSocket(HttpRequestContext requestContext)
|
||||
{
|
||||
RequestContext = requestContext;
|
||||
Stream = requestContext.ConnectionStream;
|
||||
|
||||
if ((!requestContext.Request.GetRequestHeader("upgrade", "").Contains("websocket")) && (!requestContext.Request.GetRequestHeader("connection", "").Contains("Upgrade")))
|
||||
throw new HttpException(HttpStatusCode.BadRequest, "This resource is a websocket endpoint only");
|
||||
|
||||
if (!httpRequest.GetRequestHeader("Sec-WebSocket-Version", "").Equals("13"))
|
||||
if (!requestContext.Request.GetRequestHeader("Sec-WebSocket-Version", "").Equals("13"))
|
||||
throw new HttpException(HttpStatusCode.BadRequest, "Unsupported Protocol Version (WebSocket)");
|
||||
|
||||
String wsKey = httpRequest.GetRequestHeader("Sec-WebSocket-Key");
|
||||
String wsKey = requestContext.Request.GetRequestHeader("Sec-WebSocket-Key");
|
||||
|
||||
HttpResponse httpResponse = new HttpResponse(HttpStatusCode.SwitchingProtocols);
|
||||
httpResponse.AddHeader("upgrade", "websocket");
|
||||
httpResponse.AddHeader("connection", "Upgrade");
|
||||
httpResponse.AddHeader("Sec-WebSocket-Version", "13");
|
||||
|
||||
string acceptKey = String.Format("{0}258EAFA5-E914-47DA-95CA-C5AB0DC85B11", httpRequest.GetRequestHeader("Sec-WebSocket-Key"));
|
||||
string acceptKey = String.Format("{0}258EAFA5-E914-47DA-95CA-C5AB0DC85B11", requestContext.Request.GetRequestHeader("Sec-WebSocket-Key"));
|
||||
|
||||
httpResponse.AddHeader(
|
||||
"Sec-Websocket-Accept",
|
||||
Convert.ToBase64String(SHA1.Create().ComputeHash(Encoding.ASCII.GetBytes(acceptKey)))
|
||||
);
|
||||
|
||||
httpResponse.WriteTo(Stream);
|
||||
requestContext.Response = httpResponse;
|
||||
|
||||
requestContext.HttpConnection.SendResponse(requestContext);
|
||||
State = WebSocketState.OPEN;
|
||||
}
|
||||
|
||||
public bool IsAlive => false;
|
||||
|
||||
public void Close()
|
||||
{
|
||||
switch (State)
|
||||
|
@ -103,10 +117,10 @@ namespace ln.http.websocket
|
|||
switch (webSocketFrame.Opcode)
|
||||
{
|
||||
case WebSocketOpcode.TEXT:
|
||||
Received(Encoding.UTF8.GetString(webSocketFrame.ApplicationData));
|
||||
_received(Encoding.UTF8.GetString(webSocketFrame.ApplicationData));
|
||||
break;
|
||||
case WebSocketOpcode.BINARY:
|
||||
Received(webSocketFrame.ApplicationData);
|
||||
_received(webSocketFrame.ApplicationData);
|
||||
break;
|
||||
case WebSocketOpcode.CLOSE:
|
||||
if (State == WebSocketState.OPEN)
|
||||
|
@ -133,29 +147,18 @@ namespace ln.http.websocket
|
|||
} catch (IOException)
|
||||
{
|
||||
State = WebSocketState.ERROR;
|
||||
Logging.Log(LogLevel.DEBUG, "WebSocket connection was dropped unexpected");
|
||||
Close();
|
||||
} catch (Exception e)
|
||||
{
|
||||
Logging.Log(LogLevel.ERROR, "WebSocket caught Exception: {0}", e.ToString());
|
||||
Logging.Log(e);
|
||||
Console.Error.WriteLine("WebSocket: Exception: {0}", e.ToString());
|
||||
Console.Error.WriteLine(" {0}", e.StackTrace.ToString());
|
||||
} finally
|
||||
{
|
||||
Stream.Close();
|
||||
}
|
||||
}
|
||||
|
||||
public virtual bool Received(string textMessage)
|
||||
{
|
||||
Logging.Log(LogLevel.WARNING, "WebSocket received unexpected text message:\n{0}", textMessage);
|
||||
return false;
|
||||
}
|
||||
public virtual bool Received(byte[] binaryMessage)
|
||||
{
|
||||
Logging.Log(LogLevel.WARNING, "WebSocket received unexpected binary message:\n{0}",binaryMessage.ToHexString());
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Send(WebSocketFrame frame)
|
||||
private void Send(WebSocketFrame frame)
|
||||
{
|
||||
lock (Stream)
|
||||
{
|
||||
|
@ -168,7 +171,6 @@ namespace ln.http.websocket
|
|||
{
|
||||
if (State != WebSocketState.ERROR)
|
||||
{
|
||||
Logging.Log(LogLevel.ERROR, "WebSocket.Send(): Websocket connection was dropped unexpected");
|
||||
State = WebSocketState.ERROR;
|
||||
Close();
|
||||
}
|
||||
|
@ -178,19 +180,23 @@ namespace ln.http.websocket
|
|||
throw new IOException("WebSocket is not open");
|
||||
}
|
||||
}
|
||||
|
||||
public void Send(string textMessage)
|
||||
|
||||
private void _received(string textMessage)
|
||||
{
|
||||
WebSocketFrame webSocketFrame = new WebSocketFrame(textMessage);
|
||||
Send(webSocketFrame);
|
||||
TextReceived?.Invoke(this, textMessage);
|
||||
Received(textMessage);
|
||||
}
|
||||
public void Send(byte[] binaryMessage)
|
||||
private void _received(byte[] bytes)
|
||||
{
|
||||
WebSocketFrame webSocketFrame = new WebSocketFrame(binaryMessage);
|
||||
Send(webSocketFrame);
|
||||
BytesReceived?.Invoke(this, bytes);
|
||||
Received(bytes);
|
||||
}
|
||||
|
||||
|
||||
public virtual void Received(string text){}
|
||||
public virtual void Received(byte[] bytes){}
|
||||
|
||||
public void Send(string textMessage) => Send(new WebSocketFrame(textMessage));
|
||||
public void Send(byte[] binaryMessage) => Send(new WebSocketFrame(binaryMessage));
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,16 +10,16 @@ namespace ln.http.websocket
|
|||
|
||||
public WebSocketEventType EventType { get; }
|
||||
public byte[] BinaryMessage { get; }
|
||||
public String TextMessage => Encoding.UTF8.GetString(BinaryMessage);
|
||||
|
||||
private string _textMessage;
|
||||
public String TextMessage => _textMessage ??= Encoding.UTF8.GetString(BinaryMessage);
|
||||
|
||||
public bool IsBinary { get; }
|
||||
|
||||
public String ErrorMessage { get; }
|
||||
|
||||
public WebSocketEventArgs(WebSocketFrame frame)
|
||||
{
|
||||
Frame = frame;
|
||||
|
||||
switch (frame.Opcode)
|
||||
{
|
||||
case WebSocketOpcode.BINARY:
|
||||
|
@ -28,7 +28,6 @@ namespace ln.http.websocket
|
|||
EventType = WebSocketEventType.MESSAGE;
|
||||
BinaryMessage = frame.ApplicationData;
|
||||
ErrorMessage = null;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,8 @@ using ln.type;
|
|||
|
||||
namespace ln.http.websocket
|
||||
{
|
||||
|
||||
/*
|
||||
public delegate void WebSocketStateChanged(WebSocketResponse sender, WebSocketState newState);
|
||||
public delegate void WebSocketReceivedText(WebSocketResponse sender, string text);
|
||||
public delegate void WebSocketReceivedBytes(WebSocketResponse sender, byte[] bytes);
|
||||
|
@ -19,6 +21,7 @@ namespace ln.http.websocket
|
|||
public event WebSocketReceivedBytes OnWebSocketReceivedBytes;
|
||||
|
||||
public Stream Stream { get; private set; }
|
||||
public ILogWriter _logWriter;
|
||||
|
||||
WebSocketState state = WebSocketState.HANDSHAKE;
|
||||
public WebSocketState State {
|
||||
|
@ -66,7 +69,7 @@ namespace ln.http.websocket
|
|||
WebSocketFrame webSocketFrame = new WebSocketFrame(Stream);
|
||||
|
||||
if (webSocketFrame.Opcode != WebSocketOpcode.TEXT)
|
||||
Logging.Log(LogLevel.DEBUG, "ws.opcode: {0}", webSocketFrame.Opcode);
|
||||
_logWriter.Log(LogLevel.DEBUG, "ws.opcode: {0}", webSocketFrame.Opcode);
|
||||
|
||||
switch (webSocketFrame.Opcode)
|
||||
{
|
||||
|
@ -100,12 +103,12 @@ namespace ln.http.websocket
|
|||
} catch (IOException)
|
||||
{
|
||||
State = WebSocketState.ERROR;
|
||||
Logging.Log(LogLevel.DEBUG, "WebSocket connection was dropped unexpected");
|
||||
_logWriter.Log(LogLevel.DEBUG, "WebSocket connection was dropped unexpected");
|
||||
Close();
|
||||
} catch (Exception e)
|
||||
{
|
||||
Logging.Log(LogLevel.ERROR, "WebSocket caught Exception: {0}", e.ToString());
|
||||
Logging.Log(e);
|
||||
_logWriter.Log(LogLevel.ERROR, "WebSocket caught Exception: {0}", e.ToString());
|
||||
_logWriter.Log(e);
|
||||
} finally
|
||||
{
|
||||
State = WebSocketState.CLOSED;
|
||||
|
@ -131,7 +134,7 @@ namespace ln.http.websocket
|
|||
{
|
||||
if (State != WebSocketState.ERROR)
|
||||
{
|
||||
Logging.Log(LogLevel.ERROR, "WebSocket.Send(): Websocket connection was dropped unexpected");
|
||||
_logWriter.Log(LogLevel.ERROR, "WebSocket.Send(): Websocket connection was dropped unexpected");
|
||||
State = WebSocketState.ERROR;
|
||||
Close();
|
||||
}
|
||||
|
@ -180,10 +183,11 @@ namespace ln.http.websocket
|
|||
Convert.ToBase64String(SHA1.Create().ComputeHash(Encoding.ASCII.GetBytes(acceptKey)))
|
||||
);
|
||||
|
||||
base.WriteTo(Stream);
|
||||
|
||||
State = WebSocketState.OPEN;
|
||||
}
|
||||
Run();
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue