MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

从零开始构建基于libev的Web框架

2022-05-174.7k 阅读

1. 认识libev

libev 是一个高性能的事件驱动库,它基于 Reactor 模式,旨在提供一个高效、可移植且轻量级的事件循环。它支持多种事件类型,包括文件描述符的 I/O 事件、信号、定时事件等。与其他类似库(如 libevent)相比,libev 的设计目标是尽可能地轻量级和高效,在性能关键的应用场景中表现出色。

1.1 libev 的安装与基本使用

在开始构建 Web 框架之前,需要先安装 libev 库。在大多数 Linux 系统上,可以通过包管理器进行安装:

sudo apt-get install libev-dev  # Debian/Ubuntu
sudo yum install libev-devel   # CentOS/RHEL

对于 macOS,可以使用 Homebrew:

brew install libev

安装完成后,就可以在代码中引入 libev 头文件:

#include <ev.h>

一个简单的 libev 使用示例,监听标准输入的可读事件:

#include <stdio.h>
#include <ev.h>

// 定义一个回调函数,当事件触发时会调用这个函数
static void stdin_cb(struct ev_loop *loop, struct ev_io *w, int revents) {
    char buf[1024];
    ssize_t n = read(STDIN_FILENO, buf, sizeof(buf));
    if (n > 0) {
        buf[n] = '\0';
        printf("Read from stdin: %s", buf);
    }
}

int main() {
    // 创建一个默认的事件循环
    struct ev_loop *loop = ev_default_loop(0);

    // 定义一个 ev_io 结构体来监听标准输入
    struct ev_io stdin_watcher;
    ev_io_init(&stdin_watcher, stdin_cb, STDIN_FILENO, EV_READ);

    // 将事件监视器添加到事件循环中
    ev_io_start(loop, &stdin_watcher);

    // 启动事件循环,程序会在这里阻塞,直到事件发生
    ev_run(loop, 0);

    return 0;
}

在这个示例中,我们创建了一个事件循环 loop,并定义了一个 ev_io 结构体 stdin_watcher 来监听标准输入的可读事件。当标准输入有数据可读时,stdin_cb 回调函数会被调用,读取输入的数据并打印出来。

2. 构建 Web 框架的基础 - HTTP 解析

在构建基于 libev 的 Web 框架时,首先需要能够解析 HTTP 请求。HTTP 请求由请求行、请求头和请求体组成。

2.1 HTTP 请求行解析

请求行包含请求方法、请求 URL 和 HTTP 版本。例如:GET /index.html HTTP/1.1。我们可以通过字符串解析的方式提取这些信息。

#include <stdio.h>
#include <string.h>

void parse_request_line(const char *request_line, char *method, char *url, char *http_version) {
    sscanf(request_line, "%s %s %s", method, url, http_version);
}

int main() {
    const char *request_line = "GET /index.html HTTP/1.1";
    char method[10], url[100], http_version[10];
    parse_request_line(request_line, method, url, http_version);
    printf("Method: %s\n", method);
    printf("URL: %s\n", url);
    printf("HTTP Version: %s\n", http_version);
    return 0;
}

在这个示例中,parse_request_line 函数使用 sscanf 来解析请求行,提取出请求方法、URL 和 HTTP 版本。

2.2 HTTP 请求头解析

请求头包含了关于请求的附加信息,如 Content - TypeUser - Agent 等。请求头以 Key: Value 的形式存在,每行一个头信息,以空行结束。

#include <stdio.h>
#include <string.h>

#define MAX_HEADERS 100
#define HEADER_NAME_LEN 50
#define HEADER_VALUE_LEN 200

void parse_headers(const char *headers, char (*header_names)[HEADER_NAME_LEN], char (*header_values)[HEADER_VALUE_LEN], int *header_count) {
    *header_count = 0;
    const char *p = headers;
    while (*p && *header_count < MAX_HEADERS) {
        const char *end = strchr(p, '\n');
        if (end && *end == '\n') {
            if (strncmp(p, "\r\n", 2) == 0) {
                break;
            }
            sscanf(p, "%49[^:]: %199[^\r\n]", header_names[*header_count], header_values[*header_count]);
            (*header_count)++;
            p = end + 1;
        } else {
            break;
        }
    }
}

int main() {
    const char *headers = "Content - Type: text/html\r\nUser - Agent: Mozilla/5.0\r\n\r\n";
    char header_names[MAX_HEADERS][HEADER_NAME_LEN];
    char header_values[MAX_HEADERS][HEADER_VALUE_LEN];
    int header_count;
    parse_headers(headers, header_names, header_values, &header_count);
    for (int i = 0; i < header_count; i++) {
        printf("Header %d: %s: %s\n", i + 1, header_names[i], header_values[i]);
    }
    return 0;
}

在这个代码中,parse_headers 函数通过逐行解析请求头,提取出每个头的名称和值,并存储在数组中。

2.3 HTTP 请求体解析

请求体通常在 POST 请求中携带数据。解析请求体需要根据 Content - Length 头来确定数据的长度。

#include <stdio.h>
#include <string.h>

void parse_body(const char *headers, const char *request, char *body) {
    const char *content_length_header = "Content - Length: ";
    char content_length_str[10];
    int content_length = 0;
    const char *p = strstr(headers, content_length_header);
    if (p) {
        sscanf(p + strlen(content_length_header), "%d", &content_length);
        const char *body_start = strstr(request, "\r\n\r\n");
        if (body_start) {
            body_start += 4;
            strncpy(body, body_start, content_length);
            body[content_length] = '\0';
        }
    }
}

int main() {
    const char *headers = "Content - Type: application/x - www - form - urlencoded\r\nContent - Length: 13\r\n\r\n";
    const char *request = "POST /submit HTTP/1.1\r\nContent - Type: application/x - www - form - urlencoded\r\nContent - Length: 13\r\n\r\nusername=test";
    char body[100];
    parse_body(headers, request, body);
    printf("Body: %s\n", body);
    return 0;
}

parse_body 函数中,首先从请求头中提取 Content - Length,然后找到请求体的起始位置,根据长度提取请求体内容。

3. 基于 libev 的 HTTP 服务器搭建

有了 HTTP 解析的基础,我们可以开始搭建基于 libev 的 HTTP 服务器。

3.1 创建 TCP 监听套接字

首先需要创建一个 TCP 套接字来监听客户端的连接请求。

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>

int create_listen_socket(int port) {
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        perror("socket creation failed");
        exit(EXIT_FAILURE);
    }

    int opt = 1;
    if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
        perror("setsockopt failed");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    struct sockaddr_in servaddr;
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = INADDR_ANY;
    servaddr.sin_port = htons(port);

    if (bind(sockfd, (const struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {
        perror("bind failed");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    if (listen(sockfd, 10) < 0) {
        perror("listen failed");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    return sockfd;
}

create_listen_socket 函数中,我们创建了一个 TCP 套接字,设置了 SO_REUSEADDRSO_REUSEPORT 选项以允许地址重用,绑定到指定端口并开始监听。

3.2 处理客户端连接

当有客户端连接时,我们需要接受连接并创建一个新的事件监视器来处理该连接上的 I/O 事件。

#include <ev.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>

#define BUFFER_SIZE 1024

// 定义一个结构体来存储每个客户端连接的信息
typedef struct {
    struct ev_io io_watcher;
    int sockfd;
    char buffer[BUFFER_SIZE];
    size_t buffer_len;
} client_connection;

// 客户端连接处理回调函数
static void client_connection_cb(struct ev_loop *loop, struct ev_io *w, int revents) {
    client_connection *conn = (client_connection *)w->data;
    if (revents & EV_READ) {
        ssize_t n = recv(conn->sockfd, conn->buffer + conn->buffer_len, sizeof(conn->buffer) - conn->buffer_len, 0);
        if (n <= 0) {
            if (n == 0) {
                printf("Client disconnected\n");
            } else {
                perror("recv failed");
            }
            ev_io_stop(loop, &conn->io_watcher);
            close(conn->sockfd);
            free(conn);
            return;
        }
        conn->buffer_len += n;
        // 这里可以进行 HTTP 解析
    }
}

// 监听套接字回调函数,处理新的客户端连接
static void listen_socket_cb(struct ev_loop *loop, struct ev_io *w, int revents) {
    if (revents & EV_READ) {
        int listen_sockfd = w->fd;
        struct sockaddr_in clientaddr;
        socklen_t clientaddr_len = sizeof(clientaddr);
        int client_sockfd = accept(listen_sockfd, (struct sockaddr *)&clientaddr, &clientaddr_len);
        if (client_sockfd < 0) {
            perror("accept failed");
            return;
        }
        printf("Client connected\n");

        client_connection *conn = (client_connection *)malloc(sizeof(client_connection));
        conn->sockfd = client_sockfd;
        conn->buffer_len = 0;

        ev_io_init(&conn->io_watcher, client_connection_cb, client_sockfd, EV_READ);
        conn->io_watcher.data = conn;
        ev_io_start(loop, &conn->io_watcher);
    }
}

listen_socket_cb 函数中,当有新的客户端连接时,接受连接并创建一个 client_connection 结构体来管理该连接。然后初始化一个 ev_io 监视器来监听该连接的可读事件,事件触发时会调用 client_connection_cb 函数。在 client_connection_cb 函数中,读取客户端发送的数据,并可以在此处进行 HTTP 解析。

4. 集成 HTTP 解析到服务器

将前面实现的 HTTP 解析功能集成到基于 libev 的服务器中。

4.1 完整的 HTTP 解析与处理流程

#include <ev.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>

#define BUFFER_SIZE 1024

typedef struct {
    struct ev_io io_watcher;
    int sockfd;
    char buffer[BUFFER_SIZE];
    size_t buffer_len;
    char method[10];
    char url[100];
    char http_version[10];
    char headers[BUFFER_SIZE];
    int header_count;
    char body[BUFFER_SIZE];
} client_connection;

void parse_request_line(const char *request_line, char *method, char *url, char *http_version) {
    sscanf(request_line, "%s %s %s", method, url, http_version);
}

void parse_headers(const char *headers, char (*header_names)[50], char (*header_values)[200], int *header_count) {
    *header_count = 0;
    const char *p = headers;
    while (*p && *header_count < 100) {
        const char *end = strchr(p, '\n');
        if (end && *end == '\n') {
            if (strncmp(p, "\r\n", 2) == 0) {
                break;
            }
            sscanf(p, "%49[^:]: %199[^\r\n]", (*header_names)[*header_count], (*header_values)[*header_count]);
            (*header_count)++;
            p = end + 1;
        } else {
            break;
        }
    }
}

void parse_body(const char *headers, const char *request, char *body) {
    const char *content_length_header = "Content - Length: ";
    char content_length_str[10];
    int content_length = 0;
    const char *p = strstr(headers, content_length_header);
    if (p) {
        sscanf(p + strlen(content_length_header), "%d", &content_length);
        const char *body_start = strstr(request, "\r\n\r\n");
        if (body_start) {
            body_start += 4;
            strncpy(body, body_start, content_length);
            body[content_length] = '\0';
        }
    }
}

static void client_connection_cb(struct ev_loop *loop, struct ev_io *w, int revents) {
    client_connection *conn = (client_connection *)w->data;
    if (revents & EV_READ) {
        ssize_t n = recv(conn->sockfd, conn->buffer + conn->buffer_len, sizeof(conn->buffer) - conn->buffer_len, 0);
        if (n <= 0) {
            if (n == 0) {
                printf("Client disconnected\n");
            } else {
                perror("recv failed");
            }
            ev_io_stop(loop, &conn->io_watcher);
            close(conn->sockfd);
            free(conn);
            return;
        }
        conn->buffer_len += n;

        // 查找请求行结束位置
        char *request_line_end = strchr(conn->buffer, '\n');
        if (request_line_end) {
            *request_line_end = '\0';
            parse_request_line(conn->buffer, conn->method, conn->url, conn->http_version);

            // 查找请求头结束位置
            char *headers_end = strstr(request_line_end + 1, "\r\n\r\n");
            if (headers_end) {
                int headers_len = headers_end - (request_line_end + 1);
                strncpy(conn->headers, request_line_end + 1, headers_len);
                conn->headers[headers_len] = '\0';

                parse_headers(conn->headers, NULL, NULL, &conn->header_count);

                if (headers_end + 4 < conn->buffer + conn->buffer_len) {
                    parse_body(conn->headers, conn->buffer, conn->body);
                }

                // 这里可以进行请求处理,如路由匹配等
                printf("Method: %s\n", conn->method);
                printf("URL: %s\n", conn->url);
                printf("HTTP Version: %s\n", conn->http_version);
                printf("Body: %s\n", conn->body);
            }
        }
    }
}

static void listen_socket_cb(struct ev_loop *loop, struct ev_io *w, int revents) {
    if (revents & EV_READ) {
        int listen_sockfd = w->fd;
        struct sockaddr_in clientaddr;
        socklen_t clientaddr_len = sizeof(clientaddr);
        int client_sockfd = accept(listen_sockfd, (struct sockaddr *)&clientaddr, &clientaddr_len);
        if (client_sockfd < 0) {
            perror("accept failed");
            return;
        }
        printf("Client connected\n");

        client_connection *conn = (client_connection *)malloc(sizeof(client_connection));
        conn->sockfd = client_sockfd;
        conn->buffer_len = 0;

        ev_io_init(&conn->io_watcher, client_connection_cb, client_sockfd, EV_READ);
        conn->io_watcher.data = conn;
        ev_io_start(loop, &conn->io_watcher);
    }
}

int create_listen_socket(int port) {
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        perror("socket creation failed");
        exit(EXIT_FAILURE);
    }

    int opt = 1;
    if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
        perror("setsockopt failed");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    struct sockaddr_in servaddr;
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = INADDR_ANY;
    servaddr.sin_port = htons(port);

    if (bind(sockfd, (const struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {
        perror("bind failed");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    if (listen(sockfd, 10) < 0) {
        perror("listen failed");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    return sockfd;
}

int main() {
    struct ev_loop *loop = ev_default_loop(0);
    int listen_sockfd = create_listen_socket(8080);

    struct ev_io listen_watcher;
    ev_io_init(&listen_watcher, listen_socket_cb, listen_sockfd, EV_READ);
    ev_io_start(loop, &listen_watcher);

    ev_run(loop, 0);

    close(listen_sockfd);
    return 0;
}

client_connection_cb 函数中,首先读取客户端数据,然后逐步解析请求行、请求头和请求体。解析完成后,可以在这里进行请求的处理,如路由匹配等操作。

5. 路由与请求处理

路由是 Web 框架的核心功能之一,它负责根据请求的 URL 将请求分发给相应的处理函数。

5.1 简单的路由表实现

#include <stdio.h>
#include <string.h>

#define MAX_ROUTES 100

typedef struct {
    char url[100];
    void (*handler)(const char *method, const char *url, const char *headers, const char *body);
} route;

route routes[MAX_ROUTES];
int route_count = 0;

void add_route(const char *url, void (*handler)(const char *method, const char *url, const char *headers, const char *body)) {
    if (route_count < MAX_ROUTES) {
        strcpy(routes[route_count].url, url);
        routes[route_count].handler = handler;
        route_count++;
    }
}

void handle_request(const char *method, const char *url, const char *headers, const char *body) {
    for (int i = 0; i < route_count; i++) {
        if (strcmp(routes[i].url, url) == 0) {
            routes[i].handler(method, url, headers, body);
            return;
        }
    }
    printf("No handler found for URL: %s\n", url);
}

// 示例请求处理函数
void home_handler(const char *method, const char *url, const char *headers, const char *body) {
    printf("Handling home page request\n");
}

int main() {
    add_route("/", home_handler);
    handle_request("GET", "/", "", "");
    return 0;
}

在这个示例中,我们定义了一个 route 结构体来存储 URL 和对应的处理函数。add_route 函数用于添加路由,handle_request 函数根据请求的 URL 查找并调用相应的处理函数。

5.2 集成路由到 Web 框架

将路由功能集成到之前搭建的基于 libev 的 Web 框架中。

#include <ev.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>

#define BUFFER_SIZE 1024
#define MAX_ROUTES 100

typedef struct {
    struct ev_io io_watcher;
    int sockfd;
    char buffer[BUFFER_SIZE];
    size_t buffer_len;
    char method[10];
    char url[100];
    char http_version[10];
    char headers[BUFFER_SIZE];
    int header_count;
    char body[BUFFER_SIZE];
} client_connection;

typedef struct {
    char url[100];
    void (*handler)(const char *method, const char *url, const char *headers, const char *body);
} route;

route routes[MAX_ROUTES];
int route_count = 0;

void add_route(const char *url, void (*handler)(const char *method, const char *url, const char *headers, const char *body)) {
    if (route_count < MAX_ROUTES) {
        strcpy(routes[route_count].url, url);
        routes[route_count].handler = handler;
        route_count++;
    }
}

void handle_request(const char *method, const char *url, const char *headers, const char *body) {
    for (int i = 0; i < route_count; i++) {
        if (strcmp(routes[i].url, url) == 0) {
            routes[i].handler(method, url, headers, body);
            return;
        }
    }
    printf("No handler found for URL: %s\n", url);
}

void parse_request_line(const char *request_line, char *method, char *url, char *http_version) {
    sscanf(request_line, "%s %s %s", method, url, http_version);
}

void parse_headers(const char *headers, char (*header_names)[50], char (*header_values)[200], int *header_count) {
    *header_count = 0;
    const char *p = headers;
    while (*p && *header_count < 100) {
        const char *end = strchr(p, '\n');
        if (end && *end == '\n') {
            if (strncmp(p, "\r\n", 2) == 0) {
                break;
            }
            sscanf(p, "%49[^:]: %199[^\r\n]", (*header_names)[*header_count], (*header_values)[*header_count]);
            (*header_count)++;
            p = end + 1;
        } else {
            break;
        }
    }
}

void parse_body(const char *headers, const char *request, char *body) {
    const char *content_length_header = "Content - Length: ";
    char content_length_str[10];
    int content_length = 0;
    const char *p = strstr(headers, content_length_header);
    if (p) {
        sscanf(p + strlen(content_length_header), "%d", &content_length);
        const char *body_start = strstr(request, "\r\n\r\n");
        if (body_start) {
            body_start += 4;
            strncpy(body, body_start, content_length);
            body[content_length] = '\0';
        }
    }
}

void home_handler(const char *method, const char *url, const char *headers, const char *body) {
    printf("Handling home page request\n");
}

static void client_connection_cb(struct ev_loop *loop, struct ev_io *w, int revents) {
    client_connection *conn = (client_connection *)w->data;
    if (revents & EV_READ) {
        ssize_t n = recv(conn->sockfd, conn->buffer + conn->buffer_len, sizeof(conn->buffer) - conn->buffer_len, 0);
        if (n <= 0) {
            if (n == 0) {
                printf("Client disconnected\n");
            } else {
                perror("recv failed");
            }
            ev_io_stop(loop, &conn->io_watcher);
            close(conn->sockfd);
            free(conn);
            return;
        }
        conn->buffer_len += n;

        char *request_line_end = strchr(conn->buffer, '\n');
        if (request_line_end) {
            *request_line_end = '\0';
            parse_request_line(conn->buffer, conn->method, conn->url, conn->http_version);

            char *headers_end = strstr(request_line_end + 1, "\r\n\r\n");
            if (headers_end) {
                int headers_len = headers_end - (request_line_end + 1);
                strncpy(conn->headers, request_line_end + 1, headers_len);
                conn->headers[headers_len] = '\0';

                parse_headers(conn->headers, NULL, NULL, &conn->header_count);

                if (headers_end + 4 < conn->buffer + conn->buffer_len) {
                    parse_body(conn->headers, conn->buffer, conn->body);
                }

                handle_request(conn->method, conn->url, conn->headers, conn->body);
            }
        }
    }
}

static void listen_socket_cb(struct ev_loop *loop, struct ev_io *w, int revents) {
    if (revents & EV_READ) {
        int listen_sockfd = w->fd;
        struct sockaddr_in clientaddr;
        socklen_t clientaddr_len = sizeof(clientaddr);
        int client_sockfd = accept(listen_sockfd, (struct sockaddr *)&clientaddr, &clientaddr_len);
        if (client_sockfd < 0) {
            perror("accept failed");
            return;
        }
        printf("Client connected\n");

        client_connection *conn = (client_connection *)malloc(sizeof(client_connection));
        conn->sockfd = client_sockfd;
        conn->buffer_len = 0;

        ev_io_init(&conn->io_watcher, client_connection_cb, client_sockfd, EV_READ);
        conn->io_watcher.data = conn;
        ev_io_start(loop, &conn->io_watcher);
    }
}

int create_listen_socket(int port) {
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        perror("socket creation failed");
        exit(EXIT_FAILURE);
    }

    int opt = 1;
    if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
        perror("setsockopt failed");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    struct sockaddr_in servaddr;
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = INADDR_ANY;
    servaddr.sin_port = htons(port);

    if (bind(sockfd, (const struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {
        perror("bind failed");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    if (listen(sockfd, 10) < 0) {
        perror("listen failed");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    return sockfd;
}

int main() {
    struct ev_loop *loop = ev_default_loop(0);
    int listen_sockfd = create_listen_socket(8080);

    add_route("/", home_handler);

    struct ev_io listen_watcher;
    ev_io_init(&listen_watcher, listen_socket_cb, listen_sockfd, EV_READ);
    ev_io_start(loop, &listen_watcher);

    ev_run(loop, 0);

    close(listen_sockfd);
    return 0;
}

在这个代码中,我们在 client_connection_cb 函数中调用 handle_request 函数,根据解析出的 URL 查找并调用相应的路由处理函数。

6. 响应生成与发送

处理完请求后,需要生成并发送 HTTP 响应给客户端。

6.1 构建 HTTP 响应头

#include <stdio.h>
#include <string.h>

#define MAX_HEADERS 10
#define HEADER_NAME_LEN 50
#define HEADER_VALUE_LEN 200

void build_response_headers(char *response_headers, const char *content_type, int content_length) {
    snprintf(response_headers, 1024, "HTTP/1.1 200 OK\r\nContent - Type: %s\r\nContent - Length: %d\r\n\r\n", content_type, content_length);
}

build_response_headers 函数中,我们构建了一个简单的 HTTP 响应头,包含 HTTP 版本、状态码、Content - TypeContent - Length

6.2 发送响应

将响应头和响应体发送给客户端。

#include <ev.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>

#define BUFFER_SIZE 1024
#define MAX_ROUTES 100

typedef struct {
    struct ev_io io_watcher;
    int sockfd;
    char buffer[BUFFER_SIZE];
    size_t buffer_len;
    char method[10];
    char url[100];
    char http_version[10];
    char headers[BUFFER_SIZE];
    int header_count;
    char body[BUFFER_SIZE];
} client_connection;

typedef struct {
    char url[100];
    void (*handler)(const char *method, const char *url, const char *headers, const char *body, int sockfd);
} route;

route routes[MAX_ROUTES];
int route_count = 0;

void add_route(const char *url, void (*handler)(const char *method, const char *url, const char *headers, const char *body, int sockfd)) {
    if (route_count < MAX_ROUTES) {
        strcpy(routes[route_count].url, url);
        routes[route_count].handler = handler;
        route_count++;
    }
}

void handle_request(const char *method, const char *url, const char *headers, const char *body, int sockfd) {
    for (int i = 0; i < route_count; i++) {
        if (strcmp(routes[i].url, url) == 0) {
            routes[i].handler(method, url, headers, body, sockfd);
            return;
        }
    }
    printf("No handler found for URL: %s\n", url);
    char response_headers[1024];
    build_response_headers(response_headers, "text/plain", strlen("Not Found"));
    send(sockfd, response_headers, strlen(response_headers), 0);
    send(sockfd, "Not Found", strlen("Not Found"), 0);
}

void build_response_headers(char *response_headers, const char *content_type, int content_length) {
    snprintf(response_headers, 1024, "HTTP/1.1 200 OK\r\nContent - Type: %s\r\nContent - Length: %d\r\n\r\n", content_type, content_length);
}

void home_handler(const char *method, const char *url, const char *headers, const char *body, int sockfd) {
    char response_headers[1024];
    build_response_headers(response_headers, "text/html", strlen("<html><body>Home Page</body></html>"));
    send(sockfd, response_headers, strlen(response_headers), 0);
    send(sockfd, "<html><body>Home Page</body></html>", strlen("<html><body>Home Page</body></html>"), 0);
}

void parse_request_line(const char *request_line, char *method, char *url, char *http_version) {
    sscanf(request_line, "%s %s %s", method, url, http_version);
}

void parse_headers(const char *headers, char (*header_names)[50], char (*header_values)[200], int *header_count) {
    *header_count = 0;
    const char *p = headers;
    while (*p && *header_count < 100) {
        const char *end = strchr(p, '\n');
        if (end && *end == '\n') {
            if (strncmp(p, "\r\n", 2) == 0) {
                break;
            }
            sscanf(p, "%49[^:]: %199[^\r\n]", (*header_names)[*header_count], (*header_values)[*header_count]);
            (*header_count)++;
            p = end + 1;
        } else {
            break;
        }
    }
}

void parse_body(const char *headers, const char *request, char *body) {
    const char *content_length_header = "Content - Length: ";
    char content_length_str[10];
    int content_length = 0;
    const char *p = strstr(headers, content_length_header);
    if (p) {
        sscanf(p + strlen(content_length_header), "%d", &content_length);
        const char *body_start = strstr(request, "\r\n\r\n");
        if (body_start) {
            body_start += 4;
            strncpy(body, body_start, content_length);
            body[content_length] = '\0';
        }
    }
}

static void client_connection_cb(struct ev_loop *loop, struct ev_io *w, int revents) {
    client_connection *conn = (client_connection *)w->data;
    if (revents & EV_READ) {
        ssize_t n = recv(conn->sockfd, conn->buffer + conn->buffer_len, sizeof(conn->buffer) - conn->buffer_len, 0);
        if (n <= 0) {
            if (n == 0) {
                printf("Client disconnected\n");
            } else {
                perror("recv failed");
            }
            ev_io_stop(loop, &conn->io_watcher);
            close(conn->sockfd);
            free(conn);
            return;
        }
        conn->buffer_len += n;

        char *request_line_end = strchr(conn->buffer, '\n');
        if (request_line_end) {
            *request_line_end = '\0';
            parse_request_line(conn->buffer, conn->method, conn->url, conn->http_version);

            char *headers_end = strstr(request_line_end + 1, "\r\n\r\n");
            if (headers_end) {
                int headers_len = headers_end - (request_line_end + 1);
                strncpy(conn->headers, request_line_end + 1, headers_len);
                conn->headers[headers_len] = '\0';

                parse_headers(conn->headers, NULL, NULL, &conn->header_count);

                if (headers_end + 4 < conn->buffer + conn->buffer_len) {
                    parse_body(conn->headers, conn->buffer, conn->body);
                }

                handle_request(conn->method, conn->url, conn->headers, conn->body, conn->sockfd);

                ev_io_stop(loop, &conn->io_watcher);
                close(conn->sockfd);
                free(conn);
            }
        }
    }
}

static void listen_socket_cb(struct ev_loop *loop, struct ev_io *w, int revents) {
    if (revents & EV_READ) {
        int listen_sockfd = w->fd;
        struct sockaddr_in clientaddr;
        socklen_t clientaddr_len = sizeof(clientaddr);
        int client_sockfd = accept(listen_sockfd, (struct sockaddr *)&clientaddr, &clientaddr_len);
        if (client_sockfd < 0) {
            perror("accept failed");
            return;
        }
        printf("Client connected\n");

        client_connection *conn = (client_connection *)malloc(sizeof(client_connection));
        conn->sockfd = client_sockfd;
        conn->buffer_len = 0;

        ev_io_init(&conn->io_watcher, client_connection_cb, client_sockfd, EV_READ);
        conn->io_watcher.data = conn;
        ev_io_start(loop, &conn->io_watcher);
    }
}

int create_listen_socket(int port) {
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        perror("socket creation failed");
        exit(EXIT_FAILURE);
    }

    int opt = 1;
    if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
        perror("setsockopt failed");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    struct sockaddr_in servaddr;
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = INADDR_ANY;
    servaddr.sin_port = htons(port);

    if (bind(sockfd, (const struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {
        perror("bind failed");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    if (listen(sockfd, 10) < 0) {
        perror("listen failed");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    return sockfd;
}

int main() {
    struct ev_loop *loop = ev_default_loop(0);
    int listen_sockfd = create_listen_socket(8080);

    add_route("/", home_handler);

    struct ev_io listen_watcher;
    ev_io_init(&listen_watcher, listen_socket_cb, listen_sockfd, EV_READ);
    ev_io_start(loop, &listen_watcher);

    ev_run(loop, 0);

    close(listen_sockfd);
    return 0;
}

home_handler 等请求处理函数中,构建响应头并发送响应头和响应体给客户端。处理完请求后,关闭连接并释放资源。

7. 中间件支持

中间件是 Web 框架中非常重要的一部分,它可以在请求处理的不同阶段执行一些通用的操作,如日志记录、身份验证等。

7.1 简单中间件实现

#include <stdio.h>
#include <string.h>

typedef void (*middleware)(const char *method, const char *url, const char *headers, const char *body, int *next);

void log_middleware(const char *method, const char *url, const char *headers, const char *body, int *next) {
    printf("Logging request: %s %s\n", method, url);
    *next = 1;
}

void handle_request(const char *method, const char *url, const char *headers, const char *body, middleware middlewares[], int middleware_count) {
    int next = 1;
    for (int i = 0; i < middleware_count && next; i++) {
        middlewares[i](method, url, headers, body, &next);
    }
    if (next) {
        // 这里进行实际的请求处理
        printf("Handling request: %s %s\n", method, url);
    }
}

int main() {
    middleware middlewares[1];
    middlewares[0] = log_middleware;
    handle_request("GET", "/", "", "", middlewares, 1);
    return 0;
}

在这个示例中,我们定义了一个 log_middleware 用于记录请求信息。handle_request 函数会依次调用中间件,只有当所有中间件的 next 标志都为 1 时,才会进行实际的请求处理。

7.2 集成中间件到 Web 框架

将中间件功能集成到基于 libev 的 Web 框架中。

#include <ev.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>

#define BUFFER_SIZE 1024
#define MAX_ROUTES 100
#define MAX_MIDDLEWARES 10

typedef struct {
    struct ev_io io_watcher;
    int sockfd;
    char buffer[BUFFER_SIZE];
    size_t buffer_len;
    char method[10];
    char url[100];
    char http_version[10];
    char headers[BUFFER_SIZE];
    int header_count;
    char body[BUFFER_SIZE];
} client_connection;

typedef struct {
    char url[100];
    void (*handler)(const char *method, const char *url, const char *headers, const char *body, int sockfd);
} route;

typedef void (*middleware)(const char *method, const char *url, const char *headers, const char *body, int *next);

route routes[MAX_ROUTES];
int route_count = 0;
middleware middlewares[MAX_MIDDLEWARES];
int middleware_count = 0;

void add_route(const char *url, void (*handler)(const char *method, const char *url, const char *headers, const char *body, int sockfd)) {
    if (route_count < MAX_ROUTES) {
        strcpy(routes[route_count].url, url);
        routes[route_count].handler = handler;
        route_count++;
    }
}

void add_middleware(middleware mw) {
    if (middleware_count < MAX_MIDDLEWARES) {
        middlewares[middleware_count] = mw;
        middleware_count++;
    }
}

void handle_request(const char *method, const char *url, const char *headers, const char *body, int sockfd) {
    int next = 1;
    for (int i = 0; i < middleware_count && next; i++) {
        middlewares[i](method, url, headers, body, &next);
    }
    if (next) {
        for (int i = 0; i < route_count; i++) {
            if (strcmp(routes[i].url, url) == 0) {
                routes[i].handler(method, url, headers, body, sockfd);
                return;
            }
        }
        printf("No handler found for URL: %s\n", url);
        char response_headers[1024];
        build_response_headers(response_headers, "text/plain", strlen("Not Found"));
        send(sockfd, response_headers, strlen(response_headers), 0);
        send(sockfd, "Not Found", strlen("Not Found"), 0);
    }
}

void build_response_headers(char *response_headers, const char *content_type, int content_length) {
    snprintf(response_headers, 1024, "HTTP/1.1 200 OK\r\nContent - Type: %s\r\nContent - Length: %d\r\n\r\n", content_type, content_length);
}

void home_handler(const char *method, const char *url, const char *headers, const char *body, int sockfd) {
    char response_headers[1024];
    build_response_headers(response_headers, "text/html", strlen("<html><body>Home Page</body></html>"));
    send(sockfd, response_headers, strlen(response_headers), 0);
    send(sockfd, "<html><body>Home Page</body></html>", strlen("<html><body>Home Page</body></html>"), 0);
}

void log_middleware(const char *method, const char *url, const char *headers, const char *body, int *next) {
    printf("Logging request: %s %s\n", method, url);
    *next = 1;
}

void parse_request_line(const char *request_line, char *method, char *url, char *http_version) {
    sscanf(request_line, "%s %s %s", method, url, http_version);
}

void parse_headers(const char *headers, char (*header_names)[50], char (*header_values)[200], int *header_count) {
    *header_count = 0;
    const char *p = headers;
    while (*p && *header_count < 100) {
        const char *end = strchr(p, '\n');
        if (end && *end == '\n') {
            if (strncmp(p, "\r\n", 2) == 0) {
                break;
            }
            sscanf(p, "%49[^:]: %199[^\r\n]", (*header_names)[*header_count], (*header_values)[*header_count]);
            (*header_count)++;
            p = end + 1;
        } else {
            break;
        }
    }
}

void parse_body(const char *headers, const char *request, char *body) {
    const char *content_length_header = "Content - Length: ";
    char content_length_str[10];
    int content_length = 0;
    const char *p = strstr(headers, content_length_header);
    if (p) {
        sscanf(p + strlen(content_length_header), "%d", &content_length);
        const char *body_start = strstr(request, "\r\n\r\n");
        if (body_start) {
            body_start += 4;
            strncpy(body, body_start, content_length);
            body[content_length] = '\0';
        }
    }
}

static void client_connection_cb(struct ev_loop *loop, struct ev_io *w, int revents) {
    client_connection *conn = (client_connection *)w->data;
    if (revents & EV_READ) {
        ssize_t n = recv(conn->sockfd, conn->buffer + conn->buffer_len, sizeof(conn->buffer) - conn->buffer_len, 0);
        if (n <= 0) {
            if (n == 0) {
                printf("Client disconnected\n");
            } else {
                perror("recv failed");
            }
            ev_io_stop(loop, &conn->io_watcher);
            close(conn->sockfd);
            free(conn);
            return;
        }
        conn->buffer_len += n;

        char *request_line_end = strchr(conn->buffer, '\n');
        if (request_line_end) {
            *request_line_end = '\0';
            parse_request_line(conn->buffer, conn->method, conn->url, conn->http_version);

            char *headers_end = strstr(request_line_end + 1, "\r\n\r\n");
            if (headers_end) {
                int headers_len = headers_end - (request_line_end + 1);
                strncpy(conn->headers, request_line_end + 1, headers_len);
                conn->headers[headers_len] = '\0';

                parse_headers(conn->headers, NULL, NULL, &conn->header_count);

                if (headers_end + 4 < conn->buffer + conn->buffer_len) {
                    parse_body(conn->headers, conn->buffer, conn->body);
                }

                handle_request(conn->method, conn->url, conn->headers, conn->body, conn->sockfd);

                ev_io_stop(loop, &conn->io_watcher);
                close(conn->sockfd);
                free(conn);
            }
        }
    }
}

static void listen_socket_cb(struct ev_loop *loop, struct ev_io *w, int revents) {
    if (revents & EV_READ) {
        int listen_sockfd = w->fd;
        struct sockaddr_in clientaddr;
        socklen_t clientaddr_len = sizeof(clientaddr);
        int client_sockfd = accept(listen_sockfd, (struct sockaddr *)&clientaddr, &clientaddr_len);
        if (client_sockfd < 0) {
            perror("accept failed");
            return;
        }
        printf("Client connected\n");

        client_connection *conn = (client_connection *)malloc(sizeof(client_connection));
        conn->sockfd = client_sockfd;
        conn->buffer_len = 0;

        ev_io_init(&conn->io_watcher, client_connection_cb, client_sockfd, EV_READ);
        conn->io_watcher.data = conn;
        ev_io_start(loop, &conn->io_watcher);
    }
}

int create_listen_socket(int port) {
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        perror("socket creation failed");
        exit(EXIT_FAILURE);
    }

    int opt = 1;
    if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
        perror("setsockopt failed");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    struct sockaddr_in servaddr;
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = INADDR_ANY;
    servaddr.sin_port = htons(port);

    if (bind(sockfd, (const struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {
        perror("bind failed");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    if (listen(sockfd, 10) < 0) {
        perror("listen failed");