package http import "core:fmt" import "core:net" import "core:os" import "core:slice" import "core:strings" import "core:sync" import "core:sys/posix" import "core:thread" import "core:time" Method :: enum { Get, Post, Put, Delete, Patch, Head, Options, } Status_Code :: enum { Ok = 200, BadRequest = 400, NotFound = 404, RequestTimeout = 408, InternalServerError = 500, NotImplemented = 501, ServiceUnavailable = 503, } Status_Message :: #sparse[Status_Code]string { .Ok = "200 OK", .BadRequest = "400 Bad Request", .NotFound = "404 Not Found", .RequestTimeout = "408 Request Timeout", .InternalServerError = "500 Internal Server Error", .NotImplemented = "501 Not Implemented", .ServiceUnavailable = "503 Service Unavailable", } Request :: struct { _raw_head: []u8, host: string, method: Method, target: string, // TODO: Implement URI type version: string, headers: [dynamic]Header, content_length: int, body: []u8, } Response :: struct { code: Status_Code, headers: [dynamic]Header, body: []u8, } Handler :: proc(req: Request, res: ^Response, user_data: rawptr) // TODO: stream support when body grows very large? respond :: proc { respond_empty, respond_string, respond_bytes, //respond_stream, } respond_empty :: proc(res: ^Response, code: Status_Code) { res.code = code res.body = nil } respond_string :: proc(res: ^Response, code: Status_Code, str: string) { res.code = code res.body = slice.clone(transmute([]u8)str[:]) } respond_bytes :: proc(res: ^Response, code: Status_Code, buf: []u8) { res.code = code res.body = slice.clone(buf) } /*respond_stream :: proc(res: ^Response, code: Status_Code, ...) { }*/ MAX_CONNECTIONS :: 128 serve :: proc( endpoint: net.Endpoint, h: proc(req: Request, res: ^Response, user_data: rawptr), data: rawptr, ) { posix.signal(.SIGINT, should_quit_handler) posix.signal(.SIGTERM, should_quit_handler) listener, err := net.listen_tcp(endpoint) if err != nil { fmt.eprintf("listen_tcp failed: %v", err) os.exit(1) } defer net.close(listener) net.set_option(listener, .Receive_Timeout, time.Millisecond * 10) fmt.printf("Glaze listening on %s\n", net.to_string(endpoint)) free_all(context.temp_allocator) active_connections: int for !SHOULD_QUIT { client, _, err := net.accept_tcp(listener) if err != nil { if err == .Would_Block || err == .Interrupted { continue } fmt.eprintf("accept_tcp failed: %v\n", err) os.exit(1) // continue } if active_connections >= MAX_CONNECTIONS { send_response_error(client, .ServiceUnavailable) net.close(client) continue } sync.atomic_add(&active_connections, 1) cj := new(Conn_Job) cj.conn = { sock = client, } cj.handler = h cj.user_data = data cj.active_connections = &active_connections thread.create_and_start_with_poly_data(cj, handle_conn_job, context, self_cleanup = true) } } // allocates only if escapeable character found escape_html :: proc(s: string) -> (string, bool) { escape_with := make(map[rune]string) defer delete(escape_with) escape_with['&'] = "&" escape_with['<'] = "<" escape_with['>'] = ">" escape_with['"'] = """ escape_with['\''] = "'" needs_escaping: bool for c in s { if c in escape_with { needs_escaping = true } } if needs_escaping { b: strings.Builder strings.builder_init(&b) for c in s { if c in escape_with { strings.write_string(&b, escape_with[c]) } else { strings.write_rune(&b, c) } } return strings.to_string(b), true } return s, false }