I've been using Nim for several years now. I mostly use it for small utilities and experimentation on things like microcontrollers. However, most of my time goes into other projects, mostly web-related, and thus I've wanted to integrate Nim into some of my web endeavors. To start working on this goal, almost a month ago I sat down to begin writing a web framework for use within a group of sites that my friend and I work on. The decision to write my own framework was made after playing around with Prologue and realizing that it crashes with concurrent requests under ORC. I wanted to work on an existing framework to avoid further fragmentation of the ecosystem (already a huge issue), but I had no idea where to start with fixing the crash since it had to do with shared memory between threads. Eventually, I gave up and decided to write my own framework. If only I knew just how much else was not ready to be used.
I decided to build my framework on top of httpx since it had Windows support, and I didn't want to lock Windows users out. And so I began learning the httpx API. This was when I discovered that something very important was missing: request body streaming. This may not seem like a big issue to some people, but for me, this causes loads of issues. One of the sites I wanted to rewrite in my framework is a video site. It accepts file uploads which are streamed to disk, and when you watch videos, those are pulled from some storage source, and again streamed to the browser to watch them. With this in mind, the lack of streaming I/O was a massive problem. I come from Vert.x (Java/Kotlin) and Node.js, both non-blocking, and both supporting async streaming I/O. A missing feature like this is a complete deal-breaker for me.
After that, I began researching other HTTP servers for Nim. This made me realize how woefully unfit the current Nim ecosystem is for production HTTP applications, other than blocking APIs (mostly thanks to Mummy). Finally, I bit the bullet and decided to try my hand at implementing async streaming I/O in httpx, since I figured it would be the best way to introduce it to the ecosystem. Since that point, I've been working with @ringabout recently to bring async streaming I/O to httpx. I would like to thank him for working with me, and for his contributions to the Nim language and ecosystem in general. I've got a working implementation of streaming I/O now, but it's far from stable and I'm not entirely sure if it will be usable given its poor performance at the moment.
If/until async streaming I/O comes to httpx or some other HTTP server, there are only a handful of options available to developers who want to write a web application in Nim:
(Note that these were tested using ORC with threads on)
1. AsyncHttpServer
Built into the stdlib, you have it if you have Nim. I've used this server in production for very small projects that either don't get much traffic, or don't interact with the Internet at all. It has no support for request streaming, and response streaming uses FutureStream. FutureStream currently has no limit to its internal queue, which means that if you send too fast, you will use excess memory. It also doesn't perform well.
2. HttpBeast
High performance, powers Jester, but leaks memory and crashes when you overload it with connections. It also is fundamentally incompatible with I/O streaming since both requests and responses are written entirely to memory. It also has serious performance issues that cause it to grind to a halt if you overload it with concurrent large request bodies. This makes it pretty easy to launch DoS attacks against it. Its event loop also has issues with sleepAsync, though that's a minor problem.
3. httpx
Fork of HttpBeast, lower performance but with Windows support. Has most of the problems of HttpBeast. I fixed the crash issue in it, but the large request DoS issue still exists, and I'm not sure how to fix it. I'm currently working on streaming I/O, but it's slow. I'm not an I/O or async expert, although I've read large chunks of asyncdispatch's source code and have a fairly good grasp on it. Most of the memory issues are fixed with the streaming I/O implementation, but I've gotten it to leak in some edge cases that I'm not very qualified to diagnose.
4. Chronos
Everyone knows about Chronos. It has its own HTTP server which looked promising, but after some prodding I was able to get it to leak memory when you try to read in request bodies with the post proc (reads and parses form data, performs unnecessary copies). The memory there is never freed. It's also not particularly performant once you start trying to read requests. The biggest kicker for most people is the fact that it's incompatible with asyncdispatch. If it wasn't for the memory leaks, I'd be using this right now. It's also the only one that supports HTTPS.
5. Scorper
Scorper is built with Chronos and therefore is also incompatible with asyncdispatch. It implements its own HTTP server using Chronos' I/O facilities. Under ORC, it crashes with concurrent connections. That alone is enough to turn me away from it.
6. Mummy
I need streaming I/O for my applications, so I can't use Mummy. It has the same crashing issue as HttpBeast, though this can easily be fixed by checking if the newly returned file descriptor int is higher than the max file descriptors count when accepting a socket, and if so, ignore it. With that said, I can't see any major memory leaks. This is probably due to the fact that it limits the body size. However, I need to be able to read large files for file uploads. Without that, it's very limited and can't be used for any sort of website with file uploads, static files, media streaming, etc. It really makes Mummy's range of use-cases very narrow.
With all of this said, it's no wonder that nobody is building large web projects with Nim. I understand that most users are interested in building interesting toys, but as someone who wants to use Nim in production for real, working web services, it is incredibly frustrating. I think a lot of it has to do with the poor async implementation and the lack of consensus on that. What we have now, asyncdispatch, is slow and has very few useful tools outside of bare I/O and timers. For example, FutureStream is nearly impossible to use safely because it doesn't enforce a maximum queue size. I had to implement my own async stream type for this purpose while working on httpx. On the other hand, Chronos is much better but is barely documented and is incompatible with everything else. I've read a bit about CPS but I'm not too familiar with the paradigm and therefore can't build anything useful with it at the moment.
Most people's solution to the HTTP server problem seems to be "put it behind Nginx." Not only is this ignoring the problem (why should your application server NEED to be behind a reverse proxy?), but it's also admitting that none of them are production-ready. Lack of a production-ready HTTP server makes it hard for a very significant portion of programmers to build things they need with Nim. I would like more people to use Nim, and at the very least use it personally, but every single time I've tried to implement some web application, I've been hit with roadblock after roadblock. Asyncnet isn't particularly amazing either, but it's more generic and doesn't have as many places for it to go wrong in.
This feeds back into my general frustration over the fact that most people are building toys with Nim, rather than working on building production-ready libraries and applications. This problem can only compound because it drives away other people from being able to build real applications. Keep in mind that while I'm mostly referring to web applications, I'm sure similar applies to most other areas.
I'm not here to complain and not do anything, but I feel rather alone in this since most people are content to show off their toys and try to get others hyped up about the cool features of the language. I enjoy cool language features and cool projects, but at the end of the day, I want to use those features to make something meaningful that I can run in a stable way.
There are some improvements in the overall language and I can't complain too much about that, other than ORC bugs which are being continuously fixed. This is primarily an ecosystem problem, and I keep observing most activity being from novices or hobbyists who aren't very interested in building pieces of infrastructure. I understand, I don't like building boring infrastructure libraries either, but things like HTTP servers are important and so are good async libraries. Both of these are notably missing from the ecosystem.
Hopefully some people will find this helpful and it will launch some discussion. I'm not here to cause an argument or whatever else, but I am frankly very frustrated and pessimistic about adoption of the language when something as ubiquitous as web development is severely unstable.
Well as usual "Be the change you want to see" I do work at work over the day, I usually don't have the juice left in me to do "production-ready" code on my time off. I'd rather spend my hobby time doing something I enjoy. Nim is a wonderful langauge, and it's fun to play around with, makes most stuff easy and the hard things possible. It's the perfect language for my usecase, might not be for yours.
Of course I would not complain if we got production ready libraries, I would very much applaud it, but I don't know, I can't say that I would complain that people don't do it, as I don't myself either.
This was when I discovered that something very important was missing: request body streaming. This may not seem like a big issue to some people, but for me, this causes loads of issues.
You're not the first one complaining about it and you're right that it's "more common" of a need than others give it credit for.
What I don't understand is why it's a hard problem. Sure, asynchttpserver/others doesn't support http body streaming, so patch it and make it take yet another callback so that the body can be emitted incrementally. Why is it hard? :-)
Very interesting topic. I was sure that all servers from the techempower was at least a bit tested to hold big amount of connections.
It would be good to have at least small description how to test it.
Another question is about streaming: are there big difference with websocket? The question is because I saw that mummy is fine with ws, and if sse/chunks are pretty near - is it big problem?
The interesting thing is microasynchttpserver doesn't do anything substantial itself; it just reads the header data from an AsyncSocket, passes the header data to picohttpparser, and schedules the application callback with the data, to be completed via asyncdispatch. So you're essentially benchmarking picohttpparser+asyncdispatch+some minor language overhead at 200k RPS. picohttpparser was the h2o webserver's HTTP1 parser, and h2o is known to have high performance.
It would be interesting to see if the other webservers mentioned here (GuildenStern, HttpBeast, Chronos, Mummy, etc.) get a performance boost when using picohttpparser instead of their HTTP parsers.
Makes sense. The lack of non-experimental compiler supported string views makes it really easy to write string parsing code that makes unnecessary temporary copies. For instance, this will copy in some cases:
var readOnlyObject = ObjectType(readOnlyString: “foobar”)
…
let readOnlyString = readOnlyObject.readOnlyString
Yeah, it's a real problem. Basically anywhere I touch strings I get poor performance.
Unrelated to that, but I'd like to hear your thoughts (and anyone else's), what do you think of having HTTP server context instances be thread-local for multithreading purposes? You'd need to set up a server context instance per thread, but data being thread-local would be pretty clear. I've been burned in the past with shared data between threads, and I think the only real solution is to use an event bus to implement the actor model. Everything else seems to run the risk of segfaulting.
I've been working on a web framework called KommandKit the past few days and right now I have a shared server context between threads, but the actual HTTP servers run per-thread. I've had some very confusing segfaults even with that, so I'm thinking I'll need to switch it over to being entirely thread-local. I was already planning on implementing an event bus similar to Vert.x's.
For instance, this will copy in some cases ...
It doesn't with copy arc/orc. And frankly, it's not all that hard to write fast parsers in Nim. It helps if you benchmark but if you don't, how do you end up with "fast" HTTP servers anyway?
As @ingo said, I'm thinking about having an event bus to implement the actor model.
I take a lot of inspiration from Vert.x from the JVM world, which is what I use and have used in many projects successfully. There are mini-applications called Verticles in that framework which all have access to a central application instance from which they can subscribe to event bus channels and send messages. That's the ideal for me. It solves the multithreading problem and makes it very easy to write distributed applications.