基于libevent的http接口封装(三)

这篇文章主要是讲解一下最核心的http请求和http ack部分的逻辑。在这里我封装了一个核心功能类HttpClientHelper,对外提供了几个方法:

1.bool is_start() 用来判断改服务是否启动
2.bool start() 启动服务
3.void stop() 停止服务
4.get()、post()、download()、upload() http操作方法

HttpClientHelper头文件如下:

#ifndef HTTPCLIENTHELPER_H
#define HTTPCLIENTHELPER_H


#include <thread>
#include <mutex>
#include <fstream>
#include "HttpClientCommon.h"

struct event_base;
struct evdns_base;
struct evhttp_request;
class http_client_timer;




class HttpClientHelper
{
public:
	//http请求信息
	struct Struct_HttpInfo
	{
		Enum_HttpType http_type;
		std::string http_url;
		std::unordered_map<std::string, std::string> header;
		std::string data;
		int timeout_second;
		std::shared_ptr<Struct_HttpResult> http_result = std::make_shared<Struct_HttpResult>();
		HttpCallBack http_callback;
		std::shared_ptr<std::ofstream> file_stream;
	};
	HttpClientHelper();
	~HttpClientHelper();

	bool start();
	bool is_start();

	void stop();

	void ack_head(evhttp_request*);
	void ack_body(evhttp_request*);
	void ack(evhttp_request*);
	void error(evhttp_request*);

	void get(std::string http_url, HttpCallBack http_callback,
		size_t timeout_second = 10, 
		std::unordered_map<std::string, std::string> headers = { {"Content-Type","application/x-www-form-urlencoded;charset=UTF-8"} });

	void post(std::string http_url, std::unordered_map<std::string, std::string> datas, HttpCallBack http_callback,
		size_t timeout_second = 10,
		std::unordered_map<std::string, std::string> headers = { {"Content-Type","application/json;charset=UTF-8"} });

	void download(std::string http_url, std::string download_path, HttpCallBack http_callback,
		size_t timeout_second = 10,
		std::unordered_map<std::string, std::string> headers = { {"Content-Type","application/x-www-form-urlencoded;charset=UTF-8"} });

	void upload(std::string http_url, std::unordered_map<std::string, Struct_MultiHttpPart> http_parts, HttpCallBack http_callback,
		size_t timeout_second = 10);

private:
	void add_http_info(evhttp_request*, std::shared_ptr<Struct_HttpInfo>);
	std::shared_ptr<Struct_HttpInfo> find_http_info(evhttp_request*);
	void remove_http_info(evhttp_request *);

	void start_timer(evhttp_request* http_request, size_t second);
	void remove_timer(evhttp_request* http_request);

	void run_in_thread();

	void make_request(std::shared_ptr<Struct_HttpInfo> http_info);

	std::unordered_map<evhttp_request*, std::shared_ptr<Struct_HttpInfo>> m_http_requests;
	event_base *m_httpBase = nullptr;
	evdns_base *m_httpDns = nullptr;

	std::mutex m_http_lock;

	bool m_http_start = false;
	std::shared_ptr<std::thread> m_http_thread;
	std::shared_ptr<http_client_timer> m_http_timer;
};

#endif // !HTTPCLIENTHELPER_H

启动服务逻辑比较简单,就是启动一个线程,在里面做http事件处理

if (m_httpBase)
	{
		return true;
	}

	if (!m_http_thread)
	{
		m_http_thread = std::make_shared<std::thread>(&HttpClientHelper::run_in_thread, this);
	}

	if (!m_http_timer)
	{
		m_http_timer = std::make_shared<http_client_timer>();
	}

线程的代码主要逻辑是创建创建httpbase和httpdns,然后就一直做事件派发

void HttpClientHelper::run_in_thread()
{
	m_http_start = false;
	m_httpBase = event_base_new();
	if (!m_httpBase)
	{
		fprintf(stderr, "create event base failed!\n");
		return ;
	}

	m_httpDns = evdns_base_new(m_httpBase, 1);
	if (!m_httpDns)
	{
		fprintf(stderr, "create dns base failed!\n");
		return ;
	}

	m_http_start = true;
	event_base_dispatch(m_httpBase);
}

停止服务就是,退出http事件派发和释放对象

