From 9bbd382e105704b0c09ffeb17e76529b865570f1 Mon Sep 17 00:00:00 2001 From: Toni Date: Tue, 7 Jul 2026 19:40:25 +0200 Subject: [PATCH] http server --- examples/chip8.zr | 2 +- examples/curl.zr | 8 ++-- examples/serve.zr | 96 ++++++++++++++++++++++++++++++++++++++++++ examples/tcp_server.zr | 26 ------------ std/core.zr | 14 ------ std/io.zr | 7 +++ std/json.zr | 44 +++++++++---------- std/mem.zr | 2 +- std/num.zr | 12 +++++- std/os.zr | 12 ++++++ std/posix.zr | 39 +++++++++-------- std/str.zr | 10 ----- test.zr | 3 +- 13 files changed, 178 insertions(+), 97 deletions(-) create mode 100644 examples/serve.zr delete mode 100644 examples/tcp_server.zr delete mode 100644 std/core.zr diff --git a/examples/chip8.zr b/examples/chip8.zr index 3586275..d60faf7 100644 --- a/examples/chip8.zr +++ b/examples/chip8.zr @@ -278,7 +278,7 @@ func main[argc: i64, argv: ptr] : i64 disassemble := false for i in 1..argc - arg := mem.read64(argv + i * 8) as str + arg := os.arg(argv, i) if arg->equal("-d") disassemble = true else diff --git a/examples/curl.zr b/examples/curl.zr index c344acf..c5a1eed 100644 --- a/examples/curl.zr +++ b/examples/curl.zr @@ -6,7 +6,7 @@ func main[argc: i64, argv: ptr] : i64 io.println("ERROR: url is missing") return 1 - url := mem.read64(argv + 8) as str + url := os.arg(argv, 1) if url->len() <= 7 io.println("ERROR: invalid url (Did you forget \"http://\"?)") @@ -34,9 +34,9 @@ func main[argc: i64, argv: ptr] : i64 panic("failed to connect") // seems reasonable - req := _stackalloc(4096) - req_size := str.format_into(req as str, 4096, "GET %s HTTP/1.0\r\nHost: %s\r\nConnection: close\r\n\r\n", path, host) - net.send(s, req, req_size) + req := _stackalloc(4096) as str + req_size := req->format_into(4096, "GET %s HTTP/1.0\r\nHost: %s\r\nConnection: close\r\n\r\n", path, host) + net.send(s, req as ptr, req_size) header_buf := mem.alloc(8192) as str header_size := 0 diff --git a/examples/serve.zr b/examples/serve.zr new file mode 100644 index 0000000..00a0ba0 --- /dev/null +++ b/examples/serve.zr @@ -0,0 +1,96 @@ +// needs to be compiled with -m -C "-lpthread" +include "std/net.zr" + +extern pthread_create +extern pthread_detach + +struct ConnData + sock: i64 + addr: str + +func handle_connection[conn: ConnData] : void + req := _stackalloc(10001) as str + n := net.read(conn->sock, req as ptr, 10000) + if n <= 0 + net.close(conn->sock) + conn->addr->free() + (conn as ptr)->free() + return + + req[n] = 0 + + pos := req->find("\r\n") + if pos == -1 + panic("TODO: bad request") + + first_line := req->substr_n(0, pos) + parts := first_line->split(" ") + method := parts->nth(0) as str + path := parts->nth(1) as str + + if !method->equal("GET") + // TODO: method not supported + return + + if path->equal("/") + // TODO: index + return + + // TODO: ANY validation + file_path := "."->concat(path) + + file_size, ok := os.get_file_size(file_path) + if !ok + // TODO: not found + return + + fd := _builtin_syscall(SYS_openat, AT_FDCWD, file_path, O_RDONLY, 0) + if fd < 0 + // TODO: not found + return + + resp := "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n" + net.send(conn->sock, resp as ptr, resp->len()) + + offset := 0 + while offset < file_size + sent := _builtin_syscall(SYS_sendfile, conn->sock, fd, ^offset, IP_MAXPACKET) + if sent <= 0 + break + + net.close(conn->sock) + + _builtin_syscall(SYS_close, fd) + file_path->free() + first_line->free() + parts->free_with_items() + conn->addr->free() + (conn as ptr)->free() + +func main[argc: i64, argv: ptr] : i64 + port := 8000 + + if argc > 1 + port = os.arg(argv, 1)->parse_i64() + + server_sock, ok := net.listen("0.0.0.0", port) + if !ok + panic("failed to listen") + + io.printf("Listening on :%d...\n", port) + + addr := _stackalloc(16) + while true + conn := net.accept(server_sock, addr) + if conn < 0 + io.println("ERROR: failed to accept connection") + continue + + conn_data := new* ConnData + conn_data->sock = conn + conn_data->addr = mem.alloc(30) as str + conn_data->addr->format_into(30, "%d.%d.%d.%d:%d", addr[4], addr[5], addr[6], addr[7], mem.read16be(addr + 2)) + + thread := 0 + pthread_create(^thread, 0, ^handle_connection, conn_data) + pthread_detach(thread) diff --git a/examples/tcp_server.zr b/examples/tcp_server.zr deleted file mode 100644 index 38a3f46..0000000 --- a/examples/tcp_server.zr +++ /dev/null @@ -1,26 +0,0 @@ -include "std/io.zr" -include "std/net.zr" - -func main[] : i64 - s, ok := net.listen("127.0.0.1", 8000) - if !ok - panic("failed to listen") - - io.println("Listening on port 8000...") - - resp := mem.alloc(IP_MAXPACKET) - addr := _stackalloc(16) - while true - conn := net.accept(s, addr) - if conn < 0 - continue - - io.printf("%d.%d.%d.%d:%d\n", addr[4], addr[5], addr[6], addr[7], mem.read16be(addr + 2)) - - n := net.read(conn, resp, IP_MAXPACKET) - - io.print_sized(resp, n) - io.println("") - - net.send(conn, resp, n) - net.close(conn) diff --git a/std/core.zr b/std/core.zr deleted file mode 100644 index bf89977..0000000 --- a/std/core.zr +++ /dev/null @@ -1,14 +0,0 @@ -include "std/linux_syscalls.zr" -include "std/posix.zr" - -// weird like that so num doesnt depend on io -func panic[msg: str] : void - len := 0 - while msg[len] - len += 1 - _builtin_syscall(SYS_write, 2, "PANIC: ", 7) - _builtin_syscall(SYS_write, 2, msg, len) - _builtin_syscall(SYS_write, 2, "\n", 1) - // for gdb backtrace - _builtin_syscall(SYS_kill, _builtin_syscall(SYS_getpid), SIGABRT) - _builtin_syscall(SYS_exit, 1) diff --git a/std/io.zr b/std/io.zr index e630365..6d07774 100644 --- a/std/io.zr +++ b/std/io.zr @@ -1,4 +1,11 @@ include "std/str.zr" +include "std/os.zr" + +func panic[msg: str] : void + io.printf("PANIC: %s\n", msg) + // for gdb backtrace + _builtin_syscall(SYS_kill, os.getpid(), SIGABRT) + os.exit(1) func io.printf[..] : void s := _var_arg(0) as ptr diff --git a/std/json.zr b/std/json.zr index 261bec9..39e60d8 100644 --- a/std/json.zr +++ b/std/json.zr @@ -1,11 +1,11 @@ include "std/containers.zr" -const JSON_OBJECT = 0 -const JSON_ARRAY = 1 -const JSON_STRING = 2 -const JSON_NUMBER = 3 -const JSON_NULL = 4 -const JSON_BOOL = 5 +const json.OBJECT = 0 +const json.ARRAY = 1 +const json.STRING = 2 +const json.NUMBER = 3 +const json.NULL = 4 +const json.BOOL = 5 struct json.Value type: i64 @@ -14,7 +14,7 @@ struct json.Value func json.Value.dump[v: json.Value] : str s := new str.Builder - if v->type == JSON_OBJECT + if v->type == json.OBJECT map := v->value as HashMap keys := map->keys() s->append_char('{') @@ -29,7 +29,7 @@ func json.Value.dump[v: json.Value] : str s->append((value as json.Value)->dump()) s->append_char('}') keys->free() - else if v->type == JSON_ARRAY + else if v->type == json.ARRAY arr := v->value as Array s->append_char('[') for i in 0..arr->size @@ -37,16 +37,16 @@ func json.Value.dump[v: json.Value] : str s->append_char(',') s->append((arr->nth(i) as json.Value)->dump()) s->append_char(']') - else if v->type == JSON_STRING + else if v->type == json.STRING // TODO: escape sequences s->append_char('"') s->append(v->value as str) s->append_char('"') - else if v->type == JSON_NUMBER + else if v->type == json.NUMBER s->append((v->value as i64)->to_str()) - else if v->type == JSON_NULL + else if v->type == json.NULL s->append("null") - else if v->type == JSON_BOOL + else if v->type == json.BOOL if v->value as bool s->append("true") else @@ -57,7 +57,7 @@ func json.Value.dump[v: json.Value] : str return s->build_and_free_buffer() func json.Value.free[v: json.Value] : void - if v->type == JSON_OBJECT + if v->type == json.OBJECT map := v->value as HashMap keys := map->keys() for i in 0..keys->size @@ -66,12 +66,12 @@ func json.Value.free[v: json.Value] : void keys->free() map->free_with_keys() - else if v->type == JSON_ARRAY + else if v->type == json.ARRAY arr := v->value as Array for i in 0..arr->size (arr->nth(i) as json.Value)->free() arr->free() - else if v->type == JSON_STRING + else if v->type == json.STRING (v->value as str)->free() (v as ptr)->free() @@ -92,7 +92,7 @@ func json._parse_value[s: str, i: ptr] : json.Value mem.write64(i, mem.read64(i) + 1) json._skip_whitespace(s, i) - out->type = JSON_OBJECT + out->type = json.OBJECT out->value = HashMap.new() if s[mem.read64(i)] != '}' @@ -124,7 +124,7 @@ func json._parse_value[s: str, i: ptr] : json.Value mem.write64(i, mem.read64(i) + 1) json._skip_whitespace(s, i) - out->type = JSON_ARRAY + out->type = json.ARRAY out->value = [] while s[mem.read64(i)] != ']' @@ -143,7 +143,7 @@ func json._parse_value[s: str, i: ptr] : json.Value panic("json.parse: expected ']'") mem.write64(i, mem.read64(i) + 1) else if s[mem.read64(i)] == '"' - out->type = JSON_STRING + out->type = json.STRING out->value = json._parse_string(s, i) else if s[mem.read64(i)]->is_digit() || s[mem.read64(i)] == '-' start := mem.read64(i) @@ -160,18 +160,18 @@ func json._parse_value[s: str, i: ptr] : json.Value len := mem.read64(i) - start num_str := s->substr_n(start, len) - out->type = JSON_NUMBER + out->type = json.NUMBER out->value = num_str->parse_i64() num_str->free() else if s[mem.read64(i)] == 'n' && s[mem.read64(i)+1] == 'u' && s[mem.read64(i)+2] == 'l' && s[mem.read64(i)+3] == 'l' - out->type = JSON_NULL + out->type = json.NULL mem.write64(i, mem.read64(i) + 4) else if s[mem.read64(i)] == 't' && s[mem.read64(i)+1] == 'r' && s[mem.read64(i)+2] == 'u' && s[mem.read64(i)+3] == 'e' - out->type = JSON_BOOL + out->type = json.BOOL out->value = true mem.write64(i, mem.read64(i) + 4) else if s[mem.read64(i)] == 'f' && s[mem.read64(i)+1] == 'a' && s[mem.read64(i)+2] == 'l' && s[mem.read64(i)+3] == 's' && s[mem.read64(i)+4] == 'e' - out->type = JSON_BOOL + out->type = json.BOOL out->value = false mem.write64(i, mem.read64(i) + 5) else diff --git a/std/mem.zr b/std/mem.zr index f855ea7..d3850ef 100644 --- a/std/mem.zr +++ b/std/mem.zr @@ -33,7 +33,7 @@ func mem._request_space[size: i64] : mem.Block alloc_size := (needed + 4095) & -4096 blk := _builtin_syscall(SYS_mmap, 0, alloc_size, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) as mem.Block - if (blk as ptr as i64) == -1 + if (blk as ptr as i64) == MAP_FAILED panic("mem._request_space: failed to mmap") blk->size = alloc_size - MEM_BLOCK_SIZE diff --git a/std/num.zr b/std/num.zr index 5a45023..8bb25a9 100644 --- a/std/num.zr +++ b/std/num.zr @@ -1,4 +1,4 @@ -include "std/core.zr" +include "std/io.zr" func i64.abs[n: i64] : i64 if n == -9223372036854775808 @@ -97,6 +97,16 @@ func i64.to_hex_str_buf[n: i64, buf: ptr] : void buf[len] = 0 +func i64.to_str[n: i64] : str + out := mem.alloc(21) + n->to_str_buf(out) + return out as str + +func i64.to_hex_str[n: i64] : str + out := mem.alloc(17) + n->to_hex_str_buf(out) + return out as str + func f64.to_str_buf[x: i64, buf: ptr] : void bits := x sign := (bits >> 63) & 1 diff --git a/std/os.zr b/std/os.zr index 6f61bb2..fafdbbf 100644 --- a/std/os.zr +++ b/std/os.zr @@ -9,6 +9,9 @@ func os.exit[code: i64] : void func os.getpid[] : i64 return _builtin_syscall(SYS_getpid) +func os.arg[argv: ptr, n: i64] : str + return mem.read64(argv + n * 8) as str + func os.getenv[name: str] : str, bool name_len := name->len() i := 0 @@ -96,6 +99,15 @@ func os.run_shell_command[command: str] : i64, bool func os.file_exists[path: str] : bool return _builtin_syscall(SYS_faccessat, AT_FDCWD, path, F_OK, 0) == 0 +func os.get_file_size[path: str] : i64, bool + st := _stackalloc(256) // it has 21 mixed-size fields so ptr for now + + rc := _builtin_syscall(SYS_newfstatat, AT_FDCWD, path, st, 0) + if rc != 0 + return 0, false + + return mem.read64(st + 48), true + func os.is_a_directory[path: str] : bool st := _stackalloc(256) // it has 21 mixed-size fields so ptr for now diff --git a/std/posix.zr b/std/posix.zr index 2cb0993..c7493b8 100644 --- a/std/posix.zr +++ b/std/posix.zr @@ -1,27 +1,32 @@ -const STDIN_FILENO = 0 -const STDOUT_FILENO = 1 +const STDIN_FILENO = 0 +const STDOUT_FILENO = 1 -const SIGABRT = 6 +const SIGABRT = 6 -const AT_FDCWD = -100 +const AT_FDCWD = -100 -const S_IFDIR = 16384 -const S_IFMT = 61440 +const S_IFDIR = 16384 +const S_IFMT = 61440 -const PROT_READ = 1 -const PROT_WRITE = 2 +const PROT_NONE = 0 +const PROT_READ = 1 +const PROT_WRITE = 2 +const PROT_EXEC = 4 -const MAP_PRIVATE = 2 -const MAP_ANONYMOUS = 32 +const MAP_FAILED = -1 +const MAP_SHARED = 1 +const MAP_PRIVATE = 2 +const MAP_FIXED = 16 +const MAP_ANONYMOUS = 32 -const O_RDONLY = 0 -const O_WRONLY = 1 -const O_CREAT = 64 -const O_TRUNC = 512 +const O_RDONLY = 0 +const O_WRONLY = 1 +const O_CREAT = 64 +const O_TRUNC = 512 -const SEEK_SET = 0 -const SEEK_END = 2 +const SEEK_SET = 0 +const SEEK_END = 2 -const F_OK = 0 +const F_OK = 0 const CLOCK_REALTIME = 0 diff --git a/std/str.zr b/std/str.zr index 1b03342..9e9b306 100644 --- a/std/str.zr +++ b/std/str.zr @@ -297,13 +297,3 @@ func str.Builder.free_buffer[b: str.Builder] : void b->size = 0 b->capacity = 0 -// here to prevent num depending on mem -func i64.to_str[n: i64] : str - out := mem.alloc(21) - n->to_str_buf(out) - return out as str - -func i64.to_hex_str[n: i64] : str - out := mem.alloc(17) - n->to_hex_str_buf(out) - return out as str diff --git a/test.zr b/test.zr index f1f00d5..d880935 100644 --- a/test.zr +++ b/test.zr @@ -67,11 +67,12 @@ func main[] : i64 tr := new TestRunner tr->build_blacklist = [] - tr->run_blacklist = ["raylib.zr", "sqlite_todo.zr", "guess_number.zr", "udp_server.zr", "chip8.zr", "tcp_server.zr"] + tr->run_blacklist = ["raylib.zr", "sqlite_todo.zr", "guess_number.zr", "udp_server.zr", "chip8.zr", "serve.zr"] tr->custom_build_flags = HashMap.new() tr->custom_build_flags->insert("raylib.zr", "-m -C \"-lraylib\"") tr->custom_build_flags->insert("chip8.zr", "-m -C \"-lraylib\"") tr->custom_build_flags->insert("sqlite_todo.zr", "-m -C \"-lsqlite3\"") + tr->custom_build_flags->insert("serve.zr", "-m -C \"-lpthread\"") tr->custom_run_args = HashMap.new() tr->custom_run_args->insert("curl.zr", "http://example.com")