adridoesthings revised this gist . Go to revision
1 file changed, 1 insertion, 1 deletion
unlimited-download.zig
| @@ -102,7 +102,7 @@ pub fn main() !void { | |||
| 102 | 102 | unreachable; | |
| 103 | 103 | } | |
| 104 | 104 | ||
| 105 | - | const listen_address = try std.net.Address.resolveIp(args[3], try std.fmt.parseInt(u16, args[4], 10)); | |
| 105 | + | const listen_address = try std.net.Address.parseIp(args[3], try std.fmt.parseInt(u16, args[4], 10)); | |
| 106 | 106 | var server = try listen_address.listen(.{ .reuse_address = true }); | |
| 107 | 107 | defer server.deinit(); | |
| 108 | 108 | std.log.info("Listening on http://{s}:{s}", .{ args[3], args[4] }); | |
adridoesthings revised this gist . Go to revision
No changes
adridoesthings revised this gist . Go to revision
No changes
AdriDoesThings revised this gist . Go to revision
1 file changed, 114 insertions
unlimited-download.zig(file created)
| @@ -0,0 +1,114 @@ | |||
| 1 | + | const std = @import("std"); | |
| 2 | + | const eql = std.mem.eql; | |
| 3 | + | ||
| 4 | + | pub const std_options = .{ | |
| 5 | + | .log_level = .info, | |
| 6 | + | }; | |
| 7 | + | ||
| 8 | + | fn 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 | + | ||
| 20 | + | fn 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 | + | ||
| 30 | + | fn 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 | + | ||
| 87 | + | pub 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.resolveIp(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 | + | } | |