C++高性能服务器框架----Http模块
定义了一些请求方式和HTTP响应状态的枚举方式代码语言:javascriptAI代码解释// 请求方法//....// HTTP方法枚举 {DELETE = 0, GET = 1, ... }#undef XX// 状态码// ...// HTTP状态枚举 {CONTINUE = 100, SWITCHING_PROTOCOLS = 101, ...}#undef XX是对HTTP请求协议的封装。
详细内容:日志模块,使用宏实现流式输出,支持同步日志与异步日志、自定义日志格式、日志级别、多日志分离等功能。线程模块,封装pthread相关方法,封装常用的锁包括(信号量,读写锁,自旋锁等)。IO协程调度模块,基于ucontext_t实现非对称协程模型,以线程池的方式实现多线程,多协程协同调度,同时依赖epoll实现了事件监听机制。定时器模块,使用最小堆管理定时器,配合IO协程调度模块可以完成基于协程的定时任务调度。hook模块,将同步的系统调用封装成异步操作(accept, recv, send等),配合IO协程调度能够极大的提升服务器性能。Http模块,封装了sokcet常用方法,支持http协议解析,客户端实现连接池发送请求,服务器端实现servlet模式处理客户端请求,支持单Reator多线程,多Reator多线程模式的服务器。
Http模块
1. 主要功能
- 封装了HTTP请求和响应协议(
HttpRequest和HttpResponse) - 使用nodejs的htpp_parser实现了HTTP请求、响应解析(HttpRequestParser和HttpResponseParser)
- 实现了uri解析(Uri)
- 封装了HTTP会话,负责服务器接收请求和发送响应(HttpSession)
- 封装了用于HTTP服务器的相关方法(HttpServer)
- 封装了客户端发送请求和接收响应及HTTP连接池(HttpConnection和HttpConnectionPool)
2. 功能演示
- 解析请求头
代码语言:javascript
AI代码解释
const char test_request_data[] = "POST /login?aa=bb#sss HTTP/1.1\r\n"
"Host: www.sylar.top\r\n"
"Content-Length: 10\r\n\r\n"
"1234567890";
void test_request(const char *str) {
johsonli::http::HttpRequestParser parser;
std::string tmp = str;
std::cout << "<test_request>:" << std::endl
<< tmp << std::endl;
parser.execute(&tmp[0], tmp.size());
if (parser.hasError()) {
std::cout << "parser execute fail" << std::endl;
} else {
johsonli::http::HttpRequest::ptr req = parser.getData();
std::cout << req->toString() << std::endl;
}
}
int main() {
test_request(test_request_data);
}
- 一个简单的Http服务器
代码语言:javascript
AI代码解释
johnsonli::http::HttpServer::ptr server(new johnsonli::http::HttpServer(true));
johnsonli::Address::ptr addr = johnsonli::Address::LookupAnyIPAddress("0.0.0.0:8020");
while(!server->bind(addr)) {
sleep(2);
}
server.start();
- 客户端发送请求
代码语言:javascript
AI代码解释
auto r = johnsonli::http::HttpConnection::DoGet("http://www.baidu.com:80", 300, false);
LOG_INFO(g_logger) << "result=" << r->result
<< " error=" << r->error
<< " rsp=" << (r->response ? r->response->toString() : "");
- 连接池的使用,没1秒发送一个请求
代码语言:javascript
AI代码解释
johnsonli::http::HttpConnectionPool::ptr pool(new johnsonli::http::HttpConnectionPool("www.sylar.top", "", 80, false, 10, 1000 * 30, 5));
johnsonli::IOManager::GetThis()->addTimer(1000, [pool](){
auto r = pool->doGet("/blog/", 300);
LOG_INFO(g_logger) << r->toString();
}, true);
3. 模块介绍
3.1 HttpRequest和HttpResponse

