Watson 6.3.2

dotnet add package Watson --version 6.3.2                
NuGet\Install-Package Watson -Version 6.3.2                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Watson" Version="6.3.2" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Watson --version 6.3.2                
#r "nuget: Watson, 6.3.2"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install Watson as a Cake Addin
#addin nuget:?package=Watson&version=6.3.2

// Install Watson as a Cake Tool
#tool nuget:?package=Watson&version=6.3.2                

alt tag

Watson Webserver

Simple, scalable, fast, async web server for processing RESTful HTTP/HTTPS requests, written in C#.

Package NuGet Version Downloads
Watson NuGet Version NuGet
Watson.Lite NuGet Version NuGet
Watson.Core NuGet Version NuGet

Special thanks to @DamienDennehy for allowing us the use of the Watson.Core package name in NuGet!

.NET Foundation

This project is part of the .NET Foundation along with other projects like the .NET Runtime.

New in v6.3.x

  • Minor change to chunked transfer, i.e. SendChunk now accepts isFinal as a Boolean property
  • Added support for server-sent events, included Test.ServerSentEvents project
  • Minor internal refactor

Special Thanks

I'd like to extend a special thanks to those that have helped make Watson Webserver better.

  • @notesjor @shdwp @Tutch @GeoffMcGrath @jurkovic-nikola @joreg @Job79 @at1993
  • @MartyIX @pocsuka @orinem @deathbull @binozo @panboy75 @iain-cyborn @gamerhost31
  • @nhaberl @grgouala @sapurtcomputer30 @winkmichael @sqlnew @SaintedPsycho @Return25
  • @marcussacana @samisil @Jump-Suit @ChZhongPengCheng33 @bobaoapae @rodgers-r
  • @john144 @zedle @GitHubProUser67 @bemoty @bemon @nomadeon

Watson vs Watson.Lite

Watson is a webserver that operates on top of the underlying http.sys within the operating system. Watson.Lite was created by merging HttpServerLite. Watson.Lite does not have a dependency on http.sys, and is implemented using a TCP implementation provided by CavemanTcp.

The dependency on http.sys (or lack thereof) creates subtle differences between the two libraries, however, the configuration and management of each should be consistent.

Watson.Lite is generally less performant than Watson, because the HTTP implementation is in user space.

Important Notes

  • Elevation (administrative privileges) may be required if binding an IP other than 127.0.0.1 or localhost
  • For Watson:
    • The HTTP HOST header must match the specified binding
    • For SSL, the underlying computer certificate store will be used
  • For Watson.Lite:
    • Watson.Lite uses a TCP listener; your server must be started with an IP address, not a hostname
    • The HTTP HOST header does not need to match, since the listener must be defined by IP address
    • For SSL, the certificate filename, filename and password, or X509Certificate2 must be supplied

Routing

Watson and Watson.Lite always routes in the following order (configure using Webserver.Routes):

  • .Preflight - handling preflight requests (generally with HTTP method OPTIONS)
  • .PreRouting - always invoked before any routing determination is made
  • .PreAuthentication - a routing group, comprised of:
    • .Static - static routes, e.g. an HTTP method and an explicit URL
    • .Content - file serving routes, e.g. a directory where files can be read
    • .Parameter - routes where variables are specified in the path, e.g. /user/{id}
    • .Dynamic - routes where the URL is defined by a regular expression
  • .AuthenticateRequest - demarcation route between unauthenticated and authenticated routes
  • .PostAuthentication - a routing group with a structure identical to .PreAuthentication
  • .Default - the default route; all requests go here if not routed previously
  • .PostRouting - always invoked, generally for logging and telemetry

