🌐 HTTP客户端 - libevent源码深度解析系列(二十一)

2282 字
11 分钟
🌐 HTTP客户端 - libevent源码深度解析系列(二十一)

🌐 HTTP客户端#

一、概述#

libevent不仅支持HTTP服务器,还提供了完整的HTTP客户端功能。通过evhttp模块,可以方便地发送HTTP请求、处理响应,支持GET、POST等常见HTTP方法。

二、HTTP连接对象#

2.1 创建连接#

/* 创建HTTP连接对象
* @param base event_base
* @param dns_base DNS解析器,NULL表示阻塞解析
* @param address 服务器地址
* @param port 服务器端口
*/
struct evhttp_connection *evhttp_connection_base_new(
struct event_base *base, struct evdns_base *dns_base,
const char *address, ev_uint16_t port);
/* 创建HTTP连接对象(带地址族) */
struct evhttp_connection *evhttp_connection_base_bufferevent_new(
struct event_base *base, struct evdns_base *dns_base,
struct bufferevent *bev, const char *address, ev_uint16_t port);
/* 释放HTTP连接 */
void evhttp_connection_free(struct evhttp_connection *evcon);
/* 设置连接超时 */
void evhttp_connection_set_timeout(struct evhttp_connection *evcon, int timeout);
void evhttp_connection_set_timeout_tv(struct evhttp_connection *evcon,
const struct timeval *tv);
/* 设置重试次数 */
void evhttp_connection_set_retries(struct evhttp_connection *evcon, int retry_max);
/* 设置最大连接数 */
void evhttp_connection_set_max_body_size(struct evhttp_connection *evcon,
ev_ssize_t max_body_size);

2.2 连接示例#

#include <stdio.h>
#include <stdlib.h>
#include <event2/event.h>
#include <event2/http.h>
#include <event2/buffer.h>
int main() {
struct event_base *base = event_base_new();
struct evdns_base *dns_base = evdns_base_new(base, 1);
// 创建HTTP连接
struct evhttp_connection *conn = evhttp_connection_base_new(
base, dns_base, "www.example.com", 80);
if (!conn) {
fprintf(stderr, "Failed to create connection\n");
return 1;
}
// 设置超时
evhttp_connection_set_timeout(conn, 10);
printf("HTTP connection created\n");
evhttp_connection_free(conn);
evdns_base_free(dns_base, 0);
event_base_free(base);
return 0;
}

三、HTTP请求对象#

3.1 创建请求#

/* 创建HTTP请求对象 */
struct evhttp_request *evhttp_request_new(
void (*cb)(struct evhttp_request *, void *), void *arg);
/* 释放请求对象 */
void evhttp_request_free(struct evhttp_request *req);
/* 获取请求的输出缓冲区 */
struct evbuffer *evhttp_request_get_output_buffer(struct evhttp_request *req);
/* 获取请求的输入缓冲区 */
struct evbuffer *evhttp_request_get_input_buffer(struct evhttp_request *req);
/* 获取请求的输出头 */
struct evkeyvalq *evhttp_request_get_output_headers(struct evhttp_request *req);
/* 获取请求的输入头 */
struct evkeyvalq *evhttp_request_get_input_headers(struct evhttp_request *req);
/* 获取响应码 */
int evhttp_request_get_response_code(struct evhttp_request *req);
const char *evhttp_request_get_response_code_line(struct evhttp_request *req);
/* 获取请求的连接 */
struct evhttp_connection *evhttp_request_get_connection(struct evhttp_request *req);
/* 获取请求的host */
const char *evhttp_request_get_host(struct evhttp_request *req);
/* 获取请求的URI */
enum evhttp_cmd_type evhttp_request_get_command(struct evhttp_request *req);
const char *evhttp_request_get_uri(struct evhttp_request *req);
/* 取消请求 */
void evhttp_cancel_request(struct evhttp_request *req);

3.2 请求方法#

enum evhttp_cmd_type {
EVHTTP_REQ_GET = 1 << 0,
EVHTTP_REQ_POST = 1 << 1,
EVHTTP_REQ_HEAD = 1 << 2,
EVHTTP_REQ_PUT = 1 << 3,
EVHTTP_REQ_DELETE = 1 << 4,
EVHTTP_REQ_OPTIONS = 1 << 5,
EVHTTP_REQ_TRACE = 1 << 6,
EVHTTP_REQ_CONNECT = 1 << 7,
EVHTTP_REQ_PATCH = 1 << 8
};

四、发送HTTP请求#

