Add routing
This commit is contained in:
parent
1368b7b69b
commit
4316fb2a73
7 changed files with 534 additions and 109 deletions
4
.gitmodules
vendored
4
.gitmodules
vendored
|
|
@ -1,4 +0,0 @@
|
|||
[submodule "radix_tree"]
|
||||
path = radix_tree
|
||||
url = https://codeberg.org/hugomardbrink/odin-radixtree
|
||||
update = none
|
||||
100
README.md
100
README.md
|
|
@ -1 +1,99 @@
|
|||
I'll get back to this when Odin is more mature, I've ran into countless compiler seg faults and can't be bothered to find out why anymore.
|
||||
# HTTP 1/1 server library
|
||||
|
||||
## Routing rules
|
||||
- Static first routing
|
||||
- Path variables defined with `: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 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
|
||||
|
||||
```odin
|
||||
package main
|
||||
|
||||
import "core:log"
|
||||
import "core:net"
|
||||
import "core:strings"
|
||||
import fjord "fjord"
|
||||
import http "fjord/http"
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
UserError :: enum {
|
||||
DatabaseError,
|
||||
ValidationError,
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
log.infof("Server listening on http://127.0.0.1:%d", endpoint.port)
|
||||
fjord.listen_and_serve(&server)
|
||||
}
|
||||
```
|
||||
|
||||
## Dev
|
||||
Run the test suite to check functionality, curl is a required dependancy for the test suite to work.
|
||||
```bash
|
||||
odin test .
|
||||
```
|
||||
|
|
|
|||
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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,13 +55,14 @@ status_text :: proc(status: Status) -> string {
|
|||
unmarshall_request_line :: proc(
|
||||
request: ^Request,
|
||||
reader: ^bufio.Reader,
|
||||
allocator := context.temp_allocator
|
||||
) -> RequestError {
|
||||
request_line, io_err := bufio.reader_read_slice(reader, '\n')
|
||||
if io_err != nil do return .InvalidRequestLine
|
||||
|
||||
request_line = sanitize_byte_slice(request_line)
|
||||
|
||||
parts := bytes.split(request_line, {' '})
|
||||
parts := bytes.split(request_line, {' '}, allocator)
|
||||
if len(parts) != 3 do return .InvalidRequestLine
|
||||
|
||||
request.method = method_from_str(string(parts[0]))
|
||||
|
|
@ -86,7 +87,7 @@ unmarshall_request_headers :: proc(
|
|||
header_line = sanitize_byte_slice(header_line)
|
||||
if len(header_line) == 0 do break
|
||||
|
||||
parts := bytes.split_n(header_line, {':'}, 2)
|
||||
parts := bytes.split_n(header_line, {':'}, 2, allocator)
|
||||
if len(parts) != 2 do return .InvalidHeaderFormat
|
||||
|
||||
key := string(bytes.trim_space(parts[0]))
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 6fe8dc79ee3aa57f3a8b9a427bedf7bb24cfd809
|
||||
192
router.odin
192
router.odin
|
|
@ -1,42 +1,188 @@
|
|||
#+private package
|
||||
package fjord
|
||||
|
||||
import "base:runtime"
|
||||
import "core:path/slashpath"
|
||||
import "core:strings"
|
||||
import http "http"
|
||||
import rxt "radix_tree"
|
||||
|
||||
EndpointHandler :: struct($Error_Type: typeid) {
|
||||
method_handlers: map[http.Method]#type proc(
|
||||
request: ^http.Request,
|
||||
) -> (
|
||||
http.Response,
|
||||
Error_Type,
|
||||
),
|
||||
path_variables: []string,
|
||||
PathVariable :: struct {
|
||||
name: string,
|
||||
}
|
||||
|
||||
Router :: struct {
|
||||
radix_tree: rxt.RadixTree(EndpointHandler),
|
||||
Static :: struct {}
|
||||
|
||||
RouterNodeType :: union {
|
||||
Static,
|
||||
PathVariable,
|
||||
}
|
||||
|
||||
router_init :: proc(router: ^Router, allocator := context.allocator) {
|
||||
rxt.init(&router.radix_tree, allocator)
|
||||
RouterNode :: struct($Error_Type: typeid) {
|
||||
type: RouterNodeType,
|
||||
handlers: map[http.Method]#type proc(request: ^http.Request) -> (http.Response, Error_Type),
|
||||
children: map[string]^RouterNode(Error_Type),
|
||||
}
|
||||
|
||||
router_destroy :: proc(router : ^Router) {
|
||||
rxt.destroy(&router.radix_tree)
|
||||
Router :: struct($Error_Type: typeid) {
|
||||
root_node: ^RouterNode(Error_Type),
|
||||
allocator: runtime.Allocator,
|
||||
}
|
||||
|
||||
router_lookup :: proc(router: ^Router, key: string) -> (endpoint_handler: EndpointHandler, ok: bool) {
|
||||
return rxt.lookup(&router.radix_tree, key)
|
||||
@(private = "file")
|
||||
router_node_init :: proc(
|
||||
router: ^Router($Error_Type),
|
||||
router_node: ^RouterNode(Error_Type),
|
||||
maybe_path_var_name: Maybe(string),
|
||||
) {
|
||||
router_node.children = make(
|
||||
map[string]^RouterNode(Error_Type),
|
||||
router.allocator,
|
||||
)
|
||||
|
||||
router_node.handlers = make(
|
||||
map[http.Method]#type proc(
|
||||
request: ^http.Request,
|
||||
) -> (
|
||||
http.Response,
|
||||
Error_Type,
|
||||
),
|
||||
router.allocator,
|
||||
)
|
||||
|
||||
path_var_name, ok := maybe_path_var_name.?
|
||||
if ok {
|
||||
router_node.type = PathVariable {
|
||||
name = path_var_name,
|
||||
}
|
||||
} else {
|
||||
router_node.type = Static{}
|
||||
}
|
||||
}
|
||||
|
||||
router_add_route :: proc(router: ^Router, key: []string, value: EndpointHandler) {
|
||||
joined_path := slashpath.join(key, context.allocator)
|
||||
defer delete(joined_path)
|
||||
router_init :: proc(
|
||||
router: ^Router($Error_Type),
|
||||
allocator: runtime.Allocator,
|
||||
) {
|
||||
router^ = Router(Error_Type) {
|
||||
root_node = new(RouterNode(Error_Type), allocator),
|
||||
allocator = allocator,
|
||||
}
|
||||
|
||||
rxt.insert(&router.radix_tree, joined_path[:], value)
|
||||
router_node_init(router, router.root_node, nil)
|
||||
}
|
||||
|
||||
router_remove_route :: proc(router: ^Router, key: string) -> (ok: bool){
|
||||
return rxt.remove(&router.radix_tree, key)
|
||||
router_destroy :: proc(router: ^Router($Error_Type)) {
|
||||
stack := make([dynamic]^RouterNode(Error_Type), router.allocator)
|
||||
defer delete(stack)
|
||||
|
||||
append(&stack, router.root_node)
|
||||
|
||||
for len(stack) > 0 {
|
||||
node := pop(&stack)
|
||||
for _, child in node.children {
|
||||
append(&stack, child)
|
||||
}
|
||||
|
||||
delete(node.handlers)
|
||||
delete(node.children)
|
||||
free(node)
|
||||
}
|
||||
}
|
||||
|
||||
router_lookup :: proc(
|
||||
router: ^Router($Error_Type),
|
||||
method: http.Method,
|
||||
path: []string,
|
||||
allocator := context.temp_allocator,
|
||||
) -> (
|
||||
handler: #type proc(request: ^http.Request) -> (http.Response, Error_Type),
|
||||
path_variables: map[string]string,
|
||||
ok: bool,
|
||||
) {
|
||||
path_variables = make(map[string]string, allocator)
|
||||
current_node := router.root_node
|
||||
|
||||
for segment_value in path {
|
||||
if child, found := current_node.children[segment_value]; found {
|
||||
current_node = child
|
||||
continue
|
||||
}
|
||||
|
||||
maybe_path_child: Maybe(^RouterNode(Error_Type)) = nil
|
||||
for name, child in current_node.children {
|
||||
switch type in child.type {
|
||||
case PathVariable:
|
||||
maybe_path_child = child
|
||||
path_variables[type.name] = segment_value
|
||||
break
|
||||
case Static:
|
||||
}
|
||||
}
|
||||
|
||||
if path_child, ok := maybe_path_child.?; ok {
|
||||
current_node = path_child
|
||||
} else {
|
||||
return handler, path_variables, false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if handler, ok := current_node.handlers[method]; ok {
|
||||
return handler, path_variables, true
|
||||
} else {
|
||||
return handler, path_variables, false
|
||||
}
|
||||
}
|
||||
|
||||
router_add_route :: proc(
|
||||
router: ^Router($Error_Type),
|
||||
method: http.Method,
|
||||
path: []string,
|
||||
handler: #type proc(request: ^http.Request) -> (http.Response, Error_Type),
|
||||
) {
|
||||
current_node := router.root_node
|
||||
|
||||
for segment_name in path {
|
||||
node, ok := current_node.children[segment_name]
|
||||
if !ok {
|
||||
new_node := new(RouterNode(Error_Type))
|
||||
|
||||
if strings.has_prefix(segment_name, ":") {
|
||||
router_node_init(router, new_node, segment_name[1:])
|
||||
} else {
|
||||
router_node_init(router, new_node, nil)
|
||||
}
|
||||
|
||||
current_node.children[segment_name] = new_node
|
||||
current_node = new_node
|
||||
continue
|
||||
}
|
||||
|
||||
current_node = node
|
||||
}
|
||||
|
||||
current_node.handlers[method] = handler
|
||||
}
|
||||
|
||||
router_remove_route :: proc(
|
||||
router: ^Router($Error_Type),
|
||||
method: http.Method,
|
||||
path: []string,
|
||||
) -> (
|
||||
ok: bool,
|
||||
) {
|
||||
current_node := router.root_node
|
||||
|
||||
for segment_name in path {
|
||||
node, ok := current_node.children[segment_name]
|
||||
if !ok do return false
|
||||
current_node = node
|
||||
}
|
||||
|
||||
if method not_in current_node.handlers {
|
||||
return false
|
||||
}
|
||||
|
||||
delete_key(¤t_node.handlers, method)
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
111
server.odin
111
server.odin
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue