With so many Web servers already on the market, why write your own? That’s a no-answer question for real hackers. But even if you think you’re a nerd, don’t be intimidated by the title — modern languages and frameworks have provided us with a very powerful infrastructure that allows us to build a basic Web server with very little code. In fact, the first version of the program that we’re going to cover is fully packaged and provides static file handling, and the core code is only about 70 lines. If you want a static file server, you can get down to 40 lines of code, and it’s very easy to understand.
This series of articles was largely inspired by Web Servers Succinctly from the Syncfusion Succinctly ebook series. But I wasn’t happy with some of the things in the original book, so I rewrote the article as I understood it and wrote the code myself from scratch. Some parts of the book, such as the threading model and the chapters on deploying HTTPS, were left out because I didn’t think they were particularly important for understanding how Web servers work or for ensuring continuity of code. However, the content provided by the original book is also a valuable resource for reference. If readers are interested in it and their English level is reasonable, they are recommended to read the original book by themselves from the links above.
This series will cover the following:
- Create a basic Web server
- Create a popular Middleware architecture for modern Web frameworks
- Implement Routing
- Session
- Added View Engine support
- Realize user information and user authentication
I believe that the above content is enough to give the reader a fairly deep understanding of setting up a Web server. And I can assure you, it’s not hard! Of course, this doesn’t mean that creating a production-level Web server is a breeze, because there are a lot of relatively boring, but thought-out details that need to be implemented. This article is intended to illustrate the implementation principles, so it simplifies the details that are not relevant to the core principles, as readers will see in subsequent code. I don’t want the reader to underestimate the hard work required to implement a Web server because the sample code is not complex.
The sample code of this article has also been put on Github, and the code associated with each article is placed in a separate branch for readers’ reference. Therefore, to get the sample code for this article, use the following command:
git clone https://github.com/shuhari/web-server-succinctly-example.git
git checkout -b 01-basic-server origin/01-basic-server
Copy the code
I assume that the readers of this article: * have some experience with the C# programming language and have no difficulty reading programs written in BCL; * Basic understanding of HTTP protocol. You should not need to be explained to what Request/Response/Session is.
Nothing else. Experience with ASP.NET/MVC is helpful, but not necessary, to better understand the text.
Talk is cheap, show me the code
I don’t like to start with long theories, and the sample code here is simple enough, so let’s go straight to the code. In addition, I personally prefer an interface-oriented programming style, so in this and subsequent articles, unless otherwise noted, I will show the interface first and then move on to concrete implementations. Of course, if you don’t like this sequence, you can ignore my introduction and go straight to the solution.
class Program
{
private const int concurrentCount = 20;
private const string serverUrl = "http://localhost:9000/";
public static void Main(string[] args)
{
var server = new WebServer(concurrentCount);
server.Bind(serverUrl);
server.Start();
Console.WriteLine($"Web server started at {serverUrl}. Press any key to exist...");
Console.ReadKey();
}
}
Copy the code
This is a typical Console application. As an example, we declare that the server can accept up to 20 concurrent connections and that the server listens to a local address on port 9000 (it is believed that the typical Web port 80 on most machines is already occupied by EITHER IIS or Apache/Nginx). There is nothing to explain in the code, so let’s look at the implementation of WebServer.
public class WebServer { private readonly Semaphore _sem; private readonly HttpListener _listener; public WebServer(int concurrentCount) { _sem = new Semaphore(concurrentCount, concurrentCount); _listener = new HttpListener(); } public void Bind(string url) { _listener.Prefixes.Add(url); } public void Start() { _listener.Start(); Task.Run(async () => { while (true) { _sem.WaitOne(); var context = await _listener.GetContextAsync(); _sem.Release(); HandleRequest(context); }}); } private void HandleRequest(HttpListenerContext context) { var request = context.Request; var response = context.Response; var urlPath = request.Url.LocalPath.TrimStart('/'); Console.WriteLine($"url path={urlPath}"); try { string filePath = Path.Combine("files", urlPath); byte[] data = File.ReadAllBytes(filePath); response.ContentType = "text/html"; response.ContentLength64 = data.Length; response.ContentEncoding = Encoding.UTF8; response.StatusCode = 200; response.OutputStream.Write(data, 0, data.Length); response.OutputStream.Close(); } catch (Exception ex) { Console.WriteLine(ex); Console.WriteLine(ex.StackTrace); }}}Copy the code
For brevity, I won’t show declarations of using namespaces in the article code — I think you’re good enough to recognize where they’re coming from. The only thing worth noting is HttpListener, a class from the System.Net namespace that does most of the low-level protocol handling for us and makes it very easy to create a Web Server. It would be perfectly feasible to create a server from the Socket level (there are examples on the web), but the code would need to be at least a few hundred lines.
The code here is a bit larger, but the core logic is easy to read:
- A Semaphore is defined to control the acceptable number of concurrent requests.
- Create a listener (HttpListener) to listen for requests;
- Define the address Prefixes that the server listens on;
- Define a background task that constantly listens for incoming requests. When the request is received, the listener returns an HttpListenerContext. (this context is identical to the HttpContext or HttpContextBase we use in ASP.NET applications. Definitions are almost identical);
- We can get the Request information from context.Request and write a Response to context.Response, just like an ASP.NET application.
We haven’t implemented real dynamic server functionality yet (that will be the task for a future article). But to see some results, let’s take the easy way out first: write some.html files, put them in the directory where you want to execute them, and return them directly. If you can’t find the file, the program will make an error, but it’s easy to fix, so you can practice on your own if you’re interested.
The easiest way to add static files is to create.html files directly in the project and change the compilation mode to Copy to Output directory if newer (as shown in the figure below). The examples in this article are all done under JetBrains Rider. Most users should be using Visual Studio, and they work similarly, so I won’t go through them separately.
Start the program, visit http://localhost:9000/index.html in the browser, you’ll see we add the HTML file content. Our first server program executed successfully!
As you can probably tell, this is basically a minimalist static file server. It can also, with minor modifications, return content other than.html, or files in other directories, and is a great gadget for sending/receiving files between servers. Of course, our goal is a truly dynamic server, not just a server static file, but through this program, you have a basic understanding of how a server works. In the next article, we will rebuild the program into a dynamically functional Web services framework.
series
- Write your own Web server (index) in C#
- Write a Web server in C#, part 1 – basics
- Write a Web server in C#, part 2 – middleware
- Write a Web server in C#, part 3 – routing
- Write your own Web server in C#, part 4 – Session
- Write your own Web server in C#, part 5 – view engine
- Writing your own Web server in C#, part 6 – user authentication