| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617 |
- using System;
- using System.Collections.Generic;
- using System.Net;
- using System.Net.Sockets;
- using System.Text;
- using System.Threading;
- using System.IO;
- using System.Reflection;
- using System.Linq;
- using System.Text.RegularExpressions;
- namespace System.Net.ApiService {
- public class StateObject {
- // Client socket.
- public Socket workSocket = null;
- // Size of receive buffer.
- public const int BufferSize = 2048;
- // Receive buffer.
- public byte[] buffer = new byte[BufferSize];
- // Received data string.
- public MemoryStream Content = new MemoryStream();
- public StringBuilder RawHeader = new StringBuilder();
- public RequestHeader Request;
- public ApiServer server;
- }
- public class ApiServer {
- public static ManualResetEvent allDone = new ManualResetEvent(false);
- protected ushort _port;
- protected HttpMethod _allowedMethods = HttpMethod.GET | HttpMethod.POST;
- private List<RouteElements> _routes = new List<RouteElements>();
- protected string _serverName = "BlackPro_SimpleApi";
- // TODO: wwwDirectory entsprechend einer Hosts.ini Datei anpassen, anhand der Request.Host wird ein entsprechender wwwDirectory / FavIcon / Robots.txt verwendet, der in der Hosts.ini hinterlegt wurde
- // Das soll Multiple V-Hosts in zukunft erlauben
- protected string? _wwwDirectory = "/www";
- protected bool bUseWebDirectory = true;
- protected bool bUseCompression = false;
- protected bool bAllowEmptyResponse = false;
- protected Socket listener;
- protected bool bRunning;
- protected Thread th;
- protected string[] _allowedEncodingTypes = new string[] {
- "text/html",
- "application/javascript",
- "text/javascript",
- "application/json",
- "text/json",
- "text/css",
- "text/stylesheet",
- "text/xml",
- "application/xml"
- };
- public HttpMethod AllowedMethods {
- get => this._allowedMethods;
- set => this._allowedMethods = value;
- }
- public RouteElements[] Routes {
- get { return this._routes.ToArray(); }
- }
- public bool Running {
- get { return this.bRunning; }
- }
- public bool AllowEmptyResponse {
- get { return this.bAllowEmptyResponse; }
- set { this.bAllowEmptyResponse = value; }
- }
- public bool Compression {
- get {
- return this.bUseCompression;
- }
- set {
- this.bUseCompression = value;
- }
- }
- public string ServerName {
- get {
- return this._serverName;
- }
- set {
- this._serverName = value;
- }
- }
- public bool UseWebDirectory {
- get {
- return this.bUseWebDirectory;
- }
- }
- public string WebDirectory {
- get {
- return this._wwwDirectory;
- }
- set {
- this._wwwDirectory = value;
- this.bUseWebDirectory = this._wwwDirectory != null && Directory.Exists(this._wwwDirectory) && this._wwwDirectory != "";
- }
- }
- public ushort Port {
- get {
- return this._port;
- }
- set {
- this._port = value;
- if (this.bRunning) {
- this.Stop();
- this.Start();
- }
- }
- }
- public ApiServer() {
- }
- public ApiServer(ushort port) {
- this._port = port;
- }
- public ApiServer(string www_Directory, ushort port) {
- this._port = port;
- this._wwwDirectory = www_Directory;
- }
- public void Stop() {
- this.bRunning = false;
- System.Diagnostics.Debug.WriteLine("Stop");
- try {
- allDone.Set();
- this.th.Abort();
- this.listener.Close();
- this.listener.Dispose();
- this.listener = null;
- } catch {
- }
- }
- public void Start() {
- if (this.bRunning == true)
- return;
- this.bRunning = true;
- this.th = new Thread(new ThreadStart(this.StartListen));
- //this.th.ThreadState = new object[] { this.listener, this._port, this };
- System.Diagnostics.Debug.WriteLine("Start");
- this.th.Start();
- }
- protected void StartListen() {
- // Establish the local endpoint for the socket.
- // The DNS name of the computer
- // running the listener is "host.contoso.com".
- IPHostEntry ipHostInfo = Dns.GetHostEntry(Dns.GetHostName());
- //IPAddress ipAddress = ipHostInfo.AddressList[0];
- IPAddress ipAddress = new IPAddress(new byte[] { 127, 0, 0, 1 });
- IPEndPoint localEndPoint = new IPEndPoint(ipAddress, this._port);
- // Create a TCP/IP socket.
- System.Diagnostics.Debug.WriteLine("Create listener");
- //this.listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
- this.listener = new Socket(ipAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
- // Bind the socket to the local endpoint and listen for incoming connections.
- try {
- System.Diagnostics.Debug.WriteLine("Bind listener");
- this.listener.Bind(localEndPoint);
- System.Diagnostics.Debug.WriteLine("Listen on port: " + this._port);
- this.listener.Listen(100);
- while (this.bRunning) {
- // Set the event to nonsignaled state.
- System.Diagnostics.Debug.WriteLine("Reset");
- allDone.Reset();
- // Start an asynchronous socket to listen for connections.
- System.Diagnostics.Debug.WriteLine("Begin Accept");
- this.listener.BeginAccept(new AsyncCallback(Async_AcceptCallback), new object[] { this.listener, this });
- //Accept(this, this.listener);
-
- // Wait until a connection is made before continuing.
- System.Diagnostics.Debug.WriteLine("WaitOne");
- allDone.WaitOne();
- System.Diagnostics.Debug.WriteLine("Wait done");
- }
- System.Diagnostics.Debug.WriteLine("Stop Listen");
- } catch (Exception e) {
- System.Diagnostics.Debug.WriteLine(e);
- }
- }
- protected static void Accept(ApiServer server, Socket listener) {
- // Get the socket that handles the client request.
- Socket handler = listener.Accept();
- System.Diagnostics.Debug.WriteLine("AcceptCallback");
- allDone.Set();
- // Create the state object.
- StateObject state = new StateObject();
- state.workSocket = handler;
- state.server = server;
- System.Diagnostics.Debug.WriteLine("Begin Receive");
- handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(Async_ReadRequestCallback), state);
- }
- protected static void Async_AcceptCallback(IAsyncResult ar) {
- // Signal the main thread to continue.
- System.Diagnostics.Debug.WriteLine("AcceptCallback");
- allDone.Set();
- // Get the socket that handles the client request.
- Socket listener = (Socket)((object[])ar.AsyncState)[0];
- Socket handler = listener.EndAccept(ar);
- // Create the state object.
- StateObject state = new StateObject();
- state.workSocket = handler;
- state.server = (ApiServer)((object[])ar.AsyncState)[1];
- System.Diagnostics.Debug.WriteLine("Begin Receive");
- handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(Async_ReadRequestCallback), state);
- }
- protected static void Async_ReadRequestCallback(IAsyncResult ar) {
- System.Diagnostics.Debug.WriteLine("ReadRequestCallback");
- String content = String.Empty;
- // Retrieve the state object and the handler socket
- // from the asynchronous state object.
- StateObject state = (StateObject)ar.AsyncState;
- Socket handler = state.workSocket;
- // Read data from the client socket.
- int bytesRead = handler.EndReceive(ar);
- if (bytesRead > 0) {
- // There might be more data, so store the data received so far.
- string line = Encoding.ASCII.GetString(state.buffer, 0, bytesRead);
- if (state.RawHeader.Length == 0) {
- string[] xline = line.Split("\r\n\r\n", 2);
- state.RawHeader.Append(xline[0]);
- if (xline.Length > 0) {
- for (int i = 20; i < bytesRead; i++)
- if (state.buffer[i] == '\r' && state.buffer[i + 1] == '\n' && state.buffer[i + 2] == '\r' && state.buffer[i + 3] == '\n') {
- state.Content.Write(state.buffer,i+4,bytesRead-i-4);
- break;
- }
- }
- state.Request = new RequestHeader(state.RawHeader.ToString());
- } else {
- state.Content.Write(state.buffer, 0, bytesRead);
- }
- Response resp = new Response();
- RequestEventArgs req = new RequestEventArgs();
- req.Response = resp;
- req.Request = state.Request;
- req.Socket = handler;
- req.SendAnyResponse = true;
- // Check if more data is incomming
- bool ContinueRead = false;
- HttpMethod requestsWithContent = HttpMethod.POST | HttpMethod.PUT | HttpMethod.PATCH;
- if ((state.Request.Method | requestsWithContent) == requestsWithContent && state.Request.ContentLength > 0) {
- ContinueRead = (state.Request.ContentLength > 0 && state.Request.ContentLength < state.Content.Length) || (state.Request.Boundary.Length > 0 && !line.EndsWith("--" + state.Request.Boundary + "--"));
- if (!ContinueRead && state.Request.Boundary.Length > 0) {
- // TODO: Resolver boundary here
- /*
- --boundary\r\n
- Content-Disposition: form-data; name="field1"\r\n
- \r\n
- Hier steht der Content, bis der nächste boundary beginnt\r\n
- --boundary\r\n
- Content-Disposition: form-data; name="field2"; filename="example.txt"\r\n
- \r\n
- Hier steht der Zweite Content, bis der finale boundary tag beginnt\r\n
- --boundary--
- */
- }
- }
- if (ContinueRead) {
- handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(Async_ReadRequestCallback), state);
- } else {
- // Check known files and check allowed HTTP-Method
- if ((state.Request.Path == "/ping" || state.Request.Path == "ping") && state.Request.Method == HttpMethod.GET) {
- resp.FromString(200, "pong");
- } else if (state.Request.Path == "/favicon.ico" && state.Request.Method == HttpMethod.GET) {
- resp.FromFile("favicon.ico");
- } else if (state.Request.Path == "/robots.txt" && state.Request.Method == HttpMethod.GET) {
- resp.FromFile("robots.txt");
- } else {
- if(state.Request.ContentLength>0 && state.Request.ContentLength<2048) {
- state.Request.SetPostData(state.Content.ToString());
- }
- if ((state.Request.Method | state.server._allowedMethods) != state.server._allowedMethods) {
- resp.Error(Response.eErrorCode.MethodNotAllowed);
- } else {
- state.server.RequestAttr(req);
- state.server.OnRequest(req);
- }
- }
- // Generate response header
- List<string> response_header = new List<string>();
- if (state.server.bAllowEmptyResponse && req.Response.Length == 0) {
- SetDefaultResponse(req.Response);
- }
- response_header.Add("HTTP/1.1 " + req.Response.Code + " " + req.Response.CodeName);
- response_header.Add("Server: " + state.server.ServerName);
- response_header.Add("Date: " + DateTime.Now.ToString("r"));
- bool bUseBRCompression = false;
- bool bUseGZCompression = false;
- bool bUseDeflateCompression = false;
- Span<byte> compressed_content = new Span<byte>();
- int bytesWritten;
- if (req.Response.Code >= 200 && req.Response.Code < 300 && resp.FilePath == "" && state.server.bUseCompression) {
- if (state.server._allowedEncodingTypes.Contains(req.Response.ContentType)) {
- if ((state.Request.AcceptEncoding & RequestHeader.eEncoder.br) == state.Request.AcceptEncoding) {
- if (System.IO.Compression.BrotliEncoder.TryCompress(resp.Content, compressed_content, out bytesWritten)) {
- bUseBRCompression = true;
- }
- } else if ((state.Request.AcceptEncoding & RequestHeader.eEncoder.gzip) == state.Request.AcceptEncoding) {
- using (MemoryStream mem = new MemoryStream()) {
- using (System.IO.Compression.GZipStream gzip = new IO.Compression.GZipStream(mem, IO.Compression.CompressionLevel.Optimal)) {
- gzip.Write(resp.Content, 0, resp.Content.Length);
- }
- compressed_content = mem.ToArray();
- }
- bUseGZCompression = true;
- } else if ((state.Request.AcceptEncoding & RequestHeader.eEncoder.deflate) == state.Request.AcceptEncoding) {
- using (MemoryStream mem = new MemoryStream()) {
- using (System.IO.Compression.DeflateStream defl = new IO.Compression.DeflateStream(mem, IO.Compression.CompressionLevel.Optimal)) {
- defl.Write(resp.Content, 0, resp.Content.Length);
- }
- compressed_content = mem.ToArray();
- }
- bUseDeflateCompression = true;
- }
- }
- }
- if (req.Response.Code >= 300 && req.Response.Code < 400 && req.Response.RedirectLocation != "") {
- response_header.Add("Location: " + req.Response.RedirectLocation);
- } else if (req.Response.Code >= 500) {
- // Nothing to do on 500 errors
- } else {
- if (bUseBRCompression || bUseGZCompression || bUseDeflateCompression)
- response_header.Add("Content-Length: " + compressed_content.Length);
- else
- response_header.Add("Content-Length: " + req.Response.Length);
- if (req.Response.ContentType == "") {
- response_header.Add("Content-Type: " + req.Response.ContentType);
- }
- response_header.Add("Accept-Ranges: bytes");
- response_header.Add("Age: 0");
- response_header.Add("Accept-Ranges: bytes");
- if (resp.CacheControl != null && resp.CacheControl.IsSet) {
- response_header.Add("Cache-Control: " + resp.CacheControl);
- }
- if (bUseBRCompression)
- response_header.Add("Content-Encoding: bz");
- else if (bUseGZCompression)
- response_header.Add("Content-Encoding: gzip");
- else if (bUseDeflateCompression)
- response_header.Add("Content-Encoding: deflate");
- response_header.Add("Connection: " + (resp.Connection == Response.eConnection.KeepAlive ? "keep-alive" : "Close"));
- }
- string response_header_string = "";
- foreach (string s in response_header)
- response_header_string += s + "\r\n";
- response_header_string += "\r\n";
- Send(handler, response_header_string, resp.Content);
- }
- }
- }
- protected static void SetDefaultResponse(Response res) {
- switch (res.ContentType.ToLower().Trim()) {
- case "":
- case "text/html":
- res.FromString("<html></html>", "text/html");
- break;
- case "text/stylesheet":
- case "text/css":
- res.FromString("");
- break;
- case "text/javascript":
- case "application/javascript":
- case "text/x-javascript":
- case "application/x-javascript":
- res.FromString("");
- break;
- }
- }
- private static void Send(Socket handler, String data, byte[] Content) {
- // Convert the string data to byte data using ASCII encoding.
- byte[] byteData = Encoding.ASCII.GetBytes(data);
- // Begin sending the data to the remote device.
- handler.BeginSend(byteData, 0, byteData.Length, 0, new AsyncCallback(SendResponseHeaderCallback), new object[] { handler, Content });
- }
- private static void SendResponseHeaderCallback(IAsyncResult ar) {
- try {
- // Retrieve the socket from the state object.
- Socket handler = (Socket)((object[])ar.AsyncState)[0];
- byte[] Content = (byte[])((object[])ar.AsyncState)[1];
- // Complete sending the data to the remote device.
- int bytesSent = handler.EndSend(ar);
- handler.BeginSend(Content, 0, Content.Length, 0, new AsyncCallback(SendResponseContentCallback), handler);
- } catch (Exception e) {
- System.Diagnostics.Debug.WriteLine(e);
- }
- }
- private static void SendResponseContentCallback(IAsyncResult ar) {
- try {
- // Retrieve the socket from the state object.
- Socket handler = (Socket)ar.AsyncState;
- // Complete sending the data to the remote device.
- int bytesSent = handler.EndSend(ar);
- //Console.WriteLine("Sent {0} bytes to client.", bytesSent);
- handler.Shutdown(SocketShutdown.Both);
- handler.Close();
- } catch (Exception e) {
- System.Diagnostics.Debug.WriteLine(e);
- }
- }
- public void RegisterClass(object[] instances) {
- foreach (object o in instances)
- this.RegisterClass(o);
- }
- public void RegisterClass(Type[] t) {
- foreach (Type o in t)
- this.RegisterClass(o);
- }
- public void RegisterClass(object instance) {
- Type t = instance.GetType();
- MethodInfo[] methodInfos = t.GetMethods(BindingFlags.Public | BindingFlags.Static);
- Route r = t.GetCustomAttribute<Route>();
- string baseRoute = "";
- if (r != null) {
- baseRoute = r.Path;
- }
- foreach (MethodInfo mi in methodInfos) {
- Route[] ris = mi.GetCustomAttributes<Route>().ToArray();
- foreach (Route ri in ris) {
- this._routes.Add(new RouteElements(instance, mi, ri, baseRoute));
- }
- }
- }
- public void RegisterClass(Type t) {
- Route r = t.GetCustomAttribute<Route>();
- string baseRoute = "";
- if (r != null) {
- baseRoute = r.Path;
- }
- MethodInfo[] methodInfos = t.GetMethods(BindingFlags.Public | BindingFlags.Static);
- foreach (MethodInfo mi in methodInfos) {
- Route[] ris = mi.GetCustomAttributes<Route>().ToArray();
- foreach (Route ri in ris) {
- this._routes.Add(new RouteElements(t, mi, ri, baseRoute));
- }
- }
- }
- protected void RequestAttr(RequestEventArgs e) {
- RequestHeader rh;
- foreach (RouteElements re in this._routes) {
- rh = e.Request;
- bool bIsMethod = (rh.Method | re.Method) == re.Method;
- if (rh.Path != null && re.Pattern.IsMatch(rh.Path) && bIsMethod) {
- ParameterInfo[] pis = re.Function.GetParameters();
- if (pis.Length == 2 && pis[0].ParameterType == typeof(RequestHeader) && pis[1].ParameterType == typeof(Response)) {
- re.Function.Invoke(re.Instance, new object[] { e.Request, e.Response });
- } else if (pis.Length == 1 && pis[0].ParameterType == typeof(RequestHeader)) {
- if (re.Function.ReturnType == typeof(Response)) {
- Response r = (Response)re.Function.Invoke(re.Instance, new object[] { rh });
- if (r != null) {
- e.Response = r;
- }
- } else if (re.Function.ReturnType == typeof(string)) {
- string r = (string)re.Function.Invoke(re.Instance, new object[] { rh });
- e.Response.FromString(200, r);
- } else if (re.Function.ReturnType == typeof(bool)) {
- bool r = (bool)re.Function.Invoke(re.Instance, new object[] { rh });
- if (r)
- e.Response.FromString(200, "{\"status\":\"ok\",\"code\":200,\"value\":1}", "application/json");
- else
- e.Response.FromString(200, "{\"status\":\"failed\",\"code\":200,\"value\":0}", "application/json");
- } else if (re.Function.ReturnType == typeof(object)) {
- object r = (object)re.Function.Invoke(re.Instance, new object[] { rh });
- e.Response.FromObject(200, r);
- }
- } else if (pis.Length == 1 && pis[0].ParameterType == typeof(Response)) {
- re.Function.Invoke(re.Instance, new object[] { e.Response });
- //RegEx
- } else if (pis.Length == 3 && pis[0].ParameterType == typeof(RequestHeader) && pis[1].ParameterType == typeof(Response) && pis[2].ParameterType == typeof(Match)) {
- re.Function.Invoke(re.Instance, new object[] { e.Request, e.Response, re.Pattern.Match(rh.Path) });
- } else if (pis.Length == 2 && pis[0].ParameterType == typeof(RequestHeader) && pis[1].ParameterType == typeof(Match)) {
- if (re.Function.ReturnType == typeof(Response)) {
- Response r = (Response)re.Function.Invoke(re.Instance, new object[] { rh, re.Pattern.Match(rh.Path) });
- if (r != null) {
- e.Response = r;
- }
- } else if (re.Function.ReturnType == typeof(string)) {
- string r = (string)re.Function.Invoke(re.Instance, new object[] { rh, re.Pattern.Match(rh.Path) });
- e.Response.FromString(200, r);
- } else if (re.Function.ReturnType == typeof(bool)) {
- bool r = (bool)re.Function.Invoke(re.Instance, new object[] { rh, re.Pattern.Match(rh.Path) });
- if (r)
- e.Response.FromString(200, "{\"status\":\"ok\",\"code\":200,\"value\":1}", "application/json");
- else
- e.Response.FromString(200, "{\"status\":\"failed\",\"code\":200,\"value\":0}", "application/json");
- } else if (re.Function.ReturnType == typeof(object)) {
- object r = (object)re.Function.Invoke(re.Instance, new object[] { rh, re.Pattern.Match(rh.Path) });
- e.Response.FromObject(200, r);
- }
- } else if (pis.Length == 2 && pis[0].ParameterType == typeof(Response) && pis[1].ParameterType == typeof(Match)) {
- re.Function.Invoke(re.Instance, new object[] { e.Response, re.Pattern.Match(rh.Path) });
- } else if (pis.Length == 3 && pis[0].ParameterType == typeof(RequestHeader) && pis[1].ParameterType == typeof(Response) && pis[2].ParameterType == typeof(GroupCollection)) {
- re.Function.Invoke(re.Instance, new object[] { e.Request, e.Response, re.Pattern.Match(rh.Path).Groups });
- } else if (pis.Length == 2 && pis[0].ParameterType == typeof(RequestHeader) && pis[1].ParameterType == typeof(GroupCollection)) {
- if (re.Function.ReturnType == typeof(Response)) {
- Response r = (Response)re.Function.Invoke(re.Instance, new object[] { rh, re.Pattern.Match(rh.Path).Groups });
- if (r != null) {
- e.Response = r;
- }
- } else if (re.Function.ReturnType == typeof(string)) {
- string r = (string)re.Function.Invoke(re.Instance, new object[] { rh, re.Pattern.Match(rh.Path).Groups });
- e.Response.FromString(200, r);
- } else if (re.Function.ReturnType == typeof(bool)) {
- bool r = (bool)re.Function.Invoke(re.Instance, new object[] { rh, re.Pattern.Match(rh.Path).Groups });
- if (r)
- e.Response.FromString(200, "{\"status\":\"ok\",\"code\":200,\"value\":1}", "application/json");
- else
- e.Response.FromString(200, "{\"status\":\"failed\",\"code\":200,\"value\":0}", "application/json");
- } else if (re.Function.ReturnType == typeof(object)) {
- object r = (object)re.Function.Invoke(re.Instance, new object[] { rh, re.Pattern.Match(rh.Path).Groups });
- e.Response.FromObject(200, r);
- }
- } else if (pis.Length == 2 && pis[0].ParameterType == typeof(Response) && pis[1].ParameterType == typeof(GroupCollection)) {
- re.Function.Invoke(re.Instance, new object[] { e.Response, re.Pattern.Match(rh.Path).Groups });
- }
- }
- }
- }
- public virtual void OnRequest(RequestEventArgs e) {
- EventHandler<RequestEventArgs> handler = Request;
- if (handler != null) {
- handler(this, e);
- }
- }
- public event EventHandler<RequestEventArgs> Request;
- }
- }
|