polls on atproto pollz.waow.tech
atproto zig

properly escape things

Changed files
+44 -10
backend
+3 -3
backend/src/http.zig
··· 180 break :blk 0; 181 }; 182 183 - const opt_text = if (opt == .string) opt.string else ""; 184 try response.print(alloc, 185 - \\{{"text":"{s}","count":{d}}} 186 - , .{ opt_text, count }); 187 } 188 189 try response.print(alloc,
··· 180 break :blk 0; 181 }; 182 183 + // use json.fmt to properly escape quotes and special chars 184 try response.print(alloc, 185 + \\{{"text":{f},"count":{d}}} 186 + , .{ json.fmt(opt, .{}), count }); 187 } 188 189 try response.print(alloc,
+40 -6
backend/src/main.zig
··· 1 const std = @import("std"); 2 const net = std.net; 3 const Thread = std.Thread; 4 const db = @import("db.zig"); 5 const http_server = @import("http.zig"); 6 const tap = @import("tap.zig"); 7 8 pub fn main() !void { 9 var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 10 defer _ = gpa.deinit(); 11 const allocator = gpa.allocator(); 12 13 // init sqlite - use DATA_PATH env or default to /data/pollz.db 14 - const db_path = std.posix.getenv("DATA_PATH") orelse "/data/pollz.db"; 15 try db.init(db_path); 16 defer db.close(); 17 18 - // tap handles backfill automatically - no need to call backfill.run() 19 - 20 // start tap consumer in background 21 const tap_thread = try Thread.spawn(.{}, tap.consumer, .{allocator}); 22 defer tap_thread.join(); 23 24 // start http server (bind to 0.0.0.0 for containerized deployments) 25 const address = try net.Address.parseIp("0.0.0.0", 3000); 26 var server = try address.listen(.{ .reuse_address = true }); 27 defer server.deinit(); 28 29 - std.debug.print("pollz backend listening on http://127.0.0.1:3000\n", .{}); 30 31 while (true) { 32 - const conn = try server.accept(); 33 - _ = try Thread.spawn(.{}, http_server.handleConnection, .{conn}); 34 } 35 }
··· 1 const std = @import("std"); 2 const net = std.net; 3 + const posix = std.posix; 4 const Thread = std.Thread; 5 const db = @import("db.zig"); 6 const http_server = @import("http.zig"); 7 const tap = @import("tap.zig"); 8 9 + // max concurrent http connections (prevents resource exhaustion) 10 + const MAX_HTTP_WORKERS = 16; 11 + 12 + // socket timeout in seconds 13 + const SOCKET_TIMEOUT_SECS = 30; 14 + 15 pub fn main() !void { 16 var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 17 defer _ = gpa.deinit(); 18 const allocator = gpa.allocator(); 19 20 // init sqlite - use DATA_PATH env or default to /data/pollz.db 21 + const db_path = posix.getenv("DATA_PATH") orelse "/data/pollz.db"; 22 try db.init(db_path); 23 defer db.close(); 24 25 // start tap consumer in background 26 const tap_thread = try Thread.spawn(.{}, tap.consumer, .{allocator}); 27 defer tap_thread.join(); 28 29 + // init thread pool for http connections 30 + var pool: Thread.Pool = undefined; 31 + try pool.init(.{ 32 + .allocator = allocator, 33 + .n_jobs = MAX_HTTP_WORKERS, 34 + }); 35 + defer pool.deinit(); 36 + 37 // start http server (bind to 0.0.0.0 for containerized deployments) 38 const address = try net.Address.parseIp("0.0.0.0", 3000); 39 var server = try address.listen(.{ .reuse_address = true }); 40 defer server.deinit(); 41 42 + std.debug.print("pollz backend listening on http://127.0.0.1:3000 (max {} workers)\n", .{MAX_HTTP_WORKERS}); 43 44 while (true) { 45 + const conn = server.accept() catch |err| { 46 + std.debug.print("accept error: {}\n", .{err}); 47 + continue; 48 + }; 49 + 50 + // set socket timeouts to prevent slow client attacks 51 + setSocketTimeout(conn.stream.handle, SOCKET_TIMEOUT_SECS) catch |err| { 52 + std.debug.print("failed to set socket timeout: {}\n", .{err}); 53 + }; 54 + 55 + pool.spawn(http_server.handleConnection, .{conn}) catch |err| { 56 + std.debug.print("pool spawn error: {}\n", .{err}); 57 + conn.stream.close(); 58 + }; 59 } 60 } 61 + 62 + fn setSocketTimeout(fd: posix.fd_t, secs: u32) !void { 63 + const timeout = std.mem.toBytes(posix.timeval{ 64 + .sec = @intCast(secs), 65 + .usec = 0, 66 + }); 67 + try posix.setsockopt(fd, posix.SOL.SOCKET, posix.SO.RCVTIMEO, &timeout); 68 + try posix.setsockopt(fd, posix.SOL.SOCKET, posix.SO.SNDTIMEO, &timeout); 69 + }
+1 -1
readme.md
··· 11 ## stack 12 13 - [tap](https://github.com/bluesky-social/atproto/tree/main/packages/tap) - firehose sync 14 - - [zig](https://ziglang.org) + [httpz](https://github.com/ikskuh/http.zig) - backend 15 - [atcute](https://github.com/mary-ext/atcute) - atproto client 16 - [fly.io](https://fly.io) - backend hosting 17 - [cloudflare pages](https://pages.cloudflare.com) - frontend hosting
··· 11 ## stack 12 13 - [tap](https://github.com/bluesky-social/atproto/tree/main/packages/tap) - firehose sync 14 + - [zig](https://ziglang.org) + [zqlite](https://github.com/karlseguin/zqlite.zig) - backend 15 - [atcute](https://github.com/mary-ext/atcute) - atproto client 16 - [fly.io](https://fly.io) - backend hosting 17 - [cloudflare pages](https://pages.cloudflare.com) - frontend hosting