定义了一些请求方式和HTTP响应状态的枚举方式
代码语言:javascript
AI代码解释
// 请求方法
#define HTTP_METHOD_MAP(XX) \
XX(0, DELETE, DELETE) \
XX(1, GET, GET) \
XX(2, HEAD, HEAD) \
XX(3, POST, POST) \
//....
// HTTP方法枚举 {DELETE = 0, GET = 1, ... }
enum class HttpMethod {
#define XX(num, name, string) name = num,
HTTP_METHOD_MAP(XX)
#undef XX
INVALID_METHOD
};
// 状态码
#define HTTP_STATUS_MAP(XX) \
XX(100, CONTINUE, Continue) \
XX(101, SWITCHING_PROTOCOLS, Switching Protocols) \
XX(102, PROCESSING, Processing) \
XX(200, OK, OK) \
// ...
// HTTP状态枚举 {CONTINUE = 100, SWITCHING_PROTOCOLS = 101, ...}
enum class HttpStatus {
#define XX(code, name, desc) name = code,
HTTP_STATUS_MAP(XX)
#undef XX
};
HttpRequest是对HTTP请求协议的封装。包括请求行,请求头,请求消息体等
代码语言:javascript
AI代码解释
class HttpRequest {
public:
// ...
private:
HttpMethod m_method; /// HTTP方法
uint8_t m_version; /// HTTP版本
bool m_close; /// 是否自动关闭
bool m_websocket; /// 是否为websocket
uint8_t m_parserParamFlag;
std::string m_path; /// 请求路径
std::string m_query; /// 请求参数
std::string m_fragment; /// 请求fragment
std::string m_body; /// 请求消息体
MapType m_headers; /// 请求头部MAP
MapType m_params; /// 请求参数MAP
MapType m_cookies; /// 请求Cookie MAP
};
HttpResponse是对HTTP响应的封装。包括响应行,响应头,响应消息体
代码语言:javascript
AI代码解释
class HttpResponse {
public:
// ...
private:
HttpStatus m_status; /// 响应状态
uint8_t m_version; /// 版本
bool m_close; /// 是否自动关闭
bool m_websocket; /// 是否为websocket
std::string m_body; /// 响应消息体
std::string m_reason; /// 响应原因
MapType m_headers; /// 响应头部MAP
std::vector<std::string> m_cookies;
};
3.2 HttpRequestParser和HttpResponseParser
- HTTP解析器基于nodejs/http-parser实现,通过套接字读到HTTP消息后将消息内容传递给解析器,解析器通过回调的形式通知调用方HTTP解析的内容。
3.3 Uri
对string uri的解析,具体请参看源码
3.4 HttpSession
继承自SocketStream,实现了在套接字流上读取HTTP请求与发送HTTP响应的功能,在读取HTTP请求时需要借助HTTP解析器,以便于将套接字流上的内容解析成HTTP请求。
代码语言:javascript
AI代码解释
class HttpSession : public SocketStream {
public:
typedef std::shared_ptr<HttpSession> ptr;
HttpSession(Socket::ptr sock, bool owner = true);
HttpRequest::ptr recvRequest(); // 接收HTTP请求
int sendResponse(HttpResponse::ptr rsp); // 发送HTTP响应
};
接收请求recvRequest。
-
- 循环读,直到请求行,请求头解析完毕
- 获取content-length,读取请求消息体
- 返回
HttpRequest
发送响应senResponse
代码语言:javascript
AI代码解释
int HttpSession::sendResponse(HttpResponse::ptr rsp) {
std::stringstream ss;
ss << *rsp;
std::string data = ss.str();
return writeFixSize(data.c_str(), data.size());
}
3.5 HttpServer
这里还涉及到Servlet的使用,可以在从零开始实现一个C++高性能服务器框架----Servlet模块了解详细说明。
代码语言:javascript
AI代码解释
class HttpServer : public TcpServer
{
public:
typedef std::shared_ptr<HttpServer> ptr;
/**
* @brief 构造函数
* @param[in] keepalive 是否长连接
* @param[in] worker 工作调度器
* @param[in] io_worker 负责连接socket的读写
* @param[in] accept_worker 接收连接调度器
*/
HttpServer(bool keepalive = false
,johnsonli::IOManager* worker = johnsonli::IOManager::GetThis()
,johnsonli::IOManager* io_worker = johnsonli::IOManager::GetThis()
,johnsonli::IOManager* accept_worker = johnsonli::IOManager::GetThis());
virtual void setName(const std::string& v) override;
ServletDispatch::ptr getServletDispatch() const { return m_dispatch;}
void setServletDispatch(ServletDispatch::ptr v) { m_dispatch = v;}
protected:
virtual void handleClient(Socket::ptr client) override;
private:
bool m_isKeepalive; // 是否支持长连接
ServletDispatch::ptr m_dispatch; // Servlet分发器
};
}
重写了handleClient,负责接收连接socket发出的请求,并发出响应。如果是长连接,将会循环执行这个过程。
代码语言:javascript
AI代码解释
void HttpServer::handleClient(Socket::ptr client)
{
HttpSession::ptr session(new HttpSession(client));
do {
// 接收请求
auto req = session->recvRequest();
if(!req) {
LOG_DEBUG(g_logger) << "recv http request fail, errno="
<< errno << " errstr=" << strerror(errno)
<< " cliet:" << *client << " keep_alive=" << m_isKeepalive;
break;
}
HttpResponse::ptr rsp(new HttpResponse(req->getVersion()
,req->isClose() || !m_isKeepalive));
rsp->setBody("hello world");
// 发送响应
session->sendResponse(rsp);
}while(m_isKeepalive);
}
3.6 HttpConnection
HTTP客户端,继承自SocketStream。负责发送请求和接收响应。
代码语言:javascript
AI代码解释
class HttpConnection : public SocketStream{
public:
HttpResponse::ptr recvResponse(); // 接收响应
int sendRequest(HttpRequest::ptr req); // 发送请求
// ...
};
为了方便使用,封装了几个发送请求的方法
代码语言:javascript
AI代码解释
class HttpConnection : public SocketStream{
public:
/**
* @brief 发送HTTP的GET请求
* @param[in] url 请求的url
* @param[in] timeout_ms 超时时间(毫秒)
* @param[in] is_close 是否自动关闭
* @param[in] headers HTTP请求头部参数
* @param[in] body 请求消息体
* @return 返回HTTP结果结构体
*/
static HttpResult::ptr DoGet(const std::string& url
, uint64_t timeout_ms
, bool is_close = true
, const std::map<std::string, std::string>& headers = {}
, const std::string& body = "");
/**
* @brief 发送HTTP的GET请求
* @param[in] uri URI结构体
* @param[in] timeout_ms 超时时间(毫秒)
* @param[in] is_close 是否自动关闭
* @param[in] headers HTTP请求头部参数
* @param[in] body 请求消息体
* @return 返回HTTP结果结构体
*/
static HttpResult::ptr DoGet(Uri::ptr uri
, uint64_t timeout_ms
, bool is_close = true
, const std::map<std::string, std::string>& headers = {}
, const std::string& body = "");
/**
* @brief 发送HTTP的POST请求
* @param[in] url 请求的url
* @param[in] timeout_ms 超时时间(毫秒)
* @param[in] is_close 是否自动关闭
* @param[in] headers HTTP请求头部参数
* @param[in] body 请求消息体
* @return 返回HTTP结果结构体
*/
static HttpResult::ptr DoPost(const std::string& url
, uint64_t timeout_ms
, bool is_close = true
, const std::map<std::string, std::string>& headers = {}
, const std::string& body = "");
/**
* @brief 发送HTTP的POST请求
* @param[in] uri URI结构体
* @param[in] timeout_ms 超时时间(毫秒)
* @param[in] is_close 是否自动关闭
* @param[in] headers HTTP请求头部参数
* @param[in] body 请求消息体
* @return 返回HTTP结果结构体
*/
static HttpResult::ptr DoPost(Uri::ptr uri
, uint64_t timeout_ms
, bool is_close = true
, const std::map<std::string, std::string>& headers = {}
, const std::string& body = "");
/**
* @brief 发送HTTP请求
* @param[in] method 请求类型
* @param[in] uri 请求的url
* @param[in] timeout_ms 超时时间(毫秒)
* @param[in] is_close 是否自动关闭
* @param[in] headers HTTP请求头部参数
* @param[in] body 请求消息体
* @return 返回HTTP结果结构体
*/
static HttpResult::ptr DoRequest(HttpMethod method
, const std::string& url
, uint64_t timeout_ms
, bool is_close = true
, const std::map<std::string, std::string>& headers = {}
, const std::string& body = "");
/**
* @brief 发送HTTP请求
* @param[in] method 请求类型
* @param[in] uri URI结构体
* @param[in] timeout_ms 超时时间(毫秒)
* @param[in] is_close 是否自动关闭
* @param[in] headers HTTP请求头部参数
* @param[in] body 请求消息体
* @return 返回HTTP结果结构体
*/
static HttpResult::ptr DoRequest(HttpMethod method
, Uri::ptr uri
, uint64_t timeout_ms
, bool is_close = true
, const std::map<std::string, std::string>& headers = {}
, const std::string& body = "");
private:
HttpResult::ptr HttpConnection::DoRequest(HttpRequest::ptr req
, Uri::ptr uri
, uint64_t timeout_ms
, bool is_close)
};
每个HttpConnection都应该记录自己发起连接请求的数量和创建时间,在HttpConnectionPool时会根据每个连接对象的最大请求数和存活时间来判断是否需要销毁
代码语言:javascript
AI代码解释
private:
uint64_t m_createTime = 0; // 创建时间
uint64_t m_requestCount = 0; // 发起连接请求的数量
3.7 HttpConnectionPool
HTTP连接池,负责统一管理HttpConnection对象。提前预备好一系列已接建立连接的socket,这样,在发起请求时,可以直接从中选择一个进行通信,而不用重复创建套接字→ 发起connect→ 发起请求 的流程。
更多推荐


所有评论(0)