forked from
karlseguin.tngl.sh/http.zig
An HTTP/1.1 server for zig
1const std = @import("std");
2const httpz = @import("httpz");
3const Allocator = std.mem.Allocator;
4
5const PORT = 8810;
6
7// This example demonstrates handling file uploads using multipart/form-data.
8// It shows how to:
9// 1. Parse multipart form data
10// 2. Access uploaded file content and metadata
11// 3. Save uploaded files to disk
12// 4. Handle both file and regular form fields
13
14pub fn main(init: std.process.Init) !void {
15 const allocator = init.gpa;
16
17 var server = try httpz.Server(void).init(init.io, allocator, .{
18 .address = .localhost(PORT),
19 .request = .{
20 // Configure the maximum number of multipart form fields
21 // This must be > 0 to use req.multiFormData()
22 .max_multiform_count = 10,
23
24 // Set a reasonable max body size for file uploads (10MB in this example)
25 .max_body_size = 10 * 1024 * 1024,
26 },
27 }, {});
28 defer server.deinit();
29 defer server.stop();
30
31 var router = try server.router(.{});
32
33 router.get("/", index, .{});
34 router.post("/upload", upload, .{});
35
36 std.debug.print("listening http://localhost:{d}/\n", .{PORT});
37 std.debug.print("Open http://localhost:{d}/ in your browser to test file uploads\n", .{PORT});
38
39 try server.listen();
40}
41
42fn index(_: *httpz.Request, res: *httpz.Response) !void {
43 res.content_type = .HTML;
44 res.body =
45 \\<!DOCTYPE html>
46 \\<h1>File Upload Example</h1>
47 \\<form method=post action=/upload enctype="multipart/form-data">
48 \\ <p>Name: <input name=name value=Leto></p>
49 \\ <p>Description: <textarea name=description>A test file upload</textarea></p>
50 \\ <p>File: <input type=file name=file required></p>
51 \\ <p>File 2 (optional): <input type=file name=file2></p>
52 \\ <p><button type=submit>Upload</button></p>
53 \\</form>
54 ;
55}
56
57fn upload(req: *httpz.Request, res: *httpz.Response) !void {
58 // Parse the multipart form data
59 // This returns a MultiFormKeyValue which contains both regular fields and file uploads
60 const form_data = try req.multiFormData();
61
62 // Access regular form fields
63 const name = form_data.get("name");
64 const description = form_data.get("description");
65
66 // Access uploaded files
67 // Each value has:
68 // - .value: []const u8 (the file content)
69 // - .filename: ?[]const u8 (the original filename, if provided)
70 const file = form_data.get("file");
71 const file2 = form_data.get("file2");
72
73 // Build the response
74 res.content_type = .HTML;
75 const writer = res.writer();
76
77 try writer.writeAll("<!DOCTYPE html><h1>Upload Successful!</h1>");
78
79 // Display regular form fields
80 try writer.writeAll("<h2>Form Fields:</h2><ul>");
81 if (name) |n| {
82 try writer.print("<li><strong>Name:</strong> {s}</li>", .{n.value});
83 }
84 if (description) |d| {
85 try writer.print("<li><strong>Description:</strong> {s}</li>", .{d.value});
86 }
87 try writer.writeAll("</ul>");
88
89 // Display file information and optionally save to disk
90 if (file) |f| {
91 try writer.writeAll("<h2>File Upload:</h2><ul>");
92 try writer.print("<li><strong>Filename:</strong> {s}</li>", .{f.filename orelse "no filename provided"});
93 try writer.print("<li><strong>Size:</strong> {} bytes</li>", .{f.value.len});
94 try writer.writeAll("</ul>");
95
96 // // Example: Save the file to disk
97 // if (f.filename) |filename| {
98 // const safe_filename = try std.fmt.allocPrint(res.arena, "uploaded_{s}", .{filename});
99 // const file_handle = try std.fs.cwd().createFile(safe_filename, .{});
100 // defer file_handle.close();
101
102 // try file_handle.writeAll(f.value);
103 // try writer.print("<p>✓ File saved as: <code>{s}</code></p>", .{safe_filename});
104 // }
105
106 try writer.writeAll("<h3>Content Preview:</h3><pre>");
107 try writer.print("\\x{x}", .{f.value[0..@min(200, f.value.len)]});
108 if (f.value.len > 200) {
109 try writer.writeAll("...");
110 }
111 try writer.writeAll("</pre>");
112 }
113
114 // Handle optional second file
115 if (file2) |f| {
116 try writer.writeAll("<h2>Second File Upload:</h2><ul>");
117 try writer.print("<li><strong>Filename:</strong> {s}</li>", .{f.filename orelse "no filename provided"});
118 try writer.print("<li><strong>Size:</strong> {} bytes</li>", .{f.value.len});
119 try writer.writeAll("</ul>");
120 }
121
122 // Iterate over all form fields (useful for debugging or when field names are dynamic)
123 try writer.writeAll("<h2>All Fields (Debug):</h2><ul>");
124 var it = form_data.iterator();
125 while (it.next()) |kv| {
126 if (kv.value.filename) |filename| {
127 try writer.print("<li><strong>{s}</strong> (file): {s} ({} bytes)</li>", .{ kv.key, filename, kv.value.value.len });
128 } else {
129 try writer.print("<li><strong>{s}</strong>: {s}</li>", .{ kv.key, kv.value.value });
130 }
131 }
132 try writer.writeAll("</ul>");
133
134 try writer.writeAll("<p><a href=\"/\">Upload another file</a></p>");
135}