4.1 发送请求API#

/* 发送HTTP请求
* @param evcon HTTP连接
* @param req HTTP请求对象
* @param type 请求方法
* @param uri 请求URI
* @return 0成功,-1失败
*/
int evhttp_make_request(struct evhttp_connection *evcon,
struct evhttp_request *req, enum evhttp_cmd_type type, const char *uri);

4.2 GET请求示例#

#include <stdio.h>
#include <stdlib.h>
#include <event2/event.h>
#include <event2/http.h>
#include <event2/buffer.h>
static void http_request_done(struct evhttp_request *req, void *arg) {
if (!req) {
printf("Request failed\n");
event_base_loopbreak((struct event_base *)arg);
return;
}
int response_code = evhttp_request_get_response_code(req);
printf("Response code: %d\n", response_code);
// 获取响应头
struct evkeyvalq *headers = evhttp_request_get_input_headers(req);
const char *content_type = evhttp_find_header(headers, "Content-Type");
printf("Content-Type: %s\n", content_type ? content_type : "unknown");
// 获取响应体
struct evbuffer *buf = evhttp_request_get_input_buffer(req);
size_t len = evbuffer_get_length(buf);
printf("Response length: %zu bytes\n", len);
// 打印响应内容
char *data = malloc(len + 1);
evbuffer_copyout(buf, data, len);
data[len] = '\0';
printf("Response body:\n%s\n", data);
free(data);
event_base_loopbreak((struct event_base *)arg);
}
int main() {
struct event_base *base = event_base_new();
struct evdns_base *dns_base = evdns_base_new(base, 1);
// 创建连接
struct evhttp_connection *conn = evhttp_connection_base_new(
base, dns_base, "httpbin.org", 80);
evhttp_connection_set_timeout(conn, 10);
// 创建请求
struct evhttp_request *req = evhttp_request_new(http_request_done, base);
// 添加请求头
struct evkeyvalq *output_headers = evhttp_request_get_output_headers(req);
evhttp_add_header(output_headers, "Host", "httpbin.org");
evhttp_add_header(output_headers, "User-Agent", "libevent-client/1.0");
evhttp_add_header(output_headers, "Accept", "*/*");
// 发送GET请求
printf("Sending GET request to httpbin.org/get\n");
evhttp_make_request(conn, req, EVHTTP_REQ_GET, "/get");
event_base_dispatch(base);
evhttp_connection_free(conn);
evdns_base_free(dns_base, 0);
event_base_free(base);
return 0;
}

五、POST请求#

5.1 POST请求示例#

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <event2/event.h>
#include <event2/http.h>
#include <event2/buffer.h>
static void http_request_done(struct evhttp_request *req, void *arg) {
if (!req) {
printf("Request failed\n");
event_base_loopbreak((struct event_base *)arg);
return;
}
printf("Response code: %d\n", evhttp_request_get_response_code(req));
struct evbuffer *buf = evhttp_request_get_input_buffer(req);
size_t len = evbuffer_get_length(buf);
char *data = malloc(len + 1);
evbuffer_copyout(buf, data, len);
data[len] = '\0';
printf("Response:\n%s\n", data);
free(data);
event_base_loopbreak((struct event_base *)arg);
}
int main() {
struct event_base *base = event_base_new();
struct evdns_base *dns_base = evdns_base_new(base, 1);
struct evhttp_connection *conn = evhttp_connection_base_new(
base, dns_base, "httpbin.org", 80);
evhttp_connection_set_timeout(conn, 10);
struct evhttp_request *req = evhttp_request_new(http_request_done, base);
// 设置请求头
struct evkeyvalq *output_headers = evhttp_request_get_output_headers(req);
evhttp_add_header(output_headers, "Host", "httpbin.org");
evhttp_add_header(output_headers, "Content-Type", "application/x-www-form-urlencoded");
evhttp_add_header(output_headers, "User-Agent", "libevent-client/1.0");
// 设置POST数据
struct evbuffer *output_buffer = evhttp_request_get_output_buffer(req);
const char *post_data = "name=John&age=30&city=Beijing";
evbuffer_add(output_buffer, post_data, strlen(post_data));
printf("Sending POST request to httpbin.org/post\n");
printf("Post data: %s\n", post_data);
evhttp_make_request(conn, req, EVHTTP_REQ_POST, "/post");
event_base_dispatch(base);
evhttp_connection_free(conn);
evdns_base_free(dns_base, 0);
event_base_free(base);
return 0;
}

