feat: std.http — single-worker HTTP/1.1 server core (PLAN-HTTPZ S7a)
The httpz shape, one worker, handlers inline over the std.event Loop: nonblocking accept, per-connection state machine (reading -> writing -> keepalive/close) with incremental parsing (request line, headers, Content-Length body), partial-write continuation via on-demand write interest, pipelined-request draining, and timeouts as EVICTION — request-delivery and keepalive-idle deadlines on the monotonic clock, checked after I/O each tick. Keep-alive is the HTTP/1.1 default; Connection header, HTTP/1.0, or the per-connection request_count cap turn it off. Config mirrors httpz: port/backlog/max_conn/read_buf_cap/ timeout_request_ms/timeout_keepalive_ms/request_count. API: Server.init(cfg, handler) + tick(max_wait_ms); run() is the forever-tick loop. tick makes the server drivable single-threaded — examples/1633 runs a live server and its client sockets in ONE thread, pinning: GET with keep-alive, actual connection reuse, the request cap answering Connection: close then EOF, POST body echo, 404 routing, and a half-header client evicted at the request deadline while a healthy client keeps being served. Verified under sx run AND sx build. Connection slots and read buffers are reused across connections (httpz's min_conn/buffer-pool spirit); response buffers are allocated per response and freed on completion. Serialization happens while request views are valid, the served bytes are compacted, and only then does sending start — write_more's pipelining check must see only the remainder. The std.sx barrel carries http; .ir snapshot regen is the usual mechanical renumbering. S7b adds worker counts + the handler thread pool (needs C2/S6); the epoll backend activates with the linux target (S4/S7c).
This commit is contained in:
185
examples/1633-http-server.sx
Normal file
185
examples/1633-http-server.sx
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
// std.http S7a (PLAN-HTTPZ): a live single-worker server and its
|
||||||
|
// clients driven in ONE thread via Server.tick — keep-alive reuse,
|
||||||
|
// POST body echo, the per-connection request cap closing politely,
|
||||||
|
// 404 routing, and half-a-header eviction at the request deadline
|
||||||
|
// while the server keeps serving others.
|
||||||
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
PORT :: 18933;
|
||||||
|
|
||||||
|
handler :: (req: *http.Request, resp: *http.Response) {
|
||||||
|
if req.path == "/hello" {
|
||||||
|
resp.body = concat("hello ", req.method);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if req.path == "/echo" {
|
||||||
|
resp.body = req.body;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
resp.status = 404;
|
||||||
|
resp.body = "nope";
|
||||||
|
}
|
||||||
|
|
||||||
|
contains :: (hay: string, needle: string) -> bool {
|
||||||
|
if needle.len > hay.len { return false; }
|
||||||
|
i := 0;
|
||||||
|
while i + needle.len <= hay.len {
|
||||||
|
j := 0;
|
||||||
|
ok := true;
|
||||||
|
while j < needle.len {
|
||||||
|
if hay[i + j] != needle[j] { ok = false; break; }
|
||||||
|
j += 1;
|
||||||
|
}
|
||||||
|
if ok { return true; }
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect a nonblocking loopback client.
|
||||||
|
dial :: () -> i32 {
|
||||||
|
fd := socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0);
|
||||||
|
if fd < 0 { return -1; }
|
||||||
|
addr : socket.SockAddr = .{
|
||||||
|
sin_len = 16, sin_family = xx socket.AF_INET,
|
||||||
|
sin_port = socket.htons(PORT), sin_addr = 0x0100007F,
|
||||||
|
};
|
||||||
|
if socket.connect(fd, @addr, 16) != 0 { socket.close(fd); return -1; }
|
||||||
|
if !socket.set_nonblocking(fd) { socket.close(fd); return -1; }
|
||||||
|
return fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
// True when `buf[0..len]` holds a complete response (headers + body).
|
||||||
|
resp_complete :: (buf: [*]u8, len: i64) -> bool {
|
||||||
|
s := string.{ ptr = buf, len = xx len };
|
||||||
|
he := -1;
|
||||||
|
i := 0;
|
||||||
|
while i + 3 < s.len {
|
||||||
|
if s[i] == 13 and s[i+1] == 10 and s[i+2] == 13 and s[i+3] == 10 { he = i; break; }
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
if he < 0 { return false; }
|
||||||
|
// Content-Length digits
|
||||||
|
cl : i64 = 0;
|
||||||
|
seen := false;
|
||||||
|
j := 0;
|
||||||
|
needle := "Content-Length: ";
|
||||||
|
while j + needle.len < s.len {
|
||||||
|
k := 0;
|
||||||
|
ok := true;
|
||||||
|
while k < needle.len { if s[j + k] != needle[k] { ok = false; break; } k += 1; }
|
||||||
|
if ok {
|
||||||
|
d := j + needle.len;
|
||||||
|
while d < s.len and s[d] >= 48 and s[d] <= 57 { cl = cl * 10 + (s[d] - 48); d += 1; }
|
||||||
|
seen = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
j += 1;
|
||||||
|
}
|
||||||
|
if !seen { return false; }
|
||||||
|
return len >= he + 4 + cl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send a request and tick the server until its full response arrives.
|
||||||
|
// Returns the response text ("" = the connection closed instead).
|
||||||
|
roundtrip :: (srv: *http.Server, fd: i32, reqtext: string, scratch: [*]u8) -> string {
|
||||||
|
socket.write(fd, reqtext.ptr, xx reqtext.len);
|
||||||
|
total : i64 = 0;
|
||||||
|
tries := 0;
|
||||||
|
while tries < 400 {
|
||||||
|
srv.tick(5) catch {};
|
||||||
|
n, re := socket.read_nb(fd, @scratch[total], xx (4096 - total));
|
||||||
|
if !re { total += n; }
|
||||||
|
else if re == error.Closed { return string.{ ptr = scratch, len = xx total }; }
|
||||||
|
if resp_complete(scratch, total) { return string.{ ptr = scratch, len = xx total }; }
|
||||||
|
tries += 1;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
main :: () -> i32 {
|
||||||
|
cfg : http.Config = .{
|
||||||
|
port = PORT,
|
||||||
|
timeout_request_ms = 150,
|
||||||
|
timeout_keepalive_ms = 400,
|
||||||
|
request_count = 3,
|
||||||
|
max_conn = 8,
|
||||||
|
};
|
||||||
|
srv, se := http.Server.init(cfg, handler);
|
||||||
|
if se { print("server init failed\n"); return 1; }
|
||||||
|
|
||||||
|
buf : [4096]u8 = ---;
|
||||||
|
|
||||||
|
// ── 1. GET, keep-alive default ────────────────────────────────────
|
||||||
|
c1 := dial();
|
||||||
|
if c1 < 0 { print("dial failed\n"); return 1; }
|
||||||
|
r1 := roundtrip(@srv, c1, "GET /hello HTTP/1.1\r\nHost: t\r\n\r\n", @buf[0]);
|
||||||
|
if !contains(r1, "HTTP/1.1 200 OK") { print("case1: bad status\n"); return 1; }
|
||||||
|
if !contains(r1, "hello GET") { print("case1: bad body\n"); return 1; }
|
||||||
|
if !contains(r1, "Connection: keep-alive") { print("case1: expected keep-alive\n"); return 1; }
|
||||||
|
print("GET 200, keep-alive\n");
|
||||||
|
|
||||||
|
// ── 2. same socket again: the connection was actually reused ─────
|
||||||
|
r2 := roundtrip(@srv, c1, "GET /hello HTTP/1.1\r\nHost: t\r\n\r\n", @buf[0]);
|
||||||
|
if !contains(r2, "hello GET") { print("case2: keep-alive reuse failed\n"); return 1; }
|
||||||
|
print("keep-alive reuse ok\n");
|
||||||
|
|
||||||
|
// ── 3. third request hits request_count: Connection: close + EOF ─
|
||||||
|
r3 := roundtrip(@srv, c1, "GET /hello HTTP/1.1\r\nHost: t\r\n\r\n", @buf[0]);
|
||||||
|
if !contains(r3, "Connection: close") { print("case3: expected close at cap\n"); return 1; }
|
||||||
|
drained := false;
|
||||||
|
tries := 0;
|
||||||
|
while !drained and tries < 200 {
|
||||||
|
srv.tick(5) catch {};
|
||||||
|
zq, ze := socket.read_nb(c1, @buf[0], 64);
|
||||||
|
if ze == error.Closed { drained = true; }
|
||||||
|
if !ze and zq == 0 { drained = true; }
|
||||||
|
tries += 1;
|
||||||
|
}
|
||||||
|
if !drained { print("case3: server did not close at the cap\n"); return 1; }
|
||||||
|
socket.close(c1);
|
||||||
|
print("request cap: close + EOF\n");
|
||||||
|
|
||||||
|
// ── 4. POST body echo ─────────────────────────────────────────────
|
||||||
|
c2 := dial();
|
||||||
|
if c2 < 0 { print("dial2 failed\n"); return 1; }
|
||||||
|
r4 := roundtrip(@srv, c2, "POST /echo HTTP/1.1\r\nHost: t\r\nContent-Length: 9\r\n\r\nping-pong", @buf[0]);
|
||||||
|
if !contains(r4, "ping-pong") { print("case4: body not echoed\n"); return 1; }
|
||||||
|
print("POST echo ok\n");
|
||||||
|
|
||||||
|
// ── 5. unknown path routes 404 ────────────────────────────────────
|
||||||
|
r5 := roundtrip(@srv, c2, "GET /missing HTTP/1.1\r\nHost: t\r\n\r\n", @buf[0]);
|
||||||
|
if !contains(r5, "HTTP/1.1 404 Not Found") { print("case5: expected 404\n"); return 1; }
|
||||||
|
socket.close(c2);
|
||||||
|
print("404 routing ok\n");
|
||||||
|
|
||||||
|
// ── 6. half a header is evicted at the request deadline, while a
|
||||||
|
// healthy client keeps being served ──────────────────────────
|
||||||
|
c3 := dial();
|
||||||
|
if c3 < 0 { print("dial3 failed\n"); return 1; }
|
||||||
|
half := "GET /hel";
|
||||||
|
socket.write(c3, half.ptr, xx half.len);
|
||||||
|
gone := event.deadline_in(300); // > timeout_request_ms
|
||||||
|
while !event.expired(gone) { srv.tick(5) catch {}; }
|
||||||
|
c4 := dial();
|
||||||
|
if c4 < 0 { print("dial4 failed\n"); return 1; }
|
||||||
|
r6 := roundtrip(@srv, c4, "GET /hello HTTP/1.1\r\nHost: t\r\n\r\n", @buf[0]);
|
||||||
|
if !contains(r6, "hello GET") { print("case6: healthy client starved\n"); return 1; }
|
||||||
|
evicted := false;
|
||||||
|
tries = 0;
|
||||||
|
while !evicted and tries < 200 {
|
||||||
|
srv.tick(5) catch {};
|
||||||
|
zq2, ze2 := socket.read_nb(c3, @buf[0], 64);
|
||||||
|
if ze2 == error.Closed { evicted = true; }
|
||||||
|
if !ze2 and zq2 == 0 { evicted = true; }
|
||||||
|
tries += 1;
|
||||||
|
}
|
||||||
|
if !evicted { print("case6: half-header connection never evicted\n"); return 1; }
|
||||||
|
socket.close(c3);
|
||||||
|
socket.close(c4);
|
||||||
|
print("slow client evicted, healthy client served\n");
|
||||||
|
|
||||||
|
srv.close();
|
||||||
|
print("http server ok\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -887,6 +887,54 @@ declare i1 @expired(ptr, i64) #0
|
|||||||
; Function Attrs: nounwind
|
; Function Attrs: nounwind
|
||||||
declare i64 @remaining_ms(ptr, i64) #0
|
declare i64 @remaining_ms(ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare ptr @find_header(ptr, ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare i1 @ascii_ieq(ptr, ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare ptr @reason_for(ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.init(ptr sret({ { { i64, i32, i64, i64, i64, i64, i64 }, { i32 }, i32, ptr, { ptr, ptr, ptr }, ptr }, i32 }), ptr, ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.close(ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare i64 @Server.free_slot(ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.conn_close(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare i32 @Server.tick(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.run(ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.accept_ready(ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.read_more(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.serve_buffered(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare i1 @Server.try_serve_one(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.serialize_response(ptr, ptr, i64, ptr, i1) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.write_more(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.respond_error_close(ptr, ptr, i64, i64) #0
|
||||||
|
|
||||||
; Function Attrs: nounwind
|
; Function Attrs: nounwind
|
||||||
define internal i64 @accept_c(ptr %0) #0 {
|
define internal i64 @accept_c(ptr %0) #0 {
|
||||||
entry:
|
entry:
|
||||||
|
|||||||
@@ -1379,6 +1379,54 @@ declare i1 @expired(ptr, i64) #0
|
|||||||
; Function Attrs: nounwind
|
; Function Attrs: nounwind
|
||||||
declare i64 @remaining_ms(ptr, i64) #0
|
declare i64 @remaining_ms(ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare ptr @find_header(ptr, ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare i1 @ascii_ieq(ptr, ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare ptr @reason_for(ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.init(ptr sret({ { { i64, i32, i64, i64, i64, i64, i64 }, { i32 }, i32, ptr, { ptr, ptr, ptr }, ptr }, i32 }), ptr, ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.close(ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare i64 @Server.free_slot(ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.conn_close(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare i32 @Server.tick(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.run(ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.accept_ready(ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.read_more(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.serve_buffered(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare i1 @Server.try_serve_one(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.serialize_response(ptr, ptr, i64, ptr, i1) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.write_more(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.respond_error_close(ptr, ptr, i64, i64) #0
|
||||||
|
|
||||||
; Function Attrs: nounwind
|
; Function Attrs: nounwind
|
||||||
declare void @BuildOptions.add_link_flag.77(i64, ptr) #0
|
declare void @BuildOptions.add_link_flag.77(i64, ptr) #0
|
||||||
|
|
||||||
|
|||||||
@@ -1395,6 +1395,54 @@ declare i1 @expired(ptr, i64) #0
|
|||||||
; Function Attrs: nounwind
|
; Function Attrs: nounwind
|
||||||
declare i64 @remaining_ms(ptr, i64) #0
|
declare i64 @remaining_ms(ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare ptr @find_header(ptr, ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare i1 @ascii_ieq(ptr, ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare ptr @reason_for(ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.init(ptr sret({ { { i64, i32, i64, i64, i64, i64, i64 }, { i32 }, i32, ptr, { ptr, ptr, ptr }, ptr }, i32 }), ptr, ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.close(ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare i64 @Server.free_slot(ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.conn_close(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare i32 @Server.tick(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.run(ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.accept_ready(ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.read_more(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.serve_buffered(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare i1 @Server.try_serve_one(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.serialize_response(ptr, ptr, i64, ptr, i1) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.write_more(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.respond_error_close(ptr, ptr, i64, i64) #0
|
||||||
|
|
||||||
; Function Attrs: nounwind
|
; Function Attrs: nounwind
|
||||||
declare void @BuildOptions.add_link_flag.77(i64, ptr) #0
|
declare void @BuildOptions.add_link_flag.77(i64, ptr) #0
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1371,6 +1371,54 @@ declare i1 @expired(ptr, i64) #0
|
|||||||
; Function Attrs: nounwind
|
; Function Attrs: nounwind
|
||||||
declare i64 @remaining_ms(ptr, i64) #0
|
declare i64 @remaining_ms(ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare ptr @find_header(ptr, ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare i1 @ascii_ieq(ptr, ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare ptr @reason_for(ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.init(ptr sret({ { { i64, i32, i64, i64, i64, i64, i64 }, { i32 }, i32, ptr, { ptr, ptr, ptr }, ptr }, i32 }), ptr, ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.close(ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare i64 @Server.free_slot(ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.conn_close(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare i32 @Server.tick(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.run(ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.accept_ready(ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.read_more(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.serve_buffered(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare i1 @Server.try_serve_one(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.serialize_response(ptr, ptr, i64, ptr, i1) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.write_more(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.respond_error_close(ptr, ptr, i64, i64) #0
|
||||||
|
|
||||||
; Function Attrs: nounwind
|
; Function Attrs: nounwind
|
||||||
declare void @BuildOptions.add_link_flag.77(i64, ptr) #0
|
declare void @BuildOptions.add_link_flag.77(i64, ptr) #0
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1397,6 +1397,54 @@ declare i1 @expired(ptr, i64) #0
|
|||||||
; Function Attrs: nounwind
|
; Function Attrs: nounwind
|
||||||
declare i64 @remaining_ms(ptr, i64) #0
|
declare i64 @remaining_ms(ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare ptr @find_header(ptr, ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare i1 @ascii_ieq(ptr, ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare ptr @reason_for(ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.init(ptr sret({ { { i64, i32, i64, i64, i64, i64, i64 }, { i32 }, i32, ptr, { ptr, ptr, ptr }, ptr }, i32 }), ptr, ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.close(ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare i64 @Server.free_slot(ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.conn_close(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare i32 @Server.tick(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.run(ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.accept_ready(ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.read_more(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.serve_buffered(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare i1 @Server.try_serve_one(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.serialize_response(ptr, ptr, i64, ptr, i1) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.write_more(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.respond_error_close(ptr, ptr, i64, i64) #0
|
||||||
|
|
||||||
; Function Attrs: nounwind
|
; Function Attrs: nounwind
|
||||||
declare void @BuildOptions.add_link_flag.77(i64, ptr) #0
|
declare void @BuildOptions.add_link_flag.77(i64, ptr) #0
|
||||||
|
|
||||||
|
|||||||
@@ -1373,6 +1373,54 @@ declare i1 @expired(ptr, i64) #0
|
|||||||
; Function Attrs: nounwind
|
; Function Attrs: nounwind
|
||||||
declare i64 @remaining_ms(ptr, i64) #0
|
declare i64 @remaining_ms(ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare ptr @find_header(ptr, ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare i1 @ascii_ieq(ptr, ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare ptr @reason_for(ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.init(ptr sret({ { { i64, i32, i64, i64, i64, i64, i64 }, { i32 }, i32, ptr, { ptr, ptr, ptr }, ptr }, i32 }), ptr, ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.close(ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare i64 @Server.free_slot(ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.conn_close(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare i32 @Server.tick(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.run(ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.accept_ready(ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.read_more(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.serve_buffered(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare i1 @Server.try_serve_one(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.serialize_response(ptr, ptr, i64, ptr, i1) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.write_more(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.respond_error_close(ptr, ptr, i64, i64) #0
|
||||||
|
|
||||||
; Function Attrs: nounwind
|
; Function Attrs: nounwind
|
||||||
define internal void @unused_jni(ptr %0, ptr %1, ptr %2) #0 {
|
define internal void @unused_jni(ptr %0, ptr %1, ptr %2) #0 {
|
||||||
entry:
|
entry:
|
||||||
|
|||||||
@@ -1371,6 +1371,54 @@ declare i1 @expired(ptr, i64) #0
|
|||||||
; Function Attrs: nounwind
|
; Function Attrs: nounwind
|
||||||
declare i64 @remaining_ms(ptr, i64) #0
|
declare i64 @remaining_ms(ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare ptr @find_header(ptr, ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare i1 @ascii_ieq(ptr, ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare ptr @reason_for(ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.init(ptr sret({ { { i64, i32, i64, i64, i64, i64, i64 }, { i32 }, i32, ptr, { ptr, ptr, ptr }, ptr }, i32 }), ptr, ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.close(ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare i64 @Server.free_slot(ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.conn_close(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare i32 @Server.tick(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.run(ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.accept_ready(ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.read_more(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.serve_buffered(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare i1 @Server.try_serve_one(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.serialize_response(ptr, ptr, i64, ptr, i1) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.write_more(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.respond_error_close(ptr, ptr, i64, i64) #0
|
||||||
|
|
||||||
; Function Attrs: nounwind
|
; Function Attrs: nounwind
|
||||||
define internal i32 @read_int(ptr %0, ptr %1, ptr %2) #0 {
|
define internal i32 @read_int(ptr %0, ptr %1, ptr %2) #0 {
|
||||||
entry:
|
entry:
|
||||||
|
|||||||
@@ -1371,6 +1371,54 @@ declare i1 @expired(ptr, i64) #0
|
|||||||
; Function Attrs: nounwind
|
; Function Attrs: nounwind
|
||||||
declare i64 @remaining_ms(ptr, i64) #0
|
declare i64 @remaining_ms(ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare ptr @find_header(ptr, ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare i1 @ascii_ieq(ptr, ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare ptr @reason_for(ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.init(ptr sret({ { { i64, i32, i64, i64, i64, i64, i64 }, { i32 }, i32, ptr, { ptr, ptr, ptr }, ptr }, i32 }), ptr, ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.close(ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare i64 @Server.free_slot(ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.conn_close(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare i32 @Server.tick(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.run(ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.accept_ready(ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.read_more(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.serve_buffered(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare i1 @Server.try_serve_one(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.serialize_response(ptr, ptr, i64, ptr, i1) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.write_more(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.respond_error_close(ptr, ptr, i64, i64) #0
|
||||||
|
|
||||||
; Function Attrs: nounwind
|
; Function Attrs: nounwind
|
||||||
define internal i64 @read_long(ptr %0, ptr %1, ptr %2) #0 {
|
define internal i64 @read_long(ptr %0, ptr %1, ptr %2) #0 {
|
||||||
entry:
|
entry:
|
||||||
|
|||||||
@@ -1371,6 +1371,54 @@ declare i1 @expired(ptr, i64) #0
|
|||||||
; Function Attrs: nounwind
|
; Function Attrs: nounwind
|
||||||
declare i64 @remaining_ms(ptr, i64) #0
|
declare i64 @remaining_ms(ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare ptr @find_header(ptr, ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare i1 @ascii_ieq(ptr, ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare ptr @reason_for(ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.init(ptr sret({ { { i64, i32, i64, i64, i64, i64, i64 }, { i32 }, i32, ptr, { ptr, ptr, ptr }, ptr }, i32 }), ptr, ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.close(ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare i64 @Server.free_slot(ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.conn_close(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare i32 @Server.tick(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.run(ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.accept_ready(ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.read_more(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.serve_buffered(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare i1 @Server.try_serve_one(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.serialize_response(ptr, ptr, i64, ptr, i1) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.write_more(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.respond_error_close(ptr, ptr, i64, i64) #0
|
||||||
|
|
||||||
; Function Attrs: nounwind
|
; Function Attrs: nounwind
|
||||||
define internal double @read_double(ptr %0, ptr %1, ptr %2) #0 {
|
define internal double @read_double(ptr %0, ptr %1, ptr %2) #0 {
|
||||||
entry:
|
entry:
|
||||||
|
|||||||
@@ -1371,6 +1371,54 @@ declare i1 @expired(ptr, i64) #0
|
|||||||
; Function Attrs: nounwind
|
; Function Attrs: nounwind
|
||||||
declare i64 @remaining_ms(ptr, i64) #0
|
declare i64 @remaining_ms(ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare ptr @find_header(ptr, ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare i1 @ascii_ieq(ptr, ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare ptr @reason_for(ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.init(ptr sret({ { { i64, i32, i64, i64, i64, i64, i64 }, { i32 }, i32, ptr, { ptr, ptr, ptr }, ptr }, i32 }), ptr, ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.close(ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare i64 @Server.free_slot(ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.conn_close(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare i32 @Server.tick(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.run(ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.accept_ready(ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.read_more(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.serve_buffered(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare i1 @Server.try_serve_one(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.serialize_response(ptr, ptr, i64, ptr, i1) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.write_more(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.respond_error_close(ptr, ptr, i64, i64) #0
|
||||||
|
|
||||||
; Function Attrs: nounwind
|
; Function Attrs: nounwind
|
||||||
define internal i1 @read_bool(ptr %0, ptr %1, ptr %2) #0 {
|
define internal i1 @read_bool(ptr %0, ptr %1, ptr %2) #0 {
|
||||||
entry:
|
entry:
|
||||||
|
|||||||
@@ -1371,6 +1371,54 @@ declare i1 @expired(ptr, i64) #0
|
|||||||
; Function Attrs: nounwind
|
; Function Attrs: nounwind
|
||||||
declare i64 @remaining_ms(ptr, i64) #0
|
declare i64 @remaining_ms(ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare ptr @find_header(ptr, ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare i1 @ascii_ieq(ptr, ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare ptr @reason_for(ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.init(ptr sret({ { { i64, i32, i64, i64, i64, i64, i64 }, { i32 }, i32, ptr, { ptr, ptr, ptr }, ptr }, i32 }), ptr, ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.close(ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare i64 @Server.free_slot(ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.conn_close(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare i32 @Server.tick(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.run(ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.accept_ready(ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.read_more(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.serve_buffered(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare i1 @Server.try_serve_one(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.serialize_response(ptr, ptr, i64, ptr, i1) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.write_more(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.respond_error_close(ptr, ptr, i64, i64) #0
|
||||||
|
|
||||||
; Function Attrs: nounwind
|
; Function Attrs: nounwind
|
||||||
define internal ptr @get_window(ptr %0, ptr %1, ptr %2) #0 {
|
define internal ptr @get_window(ptr %0, ptr %1, ptr %2) #0 {
|
||||||
entry:
|
entry:
|
||||||
|
|||||||
@@ -1371,6 +1371,54 @@ declare i1 @expired(ptr, i64) #0
|
|||||||
; Function Attrs: nounwind
|
; Function Attrs: nounwind
|
||||||
declare i64 @remaining_ms(ptr, i64) #0
|
declare i64 @remaining_ms(ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare ptr @find_header(ptr, ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare i1 @ascii_ieq(ptr, ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare ptr @reason_for(ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.init(ptr sret({ { { i64, i32, i64, i64, i64, i64, i64 }, { i32 }, i32, ptr, { ptr, ptr, ptr }, ptr }, i32 }), ptr, ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.close(ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare i64 @Server.free_slot(ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.conn_close(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare i32 @Server.tick(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.run(ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.accept_ready(ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.read_more(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.serve_buffered(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare i1 @Server.try_serve_one(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.serialize_response(ptr, ptr, i64, ptr, i1) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.write_more(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.respond_error_close(ptr, ptr, i64, i64) #0
|
||||||
|
|
||||||
; Function Attrs: nounwind
|
; Function Attrs: nounwind
|
||||||
define internal i32 @call_static_max(ptr %0, ptr %1, ptr %2) #0 {
|
define internal i32 @call_static_max(ptr %0, ptr %1, ptr %2) #0 {
|
||||||
entry:
|
entry:
|
||||||
|
|||||||
@@ -1371,6 +1371,54 @@ declare i1 @expired(ptr, i64) #0
|
|||||||
; Function Attrs: nounwind
|
; Function Attrs: nounwind
|
||||||
declare i64 @remaining_ms(ptr, i64) #0
|
declare i64 @remaining_ms(ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare ptr @find_header(ptr, ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare i1 @ascii_ieq(ptr, ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare ptr @reason_for(ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.init(ptr sret({ { { i64, i32, i64, i64, i64, i64, i64 }, { i32 }, i32, ptr, { ptr, ptr, ptr }, ptr }, i32 }), ptr, ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.close(ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare i64 @Server.free_slot(ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.conn_close(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare i32 @Server.tick(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.run(ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.accept_ready(ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.read_more(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.serve_buffered(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare i1 @Server.try_serve_one(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.serialize_response(ptr, ptr, i64, ptr, i1) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.write_more(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.respond_error_close(ptr, ptr, i64, i64) #0
|
||||||
|
|
||||||
; Function Attrs: nounwind
|
; Function Attrs: nounwind
|
||||||
define internal void @unused_jni(ptr %0, ptr %1, ptr %2) #0 {
|
define internal void @unused_jni(ptr %0, ptr %1, ptr %2) #0 {
|
||||||
entry:
|
entry:
|
||||||
|
|||||||
@@ -1371,6 +1371,54 @@ declare i1 @expired(ptr, i64) #0
|
|||||||
; Function Attrs: nounwind
|
; Function Attrs: nounwind
|
||||||
declare i64 @remaining_ms(ptr, i64) #0
|
declare i64 @remaining_ms(ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare ptr @find_header(ptr, ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare i1 @ascii_ieq(ptr, ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare ptr @reason_for(ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.init(ptr sret({ { { i64, i32, i64, i64, i64, i64, i64 }, { i32 }, i32, ptr, { ptr, ptr, ptr }, ptr }, i32 }), ptr, ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.close(ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare i64 @Server.free_slot(ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.conn_close(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare i32 @Server.tick(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.run(ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.accept_ready(ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.read_more(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.serve_buffered(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare i1 @Server.try_serve_one(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.serialize_response(ptr, ptr, i64, ptr, i1) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.write_more(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.respond_error_close(ptr, ptr, i64, i64) #0
|
||||||
|
|
||||||
; Function Attrs: nounwind
|
; Function Attrs: nounwind
|
||||||
define internal void @unused_jni(ptr %0, ptr %1, ptr %2) #0 {
|
define internal void @unused_jni(ptr %0, ptr %1, ptr %2) #0 {
|
||||||
entry:
|
entry:
|
||||||
|
|||||||
@@ -894,6 +894,54 @@ declare i1 @expired(ptr, i64) #0
|
|||||||
; Function Attrs: nounwind
|
; Function Attrs: nounwind
|
||||||
declare i64 @remaining_ms(ptr, i64) #0
|
declare i64 @remaining_ms(ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare ptr @find_header(ptr, ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare i1 @ascii_ieq(ptr, ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare ptr @reason_for(ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.init(ptr sret({ { { i64, i32, i64, i64, i64, i64, i64 }, { i32 }, i32, ptr, { ptr, ptr, ptr }, ptr }, i32 }), ptr, ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.close(ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare i64 @Server.free_slot(ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.conn_close(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare i32 @Server.tick(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.run(ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.accept_ready(ptr, ptr) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.read_more(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.serve_buffered(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare i1 @Server.try_serve_one(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.serialize_response(ptr, ptr, i64, ptr, i1) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.write_more(ptr, ptr, i64) #0
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare void @Server.respond_error_close(ptr, ptr, i64, i64) #0
|
||||||
|
|
||||||
; Function Attrs: nounwind
|
; Function Attrs: nounwind
|
||||||
declare void @BuildOptions.add_link_flag.77(i64, ptr) #0
|
declare void @BuildOptions.add_link_flag.77(i64, ptr) #0
|
||||||
|
|
||||||
|
|||||||
1
examples/expected/1633-http-server.exit
Normal file
1
examples/expected/1633-http-server.exit
Normal file
@@ -0,0 +1 @@
|
|||||||
|
0
|
||||||
1
examples/expected/1633-http-server.stderr
Normal file
1
examples/expected/1633-http-server.stderr
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
7
examples/expected/1633-http-server.stdout
Normal file
7
examples/expected/1633-http-server.stdout
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
GET 200, keep-alive
|
||||||
|
keep-alive reuse ok
|
||||||
|
request cap: close + EOF
|
||||||
|
POST echo ok
|
||||||
|
404 routing ok
|
||||||
|
slow client evicted, healthy client served
|
||||||
|
http server ok
|
||||||
@@ -99,3 +99,4 @@ log :: #import "modules/std/log.sx";
|
|||||||
test :: #import "modules/std/test.sx";
|
test :: #import "modules/std/test.sx";
|
||||||
time :: #import "modules/std/time.sx";
|
time :: #import "modules/std/time.sx";
|
||||||
event :: #import "modules/std/event.sx";
|
event :: #import "modules/std/event.sx";
|
||||||
|
http :: #import "modules/std/http.sx";
|
||||||
|
|||||||
496
library/modules/std/http.sx
Normal file
496
library/modules/std/http.sx
Normal file
@@ -0,0 +1,496 @@
|
|||||||
|
// std.http — single-worker HTTP/1.1 server core (PLAN-HTTPZ S7a).
|
||||||
|
//
|
||||||
|
// The httpz shape, one worker, handlers inline: a readiness Loop
|
||||||
|
// (std.event) multiplexes the listener and every connection, so an
|
||||||
|
// idle socket costs nothing and nothing ever blocks the loop. Timeouts
|
||||||
|
// are EVICTION, not blocking — each connection carries a monotonic
|
||||||
|
// deadline (request delivery / keepalive idle), checked between waits.
|
||||||
|
// Keep-alive is the default for HTTP/1.1; a Connection header, the
|
||||||
|
// per-connection request cap, or HTTP/1.0 turns it off.
|
||||||
|
//
|
||||||
|
// API: `Server.init(cfg, handler)` then either `run()` (the forever
|
||||||
|
// loop) or `tick(max_wait_ms)` — one bounded loop iteration, which is
|
||||||
|
// also how tests drive a live server and its client sockets in ONE
|
||||||
|
// thread. S7b adds worker counts + a handler thread pool behind this
|
||||||
|
// same surface; the epoll backend arrives with the linux target (S4).
|
||||||
|
//
|
||||||
|
// MEMORY: init captures the constructing allocator (the Repo pattern):
|
||||||
|
// connection slots and their read buffers live across ticks and are
|
||||||
|
// reused connection-to-connection (httpz's buffer-pool spirit);
|
||||||
|
// response bytes are allocated per response and freed when fully sent.
|
||||||
|
// Request views handed to the handler point into the connection's read
|
||||||
|
// buffer and are valid only during the handler call.
|
||||||
|
|
||||||
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
HttpErr :: error {
|
||||||
|
Bind, // socket/bind/listen failed for the configured port
|
||||||
|
Loop, // the readiness loop could not be created or waited on
|
||||||
|
}
|
||||||
|
|
||||||
|
// httpz-mirroring knobs (single-worker subset).
|
||||||
|
Config :: struct {
|
||||||
|
port: i64 = 8080;
|
||||||
|
backlog: i32 = 128;
|
||||||
|
max_conn: i64 = 256; // workers.max_conn
|
||||||
|
read_buf_cap: i64 = 65536; // workers.large_buffer_size
|
||||||
|
timeout_request_ms: i64 = 5000; // deliver a full request, or evicted
|
||||||
|
timeout_keepalive_ms: i64 = 5000;// idle between requests, or evicted
|
||||||
|
request_count: i64 = 100; // requests per connection, then close
|
||||||
|
}
|
||||||
|
|
||||||
|
// One parsed request, viewed in place over the connection's read
|
||||||
|
// buffer — valid for the duration of the handler call only.
|
||||||
|
Request :: struct {
|
||||||
|
method: string = "";
|
||||||
|
path: string = "";
|
||||||
|
version: string = "";
|
||||||
|
headers_raw: string = ""; // the raw header block (no request line)
|
||||||
|
body: string = "";
|
||||||
|
keep_alive: bool = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// What the handler fills in; the server serializes it.
|
||||||
|
Response :: struct {
|
||||||
|
status: i64 = 200;
|
||||||
|
content_type: string = "text/plain; charset=utf-8";
|
||||||
|
extra_headers: string = ""; // preformatted "Name: value\r\n" lines
|
||||||
|
body: string = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Case-insensitive header lookup in `headers_raw`; "" when absent.
|
||||||
|
// `name` must be lowercase.
|
||||||
|
find_header :: (req: *Request, name: string) -> string {
|
||||||
|
h := req.headers_raw;
|
||||||
|
i := 0;
|
||||||
|
while i < h.len {
|
||||||
|
// line start: try to match `name` case-insensitively, then ':'
|
||||||
|
j := 0;
|
||||||
|
while j < name.len and i + j < h.len {
|
||||||
|
c := h[i + j];
|
||||||
|
if c >= 65 and c <= 90 { c = c + 32; } // ASCII lower
|
||||||
|
if c != name[j] { break; }
|
||||||
|
j += 1;
|
||||||
|
}
|
||||||
|
if j == name.len and i + j < h.len and h[i + j] == 58 { // ':'
|
||||||
|
v := i + j + 1;
|
||||||
|
while v < h.len and h[v] == 32 { v += 1; }
|
||||||
|
e := v;
|
||||||
|
while e < h.len and h[e] != 13 { e += 1; }
|
||||||
|
return string.{ ptr = @h[v], len = e - v };
|
||||||
|
}
|
||||||
|
// skip to the next line
|
||||||
|
while i < h.len and h[i] != 10 { i += 1; } // '\n'
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
ascii_ieq :: (a: string, b_lower: string) -> bool {
|
||||||
|
if a.len != b_lower.len { return false; }
|
||||||
|
i := 0;
|
||||||
|
while i < a.len {
|
||||||
|
c := a[i];
|
||||||
|
if c >= 65 and c <= 90 { c = c + 32; }
|
||||||
|
if c != b_lower[i] { return false; }
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
reason_for :: (status: i64) -> string {
|
||||||
|
if status == 200 { return "OK"; }
|
||||||
|
if status == 201 { return "Created"; }
|
||||||
|
if status == 204 { return "No Content"; }
|
||||||
|
if status == 301 { return "Moved Permanently"; }
|
||||||
|
if status == 302 { return "Found"; }
|
||||||
|
if status == 400 { return "Bad Request"; }
|
||||||
|
if status == 401 { return "Unauthorized"; }
|
||||||
|
if status == 403 { return "Forbidden"; }
|
||||||
|
if status == 404 { return "Not Found"; }
|
||||||
|
if status == 405 { return "Method Not Allowed"; }
|
||||||
|
if status == 413 { return "Content Too Large"; }
|
||||||
|
if status == 431 { return "Request Header Fields Too Large"; }
|
||||||
|
if status == 500 { return "Internal Server Error"; }
|
||||||
|
if status == 503 { return "Service Unavailable"; }
|
||||||
|
return "Status";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connection slot states.
|
||||||
|
CONN_FREE :u8: 0;
|
||||||
|
CONN_READING :u8: 1; // awaiting a complete request (deadline: request)
|
||||||
|
CONN_WRITING :u8: 2; // response partially sent (deadline: request)
|
||||||
|
CONN_KEEPALIVE :u8: 3; // between requests (deadline: keepalive)
|
||||||
|
|
||||||
|
Conn :: struct {
|
||||||
|
fd: i32 = -1;
|
||||||
|
state: u8 = 0;
|
||||||
|
read_buf: [*]u8 = null; // cap = config.read_buf_cap, reused across connections
|
||||||
|
read_len: i64 = 0;
|
||||||
|
out_buf: [*]u8 = null; // per-response allocation, freed when sent
|
||||||
|
out_len: i64 = 0;
|
||||||
|
out_sent: i64 = 0;
|
||||||
|
deadline: i64 = 0;
|
||||||
|
served: i64 = 0;
|
||||||
|
close_after: bool = false;
|
||||||
|
write_armed: bool = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The listener's udata; connection udata is the slot index.
|
||||||
|
LISTENER_UDATA :usize: 0xFFFFFFFF;
|
||||||
|
|
||||||
|
Server :: struct {
|
||||||
|
cfg: Config;
|
||||||
|
loop: event.Loop;
|
||||||
|
lfd: i32 = -1;
|
||||||
|
conns: [*]Conn = null;
|
||||||
|
own_alloc: Allocator;
|
||||||
|
handler: (*Request, *Response) -> void;
|
||||||
|
|
||||||
|
init :: (cfg: Config, handler: (*Request, *Response) -> void) -> (Server, !HttpErr) {
|
||||||
|
lfd := socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0);
|
||||||
|
if lfd < 0 { raise error.Bind; }
|
||||||
|
one : i32 = 1;
|
||||||
|
socket.setsockopt(lfd, socket.SOL_SOCKET, socket.SO_REUSEADDR, @one, 4);
|
||||||
|
addr : socket.SockAddr = .{
|
||||||
|
sin_len = 16, sin_family = xx socket.AF_INET,
|
||||||
|
sin_port = socket.htons(cfg.port), sin_addr = 0, // INADDR_ANY
|
||||||
|
};
|
||||||
|
if socket.bind(lfd, @addr, 16) != 0 { socket.close(lfd); raise error.Bind; }
|
||||||
|
if socket.listen(lfd, cfg.backlog) != 0 { socket.close(lfd); raise error.Bind; }
|
||||||
|
if !socket.set_nonblocking(lfd) { socket.close(lfd); raise error.Bind; }
|
||||||
|
|
||||||
|
lp, le := event.Loop.init();
|
||||||
|
if le { socket.close(lfd); raise error.Loop; }
|
||||||
|
are := false;
|
||||||
|
lp.add_read(lfd, LISTENER_UDATA) catch { are = true; };
|
||||||
|
if are { socket.close(lfd); raise error.Loop; }
|
||||||
|
|
||||||
|
oa := context.allocator;
|
||||||
|
slots : [*]Conn = xx oa.alloc_bytes(cfg.max_conn * size_of(Conn));
|
||||||
|
i : i64 = 0;
|
||||||
|
while i < cfg.max_conn {
|
||||||
|
slots[i] = Conn.{};
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
return Server.{
|
||||||
|
cfg = cfg, loop = lp, lfd = lfd, conns = slots,
|
||||||
|
own_alloc = oa, handler = handler,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
close :: (self: *Server) {
|
||||||
|
i : i64 = 0;
|
||||||
|
while i < self.cfg.max_conn {
|
||||||
|
if self.conns[i].state != CONN_FREE { self.conn_close(i); }
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
if self.lfd >= 0 { socket.close(self.lfd); }
|
||||||
|
self.lfd = -1;
|
||||||
|
self.loop.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── slot management ──────────────────────────────────────────────
|
||||||
|
|
||||||
|
free_slot :: (self: *Server) -> i64 {
|
||||||
|
i : i64 = 0;
|
||||||
|
while i < self.cfg.max_conn {
|
||||||
|
if self.conns[i].state == CONN_FREE { return i; }
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
conn_close :: (self: *Server, slot: i64) {
|
||||||
|
c := @self.conns[slot];
|
||||||
|
if c.fd >= 0 {
|
||||||
|
self.loop.del_read(c.fd);
|
||||||
|
if c.write_armed { self.loop.del_write(c.fd); }
|
||||||
|
socket.close(c.fd);
|
||||||
|
}
|
||||||
|
if c.out_buf != null {
|
||||||
|
self.own_alloc.dealloc_bytes(xx c.out_buf, c.out_len);
|
||||||
|
c.out_buf = null;
|
||||||
|
}
|
||||||
|
// read_buf stays allocated — reused by the next connection here.
|
||||||
|
c.fd = -1;
|
||||||
|
c.state = CONN_FREE;
|
||||||
|
c.read_len = 0;
|
||||||
|
c.out_len = 0;
|
||||||
|
c.out_sent = 0;
|
||||||
|
c.served = 0;
|
||||||
|
c.close_after = false;
|
||||||
|
c.write_armed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── the tick: one bounded loop iteration ─────────────────────────
|
||||||
|
//
|
||||||
|
// Waits at most `max_wait_ms` (sooner when a connection deadline is
|
||||||
|
// nearer), services every ready fd, then evicts expired connections.
|
||||||
|
tick :: (self: *Server, max_wait_ms: i64) -> !HttpErr {
|
||||||
|
wait_ms := max_wait_ms;
|
||||||
|
i : i64 = 0;
|
||||||
|
while i < self.cfg.max_conn {
|
||||||
|
c := self.conns[i];
|
||||||
|
if c.state != CONN_FREE {
|
||||||
|
left := event.remaining_ms(c.deadline);
|
||||||
|
if left < wait_ms { wait_ms = left; }
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
evs : [64]event.Event = ---;
|
||||||
|
n, werr := self.loop.wait(.{ ptr = @evs[0], len = 64 }, wait_ms);
|
||||||
|
if werr { raise error.Loop; }
|
||||||
|
|
||||||
|
k : i64 = 0;
|
||||||
|
while k < n {
|
||||||
|
ev := evs[k];
|
||||||
|
k += 1;
|
||||||
|
if ev.udata == LISTENER_UDATA {
|
||||||
|
self.accept_ready();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
slot : i64 = xx ev.udata;
|
||||||
|
c := @self.conns[slot];
|
||||||
|
if c.state == CONN_FREE or c.fd != ev.fd { continue; } // stale event for a recycled slot
|
||||||
|
if ev.writable and c.state == CONN_WRITING {
|
||||||
|
self.write_more(slot);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ev.readable or ev.eof {
|
||||||
|
self.read_more(slot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deadline eviction — after I/O, so a request that just arrived
|
||||||
|
// under the wire is served, not evicted.
|
||||||
|
i = 0;
|
||||||
|
while i < self.cfg.max_conn {
|
||||||
|
if self.conns[i].state != CONN_FREE and event.expired(self.conns[i].deadline) {
|
||||||
|
self.conn_close(i);
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
run :: (self: *Server) {
|
||||||
|
while true {
|
||||||
|
self.tick(1000) catch {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── accept ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
accept_ready :: (self: *Server) {
|
||||||
|
while true {
|
||||||
|
fd, ae := socket.accept_nb(self.lfd);
|
||||||
|
if ae { return; } // WouldBlock = drained; Fault = nothing to do here
|
||||||
|
slot := self.free_slot();
|
||||||
|
if slot < 0 { socket.close(fd); return; } // at max_conn: shed
|
||||||
|
if !socket.set_nonblocking(fd) { socket.close(fd); return; }
|
||||||
|
c := @self.conns[slot];
|
||||||
|
if c.read_buf == null {
|
||||||
|
c.read_buf = xx self.own_alloc.alloc_bytes(self.cfg.read_buf_cap);
|
||||||
|
}
|
||||||
|
c.fd = fd;
|
||||||
|
c.state = CONN_READING;
|
||||||
|
c.read_len = 0;
|
||||||
|
c.served = 0;
|
||||||
|
c.close_after = false;
|
||||||
|
c.deadline = event.deadline_in(self.cfg.timeout_request_ms);
|
||||||
|
are := false;
|
||||||
|
self.loop.add_read(fd, xx slot) catch { are = true; };
|
||||||
|
if are { self.conn_close(slot); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── read → parse → dispatch ──────────────────────────────────────
|
||||||
|
|
||||||
|
read_more :: (self: *Server, slot: i64) {
|
||||||
|
c := @self.conns[slot];
|
||||||
|
if c.state == CONN_KEEPALIVE {
|
||||||
|
c.state = CONN_READING;
|
||||||
|
c.deadline = event.deadline_in(self.cfg.timeout_request_ms);
|
||||||
|
}
|
||||||
|
while true {
|
||||||
|
cap := self.cfg.read_buf_cap - c.read_len;
|
||||||
|
if cap <= 0 {
|
||||||
|
self.respond_error_close(slot, 431);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
nq, re := socket.read_nb(c.fd, @c.read_buf[c.read_len], xx cap);
|
||||||
|
if re == error.WouldBlock { break; }
|
||||||
|
if re { // Closed or Fault
|
||||||
|
self.conn_close(slot);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
c.read_len += nq;
|
||||||
|
}
|
||||||
|
self.serve_buffered(slot);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serve every complete request sitting in the buffer (a keep-alive
|
||||||
|
// client may pipeline; each response must finish sending before the
|
||||||
|
// next parse — a pending partial write pauses the drain and
|
||||||
|
// write_more resumes it).
|
||||||
|
serve_buffered :: (self: *Server, slot: i64) {
|
||||||
|
while self.conns[slot].state == CONN_READING {
|
||||||
|
if !self.try_serve_one(slot) { return; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse one request off the front of the buffer; false = incomplete
|
||||||
|
// (need more bytes) or the connection left the READING state.
|
||||||
|
try_serve_one :: (self: *Server, slot: i64) -> bool {
|
||||||
|
c := @self.conns[slot];
|
||||||
|
buf := string.{ ptr = c.read_buf, len = xx c.read_len };
|
||||||
|
|
||||||
|
// headers complete?
|
||||||
|
he := -1;
|
||||||
|
i := 0;
|
||||||
|
while i + 3 < buf.len {
|
||||||
|
if buf[i] == 13 and buf[i+1] == 10 and buf[i+2] == 13 and buf[i+3] == 10 { he = i; break; }
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
if he < 0 { return false; }
|
||||||
|
|
||||||
|
// request line: METHOD SP PATH SP VERSION CRLF
|
||||||
|
req : Request = .{};
|
||||||
|
p := 0;
|
||||||
|
while p < he and buf[p] != 32 { p += 1; }
|
||||||
|
req.method = string.{ ptr = c.read_buf, len = p };
|
||||||
|
p += 1;
|
||||||
|
ps := p;
|
||||||
|
while p < he and buf[p] != 32 { p += 1; }
|
||||||
|
req.path = string.{ ptr = @c.read_buf[ps], len = p - ps };
|
||||||
|
p += 1;
|
||||||
|
vs := p;
|
||||||
|
while p < he and buf[p] != 13 { p += 1; }
|
||||||
|
req.version = string.{ ptr = @c.read_buf[vs], len = p - vs };
|
||||||
|
hdr_start := p + 2;
|
||||||
|
if req.method.len == 0 or req.path.len == 0 or hdr_start > he {
|
||||||
|
self.respond_error_close(slot, 400);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
req.headers_raw = string.{ ptr = @c.read_buf[hdr_start], len = he - hdr_start + 2 };
|
||||||
|
|
||||||
|
// body per Content-Length
|
||||||
|
clen : i64 = 0;
|
||||||
|
clv := find_header(@req, "content-length");
|
||||||
|
j := 0;
|
||||||
|
while j < clv.len {
|
||||||
|
d := clv[j];
|
||||||
|
if d < 48 or d > 57 { self.respond_error_close(slot, 400); return false; }
|
||||||
|
clen = clen * 10 + (d - 48);
|
||||||
|
j += 1;
|
||||||
|
}
|
||||||
|
total := xx he + 4 + clen;
|
||||||
|
if total > self.cfg.read_buf_cap { self.respond_error_close(slot, 413); return false; }
|
||||||
|
if c.read_len < total { return false; }
|
||||||
|
req.body = string.{ ptr = @c.read_buf[he + 4], len = xx clen };
|
||||||
|
|
||||||
|
// keep-alive: 1.1 default on, 1.0 default off, header overrides
|
||||||
|
req.keep_alive = !ascii_ieq(req.version, "http/1.0");
|
||||||
|
cnv := find_header(@req, "connection");
|
||||||
|
if ascii_ieq(cnv, "close") { req.keep_alive = false; }
|
||||||
|
if ascii_ieq(cnv, "keep-alive") { req.keep_alive = true; }
|
||||||
|
|
||||||
|
// dispatch (the field must be loaded — `self.handler(...)` would
|
||||||
|
// be parsed as a dot-call on a function named `handler`)
|
||||||
|
h := self.handler;
|
||||||
|
resp : Response = .{};
|
||||||
|
h(@req, @resp);
|
||||||
|
|
||||||
|
c.served += 1;
|
||||||
|
keep := req.keep_alive and c.served < self.cfg.request_count;
|
||||||
|
|
||||||
|
// Serialize while the request views are still valid (the body
|
||||||
|
// may reference the read buffer), THEN drop the served bytes —
|
||||||
|
// write_more's pipelining check must see only the remainder —
|
||||||
|
// and only then start sending. Overlapping copy: dst < src, so
|
||||||
|
// forward byte-wise is safe.
|
||||||
|
self.serialize_response(slot, @resp, keep);
|
||||||
|
rest := c.read_len - total;
|
||||||
|
m : i64 = 0;
|
||||||
|
while m < rest {
|
||||||
|
c.read_buf[m] = c.read_buf[total + m];
|
||||||
|
m += 1;
|
||||||
|
}
|
||||||
|
c.read_len = rest;
|
||||||
|
self.write_more(slot);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── response serialization + write continuation ──────────────────
|
||||||
|
|
||||||
|
// Build the response bytes into the slot's out buffer. Does NOT
|
||||||
|
// start sending — try_serve_one compacts the read buffer between
|
||||||
|
// serialization and the first write (see the ordering note there).
|
||||||
|
serialize_response :: (self: *Server, slot: i64, resp: *Response, keep: bool) {
|
||||||
|
head := concat("HTTP/1.1 ", concat(int_to_string(resp.status), concat(" ", reason_for(resp.status))));
|
||||||
|
head = concat(head, concat("\r\nContent-Length: ", int_to_string(resp.body.len)));
|
||||||
|
head = concat(head, concat("\r\nContent-Type: ", resp.content_type));
|
||||||
|
head = concat(head, if keep then "\r\nConnection: keep-alive\r\n" else "\r\nConnection: close\r\n");
|
||||||
|
if resp.extra_headers.len > 0 { head = concat(head, resp.extra_headers); }
|
||||||
|
head = concat(head, "\r\n");
|
||||||
|
|
||||||
|
c := @self.conns[slot];
|
||||||
|
c.out_len = xx (head.len + resp.body.len);
|
||||||
|
c.out_buf = xx self.own_alloc.alloc_bytes(xx c.out_len);
|
||||||
|
memcpy(c.out_buf, head.ptr, head.len);
|
||||||
|
if resp.body.len > 0 { memcpy(@c.out_buf[head.len], resp.body.ptr, resp.body.len); }
|
||||||
|
c.out_sent = 0;
|
||||||
|
c.close_after = !keep;
|
||||||
|
}
|
||||||
|
|
||||||
|
write_more :: (self: *Server, slot: i64) {
|
||||||
|
c := @self.conns[slot];
|
||||||
|
while c.out_sent < c.out_len {
|
||||||
|
nq, we := socket.write_nb(c.fd, @c.out_buf[c.out_sent], xx (c.out_len - c.out_sent));
|
||||||
|
if we == error.WouldBlock {
|
||||||
|
if !c.write_armed {
|
||||||
|
awe := false;
|
||||||
|
self.loop.add_write(c.fd, xx slot) catch { awe = true; };
|
||||||
|
if awe { self.conn_close(slot); return; }
|
||||||
|
c.write_armed = true;
|
||||||
|
}
|
||||||
|
c.state = CONN_WRITING;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if we { self.conn_close(slot); return; }
|
||||||
|
c.out_sent += nq;
|
||||||
|
}
|
||||||
|
// fully sent
|
||||||
|
if c.write_armed {
|
||||||
|
self.loop.del_write(c.fd);
|
||||||
|
c.write_armed = false;
|
||||||
|
}
|
||||||
|
self.own_alloc.dealloc_bytes(xx c.out_buf, c.out_len);
|
||||||
|
c.out_buf = null;
|
||||||
|
c.out_len = 0;
|
||||||
|
c.out_sent = 0;
|
||||||
|
if c.close_after {
|
||||||
|
self.conn_close(slot);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if c.read_len > 0 {
|
||||||
|
// pipelined bytes already buffered: keep serving
|
||||||
|
c.state = CONN_READING;
|
||||||
|
c.deadline = event.deadline_in(self.cfg.timeout_request_ms);
|
||||||
|
self.serve_buffered(slot);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
c.state = CONN_KEEPALIVE;
|
||||||
|
c.deadline = event.deadline_in(self.cfg.timeout_keepalive_ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
// A terminal error response: serialize, send, close when done.
|
||||||
|
respond_error_close :: (self: *Server, slot: i64, status: i64) {
|
||||||
|
resp : Response = .{ status = status, body = reason_for(status) };
|
||||||
|
self.conns[slot].read_len = 0;
|
||||||
|
self.serialize_response(slot, @resp, false);
|
||||||
|
self.write_more(slot);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user