╭ web.zig ──────────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
1 const std = @import("std"); 2 3 const Asset = struct { 4 path: []const u8, 5 content_type: []const u8, 6 body: []const u8, 7 }; 8 9 const assets = [_]Asset{ 10 .{ .path = "index.html", .content_type = "text/html; charset=utf-8", .body = @embedFile("assets/index.html") }, 11 .{ .path = "script.js", .content_type = "text/javascript; charset=utf-8", .body = @embedFile("assets/script.js") }, 12 .{ .path = "term.ttf", .content_type = "font/ttf", .body = @embedFile("assets/term.ttf") }, 13 .{ .path = "haxy.wasm", .content_type = "application/wasm", .body = @embedFile("assets/haxy.wasm") }, 14 }; 15 16 pub fn run( 17 io: std.Io, 18 net_server: *std.Io.net.Server, 19 tasks: *std.Io.Group, 20 err: *std.Io.Writer, 21 ) void { 22 const Listener = struct { 23 io: std.Io, 24 net_server: *std.Io.net.Server, 25 tasks: *std.Io.Group, 26 err: *std.Io.Writer, 27 28 fn run(ctx: @This()) void { 29 while (true) { 30 const stream = ctx.net_server.accept(ctx.io) catch |accept_err| { 31 if (accept_err == error.Canceled) return; 32 logError(ctx.err, "web ui accept failed: {s}\n", .{@errorName(accept_err)}); 33 continue; 34 }; 35 36 const Connection = struct { 37 io: std.Io, 38 stream: std.Io.net.Stream, 39 err: *std.Io.Writer, 40 41 fn run(conn: @This()) void { 42 defer conn.stream.close(conn.io); 43 handleConnection(conn.io, conn.stream, conn.err) catch |request_err| { 44 logError(conn.err, "web ui request failed: {s}\n", .{@errorName(request_err)}); 45 }; 46 } 47 }; 48 49 ctx.tasks.async(ctx.io, Connection.run, .{Connection{ 50 .io = ctx.io, 51 .stream = stream, 52 .err = ctx.err, 53 }}); 54 } 55 } 56 }; 57 58 tasks.async(io, Listener.run, .{Listener{ 59 .io = io, 60 .net_server = net_server, 61 .tasks = tasks, 62 .err = err, 63 }}); 64 } 65 66 fn handleConnection( 67 io: std.Io, 68 stream: std.Io.net.Stream, 69 err: *std.Io.Writer, 70 ) !void { 71 var send_buffer = [_]u8{0} ** 4096; 72 var recv_buffer = [_]u8{0} ** 4096; 73 var conn_br = stream.reader(io, &recv_buffer); 74 var conn_bw = stream.writer(io, &send_buffer); 75 var http_server = std.http.Server.init(&conn_br.interface, &conn_bw.interface); 76 77 while (http_server.reader.state == .ready) { 78 var request = http_server.receiveHead() catch |receive_err| switch (receive_err) { 79 error.HttpConnectionClosing => break, 80 error.ReadFailed => break, 81 else => |e| return e, 82 }; 83 84 handleRequest(&http_server, &request) catch |request_err| { 85 try err.print("web ui request failed: {s}\n", .{@errorName(request_err)}); 86 try err.flush(); 87 if (http_server.reader.state == .received_head) { 88 http_server.reader.state = .ready; 89 } 90 try writeSimpleResponse(&http_server, 500, "Internal Server Error", "text/plain", @errorName(request_err)); 91 }; 92 try http_server.out.flush(); 93 break; 94 } 95 } 96 97 fn handleRequest( 98 http_server: *std.http.Server, 99 request: *std.http.Server.Request, 100 ) !void { 101 const method = if (request.head.method == .HEAD) .GET else request.head.method; 102 if (method != .GET) { 103 if (http_server.reader.state == .received_head) { 104 http_server.reader.state = .ready; 105 } 106 try writeSimpleResponse(http_server, 405, "Method Not Allowed", "text/plain", "method not allowed"); 107 return; 108 } 109 110 const uri = try std.Uri.parseAfterScheme("", request.head.target); 111 const path = uri.path.percent_encoded; 112 113 if (std.mem.eql(u8, path, "/favicon.ico")) { 114 if (http_server.reader.state == .received_head) { 115 http_server.reader.state = .ready; 116 } 117 try writeStaticResponse(http_server, 204, "No Content", "image/x-icon", "", true); 118 return; 119 } 120 121 const asset = findAsset(path) orelse { 122 if (http_server.reader.state == .received_head) { 123 http_server.reader.state = .ready; 124 } 125 try writeSimpleResponse(http_server, 404, "Not Found", "text/plain", "not found"); 126 return; 127 }; 128 129 if (http_server.reader.state == .received_head) { 130 http_server.reader.state = .ready; 131 } 132 133 try writeStaticResponse(http_server, 200, "OK", asset.content_type, asset.body, request.head.method == .HEAD); 134 } 135 136 fn findAsset(request_path: []const u8) ?Asset { 137 const path = if (std.mem.eql(u8, request_path, "/")) 138 "index.html" 139 else if (request_path.len > 1 and request_path[0] == '/') 140 request_path[1..] 141 else 142 return null; 143 144 for (assets) |asset| { 145 if (std.mem.eql(u8, path, asset.path)) return asset; 146 } 147 return null; 148 } 149 150 fn writeSimpleResponse( 151 http_server: *std.http.Server, 152 code: u16, 153 message: []const u8, 154 content_type: []const u8, 155 body: []const u8, 156 ) !void { 157 try http_server.out.print( 158 "HTTP/1.1 {d} {s}\r\nContent-Type: {s}\r\nContent-Length: {d}\r\n\r\n{s}", 159 .{ code, message, content_type, body.len, body }, 160 ); 161 } 162 163 fn writeStaticResponse( 164 http_server: *std.http.Server, 165 code: u16, 166 message: []const u8, 167 content_type: []const u8, 168 body: []const u8, 169 head_only: bool, 170 ) !void { 171 try http_server.out.print( 172 "HTTP/1.1 {d} {s}\r\nContent-Type: {s}\r\nContent-Length: {d}\r\n\r\n", 173 .{ code, message, content_type, body.len }, 174 ); 175 if (!head_only) { 176 try http_server.out.writeAll(body); 177 } 178 } 179 180 fn logError(err: *std.Io.Writer, comptime fmt: []const u8, args: anytype) void { 181 err.print(fmt, args) catch return; 182 err.flush() catch {}; 183 } 184