commit 67a03a957e616f5281451d5c1a8c06d04c194f72 Author: The Doctor Date: Thu Jun 18 12:19:06 2020 +0100 Initial commit diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e645270 --- /dev/null +++ b/.gitignore @@ -0,0 +1,353 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ diff --git a/App.config b/App.config new file mode 100644 index 0000000..8227adb --- /dev/null +++ b/App.config @@ -0,0 +1,6 @@ + + + + + + diff --git a/AsyncHttp.csproj b/AsyncHttp.csproj new file mode 100644 index 0000000..9c08185 --- /dev/null +++ b/AsyncHttp.csproj @@ -0,0 +1,64 @@ + + + + + Debug + AnyCPU + {819551D6-5FE4-4D46-99DD-BB199467C028} + Library + AsyncHttp + AsyncHttp + v4.5.2 + 512 + true + true + + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Enums.cs b/Enums.cs new file mode 100644 index 0000000..c2de336 --- /dev/null +++ b/Enums.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AsyncHttp +{ + public enum Method + { + GET, + HEAD, + POST, + PUT, + DELETE, + CONNECT, + OPTIONS, + TRACE, + PATCH, + ALL, + } +} diff --git a/IPath.cs b/IPath.cs new file mode 100644 index 0000000..d9f49af --- /dev/null +++ b/IPath.cs @@ -0,0 +1,28 @@ +namespace AsyncHttp +{ + public abstract class IPath + { + private ServerPath attr; + + public Response Response + { get; set; } + + public IPath() + { + Response = new Response(); + attr = (ServerPath)GetType().GetCustomAttributes(typeof(ServerPath), false)[0]; + } + + public string GetPathString() + { + return attr.Path; + } + + public string GetMethodString() + { + return attr.Method; + } + + public abstract void HandleRequest(Request request); + } +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..cf1ab25 --- /dev/null +++ b/LICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/MimeTypes.cs b/MimeTypes.cs new file mode 100644 index 0000000..1195699 --- /dev/null +++ b/MimeTypes.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AsyncHttp +{ + public static class MimeTypes + { + public static class Application + { + public const string AtomXml = "application/atom+xml"; + public const string AtomcatXml = "application/atomcat+xml"; + public const string Ecmascript = "application/ecmascript"; + public const string JavaArchive = "application/java-archive"; + public const string Javascript = "application/javascript"; + public const string Json = "application/json"; + public const string Mp4 = "application/mp4"; + public const string OctetStream = "application/octet-stream"; + public const string Pdf = "application/pdf"; + public const string Pkcs10 = "application/pkcs10"; + public const string Pkcs7Mime = "application/pkcs7-mime"; + public const string Pkcs7Signature = "application/pkcs7-signature"; + public const string Pkcs8 = "application/pkcs8"; + public const string Postscript = "application/postscript"; + public const string RdfXml = "application/rdf+xml"; + public const string RssXml = "application/rss+xml"; + public const string Rtf = "application/rtf"; + public const string SmilXml = "application/smil+xml"; + public const string XFontOtf = "application/x-font-otf"; + public const string XFontTtf = "application/x-font-ttf"; + public const string XFontWoff = "application/x-font-woff"; + public const string XPkcs12 = "application/x-pkcs12"; + public const string XShockwaveFlash = "application/x-shockwave-flash"; + public const string XSilverlightApp = "application/x-silverlight-app"; + public const string XhtmlXml = "application/xhtml+xml"; + public const string Xml = "application/xml"; + public const string XmlDtd = "application/xml-dtd"; + public const string XsltXml = "application/xslt+xml"; + public const string Zip = "application/zip"; + } + + public static class Audio + { + public const string Midi = "audio/midi"; + public const string Mp4 = "audio/mp4"; + public const string Mpeg = "audio/mpeg"; + public const string Ogg = "audio/ogg"; + public const string Webm = "audio/webm"; + public const string XAac = "audio/x-aac"; + public const string XAiff = "audio/x-aiff"; + public const string XMpegurl = "audio/x-mpegurl"; + public const string XMsWma = "audio/x-ms-wma"; + public const string XWav = "audio/x-wav"; + } + + public static class Image + { + public const string Bmp = "image/bmp"; + public const string Gif = "image/gif"; + public const string Jpeg = "image/jpeg"; + public const string Png = "image/png"; + public const string SvgXml = "image/svg+xml"; + public const string Tiff = "image/tiff"; + public const string Webp = "image/webp"; + } + + public static class Text + { + public const string Css = "text/css"; + public const string Csv = "text/csv"; + public const string Html = "text/html"; + public const string Plain = "text/plain"; + public const string RichText = "text/richtext"; + public const string Sgml = "text/sgml"; + public const string Yaml = "text/yaml"; + } + + public static class Video + { + public const string Threegpp = "video/3gpp"; + public const string H264 = "video/h264"; + public const string Mp4 = "video/mp4"; + public const string Mpeg = "video/mpeg"; + public const string Ogg = "video/ogg"; + public const string Quicktime = "video/quicktime"; + public const string Webm = "video/webm"; + } + } +} diff --git a/NotFoundPath.cs b/NotFoundPath.cs new file mode 100644 index 0000000..44f77a7 --- /dev/null +++ b/NotFoundPath.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AsyncHttp +{ + [ServerPath(Method.ALL, "")] + public sealed class NotFoundPath : IPath + { + public override void HandleRequest(Request request) + { + Response.Body = new byte[0]; + Response.Code = 404; + } + } +} diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..58c572b --- /dev/null +++ b/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("AsyncHttp")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("AsyncHttp")] +[assembly: AssemblyCopyright("Copyright © 2019")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("819551d6-5fe4-4d46-99dd-bb199467c028")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/README.md b/README.md new file mode 100644 index 0000000..8dc5cb3 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# AsyncHttp + An asyncronous HTTP server in C# diff --git a/Request.cs b/Request.cs new file mode 100644 index 0000000..5b15e46 --- /dev/null +++ b/Request.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.IO; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; + +namespace AsyncHttp +{ + public class Request + { + public Request(HttpListenerRequest Request = null) + { + if (Request == null) + throw new ArgumentNullException(); + + BaseUrl = Request.Url.GetLeftPart(UriPartial.Authority); + BodyStream = Request.InputStream; + ContentType = Request.ContentType; + + Cookies = Request.Cookies; + Queries = Request.QueryString; + } + + public string GetBodyString() + { + using (BodyStream) + { + using (StreamReader readStream = new StreamReader(BodyStream, Encoding.UTF8)) + { + return readStream.ReadToEnd(); + } + } + } + + public string BaseUrl + { + get; private set; + } + + public Stream BodyStream + { + get; private set; + } + + public CookieCollection Cookies + { + get; private set; + } + + public string ContentType + { + get; private set; + } + + public NameValueCollection Queries + { + get; private set; + } + } +} diff --git a/Response.cs b/Response.cs new file mode 100644 index 0000000..9760945 --- /dev/null +++ b/Response.cs @@ -0,0 +1,33 @@ +using System.IO; +using System.Net; + +namespace AsyncHttp +{ + public class Response + { + public Response() + { + Cookies = new CookieCollection(); + } + + public byte[] Body + { + get; set; + } + + public int Code + { + get; set; + } + + public string ContentType + { + get; set; + } + + public CookieCollection Cookies + { + get; set; + } + } +} diff --git a/Server.cs b/Server.cs new file mode 100644 index 0000000..89bed81 --- /dev/null +++ b/Server.cs @@ -0,0 +1,184 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Threading.Tasks; + +namespace AsyncHttp +{ + public class Server + { + private object stop_lock = new object(); + private bool stop = false; + private HttpListener listener = new HttpListener(); + private List paths = new List(); + + public int Port + { + get; set; + } + + public string CertificatePath + { + get; set; + } + + public string CertificatePassword + { + get; set; + } + + public int SSLPort + { + get; set; + } + + public Server(int Port = 8500, int SSLPort = 8443) + { + this.Port = Port; + this.SSLPort = SSLPort; + } + + public async Task StartAsync() + { + listener.Prefixes.Add("http://*:" + Port.ToString() + "/"); + + if (!string.IsNullOrEmpty(CertificatePath)) + { + try + { + var cert_path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, CertificatePath); + X509Certificate2 cert = new X509Certificate2(cert_path, CertificatePassword); + X509Store store = new X509Store(StoreName.Root, StoreLocation.LocalMachine); + store.Open(OpenFlags.ReadWrite); + if (!store.Certificates.Contains(cert)) + { + store.Add(cert); + } + store.Close(); + listener.Prefixes.Add("https://*:" + SSLPort.ToString() + "/"); + } + catch (Exception ex) + { + // notify in console + } + } + + try + { + var paths = from a in AppDomain.CurrentDomain.GetAssemblies() + from t in a.GetTypes() + let attributes = t.GetCustomAttributes(typeof(ServerPath), true) + where attributes != null && attributes.Length > 0 + select new { Type = t, Attributes = attributes.Cast() }; + + foreach (var t in paths) + { + if (t.Type.BaseType == typeof(IPath)) + { + var obj = (IPath)Activator.CreateInstance(t.Type); + this.paths.Add(obj); + } + } + + listener.Start(); + stop = false; + + while (listener.IsListening) + { + var context = await listener.GetContextAsync(); + + try + { + Console.WriteLine(context.Request.RawUrl); + await ProcessRequestAsync(context); + } + catch (Exception ex) + { + Console.WriteLine("# EXCEPTION # " + ex.StackTrace); + } + + lock (stop_lock) + { + if ((bool)stop == true) listener.Stop(); + } + } + + listener.Close(); + } + catch (Exception ex) + { + Console.WriteLine(ex.ToString()); + } + } + + public IPath GetPathForUrl(HttpListenerContext context) + { + for (var i = 0; i < paths.Count; i++) + { + var current_path = paths[i]; + if (context.Request.RawUrl == current_path.GetPathString()) + { + if (current_path.GetMethodString() != "ALL") + { + if (context.Request.HttpMethod == current_path.GetMethodString()) + { + return paths[i]; + } + } + else + return paths[i]; + } + } + + return new NotFoundPath(); + } + + private async Task ProcessRequestAsync(HttpListenerContext context) + { + Request req = new Request(context.Request); + + IPath path = GetPathForUrl(context); + + path.HandleRequest(req); + + var Response = path.Response; + context.Response.StatusCode = Response.Code == 0 ? 200 : Response.Code; + + var output_stream = context.Response.OutputStream; + + context.Response.ContentType = (!string.IsNullOrEmpty(Response.ContentType) ? + Response.ContentType: MimeTypes.Text.Plain); + + context.Response.Cookies = Response.Cookies; + + if (Response.Body != null) + { + context.Response.ContentLength64 = Response.Body.Length; + await output_stream.WriteAsync(Response.Body, 0, Response.Body.Length); + } + context.Response.Close(); + } + + public void Stop() + { + lock (stop_lock) + { + stop = true; + } + } + + public bool isRunning() + { + bool ret_val; + lock (stop_lock) + { + ret_val = !stop; + } + return ret_val; + } + } +} diff --git a/ServerPath.cs b/ServerPath.cs new file mode 100644 index 0000000..ada0383 --- /dev/null +++ b/ServerPath.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AsyncHttp +{ + [AttributeUsage(AttributeTargets.Class)] + public class ServerPath : Attribute + { + public ServerPath(Method Method, string Path) + { + this.Path = Path; + this.Method = Method.ToString(); + } + + public string Path { get; private set; } + public string Method { get; private set; } + } +}