5.2 JSON POST请求#

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <event2/event.h>
#include <event2/http.h>
#include <event2/buffer.h>
static void json_post_done(struct evhttp_request *req, void *arg) {
if (!req) {
printf("Request failed\n");
event_base_loopbreak((struct event_base *)arg);
return;
}
printf("Response code: %d\n", evhttp_request_get_response_code(req));
struct evbuffer *buf = evhttp_request_get_input_buffer(req);
size_t len = evbuffer_get_length(buf);
char *data = malloc(len + 1);
evbuffer_copyout(buf, data, len);
data[len] = '\0';
printf("Response:\n%s\n", data);
free(data);
event_base_loopbreak((struct event_base *)arg);
}
int main() {
struct event_base *base = event_base_new();
struct evdns_base *dns_base = evdns_base_new(base, 1);
struct evhttp_connection *conn = evhttp_connection_base_new(
base, dns_base, "httpbin.org", 80);
evhttp_connection_set_timeout(conn, 10);
struct evhttp_request *req = evhttp_request_new(json_post_done, base);
struct evkeyvalq *headers = evhttp_request_get_output_headers(req);
evhttp_add_header(headers, "Host", "httpbin.org");
evhttp_add_header(headers, "Content-Type", "application/json");
evhttp_add_header(headers, "Accept", "application/json");
// JSON数据
const char *json_data = "{\"username\":\"test\",\"password\":\"123456\"}";
struct evbuffer *output = evhttp_request_get_output_buffer(req);
evbuffer_add(output, json_data, strlen(json_data));
printf("Sending JSON POST request\n");
printf("JSON data: %s\n", json_data);
evhttp_make_request(conn, req, EVHTTP_REQ_POST, "/post");
event_base_dispatch(base);
evhttp_connection_free(conn);
evdns_base_free(dns_base, 0);
event_base_free(base);
return 0;
}

六、HTTP头部操作#

6.1 头部API#

/* 添加头部 */
int evhttp_add_header(struct evkeyvalq *headers, const char *key, const char *value);
/* 查找头部 */
const char *evhttp_find_header(const struct evkeyvalq *headers, const char *key);
/* 删除头部 */
int evhttp_remove_header(struct evkeyvalq *headers, const char *key);
/* 清除所有头部 */
void evhttp_clear_headers(struct evkeyvalq *headers);

6.2 常用请求头#

头部说明
Host目标主机名
User-Agent客户端标识
Accept接受的内容类型
Content-Type请求体类型
Content-Length请求体长度
Authorization认证信息
CookieCookie数据
Connection连接控制

七、HTTPS支持#

7.1 HTTPS连接#

#include <stdio.h>
#include <stdlib.h>
#include <event2/event.h>
#include <event2/http.h>
#include <event2/buffer.h>
#include <openssl/ssl.h>
static void https_request_done(struct evhttp_request *req, void *arg) {
if (!req) {
printf("Request failed\n");
event_base_loopbreak((struct event_base *)arg);
return;
}
printf("Response code: %d\n", evhttp_request_get_response_code(req));
struct evbuffer *buf = evhttp_request_get_input_buffer(req);
size_t len = evbuffer_get_length(buf);
char *data = malloc(len + 1);
evbuffer_copyout(buf, data, len);
data[len] = '\0';
printf("Response:\n%s\n", data);
free(data);
event_base_loopbreak((struct event_base *)arg);
}
int main() {
// 初始化OpenSSL
SSL_library_init();
SSL_load_error_strings();
OpenSSL_add_all_algorithms();
struct event_base *base = event_base_new();
struct evdns_base *dns_base = evdns_base_new(base, 1);
// 创建SSL上下文
SSL_CTX *ssl_ctx = SSL_CTX_new(SSLv23_client_method());
// 创建bufferevent
struct bufferevent *bev = bufferevent_openssl_socket_new(
base, -1, SSL_new(ssl_ctx), BUFFEREVENT_SSL_CONNECTING,
BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS);
// 创建HTTPS连接
struct evhttp_connection *conn = evhttp_connection_base_bufferevent_new(
base, dns_base, bev, "www.google.com", 443);
struct evhttp_request *req = evhttp_request_new(https_request_done, base);
struct evkeyvalq *headers = evhttp_request_get_output_headers(req);
evhttp_add_header(headers, "Host", "www.google.com");
evhttp_add_header(headers, "User-Agent", "libevent-https-client/1.0");
printf("Sending HTTPS request to www.google.com/\n");
evhttp_make_request(conn, req, EVHTTP_REQ_GET, "/");
event_base_dispatch(base);
evhttp_connection_free(conn);
evdns_base_free(dns_base, 0);
event_base_free(base);
SSL_CTX_free(ssl_ctx);
return 0;
}

