atproto relay in zig zlay.waow.tech
relay zig atproto
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114//! HTTP response helpers and query string parsing.//!//! pure utility module — no domain dependencies. used by all API handler modules//! to write raw HTTP responses to websocket connections and parse query parameters.
const std = @import("std");const http = std.http;const websocket = @import("websocket");
pub const Conn = websocket.Conn;
pub fn httpRespond(conn: *Conn, status: http.Status, content_type: []const u8, body: []const u8) void { var buf: [512]u8 = undefined; const header = std.fmt.bufPrint(&buf, "HTTP/1.1 {s}\r\nContent-Type: {s}\r\nContent-Length: {d}\r\nConnection: close\r\nServer: zlay\r\n\r\n", .{ httpStatusLine(status), content_type, body.len, }) catch return; conn.writeFramed(header) catch return; if (body.len > 0) conn.writeFramed(body) catch return;}
pub fn respondJson(conn: *Conn, status: http.Status, body: []const u8) void { httpRespond(conn, status, "application/json", body);}
pub fn respondText(conn: *Conn, status: http.Status, body: []const u8) void { httpRespond(conn, status, "text/plain", body);}
pub fn respondRedirect(conn: *Conn, location: []const u8) void { var buf: [1024]u8 = undefined; const header = std.fmt.bufPrint(&buf, "HTTP/1.1 302 Found\r\nLocation: {s}\r\nContent-Length: 0\r\nConnection: close\r\nServer: zlay\r\n\r\n", .{location}) catch return; conn.writeFramed(header) catch return;}
pub fn httpStatusLine(status: http.Status) []const u8 { return switch (status) { .ok => "200 OK", .bad_request => "400 Bad Request", .unauthorized => "401 Unauthorized", .forbidden => "403 Forbidden", .not_found => "404 Not Found", .method_not_allowed => "405 Method Not Allowed", .conflict => "409 Conflict", .internal_server_error => "500 Internal Server Error", else => "500 Internal Server Error", };}
// --- query string helpers ---
pub fn queryParam(query: []const u8, name: []const u8) ?[]const u8 { if (query.len == 0) return null; var iter = std.mem.splitScalar(u8, query, '&'); while (iter.next()) |pair| { const eq = std.mem.indexOfScalar(u8, pair, '=') orelse continue; if (std.mem.eql(u8, pair[0..eq], name)) { return pair[eq + 1 ..]; } } return null;}
/// like queryParam but percent-decodes the value into buf./// returns null if the param is missing, or a slice into buf with the decoded value.pub fn queryParamDecoded(query: []const u8, name: []const u8, buf: []u8) ?[]const u8 { const raw = queryParam(query, name) orelse return null; var i: usize = 0; var out: usize = 0; while (i < raw.len) { if (raw[i] == '%' and i + 2 < raw.len) { const hi = hexVal(raw[i + 1]) orelse { if (out >= buf.len) return null; buf[out] = raw[i]; out += 1; i += 1; continue; }; const lo = hexVal(raw[i + 2]) orelse { if (out >= buf.len) return null; buf[out] = raw[i]; out += 1; i += 1; continue; }; if (out >= buf.len) return null; buf[out] = (@as(u8, hi) << 4) | @as(u8, lo); out += 1; i += 3; } else if (raw[i] == '+') { if (out >= buf.len) return null; buf[out] = ' '; out += 1; i += 1; } else { if (out >= buf.len) return null; buf[out] = raw[i]; out += 1; i += 1; } } return buf[0..out];}
fn hexVal(c: u8) ?u4 { return switch (c) { '0'...'9' => @intCast(c - '0'), 'a'...'f' => @intCast(c - 'a' + 10), 'A'...'F' => @intCast(c - 'A' + 10), else => null, };}