一、evhttp介绍

在libevent中HTTP的实现主要是通过evhttp模块来完成的。当客户端发起一个HTTP请求时,libevent将该请求解析为struct evhttp_request结构体表示,并调用用户设置的请求处理函数进行处理。

struct evhttp_request结构体定义了HTTP请求的各个字段,如请求行、请求头、请求正文等。

struct evhttp_request {
    int major;  // 主版本号
    int minor;  // 次版本号
    enum evhttp_cmd_type type;  	// 请求方法(GET、POST等)
    char *uri;  					// 请求URI
    struct evkeyvalq *input_headers;// 请求头
    struct evbuffer *input_buffer;  // 请求正文
};

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,
.....
};

二、相关的API

1、evhttp_new()

struct evhttp * evhttp_new(struct event_base *base)
    
base:即指向 `event_base` 结构体的指针,用于处理 HTTP 服务器的事件

evhttp_new 用于创建和初始化一个 evhttp 结构体。evhttp 结构体表示一个 HTTP 服务器,它可以在一个或多个套接字上监听传入的 HTTP 请求,并将这些请求分派给适当的请求处理程序。

2、evhttp_free()

void evhttp_free(struct evhttp* http)

evhttp_free() 用于释放 evhttp 结构体及其相关资源。调用该函数后,将销毁与 evhttp 相关联的所有信息,包括绑定的套接字以及任何未完成的请求。

3、evhttp_bind_socket()

int evhttp_bind_socket(struct evhttp *http, const char *address, ev_uint16_t port)

参数:
    http:指向要绑定的evhttp对象的指针。
    address:要绑定的IP地址。可以是一个IPv4或IPv6地址,也可以是一个域名。如果为NULL,则默认绑定到所有可用地址。
    port:要绑定的端口号。

vhttp_bind_socket用于将HTTP服务器绑定到指定的IP地址和端口号。

4、evhttp_set_gencb()

void
evhttp_set_gencb(struct evhttp *http,
   				 void (*cb)(struct evhttp_request *, void *), 
                 void *cbarg)
    
	第一个参数是指向 evhttp 结构体的指针,表示要设置回调函数的 HTTP 服务器;
	第二个参数是指向回调函数的指针,该回调函数的原型为 void (*cb)(struct evhttp_request *, void *),其中第一个参数是指向当前 HTTP 请求的指针,第二个参数是传递给 evhttp_set_gencb() 函数的第三个参数;
    第三个参数是传递给回调函数的上下文参数 cbarg,它可以是任何数据类型的指针;

evhttp_set_gencb()用于设置通用回调函数。

5、evhttp_set_cb()

evhttp_set_cb(struct evhttp *http, 
			  const char *uri,
    		  void (*cb)(struct evhttp_request *, void *), 
    		  void *cbarg)
	第一个参数是指向 evhttp 结构体的指针,表示要设置回调函数的 HTTP 服务器;
	第二个参数是字符串类型的 URI,表示要注册回调函数的 URI;
	第三个参数是指向回调函数的指针,该回调函数的原型为 void (*cb)(struct evhttp_request *, void *),其中第一个参数是指向当前 HTTP 请求的指针,第二个参数是传递给 evhttp_set_cb() 函数的第四个参数;
	四个参数是传递给回调函数的上下文参数 cbarg,它可以是任何数据类型的指针。

evhttp_set_cb() 用于为 HTTP 服务器注册特定 URI 的请求处理程序回调函数。

6、evhttp_request_get_uri()

const char *
evhttp_request_get_uri(const struct evhttp_request *req) {
	if (req->uri == NULL)
		event_debug(("%s: request %p has no uri\n", __func__, (void *)req));
	return (req->uri);	//返回struct evhttp_reques中的URI
}

该函数只有一个参数,即指向 `evhttp_request` 结构体的指针,表示要获取 URI 的 HTTP 请求