八、错误处理#

8.1 错误回调#

/* 设置连接错误回调 */
typedef void (*evhttp_conn_error_cb)(enum evhttp_request_error, void *);
void evhttp_connection_set_closecb(struct evhttp_connection *evcon,
void (*cb)(struct evhttp_connection *, void *), void *cbarg);
/* 错误类型 */
enum evhttp_request_error {
EVREQ_HTTP_TIMEOUT, // 超时
EVREQ_HTTP_EOF, // 连接关闭
EVREQ_HTTP_INVALID_HEADER, // 无效头部
EVREQ_HTTP_BUFFER_ERROR, // 缓冲区错误
EVREQ_HTTP_REQUEST_CANCEL, // 请求取消
EVREQ_HTTP_DATA_TOO_LONG // 数据过长
};

8.2 错误处理示例#

#include <stdio.h>
#include <stdlib.h>
#include <event2/event.h>
#include <event2/http.h>
static void connection_close_cb(struct evhttp_connection *conn, void *arg) {
printf("Connection closed\n");
}
static void http_request_done(struct evhttp_request *req, void *arg) {
if (!req) {
// 获取错误类型
enum evhttp_request_error err = EVREQ_HTTP_TIMEOUT;
printf("Request error: %d\n", err);
event_base_loopbreak((struct event_base *)arg);
return;
}
int code = evhttp_request_get_response_code(req);
if (code == 0) {
printf("Connection failed\n");
} else if (code >= 400) {
printf("HTTP error: %d\n", code);
} else {
printf("Success: %d\n", code);
}
event_base_loopbreak((struct event_base *)arg);
}
int main() {
struct event_base *base = event_base_new();
struct evdns_base *dns_base = evdns_base_new(base, 1);
struct evhttp_connection *conn = evhttp_connection_base_new(
base, dns_base, "example.invalid", 80); // 无效域名
// 设置关闭回调
evhttp_connection_set_closecb(conn, connection_close_cb, NULL);
// 设置较短超时
evhttp_connection_set_timeout(conn, 3);
struct evhttp_request *req = evhttp_request_new(http_request_done, base);
evhttp_make_request(conn, req, EVHTTP_REQ_GET, "/");
event_base_dispatch(base);
evhttp_connection_free(conn);
evdns_base_free(dns_base, 0);
event_base_free(base);
return 0;
}

九、连接池与重用#

9.1 连接重用#

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <event2/event.h>
#include <event2/http.h>
#include <event2/buffer.h>
static int request_count = 0;
static const int MAX_REQUESTS = 3;
static void send_next_request(struct evhttp_connection *conn,
struct event_base *base);
static void http_request_done(struct evhttp_request *req, void *arg) {
struct evhttp_connection *conn = evhttp_request_get_connection(req);
struct event_base *base = arg;
if (req) {
printf("Request %d: Response code %d\n",
request_count, evhttp_request_get_response_code(req));
} else {
printf("Request %d failed\n", request_count);
}
request_count++;
if (request_count < MAX_REQUESTS) {
send_next_request(conn, base);
} else {
evhttp_connection_free(conn);
event_base_loopbreak(base);
}
}
static void send_next_request(struct evhttp_connection *conn,
struct event_base *base) {
char uri[64];
snprintf(uri, sizeof(uri), "/get?id=%d", request_count);
struct evhttp_request *req = evhttp_request_new(http_request_done, base);
struct evkeyvalq *headers = evhttp_request_get_output_headers(req);
evhttp_add_header(headers, "Host", "httpbin.org");
printf("Sending request %d to %s\n", request_count, uri);
evhttp_make_request(conn, req, EVHTTP_REQ_GET, uri);
}
int main() {
struct event_base *base = event_base_new();
struct evdns_base *dns_base = evdns_base_new(base, 1);
// 创建一个连接,重用发送多个请求
struct evhttp_connection *conn = evhttp_connection_base_new(
base, dns_base, "httpbin.org", 80);
evhttp_connection_set_timeout(conn, 10);
send_next_request(conn, base);
event_base_dispatch(base);
evdns_base_free(dns_base, 0);
event_base_free(base);
return 0;
}

