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 := "
Not found
" 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) } }