if (m_httpDns)
	{
		//evdns_base_free(m_httpDns, 0);
		m_httpDns = nullptr;
	}

	if (m_httpBase)
	{
		event_base_loopexit(m_httpBase, nullptr);
		event_base_free(m_httpBase);
		m_httpBase = nullptr;
	}


	if (m_http_thread && m_http_thread->joinable())
	{
		m_http_thread->join();
	}
	if (m_http_timer)
	{
		m_http_timer->stop();
	}
	
	m_http_start = false;

get、post、download、upload的逻辑都相似:创建一个请求对象,然后放到请求队列中,等待线程处理
get逻辑如下:

void HttpClientHelper::get(std::string http_url, HttpCallBack http_callback, size_t timeout_second, std::unordered_map<std::string, std::string> headers)
{
	if (!m_httpBase)
	{
		return;
	}
	std::shared_ptr<Struct_HttpInfo> http_info = std::make_shared<Struct_HttpInfo>();
	http_info->http_url = http_url;
	http_info->http_type = HTTPTYPE_GET;
	http_info->timeout_second = timeout_second;
	http_info->http_callback = http_callback;
	http_info->header = headers;

	ThreadPool::getInstance().putTask([this, http_info] {
		make_request(http_info);
	});	
}

post比get稍微复杂一点,这里需要把请求数据map,拼装成json格式的字符串,实际上可以使用json库来处理,我这里就简单的拼装了一下:

void HttpClientHelper::post(std::string http_url, std::unordered_map<std::string, std::string> datas, HttpCallBack http_callback, size_t timeout_second, std::unordered_map<std::string, std::string> headers)
{
	if (!m_httpBase)
	{
		return;
	}
	std::shared_ptr<Struct_HttpInfo> http_info = std::make_shared<Struct_HttpInfo>();
	http_info->http_url = http_url;
	http_info->http_type = HTTPTYPE_POST;
	http_info->timeout_second = timeout_second;
	http_info->http_callback = http_callback;
	http_info->header = headers;

	int count = 0;
	std::string http_data;
	for (auto it = datas.begin(); it != datas.end(); ++it)
	{
		if (count++)
		{
			http_data.append(",");
		}

		http_data.append(R"(")").append(it->first).append(R"(":")").append(it->second).append(R"(")");
	}

	if (http_data.length())
	{
		http_info->data.append("{").append(http_data).append("}");
	}

	ThreadPool::getInstance().putTask([this, http_info] {
		make_request(http_info);
	});
}

同样,download也和get一样简单:

void HttpClientHelper::download(std::string http_url, std::string download_path, HttpCallBack http_callback, size_t timeout_second, std::unordered_map<std::string, std::string> headers)
{
	if (!m_httpBase)
	{
		return;
	}
	std::shared_ptr<Struct_HttpInfo> http_info = std::make_shared<Struct_HttpInfo>();
	http_info->http_url = http_url;
	http_info->http_type = HTTPTYPE_DOWNLOAD;
	http_info->timeout_second = timeout_second;
	http_info->http_callback = http_callback;
	http_info->http_result->http_result = download_path;
	http_info->header = headers;

	ThreadPool::getInstance().putTask([this, http_info] {
		make_request(http_info);
	});
}

upload还没来得及弄,后面再补吧

最复杂的就是请求事件的处理了,需要根据请求事件的数据,创建一个http请求对象,然后调用libevent的evhttp_make_request方法进行请求

void HttpClientHelper::make_request(std::shared_ptr<Struct_HttpInfo> http_info)
{
	if (!m_httpBase)
	{
		return;
	}

	struct evhttp_request* request = evhttp_request_new(request_finished, this);
	evhttp_request_set_header_cb(request, http_head_finish);
	evhttp_request_set_chunked_cb(request, http_body_finished);
	evhttp_request_set_error_cb(request, http_request_error);
	
	add_http_info(request, http_info);
	
	const char *url = http_info->http_url.c_str();
	evhttp_uri* uri = evhttp_uri_parse(url);
	const char* host = evhttp_uri_get_host(uri);

	int port = evhttp_uri_get_port(uri);
	if (port < 0) port = 80;

	const char* request_url = url;
	const char* path = evhttp_uri_get_path(uri);
	if (!path || strlen(path) == 0)
	{
		request_url = "/";
	}

	evhttp_connection* connection = evhttp_connection_base_new(m_httpBase, m_httpDns, host, port);
	if (!connection)
	{
		fprintf(stderr, "create evhttp connection failed!\n");
		return;
	}

	auto http_headers = evhttp_request_get_output_headers(request);
	evhttp_add_header(http_headers, "Host", host);
	for (auto it = http_info->header.begin(); it != http_info->header.end(); ++it)
	{
		evhttp_add_header(http_headers, it->first.c_str(), it->second.c_str());
	}

	//timeout
	start_timer(request, http_info->timeout_second);
	//evhttp_connection_set_timeout(connection, http_info->timeout_second);

	if (http_info->http_type == HTTPTYPE_POST)
	{
		auto buffer = evhttp_request_get_output_buffer(request);
		evbuffer_add(buffer, http_info->data.c_str(), strlen(http_info->data.c_str()));
		evhttp_make_request(connection, request, EVHTTP_REQ_POST, request_url);
	}
	else if(http_info->http_type == HTTPTYPE_GET || http_info->http_type == HTTPTYPE_DOWNLOAD)
	{
		evhttp_make_request(connection, request, EVHTTP_REQ_GET, request_url);
	}	
}

