The tutorial, part 3 of 3
A web service
A high-score server with std.net.http's HttpServer, its routes as Methods, JSON in and out, and a client to try it.
This part builds a small high-score service for a game: POST /scores adds a score, and
GET /scores lists the best. It uses std.net.http’s HttpServer, whose routes are a Class’s
Async Methods, and HttpClient to try it, in the same program, so it starts, answers a few
requests, and stops.
Step 1: a route
A Class’s Async Method with @Get is a route. The server listens on a port, here port 0, which
lets the OS pick a free one, and Serve() answers requests until Stop().
Import std.core
Import std.net
Import std.net.http
Class Hello
@Get("/hello")
Async Method Hello(): HttpResponse => HttpResponse.Of(Body.Text("hello from FutureBlitz"))
End Class
Local server = HttpServer()
server.Routes(Hello())
Try
server.Listen(SocketAddress(IpAddress.Loopback, 0))
Local serving = server.Serve()
Local client = HttpClient()
Local response = Await client.GetAsync("http://127.0.0.1:{server.LocalAddress.Port}/hello")
Print(response.Status, response.Text)
server.Stop()
Await serving
Catch e As Error
PrintError("failed: {e.Message}")
End Try200 hello from FutureBlitz
Await waits for the client’s request without blocking the thread, which the server’s work goes
on running on.
Step 2: scores in and out
A route’s argument is a Struct that the server fills from the request: @JsonBody Fields from
its JSON, and the others from the path and the query, each read as its Field’s type, so
?top=x is a 400 that says why, with no code of the route’s. The response is JSON from a Struct
too, through Json.Write.
Import std.core
Import std.collections
Import std.data
Import std.net
Import std.net.http
Struct Score
Field Player: String
Field Points: Int
End Struct
Struct NewScore
@JsonBody Field Score: Score
End Struct
Struct Top
Field Top: Int = 3
End Struct
Class Scores
Field Board: Score[] = []
@Post("/scores")
Async Method Add(args: NewScore): HttpResponse
Board.Add(args.Score)
Board.SortByDescending(Function(s: Score) => s.Points)
Return HttpResponse.Of(StatusCode.CREATED, Body.Text("added"))
End Method
@Get("/scores")
Async Method Best(args: Top): HttpResponse
Local best = Board.View(0, Min(args.Top, Board.Count)).ToArray()
Return HttpResponse.Of(Body.Json(Json.Write(best)))
End Method
End Class
Local server = HttpServer()
server.Routes(Scores())
Try
server.Listen(SocketAddress(IpAddress.Loopback, 0))
Local serving = server.Serve()
Local base = "http://127.0.0.1:{server.LocalAddress.Port}"
Local client = HttpClient()
For entry In [Score("Ada", 1200), Score("Grace", 3400), Score("Alan", 2100), Score("Linus", 900)]
Local added = Await client.PostAsync("{base}/scores", Body.Json(Json.Write(entry)))
Print("POST {entry.Player}: {added.Status}")
End For
Local best = Await client.GetAsync("{base}/scores?top=2")
Print("GET /scores?top=2: {best.Status} {best.Text}")
Local wrong = Await client.GetAsync("{base}/scores?top=lots")
Print("GET /scores?top=lots: {wrong.Status} {wrong.Text}")
server.Stop()
Await serving
Catch e As Error
PrintError("failed: {e.Message}")
End TryPOST Ada: 201
POST Grace: 201
POST Alan: 201
POST Linus: 201
GET /scores?top=2: 200 [{"Player":"Grace","Points":3400},{"Player":"Alan","Points":2100}]
GET /scores?top=lots: 400 Top: expected Int32, found "lots"Step 3: running it for real
A service that runs until it’s stopped listens on a port of its own and waits on Serve():
Import std.core
Import std.net
Import std.net.http
Class Hello
@Get("/hello")
Async Method Hello(): HttpResponse => HttpResponse.Of(Body.Text("hello"))
End Class
Local server = HttpServer()
server.Routes(Hello())
Try
server.Listen(SocketAddress(IpAddress.Any, 8080))
Print("listening on port 8080")
Await server.Serve()
Catch e As Error
PrintError("failed: {e.Message}")
End Tryfb build --release makes it one executable, with std’s HTTP code linked in, so it runs on a
server with nothing installed beside it. The std.net.http reference has
the rest: route groups, static files, WebSockets, and HTTPS with certificates from ACME.
Report an issue with this page or with FutureBlitz on GitHub.