A web service with multiple routes
A complete program that responds to several addresses, reads the query string and returns both HTML and plain text.
A route in Norscode is a perfectly ordinary function. What makes it a route is that it registers itself with web.route. The runtime finds all such functions when the service starts, and builds the routing table from them.
bruk std.web som web
bruk src.kjerne.noformat som nf
funksjon html(kropp: tekst) -> ordbok_tekst {
returner web.response_builder(200, {"content-type": "text/html; charset=utf-8"}, kropp)
}
funksjon forside(ctx: ordbok_tekst) -> ordbok_tekst {
web.route("GET /")
returner html("<h1>Velkommen</h1><p><a href=/hilsen?navn=Kari>Hils</a></p>")
}
funksjon hilsen(ctx: ordbok_tekst) -> ordbok_tekst {
web.route("GET /hilsen")
la navn = web.request_query_param(ctx, "navn")
hvis navn == "" { navn = "verden" }
returner html("<h1>Hei, " + nf.html_escape(navn) + "!</h1>")
}
funksjon helse(ctx: ordbok_tekst) -> ordbok_tekst {
web.route("GET /helse")
returner web.response_builder(200, {"content-type": "text/plain"}, "ok")
}
funksjon start() -> heltall {
skriv("Tjenesten er klar")
returner 0
}What happens here
Notice the html function at the top. It is not a route — it has no web.route — but an ordinary helper function that builds a response with the right content type. When you have more than one route that returns HTML, it saves you from repeating the same header line in every response. It is a small example of something that holds generally: routes should be thin, and the actual work should be done in ordinary functions that are easy to test on their own.
ctx is the request. From it you get what the client sent: the query string with web.request_query_param, the headers with web.request_header. The runtime has no dynamic path routes — you cannot write something like a path with an embedded parameter — but the query string covers most of the same need.
The health route looks trivial, but it is the most important of the three in production. It responds without touching a database or the network, and thereby tells you just one thing: the process is alive and bound to the port. That is exactly what a process monitor needs to know, and the reason it should be kept this simple.
Start the service
NORSCODE_VM_CAPABILITIES="net.tcp" \\
NORSCODE_VM_NET_SCOPE="127.0.0.1" \\
nc serve tjeneste.no --host 127.0.0.1 --port 8080Capabilities are not decoration
Without net.tcp the service does not bind the port at all. It feels strict the first time, but that is the whole point: you see in the command itself what the program is allowed to do. Here it can speak TCP, and only with localhost. It cannot read or write disk, and cannot read environment variables.
If the service is to read a configuration file, you must add disk.read and point NORSCODE_VM_DISK_ROOT to where the file lives. Then it is there in black and white in the startup command what the service touches — and whoever reads the command knows as much as whoever has read all the code.
One thing you must not forget
The value from the query string passes through nf.html_escape before it ends up in HTML. Without it, a visitor could submit a script element as a name, and have it run in the browser of everyone who opened the link. The rule is simple and without exception: everything that comes from outside shall be escaped before it is inserted into HTML, SQL or JSON.
Related
- Store and retrieve dataCreate a table, write rows and read them back out — with the built-in database functions, and without one query per row.
- Hash a password safelyArgon2id from the standard library — the right tool for passwords, without a single external dependency.
- Reading and writing filesFile handling with capabilities — and why your program cannot read what you have not given it.