Servidor HTTP - Parte 1

O Básico

Enzo Soares

Publicado em 27 de Março de 2025 às 13:09

Boa tarde!

Agora que acabamos com o básico do Zig, vamos para algo bastante mais complexo: um servidor http. Um projeto desse tipo pode ficar bem grande e complexo, mas vou registrar aqui todo o progresso que tiver, na velocidade que ele acontecer. O código fonte está nesse repositório. Vamos lá!

Primeiro, vamos declarar os imports e ouvir em uma porta usando a biblioteca padrão:

const std = @import("std");
const print = std.debug.print;
const net = std.net;
const mem = std.mem;

pub fn main() !void {
    print("Started server\n", .{});
    const loopback = try net.Ip4Address.parse("127.0.0.1", 2500);
    const local_address = net.Address{ .in = loopback };
    var server = try local_address.listen(.{ .reuse_address = true });
    print("Listening on {}\n", .{local_address});
}

No momento, estamos apenas ouvindo a porta, mas não estamos fazendo nada com as informações que estão entrando. Para isso, vamos usar o server.accept(). Depois, vamos armazenar a requisição http em um buffer:

pub fn main() !void {
    print("Started server\n", .{});
    const loopback = try net.Ip4Address.parse("127.0.0.1", 2500);
    const local_address = net.Address{ .in = loopback };
    var server = try local_address.listen(.{ .reuse_address = true });
    print("Listening on {}\n", .{local_address});

    while (server.accept()) |connection| {
        print("Accepted connection from: {}\n", .{connection.address});
        var received_total: [2048]u8 = undefined;
        var received_total_length: usize = 0;
        while (connection.stream.read(received_total[received_total_length..])) |temp_received_length| {
            if (temp_received_length == 0) break;
            received_total_length += temp_received_length;
            if (mem.containsAtLeast(u8, received_total[0..received_total_length], 1, "\r\n\r\n")) {
                break;
            }
        } else |read_err| {
            return read_err;
        }

        const request = received_total[0..received_total_length];

        print("Request: {s}\n", .{request});
    } else |err| {
        return err;
    }
}

Esse código faz algumas coisas: 1. Como as informações da conexão vêm no formato de uma stream, primeiro as armazenamos no buffer received_total´ a medida que chegam; 2. Uma requisição http é sempre finalizada por"\r\n\r\n", então paramos de escutar quando vermos essa string; 3. Armazenamos emrequest` os dados recebidos, em um buffer do tamanho certo;

No entanto, se você rodar esse servidor e fizer uma chamada http nele, embora ele receba as informações, em nenhum momemto é retornado ao cliente uma resposta, nem a requisição é encerrada. Para isso, precisamos fazer duas simples ações: retornar o status junto com o header Connection: close e chamar a função close().

const httpHead =
    "HTTP/1.1 200 OK \r\n" ++
    "Connection: close\r\n" ++
    "\r\n";

_ = try connection.stream.write(httpHead);
connection.stream.close();

Por hoje é tudo! Até!