HTTP library for Odin
Find a file
2025-11-16 10:51:24 +01:00
http Replace work queue with Odin core thread pool 2025-11-13 11:18:48 +01:00
.gitignore Initial commit 2025-11-03 13:50:38 +01:00
.gitmodules Add routing 2025-11-09 11:11:15 +01:00
fjord_server Add custom error handler callback 2025-11-14 15:51:09 +01:00
fjord_test.odin Add custom error handler callback 2025-11-14 15:51:09 +01:00
odinfmt.json Initial commit 2025-11-03 13:50:38 +01:00
README.md Add custom error callback example to README 2025-11-16 10:51:24 +01:00
router.odin Replace work queue with Odin core thread pool 2025-11-13 11:18:48 +01:00
server.odin Add custom error handler callback 2025-11-14 15:51:09 +01:00

HTTP 1/1 server library

Routing rules

  • Static first routing
  • Path variables defined with colon prefix: :foo
  • Example: /odin/:version/download
  • Wild card routing not implemented

Compile time user errors included

When defining the server you can pass a custom error union type for the server. All the handler function will, of course, need to return these in as a pair with a response.

Memory allocation

The server will free the context temp allocators memory after the handler is run, thus, you are free to use context.temp_allocator within your handlers and expect the memory to be freed after. This becomes useful when you want to pass the response back to the server but is also ergonomic if you find yourself in need of a temp allocator in your handlers.

Example

package main

import "core:log"
import "core:net"
import "core:strings"
import fjord "fjord"
import http "fjord/http"

InternalError :: enum {
    DatabaseError,
}

BadRequestError :: enum {
    UserUnauthorised,
    InputFormatWrong,
}

Error :: union {
    InternalError,
    BadRequestError,
}

// Handler for GET /hello/:name
hello_handler :: proc(request: ^http.Request) -> (http.Response, Error) {
    name := request.path_variables["name"]

    body := strings.concatenate(
        {"<h1>Hello, ", name, "!</h1>"},
        context.temp_allocator,
    )

    response := http.make_response(
        .Ok,
        transmute([]byte)body,
        .Html,
        context.temp_allocator,
    )

    return response, nil
}

// Handler for GET /users/:id/posts/:post_id
user_post_handler :: proc(request: ^http.Request) -> (http.Response, Error) {
    user_id := request.path_variables["id"]
    post_id := request.path_variables["post-id"]

    body := strings.concatenate(
        {"<p>User ", user_id, " - Post ", post_id, "</p>"},
        context.temp_allocator,
    )

    response := http.make_response(
        .Ok,
        transmute([]byte)body,
        .Html,
        context.temp_allocator,
    )

    return response, nil
}

error_callback :: proc(error: Error) -> http.Response {
    switch err_type in error {
        case BadRequestError:
        switch err in err_type {
            case UserUnauthorised: fallthrough
            case InputFormatWrong:
                body := "Bad request error"
                return http.make_response(
                    .BadRequest,
                    transmute([]byte)body,
                    .Html,
                    context.temp_allocator,
                )
        }
        case InternalError:
        switch err in err_type {
            case DataBaseError: 
                body := "Internal server error"
                return http.make_response(
                    .InternalServerError,
                    transmute([]byte)body,
                    .Html,
                    context.temp_allocator,
                )
        }
    }
}

main :: proc() {
    context.logger = log.create_console_logger(.Info)
    defer log.destroy_console_logger(context.logger)

    endpoint := net.Endpoint {
        address = net.IP4_Address{127, 0, 0, 1},
        port    = 8080,
    }

    server: fjord.Server(UserError)
    fjord.server_init(&server, endpoint, context.allocator)
    defer fjord.server_destroy(&server)

    fjord.server_add_route(&server, .GET, {"hello", ":name"}, hello_handler)
    fjord.server_add_route(&server, .GET, {"users", ":id", "posts", ":post-id"}, user_post_handler)

    server_set_error_handler(&server, error_callback)

    log.infof("Server listening on http://127.0.0.1:%d", endpoint.port)
    fjord.server_listen_and_serve(&server)
}

Dev

Run the test suite to check functionality, curl is a required dependency for the test suite to work.

odin test .