这里写了4个http事件回调函数分别如下:

int http_head_finish(struct evhttp_request* remote_rsp, void* arg)
{
	HttpClientHelper *http_client = (HttpClientHelper*)arg;
	if (http_client)
	{
		http_client->ack_head(remote_rsp);
	}
	return 0;
}

void http_body_finished(struct evhttp_request* remote_rsp, void* arg)
{
	HttpClientHelper *http_client = (HttpClientHelper*)arg;
	if (http_client)
	{
		http_client->ack_body(remote_rsp);
	}	
}

void request_finished(struct evhttp_request* remote_rsp, void* arg)
{
	HttpClientHelper *http_client = (HttpClientHelper*)arg;
	if (http_client)
	{
		http_client->ack(remote_rsp);
	}
}

void http_request_error(evhttp_request_error http_error, void *arg)
{
	std::cout << http_error << " " << arg << std::endl;
}

问题:
实际测试中,发现中文报文显示时是乱码,这里需要和服务端定一个编码格式。比如说是utf8,然后我们收到报文时,把它转成宽字符即可。

std::string utf8_to_string(char *data, int len)
{
	int nwLen = MultiByteToWideChar(CP_UTF8, 0, data, -1, NULL, 0);

	wchar_t * pwBuf = new wchar_t[nwLen + 1];
	memset(pwBuf, 0, nwLen * 2 + 2);

	MultiByteToWideChar(CP_UTF8, 0, data, len, pwBuf, nwLen);

	int nLen = WideCharToMultiByte(CP_ACP, 0, pwBuf, -1, NULL, NULL, NULL, NULL);

	char * pBuf = new char[nLen + 1];
	memset(pBuf, 0, nLen + 1);

	WideCharToMultiByte(CP_ACP, 0, pwBuf, nwLen, pBuf, nLen, NULL, NULL);

	std::string retStr = pBuf;

	delete[]pBuf;
	delete[]pwBuf;

	pBuf = NULL;
	pwBuf = NULL;

	return retStr;
}

由于时间比较赶,先简单记录一下吧,后面抽时间再整理。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Libevent是一个开源的事件通知库,它提供了一个轻量级的、可移植的、高效的事件通知机制,用于网络编程和多线程编程。它支持多种操作系统,包括Linux、FreeBSD、OpenBSD、NetBSD、Solaris、Mac OS X和Windows等。 Libevent的C API封装了一系列函数,可以方便地使用事件通知机制进行网络编程和多线程编程。以下是一些主要的API函数: 1. event_base_new():创建一个事件基础结构体。 2. event_base_free():释放一个事件基础结构体。 3. event_base_dispatch():进入事件循环,等待事件发生并处理。 4. event_base_loopexit():退出事件循环。 5. event_new():创建一个事件结构体。 6. event_free():释放一个事件结构体。 7. event_add():将一个事件添加到事件基础中。 8. event_del():从事件基础中删除一个事件。 9. event_assign():为一个已存在的事件结构体分配一个新的事件处理器。 10. event_set():设置一个事件的事件类型、回调函数和事件标志等信息。 11. event_active():激活一个事件。 12. evbuffer_new():创建一个缓冲区。 13. evbuffer_free():释放一个缓冲区。 14. evbuffer_add():向缓冲区中添加数据。 15. evbuffer_remove():从缓冲区中读取数据。 16. evbuffer_get_length():获取缓冲区中数据的长度。 17. event_get_fd():获取一个事件的文件描述符。 18. event_get_base():获取一个事件的事件基础。 这些函数可以灵活地组合使用,实现各种网络编程和多线程编程的需求。对于初学者来说,建议先学习Libevent的基本概念和使用方法,然后结合实际项目需求使用C API进行编程。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值