Initial commit
This commit is contained in:
commit
1368b7b69b
10 changed files with 588 additions and 0 deletions
220
server.odin
Normal file
220
server.odin
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
package fjord
|
||||
|
||||
import "core:bufio"
|
||||
import "core:bytes"
|
||||
import "core:io"
|
||||
import "core:log"
|
||||
import "core:net"
|
||||
import "core:path/slashpath"
|
||||
import "core:strconv"
|
||||
import "core:strings"
|
||||
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,
|
||||
),
|
||||
}
|
||||
|
||||
@(private)
|
||||
request_ended :: proc(buf: []byte) -> bool {
|
||||
HTTP_END_SEQUENCE :: []byte{'\r', '\n', '\r', '\n'}
|
||||
return bytes.index(buf, HTTP_END_SEQUENCE) != -1
|
||||
}
|
||||
|
||||
read_connection :: proc(
|
||||
client_socket: net.TCP_Socket,
|
||||
allocator := context.temp_allocator,
|
||||
) -> (
|
||||
data: []byte,
|
||||
err: http.RequestError,
|
||||
) {
|
||||
TCP_CHUNK_SIZE :: 1024
|
||||
buffer: bytes.Buffer
|
||||
bytes.buffer_init_allocator(&buffer, 0, 0, allocator)
|
||||
|
||||
chunk: [TCP_CHUNK_SIZE]byte
|
||||
for {
|
||||
bytes_read, net_err := net.recv_tcp(client_socket, chunk[:])
|
||||
if net_err != nil do return data, .NetworkError
|
||||
|
||||
if bytes_read == 0 do break
|
||||
bytes.buffer_write(&buffer, chunk[:bytes_read])
|
||||
|
||||
if request_ended(buffer.buf[:]) do break
|
||||
}
|
||||
|
||||
return buffer.buf[:], nil
|
||||
}
|
||||
|
||||
server_init :: proc(
|
||||
server: ^Server($Error_Type),
|
||||
endpoint: net.Endpoint,
|
||||
allocator := context.allocator,
|
||||
) {
|
||||
server^ = Server(Error_Type) {
|
||||
endpoint = endpoint,
|
||||
not_found_handler = proc(
|
||||
request: ^http.Request,
|
||||
) -> (
|
||||
http.Response,
|
||||
Error_Type,
|
||||
) {
|
||||
body := "<div>Not found</div>"
|
||||
http_response := http.make_response(
|
||||
.NotFound,
|
||||
transmute([]byte)body,
|
||||
.Html,
|
||||
)
|
||||
return http_response, nil
|
||||
},
|
||||
}
|
||||
|
||||
router_init(&server.router)
|
||||
}
|
||||
|
||||
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
|
||||
server_set_not_found_handler :: proc(
|
||||
server: ^Server($Error_Type),
|
||||
not_found_handler: #type proc(
|
||||
request: ^http.Request,
|
||||
) -> (
|
||||
http.Response,
|
||||
Error_Type,
|
||||
),
|
||||
) {
|
||||
server.not_found_handler = not_found_handler
|
||||
}
|
||||
|
||||
match_handler_pattern :: proc(
|
||||
server: ^Server($Error_Type),
|
||||
method: http.Method,
|
||||
path: string,
|
||||
allocator := context.allocator,
|
||||
) -> (
|
||||
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)
|
||||
|
||||
handler = server.handlers[identifier].procedure
|
||||
|
||||
return handler, path_variables
|
||||
}
|
||||
|
||||
listen_and_serve :: proc(server: ^Server($Error_Type)) {
|
||||
server_socket, net_err := net.listen_tcp(server.endpoint)
|
||||
log.assert(net_err == nil, "Couldn't create TCP socket")
|
||||
defer net.close(server_socket)
|
||||
|
||||
for {
|
||||
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)
|
||||
continue
|
||||
}
|
||||
defer net.close(client_socket)
|
||||
|
||||
data, read_err := read_connection(
|
||||
client_socket,
|
||||
context.temp_allocator,
|
||||
)
|
||||
if read_err != nil {
|
||||
log.warnf("Failed to read request, reason: %s", read_err)
|
||||
continue
|
||||
}
|
||||
|
||||
http_request, unmarshall_err := http.unmarshall_request(
|
||||
data,
|
||||
context.temp_allocator,
|
||||
)
|
||||
if unmarshall_err != nil {
|
||||
log.warnf(
|
||||
"Failed to unmarshall request, reason: %s",
|
||||
unmarshall_err,
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
handler: #type proc(
|
||||
request: ^http.Request,
|
||||
) -> (
|
||||
http.Response,
|
||||
Error_Type,
|
||||
)
|
||||
handler, http_request.path_variables = match_handler_pattern(
|
||||
server,
|
||||
http_request.method,
|
||||
http_request.path,
|
||||
)
|
||||
|
||||
http_response, err := handler(&http_request)
|
||||
|
||||
marshalled_response := http.marshall_response(
|
||||
&http_response,
|
||||
context.temp_allocator,
|
||||
)
|
||||
net.send_tcp(client_socket, marshalled_response)
|
||||
|
||||
free_all(context.temp_allocator)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue