从零开始构建基于libev的Web框架
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 - Type
、User - 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_REUSEADDR
和 SO_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 - Type
和 Content - 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");