DEV Community

flisk
flisk

Posted on

A Goose App in the Same Shape as the Fastify Example

Part 2 of Any language on Flisk. The Fastify example in the BYO doc is three files and a command. This is the same layout. The files are Goose's, because that is the language in the image.

services/shop/goose-api/
  Dockerfile
  server.goose
  http.c
  goose-compiler/     # Linux build of the compiler, matching the image arch
Enter fullscreen mode Exit fullscreen mode

Create that directory before flisk resource. A Dockerfile that already exists is kept.

Goose's own standard library can read PORT with os.env. It does not ship sockets. The stdlib says networking arrives as extern fn when a program needs it. That is application code, the same way Fastify's listen is application code. Flisk never sees the difference.

server.goose

fn main() {
    var text: u8[>..] = [];
    let found = os.env("PORT", text);
    let port, parsed = parse_int(text);
    if !found || !parsed || port <= 0 || port > 65535 {
        abort("PORT missing or not a port");
    }
    let code = serve(port as i32);
    if code != 0 {
        abort("serve failed");
    }
}

extern fn serve(port: i32) -> i32;
Enter fullscreen mode Exit fullscreen mode

serve must keep running. A BYO container uses unless-stopped unless you set --restart no. A process that exits 0 is started again forever. --restart no is for a migrator, not for this API.

http.c

#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>

int serve(int port) {
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd < 0) return 1;
    int on = 1;
    setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));

    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons((unsigned short)port);
    addr.sin_addr.s_addr = htonl(INADDR_ANY);

    if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) != 0) return 2;
    if (listen(fd, 16) != 0) return 3;

    for (;;) {
        int client = accept(fd, 0, 0);
        if (client < 0) continue;
        char req[1024];
        int n = (int)read(client, req, sizeof(req) - 1);
        if (n < 0) n = 0;
        req[n] = 0;

        const char *body = "{\"status\":\"ok\"}\n";
        char resp;
        if (strncmp(req, "GET /health", 11) == 0) {
            snprintf(resp, sizeof(resp),
                "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: %zu\r\nConnection: close\r\n\r\n%s",
                strlen(body), body);
        } else {
            const char *missing = "not found\n";
            snprintf(resp, sizeof(resp),
                "HTTP/1.1 404 Not Found\r\nContent-Type: text/plain\r\nContent-Length: %zu\r\nConnection: close\r\n\r\n%s",
                strlen(missing), missing);
        }
        (void)write(client, resp, strlen(resp));
        close(client);
    }
}
Enter fullscreen mode Exit fullscreen mode

INADDR_ANY is 0.0.0.0. The Host header is ignored on purpose. Through flisk up that header is api.<project>.localhost. Rails blocked it until the example allowed any host. Most other checked examples did not care.

Dockerfile

The compiler you copy must be a Linux binary for the image architecture. A macOS goose on your laptop will not run inside Debian. Unpack a Linux release from aardappel/goose into goose-compiler/ so goose-compiler/goose exists.

FROM debian:bookworm-slim AS build
RUN apt-get update \
 && apt-get install -y --no-install-recommends gcc libc6-dev ca-certificates \
 && rm -rf /var/lib/apt/lists/*
COPY goose-compiler/ /opt/goose/
ENV PATH="/opt/goose:${PATH}"
WORKDIR /src
COPY server.goose http.c ./
RUN goose -o server.c server.goose \
 && cc server.c http.c -o /out/server -lm -pthread

FROM debian:bookworm-slim
COPY --from=build /out/server /usr/local/bin/server
CMD ["server"]
Enter fullscreen mode Exit fullscreen mode

No curl in the runtime image. BYO does not probe with curl.

The check every examples/byo folder uses

cd services/shop/goose-api
docker build -t goose-health .
docker run --rm -d --name goose-health -e PORT=4000 -p 4000:4000 goose-health
curl -fsS http://127.0.0.1:4000/health
docker stop goose-health
Enter fullscreen mode Exit fullscreen mode

HTTP 200 means this language meets the contract. Part 3 registers it. Part 4 is the Traefik check, which docker run cannot do.

Top comments (0)