Hi, I just wanted to mention that I have just released a very early version of Rosencrantz.
This is a DSL to write web servers, inspired by Spray and its successor Akka HTTP. It sits on top of asynchttpserver and provides a composable way to write HTTP handlers.
The documentation is here, please let me know any comment you may have. A small example, to give an idea of the feel, may look like this:
import asyncdispatch, asynchttpserver, rosencrantz
let handler = get[
path("/api/status")[
ok(getStatus())
] ~
pathChunk("/api/message")[
accept("application/json")[
intSegment(proc(id: int): auto =
let message = getMessageById(id)
ok(message)
)
]
]
] ~ post [
path("/api/new-message")[
jsonBody(proc(msg: Message): auto =
let
id = generateId()
saved = saveMessage(id, msg)
if saved: ok(id)
else: complete(Http500, "save failed")
)
]
]
let server = newAsyncHttpServer()
waitFor server.serve(Port(8080), handler)
@andrea, nice DSL, and the name too! Now we need guildenstern DSL for databases, like slick :-)
What about the plans for static files support?
I have to add them. It is not difficult, but since I had something consistent I wanted to put that out.
Now, I first have to add some tests, then I will start to implement more handlers, in particular those for static files.
PS I was thinking to save the name guildenstern for something about websockets, if anyone wants to start such a project... ;-)
Interesting DSL
I tried to compile your example code given in message 1 and I got a compile error:
example.nim(5, 8) Error: undeclared identifier: 'getStatus'
Yeah, it written in the docs but I should make it more clear: "and we assume we have functions such as getMessageById that perform the actual business logic".
The example as is will not compile: getStatus, getMessageById, generateId and saveMessage are hypothetical functions that perform the actual business logic - say, interacting with a database
@andrea Great job on Rosencrantz; the DSL is so succinct!
I was wondering how do you (or envision to) support handlers that need to add something more to the context? For example a handler that does authentication may want to add the user or user id to the context for handlers downstream to use for authorization.
Since handlers are nested, you should be able to see outer variables in the inner handlers. For instance something like
scope do:
let user = getUserData()
return someHandler[
someOtherHanderl[
ok(user)
]
]
I guess one could abstract some pattern here: if you find yourself repeating some logic in such situations, please file a PR with some generic handler for user authentication.
Another possibility would be to make the context generic (Context[A]): this would allow to attach arbitrary data to the context. I am not sure whether this is worth the extra complexity, but it is something to consider
You are right a generic context is perhaps too much complexity for the use case. I'm just starting on an app and I'll see if I find any interesting abstractions around authentication. Thank you very much!
On a broader view, as a Nim web toolkit developer, do you see the need for a unified server interface for Nim web applications (ala Python's WSGI or Ruby's Rack) to help develop "middlewares" that can be shared by the community across different web libraries?
I don't know, I have never mixed middleware from different libraries even using python - I always used Django, or Flask, or Bottle on their own. The existence of a basic HTTP server in the standard library can be used as a common ground on which one can develop frameworks (this is what Rosencrantz does), but then there are standalone projects such as HTTPBeast or Mofuw (now archived).
One thing I wanted to do is abstracting the notion of handler in order to be able to support both the stdlib server AND HTTPBeast, but I could not find the time for this yet