HttpServerLite has been merged with the .NET Foundation project WatsonWebserver as a subproject called Watson.Lite.
This repository has been moved to public archive as a result.
We are thankful to the community that has contributed to this work and welcome you to continue efforts on the new repository!
TCP-based user-space HTTP and HTTPS server, written in C#, with no dependency on http.sys.
HostBuilderfeature to quickly build servers, thank you @sapurtcomputer30!- Bugfix for
HttpContext.HttpRequest.Datanot ending, thank you @ChZhongPengCheng33
I'd like to extend a special thanks to those that have provided motivation or otherwise directly helped make HttpServerLite better.
- @winkmichael @Job79 @MartyIX @sqlnew @SaintedPsycho @Return25 @marcussacana @samisil
- @Jump-Suit @sapurtcomputer30 @ChZhongPengCheng33 @bobaoapae
HttpServerLite is quite fast, however, it's in user-space and may be slower than other webservers that have the benefit of a kernel-mode driver (such as http.sys and IIS or Watson).
Refer to the Test project for a working example.
It is important to under that that HttpServerLite is minimalistic and leaves control to you on which headers are set. Thus it is important to understand the following:
server.Settings.Headerscontains default values for a series of HTTP headers- These will be included in every response if they have a value assigned
- The values in
server.Settings.Headerscan be written directly, or- You can modify per-response values by using
ctx.Response.Headers.Add("[header]", "[value]") - Values set in
ctx.Response.Headerswill override any value inserver.Settings.Headersfor that response only
- You can modify per-response values by using
- The headers automatically set if a value is supplied include
- Access-Control-Allow-[Origin|Methods|Headers]
- Access-Control-Expose-Headers
- Accept
- Accept-[Language|Charset]
- Connection
- Host
Connectionis an example of one of these headers. By default it is set toclose, therefore you should:- Leave it as is
- Explicitly set it prior to sending a response using
ctx.Response.Headers.Add("connection", "value"), or - Set the default value in
server.Settings.Headers.Connection
ctx.Response.ContentLengthshould be set if you want theContent-Lengthheader to be sentserver.Settings.Headers.Hostshould be set when instantiating the server though it is not required
using System;
using System.Threading.Tasks;
using HttpServerLite;
namespace Test
{
class Program
{
static Webserver _Server;
static void Main(string[] args)
{
Webserver server = new Webserver("localhost", 9000, false, null, null, DefaultRoute);
server.Settings.Headers.Host = "https://localhost:9000";
server.Start();
Console.WriteLine("HttpServerLite listening on http://localhost:9000");
Console.WriteLine("ENTER to exit");
Console.ReadLine();
}
static async Task DefaultRoute(HttpContext ctx)
{
string resp = "Hello from HttpServerLite!";
ctx.Response.StatusCode = 200;
ctx.Response.ContentLength = resp.Length;
ctx.Response.ContentType = "text/plain";
await ctx.Response.SendAsync(resp);
}
}
} HttpServerLite includes the following routing capabilities. These are listed in the other in which they are processed within HttpServerLite:
server.Settings.AccessControl- access control based on IP address- You can specify the
Modeto either beDefaultPermitorDefaultDenyDefaultPermitwill allow everything unless explicitly blocked throughDenyListDefaultDenywill deny everything unless explicitly permitted throughPermitList- The default value is
DefaultPermit
- You can specify the
server.Routes.Preflight- a default route to use when the HTTP verb isOPTIONS- When set, the connection is terminated after being handled by
server.OptionsRoute
- When set, the connection is terminated after being handled by
server.Routes.PreRouting- a route through which all requests will pass, useful for authentication, logging, and other functions- If defined, return
truefrom this task if you wish to terminate the connection - Otherwise return
falseto allow routing to continue
- If defined, return
server.Routes.Content- serve GET and HEAD requests for static content based on URL path- Content will be read from the
server.Routes.Content.BaseDirectoryplus the URL path - An entire directory can be listed as a content route when adding the route
- Content will be read from the
server.Routes.Static- invoke functions based on specific HTTP method and URL combinationsserver.Routes.Parameter- invoke functions based on specific HTTP method and URLs with embedded parameters. These values are returned inHttpContext.HttpRequest.Url.Parametersserver.Routes.Dynamic- invoke functions based on specific HTTP method and a regular expression for the URLserver.Routes.Default- any request that did not match a content route, static route, or dynamic route, is routed here
Additionally, you can annotate your own methods using the StaticRoute, ParameterRoute, or DynamicRoute attributes. Methods decorated with these attributes must be marked as public.
Webserver server = new Webserver("localhost", 9000, false, null, null, DefaultRoute);
server.Start();
[StaticRoute(HttpMethod.GET, "/static")]
public static async Task MyStaticRoute(HttpContext ctx)
{
string resp = "Hello from the static route";
ctx.Response.StatusCode = 200;
ctx.Response.ContentType = "text/plain";
ctx.Response.ContentLength = resp.Length;
await ctx.Response.SendAsync(resp);
return;
}
[ParameterRoute(HttpMethod.GET, "/{version}/api/{id}")]
public static async Task MyParameterRoute(HttpContext ctx)
{
string resp = "Hello from parameter route version " + ctx.Request.Url.Parameters["version"] + " for ID " + ctx.Request.Url.Parameters["id"];
ctx.Response.StatusCode = 200;
ctx.Response.ContentType = "text/plain";
ctx.Response.ContentLength = resp.Length;
await ctx.Response.SendAsync(resp);
return;
}
[DynamicRoute(HttpMethod.GET, "^/dynamic/\\d+$")]
public static async Task MyDynamicRoute(HttpContext ctx)
{
string resp = "Hello from the dynamic route";
ctx.Response.StatusCode = 200;
ctx.Response.ContentType = "text/plain";
ctx.Response.ContentLength = resp.Length;
await ctx.Response.SendAsync(resp);
return;
}server.Callbacks.AuthorizeConnection = AuthorizeConnection;
private static bool AuthorizeConnection(string ipAddress, int port)
{
// evaluate the IP address and port
return true; // permit
return false; // deny
}
HostBuilder helps you set up your server much more easily by introducing a chain of settings and routes instead of using the server class directly.
using WatsonWebserver.Extensions.HostBuilderExtension;
Server server = new HostBuilder("127.0.0.1", 8000, false, DefaultRoute)
.MapStaticRoute(WatsonWebserver.HttpMethod.GET, GetUrlsRoute, "/links")
.MapStaticRoute(WatsonWebserver.HttpMethod.POST, CheckLoginRoute, "/login")
.MapStaticRoute(WatsonWebserver.HttpMethod.POST, TestRoute, "/test")
.Build();
server.Start();
Console.WriteLine("Server started");
Console.ReadKey();
static async Task DefaultRoute(HttpContext ctx) =>
await ctx.Response.SendAsync("Hello from default route!");
static async Task GetUrlsRoute(HttpContext ctx) =>
await ctx.Response.SendAsync("Here are your links!");
static async Task CheckLoginRoute(HttpContext ctx) =>
await ctx.Response.SendAsync("Checking your login!");
static async Task TestRoute(HttpContext ctx) =>
await ctx.Response.SendAsync("Hello from the test route!"); When you configure HttpServerLite to listen on 127.0.0.1 or localhost, it will only respond to requests received from within the local machine.
To configure access from other nodes outside of localhost, use the following:
- Specify the IP address on which HttpServerLite should listen in the Server constructor.
- If you want to listen on more than one IP address, use
*or+ - If you listen on anything other than
localhostor127.0.0.1, you may have to run HttpServerLite as administrator (operating system dependent) - If you want to use a port number less than 1024, you MUST run HttpServerLite as administrator (this is an operating system limitation)
- Open a port on your firewall to permit traffic on the TCP port upon which HttpServerLite is listening
- If you're still having problems, please do not hesitate to file an issue here, and I will do my best to help and update the documentation
Refer to CHANGELOG.md for version history.