libevent (17) evhttp API and server-side code

1. Introduction to evhttp

The implementation of HTTP in libevent is mainly done through the evhttp module. When the client initiates an HTTP request, libevent parses the request into a struct evhttp_request structure representation, and calls the request processing function set by the user for processing.

The struct evhttp_request structure defines various fields of the HTTP request, such as request line, request header, request body, etc.

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

2. Related APIs

1、evhttp_new()

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

evhttp_new is used to create and initialize an evhttp structure. The evhttp structure represents an HTTP server that can listen on one or more sockets for incoming HTTP requests and dispatch those requests to the appropriate request handler.

2、evhttp_free()

void evhttp_free(struct evhttp* http)

evhttp_free() is used to release the evhttp structure and its related resources. After calling this function, all information associated with evhttp is destroyed, including bound sockets and any outstanding requests.

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 is used to bind the HTTP server to the specified IP address and port number.

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() is used to set the generic callback function.

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() is used to register a request handler callback function for a specific URI with an HTTP server.

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 请求

Returns the URI of the HTTP request. A URI is a string of characters that a client sends when requesting a server, usually consisting of a hostname, path, and query string. For example, for a request like "http://127.0.0.1/foo?bar=baz", the URI would be "/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 方法。

The method type used to get HTTP requests.

8、evhttp_request_get_input_headers()

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

Used to get input headers in HTTP requests.

9、evhttp_request_get_input_buffer()

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

Input buffer for fetching HTTP requests.

10、evhttp_request_get_output_headers()

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

Output headers for fetching HTTP requests.

11、evhttp_add_header()

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

Used to add a new key-value pair to the header list represented by the evkeyvalq structure.

(1) If a key already exists in the header list, the value of the existing key-value pair will be replaced with the new value.

(2) If you want to add multiple headers with the same name, please use commas to separate their values. For example, you can use "Accept-Encoding: gzip, deflate" to add two Accept-Encoding headers at the same time.

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() 函数将内容添加到缓冲区中。

Output buffer for fetching HTTP requests.

13、evhttp_send_reply()

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

Used to send HTTP responses to clients.

Before calling the evhttp_send_reply() function, it must be ensured that the response status code and necessary response headers have been set, and all response data has been written to the output buffer.

3. A server-side example


#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 content:

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

operation result:

 

reference:

libevent high concurrent network programming - 04_libevent implements http server 

Guess you like

Origin blog.csdn.net/mars21/article/details/131453896