Creating a Simple HTTP Server with Zig: A Step-by-Step Project Guide
[Project] Simple HTTP Server in Zig
Description:
In this project, we will be creating a basic HTTP server using the Zig programming language. The server will set up a TCP connection to listen for incoming HTTP requests, handle those requests, and respond appropriately. The server will be capable of serving static HTML files from a directory and will implement basic routing for different HTTP endpoints.
Goals and Learning Objectives:
- Understand the Basics of Zig: Develop a solid understanding of Zig's syntax, data types, and control structures. For an in-depth guide on building with Zig, check out this step-by-step guide to building an SQLite program with Zig.
- TCP Networking: Familiarize yourself with Zig's standard library support for TCP networking.
- HTTP Protocol: Gain insights into the HTTP request/response cycle.
- File I/O: Learn to read files from the filesystem using Zig.
- Concurrency: Explore Zig's model for handling concurrency and asynchronous operations.
Steps to Complete the Project:
-
Install Zig:
- Download and install the latest version of Zig from the official Zig website.
-
Setup Project Directory:
- Create a project directory and initialize a Zig project.
mkdir simple_http_server cd simple_http_server zig init-exe
-
Set Up TCP Server:
- Initialize a TCP server that listens for incoming connections on a specified port.
const std = @import("std"); pub fn main() !void { const allocator = std.heap.page_allocator; var listener = try std.net.tcpListen(std.AddressFamily.ipv4); defer listener.deinit(); try listener.bind(.{ .address = std.net.Address.ipv4(.{ .addr = 0, .port = 8080 }), .reuse_address = true, }); while (true) { var server_socket = try listener.accept(); handleConnection(allocator, server_socket) catch |err| { std.log.err("Failed to handle connection: {}", .{err}); }; } } fn handleConnection(allocator: *std.mem.Allocator, server_socket: std.net.StreamServerSocket) !void { // Handle requests here }
-
Handle HTTP Requests:
- Parse incoming HTTP requests and determine the appropriate response.
fn handleConnection(allocator: *std.mem.Allocator, server_socket: std.net.StreamServerSocket) !void { const reader = server_socket.reader(); const writer = server_socket.writer(); var buffer: [1024]u8 = undefined; const size = try reader.readAll(buffer[0..]); const request = buffer[0..size]; std.log.info("Received request: {}", .{request}); const response = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n<h1>Hello, Zig!</h1>"; try writer.writeAll(response); server_socket.close() catch |err| { std.log.err("Failed to close connection: {}", .{err}); }; }
-
Serving Static HTML Files:
- Implement file reading to serve static HTML files from the filesystem.
fn sendFile(writer: std.io.Writer, filename: []const u8) !void { const file = try std.fs.cwd().openFile(filename, .{}); defer file.close(); var buffer: [1024]u8 = undefined; while (true) { const read_bytes = try file.read(buffer[0..]); if (read_bytes.len == 0) break; try writer.writeAll(read_bytes); } } fn handleConnection(allocator: *std.mem.Allocator, server_socket: std.net.StreamServerSocket) !void { const reader = server_socket.reader(); const writer = server_socket.writer(); var buffer: [1024]u8 = undefined; const size = try reader.readAll(buffer[0..]); const request = buffer[0..size]; std.log.info("Received request: {}", .{request}); if (std.mem.startsWith(u8, request, "GET / ")) { const response = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n"; try writer.writeAll(response); try sendFile(writer, "index.html"); } else { const response = "HTTP/1.1 404 Not Found\r\n\r\n<h1>404 - Not Found</h1>"; try writer.writeAll(response); } server_socket.close() catch |err| { std.log.err("Failed to close connection: {}", .{err}); }; }
-
Implement Basic Routing:
- Add routing support to handle different HTTP endpoints and methods.
fn handleConnection(allocator: *std.mem.Allocator, server_socket: std.net.StreamServerSocket) !void { const reader = server_socket.reader(); const writer = server_socket.writer(); var buffer: [1024]u8 = undefined; const size = try reader.readAll(buffer[0..]); const request = buffer[0..size]; std.log.info("Received request: {}", .{request}); if (std.mem.startsWith(u8, request, "GET / ")) { const response = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n"; try writer.writeAll(response); try sendFile(writer, "index.html"); } else if (std.mem.startsWith(u8, request, "GET /about ")) { const response = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n<h1>About Us</h1>"; try writer.writeAll(response); } else { const response = "HTTP/1.1 404 Not Found\r\n\r\n<h1>404 - Not Found</h1>"; try writer.writeAll(response); } server_socket.close() catch |err| { std.log.err("Failed to close connection: {}", .{err}); }; }
-
Handling Concurrency and Asynchronous Operations:
- Use Zig's concurrency model, considering the
async
andawait
keywords for handling multiple connections. For more insights on concurrency handling, you can refer to this simple in-memory key-value store in Go with concurrency handling.
pub fn main() !void { const allocator = std.heap.page_allocator; var listener = try std.net.tcpListen(std.AddressFamily.ipv4); defer listener.deinit(); try listener.bind(.{ .address = std.net.Address.ipv4(.{ .addr = 0, .port = 8080 }), .reuse_address = true, }); const acceptor = async acceptConnections(allocator, listener); try acceptor.await; } fn acceptConnections(allocator: *std.mem.Allocator, listener: std.net.StreamServerSocket) !void { while (true) { var server_socket = try listener.accept(); _ = async handleConnection(allocator, server_socket); } }
- Use Zig's concurrency model, considering the
Conclusion:
With this project, we've learned how to create a simple HTTP server in Zig, understanding its core syntax, network communication, file I/O, and concurrency model. This foundation enables us to expand further by adding more complex routes, handling various HTTP methods, implementing security features, or even building a more robust web server framework. Happy coding!
Other Xegs
- HTTP Zig Server
Simple HTTP server
- Airbnb API
Property managers
- Okta migration
Fetching all users
- Zig Stack vs Heap
Memory management