返回HTTP 请求的 URI。URI 是客户端在请求服务器时发送的字符串,该字符串通常包含一个主机名、路径和查询字符串。例如,对于 “http://127.0.0.1/foo?bar=baz” 这样的请求,URI 将是 “/foo?bar=baz”。

7、evhttp_request_get_command()

enum evhttp_cmd_type
evhttp_request_get_command(const struct evhttp_request *req) {
	return (req->type);	//返回struct evhttp_reques中请求方法(GET、POST等)
}

函数返回一个枚举类型 evhttp_cmd_type 值,表示 HTTP 请求使用的方法类型。枚举类型包括以下值:
    EVHTTP_REQ_GET: 使用 GET 方法。
    EVHTTP_REQ_POST: 使用 POST 方法。
    EVHTTP_REQ_HEAD: 使用 HEAD 方法。
    EVHTTP_REQ_PUT: 使用 PUT 方法。
    EVHTTP_REQ_DELETE: 使用 DELETE 方法。
    EVHTTP_REQ_OPTIONS: 使用 OPTIONS 方法。
    EVHTTP_REQ_TRACE: 使用 TRACE 方法。
    EVHTTP_REQ_CONNECT: 使用 CONNECT 方法。
    EVHTTP_REQ_PATCH: 使用 PATCH 方法。

用于获取 HTTP 请求的方法类型。

8、evhttp_request_get_input_headers()

struct evkeyvalq *evhttp_request_get_input_headers(struct evhttp_request *req)
{
	return (req->input_headers);	// 返回struct evhttp_reques中请求头
}

用于获取 HTTP 请求中的输入头部。

9、evhttp_request_get_input_buffer()

struct evbuffer *evhttp_request_get_input_buffer(struct evhttp_request *req)
{
	return (req->input_buffer);	// 返回struct evhttp_reques中请求正文
}

用于获取 HTTP 请求的输入缓冲区。

10、evhttp_request_get_output_headers()

struct evkeyvalq *evhttp_request_get_output_headers(struct evhttp_request *req)
{
	return (req->output_headers);
}

用于获取 HTTP 请求的输出头部。

11、evhttp_add_header()

int	evhttp_add_header(struct evkeyvalq *headers,
    				  const char *key, 
                      const char *value)
    
第一个参数是指向 evkeyvalq 结构体的指针,表示要添加头部的头部列表;
第二个参数是字符串类型的键名,表示要添加的头部的名称;
第三个参数是字符串类型的键值,表示要添加的头部的值。

用于向 evkeyvalq 结构体表示的头部列表添加一个新的键值对。

(1)如果某个键已经存在于头部列表中,则会将现有键值对的值替换为新的值。

(2)如果要添加多个具有相同名称的头部,请使用逗号分隔它们的值。例如,可以使用"Accept-Encoding: gzip, deflate"来同时添加两个Accept-Encoding头部。

12、evhttp_request_get_output_buffer()

struct evbuffer *evhttp_request_get_output_buffer(struct evhttp_request *req)
{
	return (req->output_buffer);
}

函数返回一个指向 evbuffer 结构体的指针,该结构体代表了 HTTP 请求的输出缓冲区。可以使用 evbuffer_add() 函数将内容添加到缓冲区中。

用于获取 HTTP 请求的输出缓冲区。

13、evhttp_send_reply()

void
evhttp_send_reply(struct evhttp_request *req, 
                 int code, 
                 const char *reason,
    			 struct evbuffer *databuf)
    
	第一个参数是指向 evhttp_request 结构体的指针,表示要发送响应的 HTTP 请求;
    第二个参数是整数类型的状态码,表示响应的状态码;
    第三个参数是字符串类型的原因短语,表示状态码的原因短语;
    第四个参数是指向 evbuffer 结构体的指针,表示响应正文的内容。

用于向客户端发送 HTTP 响应。

在调用 evhttp_send_reply() 函数之前,必须确保已经设置了响应状态码和必要的响应头部,并且已经将所有响应数据写入到输出缓冲区中。

三、一个server端例子


