Add routing

This commit is contained in:
Hugo Mårdbrink 2025-11-09 11:11:15 +01:00
parent 1368b7b69b
commit 4316fb2a73
7 changed files with 534 additions and 109 deletions

View file

@ -8,19 +8,21 @@ import "core:net"
import "core:path/slashpath"
import "core:strconv"
import "core:strings"
import "core:sync"
import "core:unicode"
import http "http"
Server :: struct($Error_Type: typeid) {
endpoint: net.Endpoint,
router: Router(EndpointHandler(Error_Type)),
not_found_handler: #type proc(
request: ^http.Request,
) -> (
http.Response,
Error_Type,
),
router: Router(Error_Type),
shutdown: bool,
}
@(private)
@ -75,62 +77,16 @@ server_init :: proc(
)
return http_response, nil
},
shutdown = false,
}
router_init(&server.router)
router_init(&server.router, allocator)
}
server_destroy :: proc(server: ^Server($Error_Type)) {
router_destroy(&server.router)
}
server_add_handler :: proc(
server: ^Server($Error_Type),
method: http.Method,
path: []string,
handle_proc: #type proc(
request: ^http.Request,
) -> (
http.Response,
Error_Type,
),
) {
path_variables := make([dynamic]string, context.allocator)
defer delete(path_variables)
for seg in path {
is_path_var :=
strings.has_prefix(seg, "{") && strings.has_suffix(seg, "}")
if is_path_var {
append(&path_variables, seg[1:len(seg) - 1])
}
}
joined_path := slashpath.join(path, context.allocator)
defer delete(joined_path)
if joined_path == "" {
router_add_route(
&server.router,
"/",
EndpointHandler(Error_Type){method},
)
server.handlers["/"] = EndpointHandler(Error_Type) {
method,
handle_proc,
path_variables[:],
}
} else {
server.handlers[joined_path] = Handler(Error_Type) {
method,
handle_proc,
path_variables[:],
}
}
}
// This is used instead of server_make param until compiler bug is fixed: https://github.com/odin-lang/Odin/issues/5792
// This is used instead of default server_make param until compiler bug is fixed: https://github.com/odin-lang/Odin/issues/5792
server_set_not_found_handler :: proc(
server: ^Server($Error_Type),
not_found_handler: #type proc(
@ -143,22 +99,27 @@ server_set_not_found_handler :: proc(
server.not_found_handler = not_found_handler
}
match_handler_pattern :: proc(
server_add_route :: proc(
server: ^Server($Error_Type),
method: http.Method,
path: string,
allocator := context.allocator,
) -> (
path: []string,
handler: #type proc(request: ^http.Request) -> (http.Response, Error_Type),
path_variables: map[string]string,
) {
path_variables = make(map[string]string, allocator)
segments := strings.split(path, "/", context.allocator)
defer delete(segments)
router_add_route(&server.router, method, path, handler)
}
handler = server.handlers[identifier].procedure
server_remove_route :: proc(
server: ^Server($Error_Type),
method: http.Method,
path: []string,
) -> (
ok: bool,
) {
return router_remove_route(&server.router, method, path)
}
return handler, path_variables
server_shutdown :: proc(server: ^Server($Error_Type)) {
sync.atomic_store(&server.shutdown, true)
}
listen_and_serve :: proc(server: ^Server($Error_Type)) {
@ -166,7 +127,7 @@ listen_and_serve :: proc(server: ^Server($Error_Type)) {
log.assert(net_err == nil, "Couldn't create TCP socket")
defer net.close(server_socket)
for {
for !sync.atomic_load(&server.shutdown) {
client_socket, source, net_err := net.accept_tcp(server_socket)
if net_err != nil {
log.warnf("Failed to accept TCP connection, reason: %s", net_err)
@ -201,13 +162,29 @@ listen_and_serve :: proc(server: ^Server($Error_Type)) {
http.Response,
Error_Type,
)
handler, http_request.path_variables = match_handler_pattern(
server,
http_request.method,
http_request.path,
)
ok: bool
http_response, err := handler(&http_request)
// todo: should sanitize better
path := strings.split(
http_request.path,
"/",
context.temp_allocator,
)[1:]
handler, http_request.path_variables, ok = router_lookup(
&server.router,
http_request.method,
path,
context.temp_allocator,
)
if !ok do handler = server.not_found_handler
http_response, handler_err := handler(&http_request)
if handler_err != nil {
log.warnf("Handler failed with error: %s", handler_err)
continue
}
marshalled_response := http.marshall_response(
&http_response,