| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573 |
- using System;
- using System.IO;
- using System.Net;
- using System.Net.Sockets;
- using System.Text;
- using System.Threading;
- using System.Collections;
- using System.Collections.Generic;
- using System.Reflection;
- using System.Linq;
- using System.Text.RegularExpressions;
- namespace System.Net.ApiService {
- /*
- * Useage:
- * - Create an instance with Port
- * - If you want to use EventHandler, api.Request += Api_Request;
- * private void Api_Request(object sender, RequestEventArgs e)
- * - If you want to use Annotations, create a function and add [Route("/index.html") above it
- * Multiple Routes are allowed
- * private void example1(RequestHeader rh, Response r)
- * private void example1(RequestHeader rh, Response r, Match m)
- * private void example1(RequestHeader rh, Response r, GroupCollection m)
- * private string example1(RequestHeader rh)
- * private object example1(RequestHeader rh)
- * private bool example1(RequestHeader rh)
- * private string example1(RequestHeader rh, Match m)
- * private object example1(RequestHeader rh, Match m)
- * private bool example1(RequestHeader rh, Match m)
- * private string example1(RequestHeader rh, GroupCollection m)
- * private object example1(RequestHeader rh, GroupCollection m)
- * private bool example1(RequestHeader rh, GroupCollection m)
- * [...]
- * - Return Types:
- * void, string, bool, object
- * - Parameters
- * RequestHeader, Response?, Match?/GroupCollection?
- * - Using RegEx, "." and "/" only match inside a () {} and [], so "/index.html" will link to a file instead of "(/index.html)" that would match "indexAhtml", too
- */
- public class HttpApiServer {
- protected TcpListener myListener;
- protected ushort _port;
- protected bool bRunning = false;
- protected Thread th;
- private List<RouteElements> _routes = new List<RouteElements>();
- protected HttpMethod _allowedMethods = HttpMethod.GET | HttpMethod.POST;
- 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 string Path_FavIcon = "/favicon.ico";
- protected string Path_RobotsTXT = "/robots.txt";
- private string[] _allowedEncodingTypes = new string[] {
- "text/html",
- "application/javascript",
- "text/javascript",
- "application/json",
- "text/json",
- "text/css",
- "text/stylesheet",
- "text/xml",
- "application/xml"
- };
- public RouteElements[] Routes {
- get { return this._routes.ToArray(); }
- }
- public bool Running {
- get { return this.bRunning; }
- }
- public HttpApiServer(ushort port) {
- this.Port = port;
- bUseWebDirectory = Directory.Exists(this._wwwDirectory);
- }
- public HttpApiServer(string www_directory, ushort port) {
- this.Port = port;
- this.WebDirectory = www_directory;
- }
- public bool AllowEmptyResponse {
- get { return this.bAllowEmptyResponse; }
- set { this.bAllowEmptyResponse = value; }
- }
- public HttpMethod AllowedMethods {
- get {
- return this._allowedMethods;
- }
- set {
- this._allowedMethods = 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();
- myListener = new TcpListener(this._port);
- this.Start();
- } else {
- myListener = new TcpListener(this._port);
- }
- //myListener.Start();
- }
- }
- public bool Pending {
- get => this.myListener.Pending();
- }
- public void Start() {
- this.bRunning = true;
- if (myListener==null) {
- myListener = new TcpListener(this._port);
- }
- myListener.Start();
- this.th = new Thread(new ThreadStart(this.StartListen));
- this.th.Start();
- }
- public void Stop() {
- try {
- myListener.Server.Close();
- myListener.Stop();
- myListener = null;
- } catch {
- }
- try {
- this.th.Abort();
- } catch {
- }
- this.bRunning = false;
- }
- /*public void SendHeader(string sHttpVersion, string sMIMEHeader, int iTotBytes, string sStatusCode, ref Socket mySocket) {
- String sBuffer = "";
- // if Mime type is not provided set default to text/html
- if (sMIMEHeader.Length == 0) {
- sMIMEHeader = "text/html";// Default Mime Type is text/html
- }
- sBuffer = sBuffer + sHttpVersion + sStatusCode + "\r\n";
- sBuffer = sBuffer + "Server: BPApiServer\r\n";
- sBuffer = sBuffer + "Content-Type: " + sMIMEHeader + "\r\n";
- sBuffer = sBuffer + "Accept-Ranges: bytes\r\n";
- sBuffer = sBuffer + "Content-Length: " + iTotBytes + "\r\n\r\n";
- Byte[] bSendData = Encoding.ASCII.GetBytes(sBuffer);
- SendToBrowser(bSendData, ref mySocket);
- }*/
- public bool SendToBrowser(String sData, ref Socket mySocket) {
- return SendToBrowser(Encoding.ASCII.GetBytes(sData), ref mySocket);
- }
- public bool SendToBrowser(Byte[] bSendData, ref Socket mySocket) {
- int numBytes = 0;
- try {
- if (mySocket.Connected) {
- if ((numBytes = mySocket.Send(bSendData, bSendData.Length, 0)) > 0)
- return true;
- }
- } catch (Exception e) {
- }
- return false;
- }
- protected void StartListen() {
- while (true) {
- //Accept a new connection
- Socket mySocket;
- try {
- mySocket = myListener.AcceptSocket();
- } catch {
- break;
- }
- DateTime startTime = DateTime.Now;
- TimeSpan timeout = new TimeSpan(0, 0, 10);
- if (mySocket.Connected) {
- Byte[] bReceive = new Byte[4096];
- int i = mySocket.Receive(bReceive, 0, mySocket.Available, SocketFlags.Peek);
- string sBuffer = Encoding.ASCII.GetString(bReceive, 0, i);
- string sBuffer2 = "";
- foreach(byte b in bReceive) {
- if (b > 0)
- sBuffer2 += b;
- }
- if(sBuffer2.Length==0) {
- i = mySocket.Receive(bReceive, 0, mySocket.Available, SocketFlags.Peek);
- sBuffer = Encoding.ASCII.GetString(bReceive,0,i);
- foreach (byte b in bReceive) {
- if (b > 0)
- sBuffer2 += b;
- }
- }
- System.Diagnostics.Debug.WriteLine("- Received[" + sBuffer2.Length + "]: '" + sBuffer2.Trim() + "'");
- RequestHeader header = new RequestHeader(sBuffer);
- Response resp = new Response();
- RequestEventArgs req = new RequestEventArgs();
- req.Response = resp;
- req.Request = header;
- req.Socket = mySocket;
- req.SendAnyResponse = true;
- if (header.Path == "/ping" || header.Path=="ping") {
- resp.FromString(200, "pong");
- } else if (req.Request.Path == "/favicon.ico") {
- resp.FromFile("favicon.ico");
- } else if (req.Request.Path == "/robots.txt") {
- resp.FromFile("robots.txt");
- } else {
- if ((header.Method | this._allowedMethods) != this._allowedMethods) {
- resp.Error(Response.eErrorCode.MethodNotAllowed);
- } else {
- RequestAttr(req);
- OnRequest(req);
- }
- }
- if (req.Response == null) {
- req.Response = new Response();
- req.Response.Error(Response.eErrorCode.InternalServerError);
- }
- if (req.Response == null) {
- req.Request = header;
- req.Response.Error(Response.eErrorCode.InternalServerError);
- }
- if (req.Response.Code < 400) {
- System.Diagnostics.Debug.WriteLine("Request: " + header.Path);
- if (!req.Response.IsSet) {
- System.Diagnostics.Debug.WriteLine("No response was set");
- if (this.bUseWebDirectory) {
- System.Diagnostics.Debug.WriteLine("Use www-Directory");
- System.Diagnostics.Debug.WriteLine("Dir: " + this._wwwDirectory);
- System.Diagnostics.Debug.WriteLine("File: " + req.Request.Path);
- FileInfo fi = new FileInfo(this._wwwDirectory + req.Request.Path);
- System.Diagnostics.Debug.WriteLine("Full: " + fi.FullName);
- if (fi.Exists) {
- req.Response.FromFile(fi.FullName);
- }
- }
- }
- System.Diagnostics.Debug.WriteLine("Done");
- }
- if (req.SendAnyResponse) {
- List<string> response_header = new List<string>();
- if(this.bAllowEmptyResponse && req.Response.Length==0) {
- SetDefaultResponse(req.Response);
- }
- response_header.Add("HTTP/1.1 " + req.Response.Code + " " + req.Response.CodeName);
- response_header.Add("Server: " + this._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 == "" && this.bUseCompression) {
- if (this._allowedEncodingTypes.Contains(req.Response.ContentType)) {
- if ((header.AcceptEncoding & RequestHeader.eEncoder.br) == header.AcceptEncoding) {
- if (System.IO.Compression.BrotliEncoder.TryCompress(resp.Content, compressed_content, out bytesWritten)) {
- bUseBRCompression = true;
- }
- } else if ((header.AcceptEncoding & RequestHeader.eEncoder.gzip) == header.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 ((header.AcceptEncoding & RequestHeader.eEncoder.deflate) == header.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";
- System.Diagnostics.Debug.WriteLine(response_header_string);
- this.SendToBrowser(Encoding.ASCII.GetBytes(response_header_string), ref mySocket);
- if (req.Response.FilePath != "" && req.Response.Code == 200) {
- System.IO.FileStream fs = new FileStream(req.Response.FilePath, FileMode.Open);
- BinaryReader br = new BinaryReader(fs);
- while (fs.Position < fs.Length) {
- byte[] buffer = br.ReadBytes(1000);
- this.SendToBrowser(buffer, ref mySocket);
- }
- fs.Close();
- } else {
- if (bUseBRCompression || bUseGZCompression || bUseDeflateCompression) {
- System.Diagnostics.Debug.WriteLine("Send compressed: " + compressed_content.Length);
- this.SendToBrowser(compressed_content.ToArray(), ref mySocket);
- } else {
- if (req.Response.Connection != null && req.Response.Length>0) {
- System.Diagnostics.Debug.WriteLine("Send uncompressed: " + req.Response.Content.Length);
- if (this.SendToBrowser(req.Response.Content, ref mySocket)) {
- System.Diagnostics.Debug.WriteLine("Success");
- } else {
- System.Diagnostics.Debug.WriteLine("Failed");
- }
- }
- }
- }
- }
- mySocket.Close();
- }
- }
- }
- protected 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;
- }
- }
- 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 });
- }
- }
- }
- }
- protected virtual void OnRequest(RequestEventArgs e) {
- EventHandler<RequestEventArgs> handler = Request;
- if (handler != null) {
- handler(this, e);
- }
- }
- public event EventHandler<RequestEventArgs> Request;
- }
-
- }
|