Over the past several days I've been working on an HTTP framework called 
[KommandKit](https://git.termer.net/termer/KommandKit). It's built on top of 
`microasynchttpserver` by @pwernersbach which provides an 
`asynchttpserver`-compatible server but with low-level control over streams and 
much, much higher performance thanks to a Nim wrapper over `picohttpparser`. My 
framework is built on the following principles:

>   * Performance
>   * Multithreading (and by extension, thread-safety)
>   * Security
> 


I've gotten it to a usable point right now, and I think I could use it in 
production without any issues (I've been doing performance and load testing 
consistently during development), but it's missing a few key features, which 
I'd like to discuss and get some feedback on.

The framework is built primarily for HTTP servers, but my intention is to 
provide a framework for safe cross-thread communication via an event bus that 
helps implement the actor model of communication. I take a lot of inspiration 
from [Vert.x](https://vertx.io/) in the JVM world, which uses a single 
application instance shared between threads that provides an event bus for 
communication. In Vert.x, there is the concept of a Verticle, which is 
effectively an actor. This is what I aim to do with KommandKit. However, as Nim 
is not an object-oriented language, I'm not sure what construct I should use to 
represent an actor. If anyone has any suggestions, I'd appreciate them. My 
current idea is creating a concept for an actor and making constructing them 
easier with macros, but I'm not sure.

Currently, to scale the HTTP server over CPU cores, you create a thread for 
each CPU core/thread. Those threads are passed an instance of the global 
KommandKit object which they can interact with. Since I haven't implemented the 
event bus yet, the shared object doesn't do anything right now. I'm currently 
waiting to implement that until I cam come up with how the implementation 
should look. This is something I'd like to get some feedback on. Right now I'm 
thinking there could be an outgoing `Channel` set up for each actor that's 
registered so that it can send messages to the event bus, and then any channels 
that actors subscribe to will create a corresponding incoming Channel for the 
actor that the event bus writes to. The issue here is I'm not sure how the 
actor should read from the channel. Since async work is going on in the thread 
where the actor is living, the blocking channel read procs cannot be used. 
Should I just poll? That introduces latency, and CPU usage if I'm polling for 
it too often. Perhaps there's a better construct to use, but I'm not sure. I 
may also have to open another outgoing `Channel` since messages will support 
replies.

On the HTTP side, I'm pretty happy with what I have right now. A standard 
router and middleware system is used, and everything is done with streams. I'd 
like to use Status' `faststreams` library in the future for various parts of 
it, but right now, dealing with reads and writes on the client is usable enough 
with helper procs on top of it.

Since the HTTP server is dealing with HTTP-native errors such as request 
validation errors, I decided that following in Vert.x's footsteps and allowing 
developers to assign handlers for HTTP error response codes was the best way to 
do error handling. From a normal request handler, you can return with an HTTP 
status, and optionally an `Exception` to go along with it, and then it will run 
the handler registered for that status, or a default one, making the optional 
`Exception` available. For normal exceptions caught within handlers, the server 
will invoke the HTTP 500 error handler, or the default one if no 500-specific 
handler is set up. This makes handling errors incredibly easy and idiomatic for 
HTTP. However, if anyone has suggestions, I'd be happy to hear them.

Having direct access to client read and write streams means the server can do 
everything from accepting uploads to sending files without memory issues. Right 
now things are a bit verbose with the API (you end routes with lines such as 
`return await ctx.response.endWith("Hello world")` and similar), but I'm 
working on hiding the complexity more and making it more approachable. With 
that said, I'm trying to keep the API as magic-free as possible to avoid 
confusion.

Let me know what you all think, and if you have any suggestions for the things 
I mentioned above, I'd be happy to hear them. My goal is to built a 
production-ready web framework that is performant and safe, and I think I'm on 
the right track. You can check out what the API looks at from the [HTTP server 
test](https://git.termer.net/termer/KommandKit/src/branch/master/tests/t_httpserver.nim)
 and 
[benchmark](https://git.termer.net/termer/KommandKit/src/branch/master/tests/benchmark.nim),
 although I'm going to be writing a lot more documentation once the remaining 
parts of the design are solidified. I will also open a website with more 
documentation and tutorials to make web development in Nim more approachable.

Thanks in advance for your feedback.

Reply via email to