Home

Serving Static Files with Correct MIME Types in a Zig Web Server

28 views

Serving files with appropriate MIME types is essential for any web server to ensure that clients (e.g., browsers) correctly handle and display the content. In Zig, you can achieve this by mapping file extensions to MIME types and then serving the files with the appropriate Content-Type header.

Here's a step-by-step example of how you might implement this in a simple Zig server:

Step 1: Define MIME Types Mapping

Create a function or a constant table to map file extensions to MIME types.

const std = @import("std");

fn getMimeType(ext: []const u8) []const u8 {
    const mime_types = [_][2][]const u8{
        {"html", "text/html"},
        {"css", "text/css"},
        {"js", "application/javascript"},
        {"json", "application/json"},
        {"png", "image/png"},
        {"jpg", "image/jpeg"},
        {"jpeg", "image/jpeg"},
        {"gif", "image/gif"},
        {"svg", "image/svg+xml"},
        {"txt", "text/plain"},
        // Add more types as needed
    };

    for (mime_types) |pair| {
        if (std.mem.eql(u8, ext, pair[0])) {
            return pair[1];
        }
    }

    return "application/octet-stream"; // Default MIME type
}

Step 2: Extract File Extension

Create a function to extract the file extension from a file path or name:

fn getFileExtension(filename: []const u8) []const u8 {
    const dot_pos = std.mem.lastIndexOfScalar(u8, filename, '.');
    if (dot_pos == null) {
        return ""; // No extension
    }

    return filename[dot_pos.? + 1..];
}

Step 3: Serve Static Files with Correct MIME Type

Use the mapping and file extension extraction to serve static files:

const std = @import("std");

fn handleRequest(request: *std.http.Request, response: *std.http.Response) !void {
    const allocator = std.heap.page_allocator;

    const requested_path = request.path.toSliceAlloc(allocator) catch return error.OutOfMemory;
    defer allocator.free(requested_path);

    const static_dir = "static";
    const filepath = try std.fmt.allocPrint(allocator, "{}/{}", .{static_dir, requested_path});
    defer allocator.free(filepath);

    const file = try std.fs.cwd().openFile(filepath, .{ .read = true });
    defer file.close();

    const file_size = try file.getEndPos();
    var buffer = try allocator.alloc(u8, file_size);
    defer allocator.free(buffer);

    try file.readAll(buffer);

    const file_extension = getFileExtension(requested_path);
    const mime_type = getMimeType(file_extension);

    response.addHeader("Content-Type", mime_type);
    try response.writeAll(buffer);
    response.done();
}

pub fn main() void {
    const server = std.http.Server.init();
    defer server.deinit();

    try server.listen("0.0.0.0", 8080, handleRequest);
    server.run() catch std.log.err("Server error: {}\n", .{});
}

Full Example Code

Combining everything together, here is a full example to serve static files with appropriate MIME types:

const std = @import("std");

fn getMimeType(ext: []const u8) []const u8 {
    const mime_types = [_][2][]const u8{
        {"html", "text/html"},
        {"css", "text/css"},
        {"js", "application/javascript"},
        {"json", "application/json"},
        {"png", "image/png"},
        {"jpg", "image/jpeg"},
        {"jpeg", "image/jpeg"},
        {"gif", "image/gif"},
        {"svg", "image/svg+xml"},
        {"txt", "text/plain"},
        // Add more types as needed
    };

    for (mime_types) |pair| {
        if (std.mem.eql(u8, ext, pair[0])) {
            return pair[1];
        }
    }

    return "application/octet-stream"; // Default MIME type
}

fn getFileExtension(filename: []const u8) []const u8 {
    const dot_pos = std.mem.lastIndexOfScalar(u8, filename, '.');
    if (dot_pos == null) {
        return ""; // No extension
    }

    return filename[dot_pos.? + 1..];
}

fn handleRequest(request: *std.http.Request, response: *std.http.Response) !void {
    const allocator = std.heap.page_allocator;

    const requested_path = request.path.toSliceAlloc(allocator) catch return error.OutOfMemory;
    defer allocator.free(requested_path);

    const static_dir = "static";
    const filepath = try std.fmt.allocPrint(allocator, "{}/{}", .{static_dir, requested_path});
    defer allocator.free(filepath);

    const file = try std.fs.cwd().openFile(filepath, .{ .read = true });
    defer file.close();

    const file_size = try file.getEndPos();
    var buffer = try allocator.alloc(u8, file_size);
    defer allocator.free(buffer);

    try file.readAll(buffer);

    const file_extension = getFileExtension(requested_path);
    const mime_type = getMimeType(file_extension);

    response.addHeader("Content-Type", mime_type);
    try response.writeAll(buffer);
    response.done();
}

pub fn main() void {
    const server = std.http.Server.init();
    defer server.deinit();

    try server.listen("0.0.0.0", 8080, handleRequest);
    server.run() catch std.log.err("Server error: {}\n", .{});
}

Explanation:

  1. MIME Types Mapping: A function (getMimeType) that maps file extensions to MIME types.
  2. File Extension Extraction: A function (getFileExtension) to extract the file extension from a file name.
  3. Handling Requests: A handleRequest function that reads the requested file, determines the MIME type, and serves the file with the correct Content-Type header.
  4. Server Initialization: A simple server setup that listens on port 8080 and handles requests using the handleRequest function.

This example demonstrates a basic HTTP server in Zig that correctly serves static files with their appropriate MIME types based on file extensions. You can extend the MIME types table and add more sophisticated error handling as needed.