If you do not wish to use authentication, you should map your routes in the .PreAuthentication routing group (though technically they can be placed in .PostAuthentication or .Default assuming the AuthenticateRequest method is null.

As a general rule, never try to send data to an HttpResponse while in the .PostRouting route. If a response has already been sent, the attempt inside of .PostRouting will fail.

Authentication

It is recommended that you implement authentication in .AuthenticateRequest. Should a request fail authentication, return a response within that route. The HttpContextBase class has properties that can hold authentication-related or session-related metadata, specifically, .Metadata.

Access Control

By default, Watson and Watson.Lite will permit all inbound connections.

  • If you want to block certain IPs or networks, use Server.AccessControl.DenyList.Add(ip, netmask)
  • If you only want to allow certain IPs or networks, and block all others, use:
    • Server.AccessControl.Mode = AccessControlMode.DefaultDeny
    • Server.AccessControl.PermitList.Add(ip, netmask)

Simple Example

Refer to Test.Default for a full example.

using System.IO;
using System.Text;
using WatsonWebserver;

static void Main(string[] args)
{
  WebserverSettings settings = new WebserverSettings("127.0.0.1", 9000);
  Webserver server = new Webserver(settings, DefaultRoute);
  server.Start();
  Console.ReadLine();
}

static async Task DefaultRoute(HttpContextBase ctx) =>
  await ctx.Response.Send("Hello from the default route!");

Then, open your browser to http://127.0.0.1:9000/.

Example with Routes

Refer to Test.Routing for a full example.

using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using WatsonWebserver;

static void Main(string[] args)
{
  WebserverSettings settings = new WebserverSettings("127.0.0.1", 9000);
  Webserver server = new Webserver(settings, DefaultRoute);

  // add content routes
  server.Routes.PreAuthentication.Content.Add("/html/", true);
  server.Routes.PreAuthentication.Content.Add("/img/watson.jpg", false);

  // add static routes
  server.Routes.PreAuthentication.Static.Add(HttpMethod.GET, "/hello/", GetHelloRoute);
  server.Routes.PreAuthentication.Static.Add(HttpMethod.GET, "/howdy/", async (HttpContextBase ctx) =>
  {
      await ctx.Response.Send("Hello from the GET /howdy static route!");
      return;
  });

  // add parameter routes
  server.Routes.PreAuthentication.Parameter.Add(HttpMethod.GET, "/{version}/bar", GetBarRoute);

  // add dynamic routes
  server.Routes.PreAuthentication.Dynamic.Add(HttpMethod.GET, new Regex("^/foo/\\d+$"), GetFooWithId);  
  server.Routes.PreAuthentication.Dynamic.Add(HttpMethod.GET, new Regex("^/foo/?$"), GetFoo); 

  // start the server
  server.Start();

  Console.WriteLine("Press ENTER to exit");
  Console.ReadLine();
}

static async Task GetHelloRoute(HttpContextBase ctx) =>
  await ctx.Response.Send("Hello from the GET /hello static route!");

static async Task GetBarRoute(HttpContextBase ctx) =>
  await ctx.Response.Send("Hello from the GET /" + ctx.Request.Url.Parameters["version"] + "/bar route!");

static async Task GetFooWithId(HttpContextBase ctx) =>
  await ctx.Response.Send("Hello from the GET /foo/[id] dynamic route!");
 
static async Task GetFoo(HttpContextBase ctx) =>
  await ctx.Response.Send("Hello from the GET /foo/ dynamic route!");

static async Task DefaultRoute(HttpContextBase ctx) =>
  await ctx.Response.Send("Hello from the default route!");

Route with Exception Handler

server.Routes.PreAuthentication.Static.Add(HttpMethod.GET, "/hello/", GetHelloRoute, MyExceptionRoute);

static async Task GetHelloRoute(HttpContextBase ctx) => throw new Exception("Whoops!");

static async Task MyExceptionRoute(HttpContextBase ctx, Exception e)
{
  ctx.Response.StatusCode = 500;
  await ctx.Response.Send(e.Message);
}

Permit or Deny by IP or Network

Webserver server = new Webserver("127.0.0.1", 9000, false, DefaultRoute);

// set default permit (permit any) with deny list to block specific IP addresses or networks
server.Settings.AccessControl.Mode = AccessControlMode.DefaultPermit;
server.Settings.AccessControl.DenyList.Add("127.0.0.1", "255.255.255.255");  

// set default deny (deny all) with permit list to permit specific IP addresses or networks
server.Settings.AccessControl.Mode = AccessControlMode.DefaultDeny;
server.Settings.AccessControl.PermitList.Add("127.0.0.1", "255.255.255.255");

Chunked Transfer-Encoding

Watson supports both receiving chunked data and sending chunked data (indicated by the header Transfer-Encoding: chunked).

Refer to Test.ChunkServer for a sample implementation.

Receiving Chunked Data

static async Task UploadData(HttpContextBase ctx)
{
  if (ctx.Request.ChunkedTransfer)
  {
    bool finalChunk = false;
    while (!finalChunk)
    {
      Chunk chunk = await ctx.Request.ReadChunk();
      // work with chunk.Length and chunk.Data (byte[])
      finalChunk = chunk.IsFinalChunk;
    }
  }
  else
  {
    // read from ctx.Request.Data stream   
  }
}

Sending Chunked Data

static async Task DownloadChunkedFile(HttpContextBase ctx)
{
  using (FileStream fs = new FileStream("./img/watson.jpg", , FileMode.Open, FileAccess.Read))
  {
    ctx.Response.StatusCode = 200;
    ctx.Response.ChunkedTransfer = true;

    byte[] buffer = new byte[4096];
    while (true)
    {
      int bytesRead = await fs.ReadAsync(buffer, 0, buffer.Length);
      byte[] data = new byte[bytesRead];
      Buffer.BlockCopy(buffer, 0, bytesRead, data, 0); // only copy the read data

      if (bytesRead > 0)
      {
        await ctx.Response.SendChunk(data, false);
      }
      else
      {
        await ctx.Response.SendChunk(Array.Empty<byte>(), true);
        break;
      }
    }
  }

  return;
}

Server-Sent Events

Watson supports sending server-sent events. Refer to Test.ServerSentEvents for a sample implementation. The SendEvent method handles prepending data: and the following \n\n to your message.

Sending Events

static async Task SendEvents(HttpContextBase ctx)
{
  ctx.Response.StatusCode = 200;
  ctx.Response.ServerSentEvents = true;

  while (true)
  {
    string data = GetNextEvent(); // your implementation
    if (!String.IsNullOrEmpty(data))
    {
      await ctx.Response.SendEvent(data);
    }
    else
    {
      break;
    }
  }

  return;
}

HostBuilder

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. Special thanks to @sapurtcomputer30 for producing this fine feature!

Refer to Test.HostBuilder for a full sample implementation.

using WatsonWebserver.Extensions.HostBuilderExtension;

Webserver server = new HostBuilder("127.0.0.1", 8000, false, DefaultRoute)
  .MapStaticRoute(HttpMethod.GET, GetUrlsRoute, "/links")
  .MapStaticRoute(HttpMethod.POST, CheckLoginRoute, "/login")
  .MapStaticRoute(HttpMethod.POST, TestRoute, "/test")
  .Build();

server.Start();

Console.WriteLine("Server started");
Console.ReadKey();

static async Task DefaultRoute(HttpContextBase ctx) => 
    await ctx.Response.Send("Hello from default route!"); 

static async Task GetUrlsRoute(HttpContextBase ctx) => 
    await ctx.Response.Send("Here are your links!"); 

static async Task CheckLoginRoute(HttpContextBase ctx) => 
    await ctx.Response.Send("Checking your login!"); 

static async Task TestRoute(HttpContextBase ctx) => 
    await ctx.Response.Send("Hello from the test route!"); 

Accessing from Outside Localhost

Watson

When you configure Watson 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 exact DNS hostname upon which Watson should listen in the Server constructor
  • The HOST header on HTTP requests MUST match the supplied listener value (operating system limitation)
  • If you want to listen on more than one hostname or IP address, use * or +. You MUST run as administrator (operating system limitation)
  • If you want to use a port number less than 1024, you MUST run as administrator (operating system limitation)
  • Open a port on your firewall to permit traffic on the TCP port upon which Watson is listening
  • You may have to add URL ACLs, i.e. URL bindings, within the operating system using the netsh command:
    • Check for existing bindings using netsh http show urlacl
    • Add a binding using netsh http add urlacl url=http://[hostname]:[port]/ user=everyone listen=yes
    • Where hostname and port are the values you are using in the constructor
    • If you are using SSL, you will need to install the certificate in the certificate store and retrieve the thumbprint
    • Refer to https://github.com/jchristn/WatsonWebserver/wiki/Using-SSL-on-Windows for more information, or if you are using SSL
  • If you're still having problems, start a discussion here, and I will do my best to help and update the documentation.

Watson.Lite

When you configure Watson.Lite to listen on 127.0.0.1, it will only respond to requests received from within the local machine.

To configure access from other nodes outside of the local machine, use the following:

  • Specify the IP address of the network interface on which Watson.Lite should listen
  • If you want to listen on more than one network interface, use * or +. You MUST run as administrator (operating system limitation)
  • If you want to use a port number less than 1024, you MUST run as administrator (operating system limitation)
  • Open a port on your firewall to permit traffic on the TCP port upon which Watson is listening
  • If you are using SSL, you will need to have one of the following when instantiating:
    • The X509Certificate2 object
    • The filename and password to the certificate
  • If you're still having problems, start a discussion here, and I will do my best to help and update the documentation.

Running in Docker

Please refer to the Test.Docker project and the Docker.md file therein.

Version History

Refer to CHANGELOG.md for version history.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
.NET Core netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.1 is compatible. 
.NET Framework net462 is compatible.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 is compatible.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (10)

Showing the top 5 NuGet packages that depend on Watson:

Package Downloads
S3ServerInterface

Emulated Amazon Web Services (AWS) Simple Storage Service (S3) server-side interface.

Komodo.Server

Komodo is an information search, metadata, storage, and retrieval platform. Komodo.Server is a standalone RESTful server. Use Komodo.Daemon if you wish to integrate search directly within your application.

S3Server

Emulated Amazon Web Services (AWS) Simple Storage Service (S3) server-side interface.

RestDb

RestDb is a platform that enables a RESTful API interface in front of Microsoft SQL Server, MySQL, PostgreSQL, and Sqlite databases.

Kvpbase.StorageServer

Scalable, simple RESTful object storage platform.

GitHub repositories (1)

Showing the top 1 popular GitHub repositories that depend on Watson:

Repository Stars
rocksdanister/lively
Free and open-source software that allows users to set animated desktop wallpapers and screensavers powered by WinUI 3.
Version Downloads Last updated
6.3.2 206 12/13/2024
6.3.0 162 12/13/2024
6.2.3 986 11/8/2024
6.2.2 4,303 8/2/2024
6.2.1 369 7/14/2024
6.2.0 616 6/30/2024
6.1.10 1,018 5/21/2024
6.1.9 417 4/30/2024
6.1.8 918 3/28/2024
6.1.6 2,522 1/12/2024
6.1.5 688 12/18/2023
6.1.3 4,777 12/14/2023
6.1.2 679 11/27/2023
6.1.1 767 11/13/2023
6.0.3 551 11/12/2023
6.0.2 721 11/2/2023
6.0.1 590 11/2/2023
6.0.0 590 11/2/2023
5.1.3 2,828 10/17/2023
5.1.2 2,695 8/19/2023
5.1.1 13,119 7/5/2023
5.1.0 839 7/4/2023
5.0.3 6,671 5/18/2023
5.0.2 891 5/18/2023
5.0.1 2,046 3/26/2023
5.0.0 1,018 3/14/2023
4.3.8 1,801 2/23/2023
4.3.7 904 2/23/2023
4.3.6 3,016 1/27/2023
4.3.5 7,949 11/26/2022
4.3.4 25,286 10/12/2022
4.3.3 1,291 10/12/2022
4.3.2 10,615 8/16/2022
4.3.1 1,091 8/16/2022
4.3.0 1,934 7/28/2022
4.2.2.13 2,297 5/2/2022
4.2.2.12 1,079 5/2/2022
4.2.2.11 21,837 4/1/2022
4.2.2.10 1,634 4/1/2022
4.2.2.9 1,582 3/31/2022
4.2.2.8 1,902 3/30/2022
4.2.2.7 1,135 3/20/2022
4.2.2.6 1,381 2/7/2022
4.2.2.5 3,894 1/19/2022
4.2.2.4 7,637 11/29/2021
4.2.2.3 2,565 11/12/2021
4.2.2.2 26,736 11/1/2021
4.2.2 3,490 10/6/2021
4.2.0.2 22,877 7/7/2021
4.2.0.1 3,808 6/1/2021
4.2.0 1,071 6/1/2021
4.1.3 2,509 4/28/2021
4.1.2 3,646 3/9/2021
4.1.1 6,759 2/22/2021
4.1.0 2,178 2/7/2021
4.0.0.3 6,613 12/26/2020
4.0.0.2 1,579 11/18/2020
4.0.0.1 1,319 11/18/2020
3.3.0.4 2,078 11/15/2020
3.3.0.3 1,358 11/11/2020
3.3.0.2 2,105 10/22/2020
3.3.0.1 2,081 10/14/2020
3.3.0 1,263 10/14/2020
3.2.0 1,772 10/10/2020
3.1.0.2 5,754 8/30/2020
3.1.0.1 7,529 7/20/2020
3.1.0 1,257 7/19/2020
3.0.13 1,658 7/12/2020
3.0.12.1 3,634 6/29/2020
3.0.12 1,390 6/27/2020
3.0.11 6,619 6/2/2020
3.0.10 16,537 4/10/2020
3.0.9 33,731 2/15/2020
3.0.8 3,154 2/13/2020
3.0.7 9,189 1/26/2020
3.0.6.1 3,073 1/24/2020
3.0.6 3,780 1/21/2020
3.0.5 4,436 1/16/2020
3.0.4 27,147 10/2/2019
3.0.3 1,694 9/19/2019
3.0.2 1,320 9/19/2019
3.0.1 2,451 9/3/2019
2.1.7 1,490 8/23/2019
2.1.6 2,106 8/12/2019
2.1.5 1,455 8/3/2019
2.1.4 1,327 8/2/2019
2.1.3 1,361 8/1/2019
2.1.2 3,059 7/4/2019
2.1.1 2,318 6/23/2019
2.1.0 1,369 6/23/2019
2.0.5 1,690 6/20/2019
2.0.4 2,752 6/16/2019
2.0.3 1,378 6/13/2019
2.0.2 2,317 4/30/2019
1.6.1 1,450 4/22/2019
1.6.0 1,832 3/28/2019
1.5.2 1,378 3/27/2019
1.5.1 1,411 3/13/2019
1.4.0 1,485 2/26/2019
1.3.2 1,397 2/25/2019
1.3.1 1,504 2/4/2019
1.2.11 2,216 3/26/2018
1.2.10.1 1,880 2/1/2018
1.2.9 1,826 8/28/2017
1.2.8 1,661 8/28/2017
1.2.7 1,669 8/26/2017
1.2.6 1,690 8/15/2017
1.2.5 1,656 8/10/2017
1.2.4 1,762 6/23/2017
1.2.3 1,757 6/7/2017
1.2.2 1,724 6/3/2017
1.2.1 1,823 5/5/2017
1.2.0 1,741 4/27/2017
1.1.4 1,752 4/10/2017
1.1.3 1,728 4/7/2017
1.0.9 1,812 12/14/2016

Dependency update