After a long break from working on my hobby operating system, I finally got back into it and finished a very important milestone: a working web server.
Networking was always integral to my hobby project. The first goal was getting the basic networking stack working: Ethernet, IP, ARP, UDP, TCP, DHCP and DNS. Besides TCP this was rather straightforward, but when moving onto HTTP things broke.
Networking stack code
This led to my first break from the project, but also left a nagging thought in my mind, wanting to make it work. I finally sat down and started debugging.
I eventually found the culprit after hours of dissecting my own code, the problem was a broken implementation of the terminal buffer, overwriting a lock in another process… fun. Additionally, the E1000 network driver did not correctly handle incoming packets, which I finally got working by handling bursts of packets.
After getting an HTML page returned from the web engine I started noticing lots of performance errors and hangs from TCP, mainly because quickly refreshing the browser led to a spam of RST packets which were not handled correctly.
After a few hours of tinkering I finally got the RST packets working and the network stack is now able to handle a packet spam from the browser.
Keeping with the spirit of this hobby OS I want to write everything from scratch, luckily I already had implemented a pretty complete HTTP parser for my other project c-web-modules. So I extracted the HTTP parser as a standalone library and ported it to my OS.
After the HTTP engine was done I moved onto the web engine, focusing on something small, rather than big and fancy. Mainly routing was important and adding route handlers. Allowing the user to specify a route, method and lambda function handler.
/* Simple routing */
engine.get("/", [](const http::Request& req, http::Response& res) {
(void)req;
res.setBody("hello world!");
....
});It’s a tiny example, but it mirrors how a lot of modern C++ and web frameworks think about routing: match a path + method, call a handler, build a response.
#lang:plaintext
[ Browser ]
|
v
[ Web Server (userspace):
WebEngine | HTTPEngine | FileRepository ]
|
v
[ Network stack:
TCP/UDP | IP | ARP | DHCP | DNS | Ethernet(E1000) ]The last step was updating the userspace program with the new HTTP and Web engine. Finally I added a way to serve files using a FileRepository which supports caching. Now I can edit the files inside the operating system and then serve them with the web server.
#lang:cpp
WebEngine webEngine(80, 16);
web::FileRepository fileRepo;
/* Simple static pages */
webEngine.get("/home", [&fileRepo](const http::Request& req, http::Response& res) {
(void)req;
res.sendFile(fileRepo, "/web/index.htm");
});
webEngine.get("/about", [&fileRepo](const http::Request& req, http::Response& res) {
(void)req;
res.sendFile(fileRepo, "/web/about.htm");
});
webEngine.get("/status", [&fileRepo](const http::Request& req, http::Response& res) {
(void)req;
res.sendFile(fileRepo, "/web/status.htm");
});
webEngine.run();
The next thing on the TODO list will be to add a more fancy UI for the webserver and a way to close it gracefully.
(Graceful shutdown is one of those “boring” features you don’t miss… until the first time you corrupt something on exit.)
When this is finished, the biggest task so far will begin… the web browser.