Skip to content
FutureBlitz

    Install

    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 Try
    Output
    200 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 Try
    Output
    POST 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 Try

    fb 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.