Last active 1719876461

This is a http server that serves a infinitely big file filled with random content

unlimited-download.zig Raw
1const std = @import("std");
2const eql = std.mem.eql;
3
4pub const std_options = .{
5 .log_level = .info,
6};
7
8fn http_error(writer: std.net.Stream.Writer, status_code: u16) !void {
9 const status = switch (status_code) {
10 200 => "OK",
11 400 => "Bad Request",
12 404 => "Not Found",
13 405 => "Method Not Allowed",
14 else => @panic("Invalid status"),
15 };
16
17 try writer.print("HTTP/1.1 {d} {s}\r\nServer: zig\r\nContent-Type: text/plain\r\nContent-Length: {d}\r\n\r\n{s}", .{ status_code, status, status.len, status });
18}
19
20fn format_bytes(buf: []u8, bytes: usize) ![]u8 {
21 const fbytes: f32 = @floatFromInt(bytes);
22 return switch (bytes) {
23 0...1024 => std.fmt.bufPrint(buf, "{d} B", .{bytes}),
24 1025...(1024 * 1024) => std.fmt.bufPrint(buf, "{d:.1} KiB", .{fbytes / 1024}),
25 (1024 * 1024 + 1)...(1024 * 1024 * 1024) => std.fmt.bufPrint(buf, "{d:.1} MiB", .{fbytes / 1024 / 1024}),
26 else => std.fmt.bufPrint(buf, "{d:.1} GiB", .{fbytes / 1024 / 1024 / 1024}),
27 };
28}
29
30fn handle_connection(connection: std.net.Server.Connection, path: []u8, filename: []u8) !void {
31 const writer = connection.stream.writer();
32
33 {
34 var buffer: [255]u8 = undefined;
35 const n = try connection.stream.read(&buffer);
36 if (n < 12) {
37 return http_error(writer, 400);
38 }
39
40 if (!eql(u8, buffer[0..4], "GET ")) {
41 return http_error(writer, 405);
42 }
43
44 if (n < (11 + path.len) or !eql(u8, buffer[4 .. 4 + path.len], path)) {
45 return http_error(writer, 404);
46 }
47 }
48
49 var prng = std.rand.DefaultPrng.init(blk: {
50 var seed: u64 = undefined;
51 try std.posix.getrandom(std.mem.asBytes(&seed));
52 break :blk seed;
53 });
54 const random = prng.random();
55
56 std.log.info("Client {} connected", .{connection.address});
57 defer {
58 connection.stream.close();
59 std.log.info("Client {} disconnected", .{connection.address});
60 }
61
62 try writer.print("HTTP/1.1 200 OK\r\nServer: zig\r\nContent-Type: application/octet-stream\r\nContent-Disposition: attachment; filename=\"{s}\"\r\n\r\n", .{filename});
63
64 var downloaded: usize = 0;
65 var out_counter: usize = 0;
66
67 while (true) {
68 var writeBuffer: [1024]u8 = undefined;
69 random.bytes(&writeBuffer);
70 const wrote = connection.stream.write(&writeBuffer) catch |err| switch (err) {
71 error.BrokenPipe => break,
72 error.ConnectionResetByPeer => break,
73 else => return err,
74 };
75 downloaded += wrote;
76 out_counter += wrote;
77
78 if (out_counter >= 1024 * 1024 * 100) {
79 var buf: [16]u8 = undefined;
80 const downloaded_fmt = try format_bytes(&buf, downloaded);
81 std.log.info("Client {} downloaded {s}", .{ connection.address, downloaded_fmt });
82 out_counter = 0;
83 }
84 }
85}
86
87pub fn main() !void {
88 var gpa = std.heap.GeneralPurposeAllocator(.{}){};
89 const allocator = gpa.allocator();
90 defer {
91 const check = gpa.deinit();
92 if (check == .leak) @panic("memory leaks");
93 }
94
95 const args = try std.process.argsAlloc(allocator);
96 defer std.process.argsFree(allocator, args);
97
98 if (args.len != 5) {
99 const writer = std.io.getStdErr().writer();
100 try writer.print("Usage: {s} DOWNLOAD_PATH DOWNLOAD_FILENAME ADDRESS PORT\n", .{args[0]});
101 std.process.exit(1);
102 unreachable;
103 }
104
105 const listen_address = try std.net.Address.parseIp(args[3], try std.fmt.parseInt(u16, args[4], 10));
106 var server = try listen_address.listen(.{ .reuse_address = true });
107 defer server.deinit();
108 std.log.info("Listening on http://{s}:{s}", .{ args[3], args[4] });
109
110 while (true) {
111 const connection = try server.accept();
112 _ = try std.Thread.spawn(.{}, handle_connection, .{ connection, args[1], args[2] });
113 }
114}
115