Add routing
This commit is contained in:
parent
1368b7b69b
commit
4316fb2a73
7 changed files with 534 additions and 109 deletions
230
fjord_test.odin
230
fjord_test.odin
|
|
@ -1,30 +1,75 @@
|
|||
package fjord
|
||||
|
||||
import "core:fmt"
|
||||
import "core:log"
|
||||
import "core:net"
|
||||
import "core:os/os2"
|
||||
import "core:strings"
|
||||
import "core:thread"
|
||||
import "core:time"
|
||||
|
||||
import "core:testing"
|
||||
|
||||
import http "http"
|
||||
|
||||
Error :: enum {}
|
||||
Error :: enum {
|
||||
TestError,
|
||||
}
|
||||
|
||||
handler :: proc(request: ^http.Request) -> (http.Response, Error) {
|
||||
body := "<div>Hello</div>"
|
||||
response := http.make_response(.Ok, transmute([]byte)body, .Html)
|
||||
first_handler :: proc(request: ^http.Request) -> (http.Response, Error) {
|
||||
entity := request.path_variables["entity"]
|
||||
|
||||
body := strings.concatenate(
|
||||
{"<div>Hello first", entity, "</div>"},
|
||||
context.temp_allocator,
|
||||
)
|
||||
response := http.make_response(
|
||||
.Ok,
|
||||
transmute([]byte)body,
|
||||
.Html,
|
||||
context.temp_allocator,
|
||||
)
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
get_path_var_handler :: proc(request: ^http.Request) -> (http.Response, Error) {
|
||||
body := "<div>Hello</div>"
|
||||
response := http.make_response(.Ok, transmute([]byte)body, .Html)
|
||||
second_handler :: proc(request: ^http.Request) -> (http.Response, Error) {
|
||||
entity := request.path_variables["entity"]
|
||||
|
||||
body := strings.concatenate(
|
||||
{"<div>Hello second", entity, "</div>"},
|
||||
context.temp_allocator,
|
||||
)
|
||||
response := http.make_response(
|
||||
.Ok,
|
||||
transmute([]byte)body,
|
||||
.Html,
|
||||
context.temp_allocator,
|
||||
)
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
third_handler :: proc(request: ^http.Request) -> (http.Response, Error) {
|
||||
entity := request.path_variables["entity"]
|
||||
other_entity := request.path_variables["other_entity"]
|
||||
|
||||
body := strings.concatenate(
|
||||
{"<div>Hello third", entity, "and", other_entity, "</div>"},
|
||||
context.temp_allocator,
|
||||
)
|
||||
response := http.make_response(
|
||||
.Ok,
|
||||
transmute([]byte)body,
|
||||
.Html,
|
||||
context.temp_allocator,
|
||||
)
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_basic_ok :: proc(t: ^testing.T) {
|
||||
test_router_ok :: proc(t: ^testing.T) {
|
||||
context.logger = log.create_console_logger(.Info)
|
||||
defer log.destroy_console_logger(context.logger)
|
||||
|
||||
|
|
@ -37,7 +82,170 @@ test_basic_ok :: proc(t: ^testing.T) {
|
|||
server_init(&server, endpoint, context.allocator)
|
||||
defer server_destroy(&server)
|
||||
|
||||
server_add_handler(&server, .GET, {}, handler)
|
||||
server_add_handler(&server, .GET, {"hello", "{name-thing}"}, handler)
|
||||
listen_and_serve(&server)
|
||||
server_add_route(&server, .GET, {"hello", ":entity"}, first_handler)
|
||||
server_add_route(
|
||||
&server,
|
||||
.GET,
|
||||
{"hello", ":entity", "only"},
|
||||
second_handler,
|
||||
)
|
||||
server_add_route(
|
||||
&server,
|
||||
.GET,
|
||||
{"hello", ":entity", "and", ":other_entity"},
|
||||
third_handler,
|
||||
)
|
||||
|
||||
handler, path_vars, ok := router_lookup(
|
||||
&server.router,
|
||||
.GET,
|
||||
{"hello", "world"},
|
||||
context.temp_allocator,
|
||||
); assert(ok)
|
||||
assert(handler == first_handler)
|
||||
assert(path_vars["entity"] == "world")
|
||||
|
||||
handler, path_vars, ok = router_lookup(
|
||||
&server.router,
|
||||
.GET,
|
||||
{"hello", "lonely world", "only"},
|
||||
context.temp_allocator,
|
||||
); assert(ok)
|
||||
assert(handler == second_handler)
|
||||
assert(path_vars["entity"] == "lonely world")
|
||||
|
||||
handler, path_vars, ok = router_lookup(
|
||||
&server.router,
|
||||
.GET,
|
||||
{"hello", "world", "and", "worlds friend"},
|
||||
context.temp_allocator,
|
||||
); assert(ok)
|
||||
assert(handler == third_handler)
|
||||
assert(path_vars["entity"] == "world")
|
||||
assert(path_vars["other_entity"] == "worlds friend")
|
||||
|
||||
ok = server_remove_route(&server, .GET, {"hello", ":entity"}); assert(ok)
|
||||
ok = server_remove_route(&server, .GET, {"hello", ":entity"}); assert(!ok)
|
||||
|
||||
free_all(context.temp_allocator)
|
||||
}
|
||||
|
||||
start_concurrent_server :: proc(
|
||||
server: ^Server($Error_Type),
|
||||
) -> ^thread.Thread {
|
||||
ServerThreadData :: struct {
|
||||
server: ^Server(Error_Type),
|
||||
thread: ^thread.Thread,
|
||||
}
|
||||
|
||||
server_thread_proc :: proc(t: ^thread.Thread) {
|
||||
d := (^ServerThreadData)(t.data)
|
||||
listen_and_serve(d.server)
|
||||
}
|
||||
|
||||
d := new(ServerThreadData, context.temp_allocator)
|
||||
d.server = server
|
||||
|
||||
if d.thread = thread.create(server_thread_proc); d.thread != nil {
|
||||
d.thread.init_context = context
|
||||
d.thread.data = rawptr(d)
|
||||
thread.start(d.thread)
|
||||
}
|
||||
|
||||
// Give server some time to start
|
||||
time.sleep(100 * time.Millisecond)
|
||||
return d.thread
|
||||
}
|
||||
|
||||
shutdown_concurrent_server :: proc(
|
||||
server: ^Server($Error_Type),
|
||||
t: ^thread.Thread,
|
||||
) {
|
||||
server_shutdown(server)
|
||||
|
||||
// Send a dummy request to unblock accept_tcp, todo: fix this immidiate server shutdown
|
||||
_, _, _, err := os2.process_exec(
|
||||
{command = []string{"curl", "-s", "http://127.0.0.1:8080/dummy"}},
|
||||
context.temp_allocator,
|
||||
)
|
||||
assert(err == nil)
|
||||
|
||||
thread.join(t)
|
||||
thread.destroy(t)
|
||||
}
|
||||
|
||||
assert_endpoint :: proc(url: string, expected_response: string) {
|
||||
state, stdout, stderr, err := os2.process_exec(
|
||||
{command = []string{"curl", "-s", url}},
|
||||
context.temp_allocator,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
log.errorf("Failed to execute curl: %v", err)
|
||||
assert(false, "curl execution failed")
|
||||
return
|
||||
}
|
||||
|
||||
if state.exit_code != 0 {
|
||||
log.errorf("curl failed with exit code: %d", state.exit_code)
|
||||
assert(false, "curl returned non-zero exit code")
|
||||
return
|
||||
}
|
||||
|
||||
response := string(stdout)
|
||||
log.assertf(
|
||||
response == expected_response,
|
||||
"Expected: %s, but got: %s",
|
||||
response,
|
||||
expected_response,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@(test)
|
||||
test_server_general_ok :: proc(t: ^testing.T) {
|
||||
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: Server(Error)
|
||||
server_init(&server, endpoint, context.allocator)
|
||||
defer server_destroy(&server)
|
||||
|
||||
server_add_route(&server, .GET, {"hello", ":entity"}, first_handler)
|
||||
server_add_route(
|
||||
&server,
|
||||
.GET,
|
||||
{"hello", ":entity", "only"},
|
||||
second_handler,
|
||||
)
|
||||
server_add_route(
|
||||
&server,
|
||||
.GET,
|
||||
{"hello", ":entity", "and", ":other_entity"},
|
||||
third_handler,
|
||||
)
|
||||
|
||||
t := start_concurrent_server(&server)
|
||||
|
||||
assert_endpoint(
|
||||
"http://127.0.0.1:8080/hello/world",
|
||||
"<div>Hello firstworld</div>",
|
||||
)
|
||||
assert_endpoint(
|
||||
"http://127.0.0.1:8080/hello/lonely%20world/only",
|
||||
"<div>Hello secondlonely%20world</div>",
|
||||
)
|
||||
assert_endpoint(
|
||||
"http://127.0.0.1:8080/hello/world/and/worlds%20friend",
|
||||
"<div>Hello thirdworldandworlds%20friend</div>",
|
||||
)
|
||||
|
||||
shutdown_concurrent_server(&server, t)
|
||||
|
||||
free_all(context.temp_allocator)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue