const std = @import("std"); const web = @import("zhp"); const hzzp = @import("hzzp"); const mimetypes = @import("mimetypes"); const Request = web.Request; const Response = web.Response; const images_dir_path = "./images"; var registry: mimetypes.Registry = undefined; var gpa = std.heap.GeneralPurposeAllocator(.{}){}; const IndexHandler = struct { pub fn get(self: *@This(), req: *Request, resp: *Response) !void { try resp.stream.writeAll("Hello, World!"); } }; const UploadHandler = struct { const start = std.mem.indexOf(u8, template, key).?; const end = start + key.len; pub fn post(self: *@This(), req: *Request, resp: *Response) !void { var content_type = req.headers.getDefault("Content-Type", ""); if (!std.mem.startsWith(u8, content_type, "multipart/form-data")) { resp.status = web.responses.BAD_REQUEST; return; } var form = web.forms.Form.init(resp.allocator); form.parse(req) catch |err| switch (err) { error.NotImplemented => { resp.status = web.responses.REQUEST_ENTITY_TOO_LARGE; try resp.stream.writeAll("TODO: Handle large uploads"); return; }, else => return err, }; var part = form.files.get("file").?; var extensions = registry.getExtensionsByType(part.content_type); if (extensions == null) return error.InvalidContentMimeType; const extension = extensions.?.items[0]; var image_id_buffer: [256]u8 = undefined; const image_id = generateImageId(&image_id_buffer); var image_path_buffer: [512]u8 = undefined; const image_path = try std.fmt.bufPrint( &image_path_buffer, "{s}/{s}{s}", .{ images_dir_path, image_id, extension }, ); const image_file = try std.fs.cwd().createFile(image_path, .{}); try image_file.writer().writeAll(part.body); try resp.stream.writeAll(image_path); return; } }; const FetchHandler = struct { pub fn get(self: *@This(), req: *Request, resp: *Response) !void { const filename = req.args.?[0].?; std.log.info("got name: {s}", .{filename}); var image_path_buffer: [512]u8 = undefined; const images_dir = try std.fs.cwd().openDir(images_dir_path, .{}); const image_path = try std.fmt.bufPrint( &image_path_buffer, "{s}/{s}", .{ images_dir_path, filename }, ); // TODO return 404 on error const image_file = try std.fs.cwd().openFile(image_path, .{ .read = false }); while (true) { var file_write_buffer: [1024]u8 = undefined; const bytes_read = try image_file.read(&file_write_buffer); if (bytes_read == 0) return; try resp.stream.writeAll(&file_write_buffer); } } }; pub const io_mode = .evented; pub const log_level = .debug; // The routes must be defined in the "root" pub const routes = [_]web.Route{ web.Route.create("index", "/", IndexHandler), web.Route.create("upload", "/api/upload", UploadHandler), web.Route.create("fetch", "/i/(.*)", FetchHandler), }; pub const middleware = [_]web.Middleware{ web.Middleware.create(web.middleware.LoggingMiddleware), }; pub fn main() !void { std.log.info("welcome to webscale", .{}); defer std.debug.assert(!gpa.deinit()); const allocator = &gpa.allocator; registry = mimetypes.Registry.init(std.heap.page_allocator); defer registry.deinit(); try registry.load(); // TODO: configurable path via env var try std.fs.cwd().makePath(images_dir_path); var app = web.Application.init(allocator, .{ .debug = true }); defer app.deinit(); try app.listen("127.0.0.1", 8080); try app.start(); } fn generateImageId(buffer: []u8) []const u8 { var i: usize = 0; const seed = @truncate(u64, @bitCast(u128, std.time.nanoTimestamp())); var r = std.rand.DefaultPrng.init(seed); while (i < 16) : (i += 1) { // random ascii lowercase char var idx = @intCast(u8, r.random.uintLessThan(u5, 24)); var letter = @as(u8, 97) + idx; buffer[i] = letter; } return buffer[0..i]; }