This assembly provides a flexible, high-performance HTTP server (webserver).
The main class to consider is HttpServer. Instantiate this class and call HttpServer.StartListening(bool) to try it out:
var server = new HttpServer(80);
server.StartListening(blocking: true);
The number 80
in this example is the port number, and “blocking” means that the program will keep
running, allowing you to try out the webserver. Now direct your browser to http://localhost
and you
will see a response. For obvious reasons, that response is a 404 Not Found error, since we haven’t taught the
server anything to do yet.
What the server does is controlled by “handlers”. A handler is a function that accepts an HttpRequest object — which contains all the information about the request received from the browser —
and returns an HttpResponse object. That response object tells the server everything it needs to
send to the browser, whether it be a webpage, a file, a redirect, a WebSocket connection or anything else.
Let’s try something simple and just return some plain text:
var server = new HttpServer(80);
server.Handler = req => HttpResponse.PlainText("Hello, World!");
server.StartListening(true);
(From now on, we will omit the first and third line and concentrate on just the handlers.) Now if you reload
http://localhost
in your browser, you will see the text “Hello, World!” rendered as a plain-text file.
Your handler worked!
However, if you go to http://localhost/foo
or http://localhost/?bar=baz
, you will also see the
“Hello, World!” text. The handler we defined simply ignores the URL and returns the plain text no matter what.
In most cases, this is not what we want, so we need something that resolves URLs and calls different handlers
depending on the URL.
This is what UrlResolver is for. You instantiate it, give it a bunch of URL fragments to work
with, and a handler for each:
var resolver = new UrlResolver(
new UrlMapping(path: "/foo", handler: req => HttpResponse.PlainText("The /foo URL returns plain text.")),
new UrlMapping(path: "/bar", handler: req => HttpResponse.Redirect("/baz")),
new UrlMapping(path: "/baz", handler: req => HttpResponse.PlainText("The /bar URL redirects to /baz. Did you notice the URL in the location bar change?")),
new UrlMapping(handler: req => HttpResponse.Html("<ul><li><a href='/foo'>Go to /foo.</a></li><li><a href='/bar'>Go to /bar.</a></li></ul>"))
);
server.Handler = resolver.Handle;
Now try these new URLs in your browser and you will see they behave exactly as you’d expect. You’ll also note
that http://localhost/
shows the HTML from the last mapping.
Now try something like http://localhost/foo/quux
. You will find that the handler for /foo
still
triggers and the plain-text response for /foo
shows. This is because, by default, paths in a UrlMapping are really path prefixes: every URL path that begins with /foo/
matches, as well as
/foo
itself (but not, for example, /foobar
). This makes it super easy to process the URL in
stages. For example, you could have a wiki under http://localhost/wiki/
and something else entirely
under http://localhost/blog/
and they can each have their sub-URLs.
It’s super easy to process the rest of the URL because the request object exposes it conveniently. Let’s
change the handlers so that they display the rest of the URL:
new UrlMapping(path: "/foo", handler: req => HttpResponse.PlainText("The additional path after /foo is: " + req.Url.Path)),
new UrlMapping(path: "/bar", handler: req => HttpResponse.Redirect("/baz")),
new UrlMapping(path: "/baz", handler: req => HttpResponse.PlainText("The additional path after /baz is: " + req.Url.Path)),
Now if you go to http://localhost/foo/quux
, you will see that HttpUrl.Path contains only
/quux
— the part that has already been resolved has been removed.
If you go to http://localhost/bar/quux
, of course it will still redirect to /baz
without the
extra path. But there’s bigger problem with the redirect: imagine I nested your UrlResolver
inside an
outer one so that I can put everything under a /examples
URL, i.e. /examples/foo
,
/examples/bar
and /examples/baz
. The above code will still redirect to /baz
instead of
/examples/baz
, which would be wrong. HttpUrl provides some powerful extension methods for
correct manipulation of URLs. Consider changing the second handler to something like this:
new UrlMapping(path: "/bar", handler: req => HttpResponse.Redirect(req.Url.WithPathParent().WithPath("/baz"))),
Now the URL will be changed correctly no matter how many URL resolvers have been involved. Similar extension
methods exist to change the query parameters, and all of the above can be done with subdomains instead of URL
paths as well.
The HttpUrl object also provides convenient access to data submitted through an HTML form and
files uploaded via an <input type='file'>
element.
You can also use a redirect in the main handler to ensure that your users always use HTTPS (encrypted
traffic):
var options = new HttpServerOptions();
options.AddEndpoint("http", null, 80, secure: false);
options.AddEndpoint("https", null, 443, secure: true);
options.CertificatePath = @"..."; // path to HTTPS certificate file
var server = new HttpServer(options);
var resolver = new UrlResolver(
// [...]
);
server.Handler = req => req.Url.Https ? resolver.Handle(req) : HttpResponse.Redirect(req.Url.WithHttps(true));
Finally, go to http://localhost/blah
. You will notice that the last handler — the one that returns the
HTML — still triggers for any URL that isn’t already covered by a more specific handler. We usually don’t want
this. We can limit a UrlMapping
by setting specificPath
to true
:
new UrlMapping(path: "/", specificPath: true, handler: req => HttpResponse.Html("<ul><li><a href='/foo'>Go to /foo.</a></li><li><a href='/bar'>Go to /bar.</a></li></ul>"))
Now the HTML is returned only at the root URL, while all other URLs go back to returning 404 errors.
Generating the page HTML as a single huge string is tedious, error-prone, inextensible and has poor
performance. For this reason, HttpResponse also accepts a Tag object
from the related RT.TagSoup
library. This allows you to write HTML in very natural C# syntax:
new UrlMapping(path: "/", specificPath: true, handler: req => HttpResponse.Html(
new UL(
new LI(new A { href = req.Url.WithPath("/foo").ToHref() }._("Go to /foo.")),
new LI(new A { href = req.Url.WithPath("/bar").ToHref() }._("Go to /bar.")))))
The short time necessary to get used to this syntax is more than worth it once you consider the great
advantages of TagSoup:
You can fluidly and intuitively insert variables, expressions and method calls and you never need to
worry about HTML escaping. Everything you pass into a tag or attribute value is automatically escaped
correctly.
All of the HTML is evaluated as lazily as possible. To demonstrate this, let’s output a filtered list:
new UrlMapping(path: "/", specificPath: true, handler: req => HttpResponse.Html(
new UL(myList.Where(item => someCondition(item)).Select(item => new LI(item)))))
Due to the lazy nature of .Where()
and .Select()
, the list is processed and the HTML
generated incrementally. The server never generates a single humongous string in memory containing the
entire page; it sends the HTML to the browser as efficiently as possible.
The server starts responding to the browser while the page is still processing, thus giving a
subjective impression of improved performance.
-
You can write your own methods that generate HTML incrementally by declaring them as
IEnumerable<object>
and returning pieces of HTML using yield return
. Calling such a
method inside a TagSoup tree causes that method to be evaluated just as lazily as the above. -
If the HTTP connection is interrupted (for example because the user pressed Escape in the browser),
HttpServer also stops generating the HTML. Therefore, any expensive operation (such as iterating over a
large database result set) that generates HTML on the fly, is automatically halted.
Apart from plain text and HTML (and other text-based items such as CSS and JavaScript), HttpServer can deal
efficiently with files using HttpResponse.File(string, string, int?, DateTime?). For example:
new UrlMapping(path: "/image", handler: req => HttpResponse.File(@"C:\images\image.jpg", "image/jpeg", ifModifiedSince: req.Headers.IfModifiedSince)),
new UrlMapping(path: "/zipfile", handler: req => HttpResponse.File(@"C:\zipfiles\zipfile.zip"))
The first example demonstrates how you specify the Content-Type header so that the browser knows it is
receiving an image. HttpServer automatically sends headers that allow the browser to cache the file and later
ask whether the file has changed. If the file has not changed, HttpServer can respond with a “304 Not
Modified” response instead of sending the file again.
In the second example, we omit the Content-Type, which defaults to "application/octet-stream"
for
binary files and "text/plain; charset=utf-8"
for text files. In our case, the file is a ZIP file which
could be large, maybe gigabytes. HttpServer will correctly handle large files and not attempt to load the
entire file into memory. It correctly supports download resuming and segmented downloading, allowing the use
of download managers to download large files. You need to do nothing special on your end to enable all of
these features.
With FileSystemHandler you can also serve entire directories from the local file system and have
HttpServer automatically resolve paths to folders. The following example will provide full access to every
file under C:\DataFiles
, including listing the contents of directories. To forbid the listing of
directory contents, pass an extra FileSystemOptions object to the constructor.
new UrlMapping(path: "/files", handler: new FileSystemHandler(@"C:\DataFiles").Handle)
Finally, a response to a request can be a WebSocket connection. Implement the abstract WebSocket
class to implement your WebSocket server behavior, then return a response as follows, replacing
MyWebSocket
with the name of your derived type:
new UrlMapping(path: "/socket", handler: req => HttpResponse.WebSocket(new MyWebSocket()))
If you wish to customize the look of error pages consistently across your site, use HttpServer.ErrorHandler and/or HttpServer.ResponseExceptionHandler. You can also use
these to log error conditions to a database and/or send e-mails to administrators.
AjaxHandler<TApi> provides a flexible and easy-to-use means of providing methods that can be
invoked by client-side JavaScript code via AJAX. Simply declare a class containing the permissible AJAX calls
as methods decorated with AjaxMethodAttribute and use that class for TApi
.
The Session class provides a means for user sessions, i.e. server-side data that persists
between requests (by using a cookie, which the server takes care of fully automatically). See Session for details of how this is used. FileSession is a concrete implementation of
the abstract Session class which stores the session data in local files, but you can also derive
your own to store the session in a database or some other way.
Once you have sessions, you can use Authenticator for a rudimentary login system. Again, FileAuthenticator is an example derived class which takes the username/password information from a
local file, but you can derive your own to retrieve the information from a database or some other way.
Types in this namespace
|
Specifies that AjaxHandler<TApi> should use this method to convert serialized values from one type
to another before calling an AJAX method. |
| Indicates that an error occurred during processing of an AJAX request. |
|
Provides a means to call methods decorated with an AjaxMethodAttribute via AJAX, using JSON as the
data interchange format. |
| Specifies the exception behaviour for AjaxHandler<TApi>. |
|
Indicates that, during processing of an AJAX request, the data containing the method parameters could not be
parsed. |
|
Indicates that, during processing of an AJAX request, the value for a method parameter could not be deseralized. |
| Indicates that the value returned by an AJAX method could not be serialized. |
| Specifies that a method can be called via AJAX using an AjaxHandler<TApi>. |
| Indicates that an AJAX method declaration is invalid. |
| Indicates that an AJAX method was requested that does not exist. |
|
Indicates that, during processing of an AJAX request, a non-optional method parameter was missing from the JSON. |
| Specifies that a parameter receives the HTTP request object. To be used on methods with AjaxMethodAttribute. |
| Provides functionality for a login system. |
|
Encapsulates an HTTP cookie.
|
|
Controls which style of directory listing should be used by FileSystemHandler to list the contents
of directories. |
| Provides functionality for a login system. |
|
Provides functionality to track user sessions in HTTP requests using cookies and to store such user sessions in
the local file system. |
|
Provides a handler for HttpServer that can return files and list the contents of directories on the
local file system. |
| Contains configuration settings for a FileSystemHandler. |
| Identifies a type of response generated by FileSystemHandler. |
|
Represents a file upload contained in an HTTP request with a body.
|
| Contains values for the supported values of the Accept-Ranges HTTP request header. |
|
Encapsulates the possible values of the Cache-Control HTTP request or response header.
|
|
Contains values for the Cache-Control HTTP request or response header. None of these currently have any effect. |
| Contains values for the supported values of the Connection HTTP request or response header. |
|
Encapsulates the possible values of the Content-Disposition HTTP response header.
|
|
Contains values for the supported values of the Content-Disposition HTTP entity header, minus the filename. |
|
Contains values for the supported values of the Accept-Encoding HTTP request header and the Content-Encoding HTTP
response header. |
|
Encapsulates the possible values of the Content-Range HTTP response header.
|
|
Specifies possible values for the SameSite option on HTTP cookies. |
| Contains endpoint information that specifies where the HTTP server will listen for incoming connections. |
|
Implements parse routines for the HttpEnums. These routines are preferred to Enum.Parse(Type, string) because no reflection is involved. |
| Provides an exception that carries an HTTP status code. |
| Contains helper methods to deal with various things in HTTP. |
| Contains values for the supported HTTP methods. |
| Provides an exception that indicates that a resource was not found. |
|
Contains values for the supported values of the Content-Type HTTP request header when used in HTTP POST/PUT/PATCH
requests. |
| Contains values for the supported HTTP protocol versions. |
|
Encapsulates one of the ranges specified in a Range HTTP request header.
|
|
Encapsulates an HTTP request, including its method, URL and headers. HttpServer generates this when it receives an
HTTP request and passes it to the relevant request handler.
|
|
Encapsulates all supported HTTP request headers. These will be set by the server when it receives the request. |
|
Indicates that an error has occurred while parsing a request. This usually indicates that the request was
malformed in some way. |
|
Encapsulates a response to an HTTP request. Concrete classes implementing this are HttpResponseContent and HttpResponseWebSocket. |
|
Encapsulates an HTTP response, to be sent by HttpServer to the HTTP client that sent the original
request. A request handler must return an HttpResponse object to the HttpServer when handling a
request. |
|
Encapsulates all supported HTTP response headers. A request handler can set these appropriately to cause the
server to emit the required headers. See Remarks for a list of headers which are set by default. |
|
Encapsulates a response to an HTTP request that indicates to the client that they should switch to the WebSocket
protocol. |
| Provides an HTTP server. |
| Encapsulates information about a server certificate. |
| Contains configuration settings for an HttpServer. |
| Contains definitions for all the HTTP status codes defined in HTTP/1.1. |
| Extension methods for HttpStatusCode. |
| Contains values for the supported values of the Transfer-Encoding HTTP response header. |
| Encapsulates information about a URL that identifies a resource on an HTTP server. |
| Supports base functionality of an HTTP URL. |
| Provides extension functionality on the IHttpUrl type. |
|
Provides functionality required by Session.EnableAutomatic<TSession>(HttpRequest, Func<TSession, HttpResponse>). |
|
Encapsulates a collection that maps keys to collections of values.
Provides the ability to cheaply make the collection fully read-only.
Provides a sort of auto-vivification for convenience.
|
| Extension methods for NameValuesCollection<TValue>. |
| Specifies possible protocols to hook to in a UrlHook. |
|
Encapsulates a value with a Q rating, where Q is between 0 and 1. Provides a comparer such
that the values with Q = 1 are the smallest.
|
|
Provides functionality to track user sessions in HTTP requests using cookies. See remarks for usage guidelines. |
| Specifies an action to perform on the session object when the request handler returns. |
| Provides extension methods for HTTP session handling. |
|
Provides functionality to track a session ID in HTTP requests using cookies. |
|
Encapsulates properties of a URL that can be mapped to a request handler using UrlMapping. This
class is immutable. |
|
Encapsulates a mapping from URLs to a request handler. Add instances of this class to a UrlResolver
to map URL paths or domains to request handlers. This class is immutable. |
|
Allows you to specify mappings that map URLs to HTTP request handlers. |
| Contains possible values for the HttpResponseContent.UseGzip option. |
|
Represents a collection of values associated with a single key inside a NameValuesCollection<TValue>.
|
|
Provides the base class for your application to implement a WebSocket connection. Derive from this class and then
pass an instance of your derived class to HttpResponse.WebSocket(WebSocket, string, HttpResponseHeaders). |
| Encapsulates a string value that can additionally be either weak or not. |