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:sync" import "core:unicode" import http "http" Server :: struct($Error_Type: typeid) { endpoint: net.Endpoint, not_found_handler: #type proc( request: ^http.Request, ) -> ( http.Response, Error_Type, ), router: Router(Error_Type), shutdown: bool, } @(private) read_connection :: proc( client_socket: net.TCP_Socket, allocator := context.temp_allocator, ) -> ( data: []byte, err: http.RequestError, ) { TCP_CHUNK_SIZE :: 1024 HTTP_END_SEQUENCE :: []byte{'\r', '\n', '\r', '\n'} 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 prev_len := len(buffer.buf) bytes.buffer_write(&buffer, chunk[:bytes_read]) search_start := max(0, prev_len - 3) if bytes.index(buffer.buf[search_start:], HTTP_END_SEQUENCE) != -1 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 := "
Not found
" http_response := http.make_response( .NotFound, transmute([]byte)body, .Html, ) return http_response, nil }, shutdown = false, } router_init(&server.router, allocator) } server_destroy :: proc(server: ^Server($Error_Type)) { router_destroy(&server.router) } // 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( request: ^http.Request, ) -> ( http.Response, Error_Type, ), ) { server.not_found_handler = not_found_handler } server_add_route :: proc( server: ^Server($Error_Type), method: http.Method, path: []string, handler: #type proc(request: ^http.Request) -> (http.Response, Error_Type), ) { router_add_route(&server.router, method, path, handler) } server_remove_route :: proc( server: ^Server($Error_Type), method: http.Method, path: []string, ) -> ( ok: bool, ) { return router_remove_route(&server.router, method, path) } server_shutdown :: proc(server: ^Server($Error_Type)) { sync.atomic_store(&server.shutdown, true) } 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 !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) 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, ) ok: bool // 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, context.temp_allocator, ) net.send_tcp(client_socket, marshalled_response) free_all(context.temp_allocator) } }