I'd like to add a Task for basic-webserver that can run server initialisation setup.
Just wondering if anyone has any thoughts on this.
So the API I'm think we would change to is basically
main : {
init : Task {} [Exit I32 Str]_,
handle : Request -> Task Response [],
}
Potential use-cases I have in mind
tailwindcss, rtl etc.Do you mean basic web server?
oops, yes -- fixed the thread name
Essentially, a little basic-cli like script that can run before the server start handling requests
Oh, for the sqlite setup we would need to return a Model
So actually something like
main : {
init : Task model [Exit I32 Str]_,
handle : Request, model -> Task Response [],
}
Here the model is read-only. We can support Tasks that can update the model in future, but for that we need to do a big upgrade to handle multithreaded state that plays nicely with roc which is difficult before we have effect interpreters and passed in allocators from what I understand.
The platform will just box the Model to pass to the host, and the host just passes that in with each request
Yeah, host should just need to set the box refcount to constant
And I think it should work
I'm not sure about it running RTL at startup, but no way to stop it.
yeah I like this! :thumbs_up:
assuming it can work using today's compiler :big_smile:
Brendan Hansknecht said:
I'm not sure about it running RTL at startup, but no way to stop it.
I was thinking of doing that using a Command, so not necessarily building this in or anything.
Yeah I know
Richard Feldman said:
assuming it can work using today's compiler :big_smile:
It can, but you have to wire some stuff manually and it's a bit jank
We do it for wasm4
Ok, so WIP but I'm thinking of going with this API
platform "webserver"
requires { Model } { server : {
init : Task Model [Exit I32 Str]_,
respond : Http.Request, Model -> InternalTask.Task Http.Response [ServerErr Str]_,
} }
exposes [
Path,
Dir,
Env,
File,
FileMetadata,
Http,
Stderr,
Stdout,
Task,
Tcp,
Url,
Utc,
Sleep,
Command,
SQLite3,
]
packages {}
imports [
Task.{ Task },
Stderr.{ line },
]
provides [forHost]
import InternalTask
import Http
import InternalHttp
import Task
ForHost : {
init : Task (Box Model) I32,
respond : InternalHttp.RequestToAndFromHost, Box Model -> InternalTask.Task InternalHttp.ResponseToHost [],
}
forHost : ForHost
forHost = { init, respond }
init : Task (Box Model) I32
init =
Task.attempt server.init \res ->
when res is
Ok model -> Task.ok (Box.box model)
Err (Exit code str) ->
if Str.isEmpty str then
Task.err code
else
line str
|> Task.onErr \_ -> Task.err code
|> Task.await \{} -> Task.err code
Err err ->
line
"""
Program exited with error:
$(Inspect.toStr err)
Tip: If you do not want to exit on this error, use `Task.mapErr` to handle the error.
Docs for `Task.mapErr`: <https://www.roc-lang.org/packages/basic-cli/Task#mapErr>
"""
|> Task.onErr \_ -> Task.err 1
|> Task.await \_ -> Task.err 1
respond : InternalHttp.RequestToAndFromHost, Box Model -> InternalTask.Task InternalHttp.ResponseToHost []
respond = \request, boxedModel ->
when server.respond (InternalHttp.fromHostRequest request) (Box.unbox boxedModel) |> Task.result! is
Ok response -> Task.ok response
Err (ServerErr msg) ->
# prints the error message to stderr
line! msg
# returns a http server error response
InternalTask.ok {
status: 500,
headers: [],
body: [],
}
Err err ->
line!
"""
Server error:
$(Inspect.toStr err)
Tip: If you do not want to see this error, use `Task.mapErr` to handle the error.
Docs for `Task.mapErr`: <https://www.roc-lang.org/packages/basic-webserver/Task#mapErr>
"""
InternalTask.ok {
status: 500,
headers: [],
body: [],
}
Taking some inspiration from this discussion https://roc.zulipchat.com/#narrow/stream/383402-API-Design/topic/Default.20error.20handling.20in.20basic-webserver/near/436375969
Here's how hello-web looks -- I prefer it without all the type annotations... but leaving here for completness
app [Model, server] { pf: platform "../platform/main.roc" }
import pf.Stdout
import pf.Task exposing [Task]
import pf.Http exposing [Request, Response]
import pf.Utc
Model : {}
server = { init, respond }
init : Task Model [Exit I32 Str]_
init = Task.ok {}
respond : Request, Model -> Task Response [ServerErr Str]_
respond = \req, _ ->
# Log request datetime, method and url
datetime = Utc.now! |> Utc.toIso8601Str
Stdout.line! "$(datetime) $(Http.methodToStr req.method) $(req.url)"
Task.ok { status: 200, headers: [], body: Str.toUtf8 "<b>Hello, world!</b>\n" }
Ok, I've made a PR https://github.com/roc-lang/basic-webserver/pull/64
I've only upgraded the stubbed app and hello-web examples.
I am not super confident in how I've done things. Sometimes it runs perfectly fine, and then sometimes I make a random change, and all of a sudden it starts crashing -- but I'm not sure, maybe I've got it right now.
If you want to check this out, just jump on that branch then.
$ nix develop
$ roc build.toc
$ roc examples/hello-web.roc
@Brendan Hansknecht could you please have a look at my implementation, particularly how I'm threading the captures and Model through to each request.
Next step is to update the examples to new API and testing. But this should be pretty straightforward as long as the impl is correct.
I had trouble getting it working with a singleton global. I just remembered why I was trying to do that. I dont want the roc init task running multiple times because it's effectful. So I'll need to find a way to run that once, and then give each thread a copy of the Model and captures.
Probably tomorrow I can take some time and try to fix up the implementation.
I've included a simple_bench binary to help throw requests at the server and find issues.
It's working so well, I know my implementation as a serious problem somewhere :sweat_smile:
Just a note, you can also throw request with a lot of tools. wrk is a simple one: wrk -t 4 -c 64 -d 20 http://127.0.0.1:8000 is what I have been doing for simple benchmarking.
Also, probably need to pull up a linux machine and look at this in valgrind. Though lifetimes should be correct now, something is still breaking memory.
Oh, I found it. Simple double free.
Ok, I think I have pushed fixes such that it is ready to just port all the examples over
I pushed a bunch more updates, may not be 100% yet, but I think essentially everything is working and updated.
I think https://github.com/roc-lang/basic-webserver/pull/64 is fully ready for review and merge.
Awesome :heart: I'll check it out
Last updated: Jun 16 2026 at 16:19 UTC