atproto relay implementation in zig zlay.waow.tech
9
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 113 lines 3.9 kB view raw
1//! HTTP response helpers and query string parsing. 2//! 3//! pure utility module — no domain dependencies. used by all API handler modules 4//! to write raw HTTP responses to websocket connections and parse query parameters. 5 6const std = @import("std"); 7const http = std.http; 8const websocket = @import("websocket"); 9 10pub const Conn = websocket.Conn; 11 12pub fn httpRespond(conn: *Conn, status: http.Status, content_type: []const u8, body: []const u8) void { 13 var buf: [512]u8 = undefined; 14 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", .{ 15 httpStatusLine(status), 16 content_type, 17 body.len, 18 }) catch return; 19 conn.writeFramed(header) catch return; 20 if (body.len > 0) conn.writeFramed(body) catch return; 21} 22 23pub fn respondJson(conn: *Conn, status: http.Status, body: []const u8) void { 24 httpRespond(conn, status, "application/json", body); 25} 26 27pub fn respondText(conn: *Conn, status: http.Status, body: []const u8) void { 28 httpRespond(conn, status, "text/plain", body); 29} 30 31pub fn respondRedirect(conn: *Conn, location: []const u8) void { 32 var buf: [1024]u8 = undefined; 33 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; 34 conn.writeFramed(header) catch return; 35} 36 37pub fn httpStatusLine(status: http.Status) []const u8 { 38 return switch (status) { 39 .ok => "200 OK", 40 .bad_request => "400 Bad Request", 41 .unauthorized => "401 Unauthorized", 42 .forbidden => "403 Forbidden", 43 .not_found => "404 Not Found", 44 .method_not_allowed => "405 Method Not Allowed", 45 .conflict => "409 Conflict", 46 .internal_server_error => "500 Internal Server Error", 47 else => "500 Internal Server Error", 48 }; 49} 50 51// --- query string helpers --- 52 53pub fn queryParam(query: []const u8, name: []const u8) ?[]const u8 { 54 if (query.len == 0) return null; 55 var iter = std.mem.splitScalar(u8, query, '&'); 56 while (iter.next()) |pair| { 57 const eq = std.mem.indexOfScalar(u8, pair, '=') orelse continue; 58 if (std.mem.eql(u8, pair[0..eq], name)) { 59 return pair[eq + 1 ..]; 60 } 61 } 62 return null; 63} 64 65/// like queryParam but percent-decodes the value into buf. 66/// returns null if the param is missing, or a slice into buf with the decoded value. 67pub fn queryParamDecoded(query: []const u8, name: []const u8, buf: []u8) ?[]const u8 { 68 const raw = queryParam(query, name) orelse return null; 69 var i: usize = 0; 70 var out: usize = 0; 71 while (i < raw.len) { 72 if (raw[i] == '%' and i + 2 < raw.len) { 73 const hi = hexVal(raw[i + 1]) orelse { 74 if (out >= buf.len) return null; 75 buf[out] = raw[i]; 76 out += 1; 77 i += 1; 78 continue; 79 }; 80 const lo = hexVal(raw[i + 2]) orelse { 81 if (out >= buf.len) return null; 82 buf[out] = raw[i]; 83 out += 1; 84 i += 1; 85 continue; 86 }; 87 if (out >= buf.len) return null; 88 buf[out] = (@as(u8, hi) << 4) | @as(u8, lo); 89 out += 1; 90 i += 3; 91 } else if (raw[i] == '+') { 92 if (out >= buf.len) return null; 93 buf[out] = ' '; 94 out += 1; 95 i += 1; 96 } else { 97 if (out >= buf.len) return null; 98 buf[out] = raw[i]; 99 out += 1; 100 i += 1; 101 } 102 } 103 return buf[0..out]; 104} 105 106fn hexVal(c: u8) ?u4 { 107 return switch (c) { 108 '0'...'9' => @intCast(c - '0'), 109 'a'...'f' => @intCast(c - 'a' + 10), 110 'A'...'F' => @intCast(c - 'A' + 10), 111 else => null, 112 }; 113}