十、完整示例:HTTP下载器#

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <event2/event.h>
#include <event2/http.h>
#include <event2/buffer.h>
typedef struct {
struct event_base *base;
struct evhttp_connection *conn;
FILE *output_file;
const char *url;
const char *host;
int port;
const char *path;
size_t total_bytes;
} download_context_t;
static void download_chunk_cb(struct evhttp_request *req, void *arg);
static void download_complete(struct evhttp_request *req, void *arg) {
download_context_t *ctx = arg;
if (!req) {
printf("Download failed\n");
} else {
int code = evhttp_request_get_response_code(req);
printf("\nDownload complete: HTTP %d, %zu bytes\n",
code, ctx->total_bytes);
}
if (ctx->output_file) {
fclose(ctx->output_file);
}
evhttp_connection_free(ctx->conn);
event_base_loopbreak(ctx->base);
}
static void download_chunk_cb(struct evhttp_request *req, void *arg) {
download_context_t *ctx = arg;
if (!req) return;
struct evbuffer *buf = evhttp_request_get_input_buffer(req);
size_t len = evbuffer_get_length(buf);
if (len > 0 && ctx->output_file) {
char *data = malloc(len);
evbuffer_remove(buf, data, len);
fwrite(data, 1, len, ctx->output_file);
free(data);
ctx->total_bytes += len;
printf("\rDownloaded: %zu bytes", ctx->total_bytes);
fflush(stdout);
}
// 检查是否完成
if (evbuffer_get_length(buf) == 0) {
download_complete(req, arg);
}
}
static void start_download(download_context_t *ctx) {
struct evhttp_request *req = evhttp_request_new(download_chunk_cb, ctx);
struct evkeyvalq *headers = evhttp_request_get_output_headers(req);
evhttp_add_header(headers, "Host", ctx->host);
evhttp_add_header(headers, "User-Agent", "libevent-downloader/1.0");
evhttp_add_header(headers, "Connection", "close");
printf("Downloading from %s:%d%s\n", ctx->host, ctx->port, ctx->path);
evhttp_make_request(ctx->conn, req, EVHTTP_REQ_GET, ctx->path);
}
int main(int argc, char *argv[]) {
if (argc < 2) {
printf("Usage: %s <url> [output_file]\n", argv[0]);
printf("Example: %s http://example.com/file.zip output.zip\n", argv[0]);
return 1;
}
const char *url = argv[1];
const char *output_filename = argc > 2 ? argv[2] : "download.bin";
// 解析URL(简化版)
char host[256] = {0};
char path[1024] = {0};
int port = 80;
const char *host_start = strstr(url, "://");
if (host_start) {
host_start += 3;
} else {
host_start = url;
}
const char *path_start = strchr(host_start, '/');
if (path_start) {
strncpy(host, host_start, path_start - host_start);
strcpy(path, path_start);
} else {
strcpy(host, host_start);
strcpy(path, "/");
}
char *port_str = strchr(host, ':');
if (port_str) {
*port_str = '\0';
port = atoi(port_str + 1);
}
printf("Host: %s, Port: %d, Path: %s\n", host, port, path);
struct event_base *base = event_base_new();
struct evdns_base *dns_base = evdns_base_new(base, 1);
download_context_t ctx = {
.base = base,
.host = host,
.port = port,
.path = path,
.total_bytes = 0
};
ctx.output_file = fopen(output_filename, "wb");
if (!ctx.output_file) {
printf("Failed to open output file: %s\n", output_filename);
return 1;
}
ctx.conn = evhttp_connection_base_new(base, dns_base, host, port);
evhttp_connection_set_timeout(ctx.conn, 30);
start_download(&ctx);
event_base_dispatch(base);
evdns_base_free(dns_base, 0);
event_base_free(base);
return 0;
}

十一、最佳实践#

场景建议
连接重用同一服务器的多个请求重用连接
超时设置根据网络环境设置合理的超时时间
错误重试对于临时错误实现重试机制
大文件下载使用chunk回调逐步处理,避免内存溢出
HTTPS生产环境优先使用HTTPS
User-Agent设置合理的User-Agent避免被拦截

其他相关资料 请移步到个人主页

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!

打赏
🌐 HTTP客户端 - libevent源码深度解析系列(二十一)
https://xutopia.top/blog/libevent-http-client/
作者
xutopia77
发布于
2026-04-11
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
xutopia77
以 C/C++ 为根基做系统与音视频,用全栈能力把想法落地,同时把思考写成文字。
分类
标签
站点统计
文章
48
分类
12
标签
22
总字数
95,084
运行时长
0 天
最后活动
0 天前
文章目录