#include <iostream>
#include <thread>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <event2/event.h>
#include <event2/listener.h>
#include <event2/http.h>
#include <event2/thread.h>
#include <event2/buffer.h>
#include <event2/keyvalq_struct.h>

#define WEBROOT "."
#define DEFAULT_INDEX "index.html"

using namespace std;

void http_cb(struct evhttp_request* request, void* arg){
    cout<<"http_cb"<<endl;
	//1 获取浏览器的请求信息
	//uri
	const char* uri = evhttp_request_get_uri(request);
	cout <<"uri:"<<uri<<endl;
	string cmdtype;
	
	//请求类型 GET POST
	switch(evhttp_request_get_command(request)){
	    case EVHTTP_REQ_GET:
		    cmdtype = "GET";
			break;
		case EVHTTP_REQ_POST:
		    cmdtype = "POST";
			break;
	}
	cout<<"cmdtype:"<<cmdtype<<endl;
	
	//消息报头
	evkeyvalq* headers = evhttp_request_get_input_headers(request);
	cout<<"------------------ headers ------------------"<<endl;
	for(evkeyval* p = headers->tqh_first; p != NULL; p = p->next.tqe_next){
	    cout <<p->key <<":"<<p->value<<endl;
	}
	
	//请求正文(GET为空 POST有表单信息)
	evbuffer* inbuf = evhttp_request_get_input_buffer(request);
	char buf[1024] = {0};
	cout<<"------------------ input data ------------------"<<endl;
	while(evbuffer_get_length(inbuf)){
	   int n = evbuffer_remove(inbuf, buf, sizeof(buf) - 1);
	   if(n > 0){
	       strcpy(buf+n, "\0");
		   cout << buf <<endl;
	   }
	}
	
	//2 回复浏览器
	//分析出请求的文件 uri
	//设置根目录
	//windows上要加上(项目属性 - c/c++预处理器 加上: CRT_SECURE_NO_WARNINGS)
	string filepath = WEBROOT;
	filepath += uri;
	if(strcmp(uri, "/") == 0){
	    filepath += DEFAULT_INDEX;
	}
	
	FILE* fp = fopen(filepath.c_str(), "rb");
	cout<<"filepath:"<<filepath<<endl;
	if(fp){	
	    //状态行 消息报头 响应正文
	    evbuffer* outbuf = evhttp_request_get_output_buffer(request);
		for(; ;){
		    int len = fread(buf, 1, sizeof(buf), fp);
			
			if(len <= 0) break;
			evbuffer_add(outbuf, buf, len);
		}
		
		fclose(fp);
	    evhttp_send_reply(request, HTTP_OK, "", outbuf);
	}else{
	    evhttp_send_reply(request, HTTP_NOTFOUND, "", 0);
		return;
	}
}

int main()
{
#ifdef _WIN32
    //windows初始化socket库
    WSADATA wsa;
    WSAStartup(MAKEWORD(2, 2), &wsa);
#else
    //发送数据给已关闭socket时,忽略管道信息.
    //否则可能导致程序dump.
    if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) {
        return -1;
    }
#endif
    cout<<"test httpserver!"<<endl;

    //创建 event_base 对象,一个 event_base 对象相当于一个 Reactor 实例
	event_base *base = event_base_new();
	
	//http服务器
    //1.创建evhttp上下文
	evhttp *evh = evhttp_new(base);
	
	//2.绑定端口和ip
	if(evhttp_bind_socket(evh, "0.0.0.0", 8989) != 0){
	    cout<< "evhttp_bind_socket failed!" <<endl;
	}
    
	//3.设定回调函数
	evhttp_set_gencb(evh, http_cb, 0);
	
    event_base_dispatch(base);
    event_base_free(base);
	evhttp_free(evh);

#ifdef _WIN32
    //清理window的socket库
    WSACleanup();
#endif

    return 0;
}

index.html内容:

<html>
<head>
<title>
demo
</title>
</head>
<body>
hello world
</body>
</html>

运行结果:

 

参考:

libevent高并发网络编程 - 04_libevent实现http服务器 

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