7.6.3. HV-03 — HTTP Server Basics
This tutorial covers building an HTTP server with the dashv module —
defining routes for all HTTP methods, reading path and query parameters,
setting response headers, and returning JSON responses.
Prerequisites: HV-01 — Simple HTTP Requests (client-side basics).
7.6.3.1. Server Class
Every server extends HvWebServer and overrides onInit to register
routes. The four required WebSocket callbacks can be left empty:
class MyServer : HvWebServer {
def override onInit {
GET("/hello") <| @(var req : HttpRequest?; var resp : HttpResponse?) : http_status {
return resp |> TEXT_PLAIN("Hello, world!")
}
}
}
Every handler receives the request and response by reference and must
return an http_status value.
WebSocket callbacks (onWsOpen, onWsClose, onWsMessage) and
onTick have empty defaults in the base class — override them only
when you need WebSocket support.
7.6.3.2. GET Route
GET("/hello") <| @(var req : HttpRequest?; var resp : HttpResponse?) : http_status {
return resp |> TEXT_PLAIN("Hello, world!")
}
TEXT_PLAIN sets Content-Type: text/plain and returns http_status.OK by default.
Pass an optional status to override: TEXT_PLAIN(resp, text, http_status.BAD_REQUEST).
7.6.3.3. POST Route
POST("/echo") <| @(var req : HttpRequest?; var resp : HttpResponse?) : http_status {
return resp |> TEXT_PLAIN(string(req.body))
}
req.body contains the raw request body. Cast to string when
passing to functions that expect string.
7.6.3.4. All HTTP Methods
Use PUT, PATCH, DELETE, HEAD, and ANY to register
handlers for additional methods:
PUT("/data") <| @(var req : HttpRequest?; var resp : HttpResponse?) : http_status {
return resp |> TEXT_PLAIN("updated")
}
PATCH("/data") <| @(var req : HttpRequest?; var resp : HttpResponse?) : http_status {
return resp |> TEXT_PLAIN("patched")
}
DELETE("/data") <| @(var req : HttpRequest?; var resp : HttpResponse?) : http_status {
return resp |> TEXT_PLAIN("deleted")
}
HEAD("/data") <| @(var req : HttpRequest?; var resp : HttpResponse?) : http_status {
return http_status.OK
}
// ANY registers a handler for all methods at once
ANY("/universal") <| @(var req : HttpRequest?; var resp : HttpResponse?) : http_status {
return resp |> TEXT_PLAIN("method was {req.method}")
}
7.6.3.5. Path Parameters
Use :name in the route to capture path segments. Read them with
get_param:
GET("/users/:id") <| @(var req : HttpRequest?; var resp : HttpResponse?) : http_status {
let id = get_param(req, "id")
return resp |> TEXT_PLAIN("user {id}")
}
Multiple path parameters work naturally:
GET("/users/:id/posts/:post_id") <| @(var req : HttpRequest?; var resp : HttpResponse?) : http_status {
let user_id = get_param(req, "id")
let post_id = get_param(req, "post_id")
return resp |> TEXT_PLAIN("user {user_id}, post {post_id}")
}
7.6.3.6. Query Parameters
Iterate all query parameters with each_param:
GET("/search") <| @(var req : HttpRequest?; var resp : HttpResponse?) : http_status {
var parts : array<string>
each_param(req) <| $(key, value : string) {
parts |> push("{key}={value}")
}
return resp |> TEXT_PLAIN(join(parts, ", "))
}
Individual query parameters can also be read with get_param by name.
7.6.3.7. Response Headers
set_header on the response object adds custom headers:
GET("/api/info") <| @(var req : HttpRequest?; var resp : HttpResponse?) : http_status {
set_header(resp, "X-Request-Id", "42")
set_header(resp, "X-Server", "daslang")
return resp |> TEXT_PLAIN("ok")
}
7.6.3.8. JSON Responses
Combine daslib/json_boost with the JSON response helper. Build a
JsonValue tree using JV() — which accepts structs, named tuples,
arrays, and scalars — serialize it with write_json, and pass the
string to JSON(resp, ...):
require daslib/json_boost
GET("/api/data") <| @(var req : HttpRequest?; var resp : HttpResponse?) : http_status {
let payload : tuple<message:string; count:int> = ("hello", 42)
return resp |> JSON(write_json(JV(payload)))
}
JSON sets Content-Type: application/json and returns 200.
JV() converts named tuple fields into JSON object keys automatically.
Note
JSON() always returns status 200. For non-200 JSON responses,
set the body and content-type manually — see
HV-04 — Advanced HTTP Server Features.
7.6.3.9. Server Lifecycle
Start the server on a background thread, run your test code, then stop:
// Helper: run server, call block, stop
def with_test_server(port : int; blk : block<(base_url : string) : void>) {
with_job_status(1) $(started) {
with_job_status(1) $(finished) {
with_atomic32() $(stop_flag) {
new_thread() @() {
var server = new MyServer()
server->init(port)
server->start()
started |> notify_and_release
while (stop_flag |> get == 0) {
server->tick()
sleep(10u)
}
server->stop()
finished |> notify_and_release
}
started |> join
invoke(blk, "http://127.0.0.1:{port}")
stop_flag |> set(1)
finished |> join
}
}
}
}
7.6.3.10. Quick Reference
Function |
Description |
|---|---|
|
Register GET route |
|
Register POST route |
|
Register PUT route |
|
Register PATCH route |
|
Register DELETE route |
|
Register HEAD route |
|
Register handler for all methods |
|
text/plain response (default 200) |
|
application/json response (default 200) |
|
Read path/query parameter |
|
Iterate all query parameters |
|
Set response header |
See also
Full source: tutorials/dasHV/03_http_server.das
Next tutorial: HV-04 — Advanced HTTP Server Features
Previous tutorial: HV-02 — Advanced HTTP Requests