准备:调试工具POSTMAN
简介:用于调试B/S模式程序的一款产品,可以发送几乎所有类型的HTTP请求!Postman在发送网络HTTP请求方面可以说是Chrome插件类产品中的代表产品之一。
参考出处:
· 小坑:最好使用客户端版本,网页版要使用浏览器作为代理,默认强制使用SSL,导致无法访问资源。
HTTP Server关系图
1. HTTP Session封装
目的:将服务器端accept
生成出来的socket
封装成一个session
结构,是一个被动发起的连接,负责收取来自客户端的HTTP请求报文,并且给客户端回发响应报文。
核心逻辑:先解析出报文头部,再解析报文实体
创建HttpRequestParser对象,获取当前所能接收的最大报文首部长度,根据这个长度创建一块缓冲区用于暂存接收到的请求报文
采取一边接收报文,一边解析报文头部的策略:
while(1)
{
- 尝试通过套接字
Socket对象
接收报文 - 根据实际接收到的报文长度
ret
,尝试调用HttpRequestParser::execute()
执行报文解析动作,并且计算一个偏移量offset = 实际接收到的报文长度 - 实际解析的报文长度
判断当前接收的却没有解析掉的报文是否超过最大限定值(4KB)。 - 判断解析是否已经全部完成:是,就执行
break
;否则,继续接收报文,继续解析。
}
- 存储报文实体内容。获取报文实体的长度大小,根据报文实体长度 和 2. 中得到的一个偏移量
offset
来判断一下当前的报文实体是否接收完整:- 报文实体长度 >
offset
, 说明当前收到的报文实体不完整。设置一个循环,再一次接收客户端传输来的报文实体,直至报文实体完整。 - 报文实体长度 <=
offset
,说明当前收到的报文实体已经完整
- 报文实体长度 >
将完整的报文实体放入
HttpRequestParser::HttpRequest对象
之中 ```cpp //收到HTTP请求报文 HttpRequest::ptr HttpSession::recvRequest() { HttpRequestParser::ptr parser(new HttpRequestParser); //使用某一时刻的值即可 不需要实时的值 uint64_t buff_size = HttpRequestParser::GetHttpRequestBufferSize();//创建一个接受请求报文的缓冲区 指定析构 std::shared_ptr
buffer(new char[buff_size], { delete[] ptr;
});
char *data = buffer.get(); int offset = 0;
//一边读一遍解析 while(1) {
int ret = read(data + offset, buff_size - offset);
if(ret <= 0)
{
KIT_LOG_ERROR(g_logger) << "HttpSession::recvRequest read error, errno=" << errno
<< ", is:" << strerror(errno);
return nullptr;
}
ret += offset;
size_t n = parser->execute(data, ret);
if(parser->hasError())
{
KIT_LOG_ERROR(g_logger) << "HttpSession::recvRequest parser error";
return nullptr;
}
offset = ret - n;
if(offset == (int)buff_size)
{
KIT_LOG_WARN(g_logger) << "HttpSession::recvRequest http requestion out of buffer's range";
return nullptr;
}
//如果解析已经结束
if(parser->isFinished())
break;
}
//获取实体长度
int64_t len = parser->getContenLength();
if(len > HttpRequestParser::GetHttpMaxBodySize())
{
KIT_LOG_WARN(g_logger) << "HttpSession::recvRequest body length too long!";
return nullptr;
}
//将报文实体读出 并且设置到HttpRequest对象中去
if(len > 0)
{
std::string body;
body.resize(len);
//这里offset 就是报文首部解析完之后 剩余的报文实体的字节数
if(len > offset)
body.append(data, offset);
else
body.append(data, len);
len -= offset;
//如果还有剩余的报文实体长度 说明报文实体没有在本次传输中全部被接收
//设置一个循环继续接收报文实体
while(len > 0)
{
if(readFixSize(&body[body.size()], len) <= 0)
{
KIT_LOG_ERROR(g_logger) << "HttpSession::recvRequest readFixSize error, errno=" << errno
<< ", is:" << strerror(errno);
return nullptr;
}
}
//等到报文实体完整之后 装入对象之中
parser->getData()->setBody(body);
}
return parser->getData();
}
<a name="GRvZz"></a>
## 1.2 `sendResponse()`
功能:传入已经组装好的报文,给客户端发送HTTP响应报文
```cpp
//发回HTTP响应报文
int HttpSession::sendResponse(HttpResponse::ptr rsp)
{
std::stringstream ss;
ss << *rsp;
std::string data = ss.str();
return writeFixSize(data.c_str(), data.size());
}
1.3 HTTP Server类封装
功能:利用已经封装好的HttpSession
类,搭建一个简单的HTTP交互服务器。接收HTTP请求报文,正确解析后,给客户端回发一个简单的响应报文,封装原来的请求报文。
namespace kit_server
{
namespace http
{
class HttpServer: public TcpServer
{
public:
typedef std::shared_ptr<HttpServer> ptr;
HttpServer(bool keepalive = false, IOManager* worker = IOManager::GetThis(),
IOManager* accept_worker = IOManager::GetThis());
bool getKeepalive() const {return m_isKeepalive;}
protected:
//处理已经连接的客户端 完成数据交互通信
virtual void handleClient(Socket::ptr client) override;
private:
//是否支持长连接
bool m_isKeepalive;
};
}
}
**handleClient()**
函数中负责请求和应答收发: ```cpp //处理已经连接的客户端 完成数据交互通信 void HttpServer::handleClient(Socket::ptr client) { KIT_LOG_INFO(g_logger) << “HttpServer::handleClient, client=” << *client; HttpSession::ptr session(new HttpSession(client));do {
// 接收请求报文
auto req = session->recvRequest();
if(!req)
{
KIT_LOG_WARN(g_logger) << "recv http request fail, errno=" << errno
<< ", is:" << strerror(errno)
<< ", client=" << *client;
break;
}
//回复响应报文
HttpResponse::ptr rsp(new HttpResponse(req->getVersion(), req->isClose() || !m_isKeepalive));
rsp->setBody(std::string("hello kit!"));
session->sendResponse(rsp);
} while (m_isKeepalive);
session->close();
}
<a name="KLhN2"></a>
# 3. HTTP Servlet封装
概念:Servlet是Server Applet的一个简称,直译就是服务小程序的意思。该概念常见于Java中。一般而言,B/S模型(浏览器-服务器模型),通过浏览器发送访问请求,服务器接收请求,并对这些请求作出处理。而servlet就是专门对请求作出处理的组件。
<a name="fVjx1"></a>
## 2.1 类关系设计
![](https://cdn.nlark.com/yuque/0/2022/jpeg/25460685/1646639953768-c45addb4-aa35-4637-8ee4-565f55dd75c5.jpeg)
<a name="zGGz6"></a>
### 2.1.1 `Servlet`类
```cpp
/**
* @brief 服务类基类
*/
class Servlet
{
public:
typedef std::shared_ptr<Servlet> ptr;
/**
* @brief 服务类构造函数
* @param[in] name 服务名称
*/
Servlet(const std::string& name):m_name(name) { }
/**
* @brief 服务类析构函数
*/
virtual ~Servlet() {}
/**
* @brief 执行服务(虚接口)
* @param[in] request HTTP请求报文
* @param[in] response HTTP响应报文
* @param[in] session HTTP主动连接会话
* @return int32_t
*/
virtual int32_t handle(HttpRequest::ptr request, HttpResponse::ptr response, HttpSession::ptr session) = 0;
/**
* @brief 获取服务名称
* @return const std::string&
*/
const std::string& getName() const {return m_name;}
protected:
/// 服务名称
std::string m_name;
};
2.1.2 FunctionServlet
类
/**
* @brief 函数服务类
*/
class FunctionServlet: public Servlet
{
public:
typedef std::shared_ptr<FunctionServlet> ptr;
typedef std::function<int32_t (HttpRequest::ptr request,
HttpResponse::ptr response, HttpSession::ptr session)> callback;
/**
* @brief 函数服务类
* @param[in] cb 服务回调函数
* @param[in] name 服务名称
*/
FunctionServlet(callback cb, const std::string& name = "FunctionServlet");
/**
* @brief 执行服务
* @param[in] request HTTP请求报文
* @param[in] response HTTP响应报文
* @param[in] session HTTP主动连接会话
* @return int32_t
*/
int32_t handle(HttpRequest::ptr request, HttpResponse::ptr response, HttpSession::ptr session) override;
private:
/// 回调函数
callback m_cb;
};
2.1.3ServletDispatch
类
- 使用
unordered_map
容器存储精准匹配的服务对象,不允许同一个资源有重复的处理。 使用
vector<pair< > >
组合容器存储模糊匹配的服务对象,允许同一个资源有重复的处理。由于服务也属于一种公共资源,应考虑线程安全的问题,对服务的管理属于一种”读多写少”的场景,考虑使用读写锁。 ```cpp class ServletDispatch: public Servlet { public: typedef std::shared_ptr
ptr; typedef RWMutex MutexType; ServletDispatch(const std::string& name = “ServletDispatch”);
//服务执行 int32_t handle(HttpRequest::ptr request, HttpResponse::ptr response, HttpSession::ptr session) override;
//添加服务 void addServlet(const std::string& uri, Servlet::ptr slt); void addServlet(const std::string& uri, FunctionServlet::callback cb); void addGlobServlet(const std::string& uri, Servlet::ptr slt); void addGlobServlet(const std::string& uri, FunctionServlet::callback cb);
//删除精准匹配服务 void delServlet(const std::string& uri); //删除模糊匹配服务 void delGlobServlet(const std::string& uri);
//获取默认服务 Servlet::ptr getDefault() const {return m_default;} //设置默认服务 void setDefault(Servlet::ptr v) {m_default = v;}
//获取精准匹配服务对象 Servlet::ptr getServlet(const std::string& uri); //获取模糊匹配服务对象 Servlet::ptr getGlobServlet(const std::string& uri); //获取任意一个符合的服务对象 Servlet::ptr getMatchedServlet(const std::string& uri);
private:
//uri—->servlet 精准匹配 temp/xxxx
std::unordered_map
};
<a name="ja5DC"></a>
## 2.2 核心接口
<a name="VKHqb"></a>
### `handle()`
通过虚基类`Servlet`定义出一个接口`handle()`,负责去执行不同请求所要对应的服务。
- `FunctionServlet`继承后,通过回调函数的形式,存储相应的请求所要做的处理动作,通过`handel()`去执行该回调函数。
```cpp
int32_t FunctionServlet::handle(HttpRequest::ptr request, HttpResponse::ptr response, HttpSession::ptr session)
{
return m_cb(request, response, session);
}
ServletDispatch
继承后,负责管理所有的服务对象,支持精准资源请求、模糊资源请求,通过handle()
去执行分管的子服务的回调函数。当一个请求到达服务器端,优先精准匹配符合的请求资源,如果没有就去检索有可能符合的请求资源。都没有找到相对应的资源,将使用一个默认的服务来处理这个可能不太合法的请求。 ```cpp
//服务执行 int32_t ServletDispatch::handle(HttpRequest::ptr request, HttpResponse::ptr response, HttpSession::ptr session) { auto slt = getMatchedServlet(request->getPath()); if(slt) slt->handle(request, response, session);
return 0;
}
<a name="l1mLT"></a>
## 2.3 HTTP Server类封装
功能:综合之前`HttpSession`类的功能,将`Servlet`类的功能加入,实现不同请求对应不同服务。
- **添加相应的服务管理对象**`**Servlet**`
```cpp
/**
* @brief HTTP服务器类
*/
class HttpServer: public TcpServer
{
public:
typedef std::shared_ptr<HttpServer> ptr;
/**
* @brief HTTP服务器类构造函数
* @param[in] keepalive 是否是长连接 默认否
* @param[in] worker 服务执行调度器 默认是当前线程持有的调度器
* @param[in] accept_worker 接收连接调度器 默认是当前线程持有的调度器
*/
HttpServer(bool keepalive = false, IOManager* worker = IOManager::GetThis(),
IOManager* accept_worker = IOManager::GetThis());
/**
* @brief 获取长连接状态
* @return true 处于长连接
* @return false 处于短连接
*/
bool getKeepalive() const {return m_isKeepalive;}
/**
* @brief 获取服务分发器
* @return ServletDispatch::ptr
*/
ServletDispatch::ptr getServletDispatch() const {return m_dispatch;}
/**
* @brief 设置服务分发器
* @param[in] v
*/
void setServletDispatch(ServletDispatch::ptr v) {m_dispatch = v;}
/**
* @brief 设置服务器名称
* @param[in] v
*/
virtual void setName(const std::string& v) override;
protected:
/**
* @brief 处理已经连接的客户端 完成数据交互通信
* @param[in] client 和客户端通信的Socket对象智能指针
*/
virtual void handleClient(Socket::ptr client) override;
private:
/// 是否支持长连接
bool m_isKeepalive;
/// 服务分发器
ServletDispatch::ptr m_dispatch;
};
**handleClient()**
函数从原来的固定一收一答,转为根据不同请求获取不同服务,并设置相对应的响应报文。 ```cpp //处理已经连接的客户端 完成数据交互通信 void HttpServer::handleClient(Socket::ptr client) { KIT_LOG_INFO(g_logger) << “HttpServer::handleClient, client=” << *client; HttpSession::ptr session(new HttpSession(client));do {
// 接收请求报文
auto req = session->recvRequest();
if(!req)
{
KIT_LOG_WARN(g_logger) << "recv http request fail, errno=" << errno
<< ", is:" << strerror(errno)
<< ", client=" << *client;
break;
}
//回复响应报文
HttpResponse::ptr rsp(new HttpResponse(req->getVersion(), req->isClose() || !m_isKeepalive));
m_dispatch->handle(req, rsp, session);
// rsp->setBody(std::string("hello kit!"));
session->sendResponse(rsp);
} while (m_isKeepalive);
session->close();
}
<a name="GAatT"></a>
# 3. HTTP Result 结构体封装
目的:封装对远端服务器发起请求后的HTTP报文处理结果
<a name="Ao3go"></a>
## 3.1 成员变量
```cpp
struct HttpResult
{
....
....
/**
* @brief 错误码枚举类型
*/
enum class Error
{
//成功
OK,
//非法URL
INVALID_URL,
//非法地址
INVALID_ADDR,
//非法套接字
INVALID_SOCK,
//连接失败
CONNECT_FAIL,
//发送请求失败
SEND_REQ_FAIL,
//接收响应超时
RECV_RSP_TIMEOUT,
//连接池取出连接失败
POOL_GET_CONNECT_FAIL,
//连接池非法sokcet
POOL_INVALID_SOCK,
};
//错误码
int result;
//HTTP响应报文对象智能指针
HttpResponse::ptr response;
//错误原因短语
std::string error;
};
3.2 接口
3.2.1 构造函数
/**
* @brief HTTP报文处理结果结构体构造函数
* @param[in] _result 错误码
* @param[in] _response HTTP响应报文对象智能指针
* @param[in] _error 错误原因短语
*/
HttpResult(int _result, HttpResponse::ptr _response, const std::string& _error)
:result(_result)
,response(_response)
,error(_error)
{
}
3.2.2 toString()
功能:HTTP报文处理结果以字符串形式输出
/**
* @brief HTTP报文处理结果以字符串形式输出
* @return std::string
*/
std::string toString() const;
std::string HttpResult::toString() const
{
std::stringstream ss;
ss << "[HttpResult result=" << result
<< ", error_str=]" << error
<< ", response=\n" << (response ? response->toString() : "nullptr");
return ss.str();
}
4. HTTP Connection类封装
目的:将服务器创建出来发起主动连接的socket
封装成一个Connection
。和之前HttpSeesion
类的功能一模一样,只是此时的服务器作为一个客户端对远端另外一台服务器发起资源请求。
和**HttpSession**
类接口的功能正好相反
· 类关系设计
4.1 成员变量
class HttpConnection
{
....
....
private:
//连接创建时间
uint64_t m_createTime;
//连接上的请求数
uint64_t m_requestCount;
};
4.2 接口
4.2.1 recvResponse()
(核心)
功能:接收HTTP响应报文。支持HTTP chunck发包形式。
- 核心逻辑:先解析出报文头部,再解析报文实体
创建HttpRequestParser对象,获取当前所能接收的最大报文首部长度,根据这个长度创建一块缓冲区用于暂存接收到的请求报文
采取一边接收报文,一边解析报文头部的策略:
while(1)
{
- 尝试通过套接字
Socket对象
接收报文 - 根据实际接收到的报文长度
ret
,尝试调用HttpResponseParser::execute()
执行报文解析动作,并且计算一个偏移量offset = 实际接收到的报文长度 - 实际解析的报文长度
判断当前的报文是否超过最大报文首部限定值(4KB)。 - 判断解析是否已经全部完成:是,就执行
break
;否则,继续接收报文,继续解析。
}
根据解析出的报文首部,看首部字段
Transfer-Encoding:chunked
,对应到代码中是之前HTTP解析模块中httpclient_parser:chuncked
是否被置1:是,就表明接下来的响应报文实体将以chunck形式发送;否则,就是首部报文后紧跟需要的报文实体。- 报文实体是chunck形式:
do{
do{
1. 尝试接收一次`数据量=剩余缓存空间大小`的数据,尽可能的多读出报文主体
1. 调用`HttpResponseParser::execute()`进行解析。同样需要计算一个偏移量`len = 实际接收到的报文长度 - 实际解析的报文长度`判断当前的报文是否超过最大报文首部限定值(4KB)。
}while (!parser->isFinished()); //当前报文分块是否解析完成
len -= 2;
(减2非常关键 因为报文首部和实体中间还有一个空行\r\n将它算作首部的长度 不应算在body的长度中)
if(client_parser.content_len <= len)
{
如果当前解析包中的主体长度 <= 当前已经接受到的并且解析好的报文主体长度
说明缓冲区中当前分块的数据量较少,另一个分块的的数据量较多。将client_parser.content_len
这么多的数据放入body
,然后使用memmove()
将缓冲区中的未填装好的数据挪到缓冲区前覆盖已经填装掉掉的数据区域
}else
(client_parser.content_len > len
){
如果当前解析包中的主体长度 > 当前已经接受到的并且解析好的报文主体长度
说明当前缓冲区中所有的数据都是当前分块的,并且还有一部分数据没有接受到,还需要等待接收。将len
(当前缓冲区中所有的数据)长度的数据放入body
,计算剩余待接收的数据量left
,通过read
直接读取缓冲区中,又放入body
中。
}
} while (!client_parser.chunks_done); //所有分块是否全部接受完毕
最后将完整的报文实体放入HttpRequestParser::HttpRequest对象
之中
- 报文实体非chunck形式:(做法就和
**recvRequest()**
中一样):- 存储报文实体内容。获取报文实体的长度大小,根据报文实体长度 和 2. 中得到的一个偏移量
offset
来判断一下当前的报文实体是否接收完整:
- 存储报文实体内容。获取报文实体的长度大小,根据报文实体长度 和 2. 中得到的一个偏移量
- 报文实体长度 >
offset
, 说明当前收到的报文实体不完整。设置一个循环,再一次接收客户端传输来的报文实体,直至报文实体完整。 - 报文实体长度 <=
offset
,说明当前收到的报文实体已经完整
- 将完整的报文实体放入
HttpRequestParser::HttpRequest对象
之中
/**
* @brief 接收HTTP响应报文
* @return HttpResponse::ptr
*/
HttpResponse::ptr recvResponse();
HttpResponse::ptr HttpConnection::recvResponse()
{
HttpResponseParser::ptr parser(new HttpResponseParser);
//使用某一时刻的值即可 不需要实时的值
uint64_t buff_size = HttpResponseParser::GetHttpResponseBufferSize();
//创建一个接受响应报文的缓冲区 指定析构
std::shared_ptr<char> buffer(new char[buff_size + 1], [](char *ptr){
delete[] ptr;
});
char *data = buffer.get();
int offset = 0;
//一边读一遍解析
while(1)
{
int ret = read(data + offset, buff_size - offset);
if(ret <= 0)
{
KIT_LOG_ERROR(g_logger) << "HttpConnection::recvResponse read error, errno=" << errno
<< ", is:" << strerror(errno);
close();
return nullptr;
}
ret += offset;
data[ret] = '\0';
size_t n = parser->execute(data, ret, false);
if(parser->hasError())
{
KIT_LOG_ERROR(g_logger) << "HttpConnection::recvResponse parser error";
close();
return nullptr;
}
offset = ret - n;
if(offset == (int)buff_size)
{
KIT_LOG_WARN(g_logger) << "HttpConnection::recvResponse http response buffer out of range";
close();
return nullptr;
}
//如果解析已经结束
if(parser->isFinished())
break;
}
KIT_LOG_DEBUG(g_logger) << "已经解析得到的报文:\n" << parser->getData()->toString();
auto& client_parser = parser->getParser();
//如果发送方式为chunked 分块发送 就需要分块接收
if(client_parser.chunked)
{
std::string body;
//拿到剩余报文的长度
int len = offset;
do
{
// KIT_LOG_DEBUG(g_logger) << "报文正在 chuncked!!!!!!";
do
{
//尝试读取一次 剩余的缓存空间大小的数据 尽可能的多读出主体
int ret = read(data + len, buff_size - len);
if(ret <= 0)
{
KIT_LOG_ERROR(g_logger) << "HttpConnection::recvResponse read error, errno=" << errno
<< ", is:" << strerror(errno);
close();
return nullptr;
}
len += ret;
data[len] = '\0';
//开启分块解析
size_t n = parser->execute(data, len, true);
if(parser->hasError())
{
KIT_LOG_ERROR(g_logger) << "HttpConnection::recvResponse parser error";
close();
return nullptr;
}
len -= n;
if(len == (int)buff_size) //读了数据但是没有解析 直到自己定义的缓存已经满了
{
KIT_LOG_WARN(g_logger) << "HttpConnection::recvResponse http response buffer out of range";
close();
return nullptr;
}
} while (!parser->isFinished()); //当前分块是否已经解析完毕
/*这个减2非常关键 因为报文首部和实体中间还有一个空行\r\n将它算作首部的长度 不应算在body的长度中*/
len -= 2;
//如果当前解析包中的主体长度小于 当前已经接受到的并且解析好的报文主体长度
if(client_parser.content_len <= len)
{
body.append(data, client_parser.content_len);
//将已经装入的部分的内存 移动覆盖前面的内存
memmove(data, data + client_parser.content_len, len - client_parser.content_len);
len -= client_parser.content_len;
}
else //否则当前解析包中的主体长度 大于 当前接收到的并且解析好的报文主体长度
{
body.append(data, len);
//计算还有多少报文主体未接收到
int left = client_parser.content_len - len;
while(left > 0)
{
//继续接收剩余的报文主体
int ret = read(data, left > (int)buff_size ? (int)buff_size : left);
if(ret <= 0)
{
KIT_LOG_ERROR(g_logger) << "HttpConnection::recvResponse read error, errno=" << errno
<< ", is:" << strerror(errno);
close();
return nullptr;
}
body.append(data, ret);
left -= ret;
}
len = 0;
}
} while (!client_parser.chunks_done); //所有分块是否全部接受完毕
parser->getData()->setBody(body);
}
else
{
//获取实体长度
int64_t len = parser->getContenLength();
//将报文实体读出 并且设置到HttpRequest对象中去
if(len > 0)
{
std::string body;
body.resize(len);
int real_len = 0;
//这里offset 就是报文首部解析完之后 剩余的报文实体的字节数
if(len >= offset)
{
memcpy(&body[0], data, offset);
//body.append(data, offset);
real_len = offset;
}
else
{
memcpy(&body[0], data, len);
// body.append(data, len);
real_len = len;
}
len -= offset;
//如果还有剩余的报文实体长度 说明报文实体没有在本次传输中全部被接收
//设置一个循环继续接收报文实体
if(len > 0)
{
if(readFixSize(&body[real_len], real_len) <= 0)
{
KIT_LOG_ERROR(g_logger) << "HttpConnection::recvResponse readFixSize error, errno=" << errno
<< ", is:" << strerror(errno);
close();
return nullptr;
}
}
//等到报文实体完整之后 装入对象之中
parser->getData()->setBody(body);
}
}
return parser->getData();
}
4.2.2 sendRequest()
功能:传入已经组装好的报文,给远端服务器发送HTTP请求报文
//发送HTTP请求报文
int HttpConnection::sendRequest(HttpRequest::ptr rsp)
{
std::stringstream ss;
ss << *rsp;
std::string data = ss.str();
return writeFixSize(data.c_str(), data.size());
}
4.2.3 常用接口
/**
* @brief 设置连接创建时间
* @param[in] v 具体创建时间 单位ms
*/
void setCreateTime(uint64_t v) {m_createTime = v;}
/**
* @brief 获取连接创建时间
* @return uint64_t 单位ms
*/
uint64_t getCreateTime() const {return m_createTime;}
/**
* @brief HTTP连接上请求数+1
*/
void addRequestCount() {++m_requestCount;}
/**
* @brief 获取HTTP连接上请求数
* @return uint64_t
*/
uint64_t getRequestCount() const {return m_requestCount;}
4.2.4 发起HTTP请求系列函数
4.2.4.1 DoRequest(HttpRequest::ptr req, Uri::ptr uri, uint64_t timeout_ms)
功能:负责将HTTP请求发给目标服务器。有多个以此函数为基础,扩展出来的重载函数/便捷接口
/**
* @brief 负责将HTTP请求发给目标服务器
* @param[in] req 指定的HTTP请求智能指针
* @param[in] uri 指定的URI智能指针
* @param[in] timeout_ms 接收超时时间 单位ms
* @return HttpResult::ptr
*/
static HttpResult::ptr DoRequest(HttpRequest::ptr req, Uri::ptr uri, uint64_t timeout_ms);
HttpResult::ptr HttpConnection::DoRequest(HttpRequest::ptr req, Uri::ptr uri, uint64_t timeout_ms)
{
//通过URI对象智能指针创建地址对象
Address::ptr addr = uri->createAddress();
KIT_LOG_DEBUG(g_logger) << "DoRequest addr=" << *addr;
if(!addr)
{
KIT_LOG_ERROR(g_logger) << "HttpConnection::DoRequest get addr fail";
return std::make_shared<HttpResult>((int)HttpResult::Error::INVALID_ADDR, nullptr, "invalid addr=" + uri->getHost());
}
//通过地址对象创建套接字对象
Socket::ptr sock = Socket::CreateTCP(addr);
if(!sock)
{
KIT_LOG_ERROR(g_logger) << "HttpConnection::DoRequest create socket fail";
return std::make_shared<HttpResult>((int)HttpResult::Error::INVALID_SOCK, nullptr, "invalid sock addr=" + addr->toString());
}
//通过套接字连接目标服务器
if(!sock->connect(addr))
{
KIT_LOG_ERROR(g_logger) << "HttpConnection::DoRequest socket connect fail";
return std::make_shared<HttpResult>((int)HttpResult::Error::CONNECT_FAIL, nullptr, "invalid sock addr=" + addr->toString());
}
//为套接字设置接收超时时间
sock->setRecvTimeout(timeout_ms);
//通过连接成功的套接字创建HTTP连接
HttpConnection::ptr con = std::make_shared<HttpConnection>(sock);
//通过连接发送HTTP请求报文
int ret = con->sendRequest(req);
if(ret < 0)
{
KIT_LOG_ERROR(g_logger) << "HttpConnection::DoRequest write fail, errno=" << errno
<< ", is:" << strerror(errno);
return std::make_shared<HttpResult>((int)HttpResult::Error::SEND_REQ_FAIL, nullptr, "send request fail, addr=" + addr->toString() + ", errno=" + std::to_string(errno) + ",is:" + std::string(strerror(errno)));
}
//通过连接接收HTTP请求报文
auto rsp = con->recvResponse();
if(!rsp)
{
KIT_LOG_WARN(g_logger) << "HttpConnection::DoRequest recv response maybe timeout";
return std::make_shared<HttpResult>((int)HttpResult::Error::RECV_RSP_TIMEOUT, nullptr, "recv response maybe timeout, addr=" + addr->toString() + ", timeout=" + std::to_string(timeout_ms));
}
//返回HTTP处理结果的结构体指针指针
return std::make_shared<HttpResult>((int)HttpResult::Error::OK, rsp, "OK");
}
4.2.4.2 重载/便捷接口
/**
* @brief 用GET方法将HTTP请求发给目标服务器
* @param[in] url 指定访问的网址
* @param[in] timeout_ms 接收超时时间 单位ms
* @param[in] headers 指定的首部字段组
* @param[in] body 指定的报文主体内容
* @return HttpResult::ptr
*/
static HttpResult::ptr DoGet(const std::string& url, uint64_t timeout_ms,
const std::map<std::string, std::string>& headers = {}, const std::string& body = "");
/**
* @brief 用POST方法将HTTP请求发给目标服务器
* @param[in] url 指定访问的网址
* @param[in] timeout_ms 接收超时时间 单位ms
* @param[in] headers 指定的首部字段组
* @param[in] body 指定的报文主体内容
* @return HttpResult::ptr
*/
static HttpResult::ptr DoPost(const std::string& url, uint64_t timeout_ms,
const std::map<std::string, std::string>& headers = {}, const std::string& body = "");
/**
* @brief 用GET方法将HTTP请求发给目标服务器
* @param[in] uri 指定URI对象智能指针
* @param[in] timeout_ms 接收超时时间 单位ms
* @param[in] headers 指定的首部字段组
* @param[in] body 指定的报文主体内容
* @return HttpResult::ptr
*/
static HttpResult::ptr DoGet(Uri::ptr uri, uint64_t timeout_ms,
const std::map<std::string, std::string>& headers = {}, const std::string& body = "");
/**
* @brief 用POST方法将HTTP请求发给目标服务器
* @param[in] uri 指定URI对象智能指针
* @param[in] timeout_ms 接收超时时间 单位ms
* @param[in] headers 指定的首部字段组
* @param[in] body 指定的报文主体内容
* @return HttpResult::ptr
*/
static HttpResult::ptr DoPost(Uri::ptr uri, uint64_t timeout_ms,
const std::map<std::string, std::string>& headers = {}, const std::string& body = "");
/**
* @brief 负责将HTTP请求发给目标服务器
* @param[in] method HTTP请求方法
* @param[in] url 指定访问的网址
* @param[in] timeout_ms 接收超时时间 单位ms
* @param[in] headers 指定的首部字段组
* @param[in] body 指定的报文主体内容
* @return HttpResult::ptr
*/
static HttpResult::ptr DoRequest(HttpMethod method, const std::string& url, uint64_t timeout_ms,
const std::map<std::string, std::string>& headers = {}, const std::string& body = "");
/**
* @brief 负责将HTTP请求发给目标服务器
* @param[in] method HTTP请求方法
* @param[in] uri 指定URI对象智能指针
* @param[in] timeout_ms 接收超时时间 单位ms
* @param[in] headers 指定的首部字段组
* @param[in] body 指定的报文主体内容
* @return HttpResult::ptr
*/
static HttpResult::ptr DoRequest(HttpMethod method, Uri::ptr uri, uint64_t timeout_ms,
const std::map<std::string, std::string>& headers = {}, const std::string& body = "");
HttpResult::ptr HttpConnection::DoGet(const std::string& url,
uint64_t timeout_ms, const std::map<std::string, std::string>& headers, const std::string& body)
{
//通过字符串解析并创建URI对象
Uri::ptr uri = Uri::Create(url);
if(!uri)
{
KIT_LOG_ERROR(g_logger) << "HttpConnection::DoGet Uri parser error";
return std::make_shared<HttpResult>((int)HttpResult::Error::INVALID_URL, nullptr, "invalid url: " + url);
}
return DoGet(uri, timeout_ms, headers, body);
}
HttpResult::ptr HttpConnection::DoPost(const std::string& url,
uint64_t timeout_ms, const std::map<std::string, std::string>& headers, const std::string& body)
{
//通过字符串解析并创建URI对象
Uri::ptr uri = Uri::Create(url);
if(!uri)
{
KIT_LOG_ERROR(g_logger) << "HttpConnection::DoPost Uri parser error";
return std::make_shared<HttpResult>((int)HttpResult::Error::INVALID_URL, nullptr, "invalid url: " + url);
}
return DoPost(uri, timeout_ms, headers, body);
}
HttpResult::ptr HttpConnection::DoGet(Uri::ptr uri,
uint64_t timeout_ms, const std::map<std::string, std::string>& headers, const std::string& body)
{
return DoRequest(HttpMethod::GET, uri, timeout_ms, headers, body);
}
HttpResult::ptr HttpConnection::DoPost(Uri::ptr uri,
uint64_t timeout_ms, const std::map<std::string, std::string>& headers, const std::string& body)
{
return DoRequest(HttpMethod::POST, uri, timeout_ms, headers, body);
}
HttpResult::ptr HttpConnection::DoRequest(HttpMethod method, const std::string& url,
uint64_t timeout_ms, const std::map<std::string, std::string>& headers, const std::string& body)
{
//通过字符串解析并创建URI对象
Uri::ptr uri = Uri::Create(url);
if(!uri)
{
KIT_LOG_ERROR(g_logger) << "HttpConnection::DoRequest Uri parser error";
return std::make_shared<HttpResult>((int)HttpResult::Error::INVALID_URL, nullptr, "invalid url: " + url);
}
return DoRequest(method, uri, timeout_ms, headers, body);
}
HttpResult::ptr HttpConnection::DoRequest(HttpMethod method, Uri::ptr uri,
uint64_t timeout_ms, const std::map<std::string, std::string>& headers, const std::string& body)
{
/*组装HTTP请求报文*/
HttpRequest::ptr req = std::make_shared<HttpRequest>();
req->setMethod(method); //设置请求方法
req->setPath(uri->getPath()); //设置请求资源路径
req->setQuery(uri->getQuery()); //设置查询参数
req->setFragment(uri->getFragment()); //设置分段标识符
bool has_host = false;
for(auto &x : headers)
{
if(strcasecmp(x.first.c_str(), "connection") == 0 &&
strcasecmp(x.second.c_str(), "keep-alive") == 0)
{
req->setClose(false);
}
if(has_host && strcasecmp(x.first.c_str(), "host") == 0)
has_host = !x.second.empty();
req->setHeader(x.first, x.second);
}
if(!has_host)
req->setHeader("host", uri->getHost());
req->setBody(body);
return DoRequest(req, uri, timeout_ms);
}
紧急:需要理解ragel的语法规则、正则表达式规则
5. URI 封装
参考出处:RFC 3986 文档
https://www.ietf.org/rfc/rfc3986
ragel官方文档:
http://www.colm.net/files/ragel/ragel-guide-6.10.pdf
- uri官方标准格式规范:
5.1 成员变量
class Uri
{
...
...
private:
//scheme字段
std::string m_scheme;
//用户认证信息字段
std::string m_userinfo;
//域名字段
std::string m_host;
//端口号字段
int16_t m_port;
//资源路径字段
std::string m_path;
//查询参数字段
std::string m_query;
//片段标识符字段
std::string m_fragment;
};
5.2 ragel文件编写uri.rl
功能:ragel状态机以及URI类的实现都放在这个文件中
通过ragel -C -G2 uri.rl -o uri.rl.cpp
最终生成我们所需的.cpp文件,会在调用状态机的地方自动生成C/C++的代码嵌入到源代码中。
/*
see RFC 3986:
Appendix A. Collected ABNF for URI
URI = scheme ":" hier-part [ "?" query ] [ "" fragment ]
hier-part = "//" authority path-abempty
/ path-absolute
/ path-rootless
/ path-empty
URI-reference = URI / relative-ref
absolute-URI = scheme ":" hier-part [ "?" query ]
relative-ref = relative-part [ "?" query ] [ "" fragment ]
relative-part = "//" authority path-abempty
/ path-absolute
/ path-noscheme
/ path-empty
scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
authority = [ userinfo "@" ] host [ ":" port ]
userinfo = *( unreserved / pct-encoded / sub-delims / ":" )
host = IP-literal / IPv4address / reg-name
port = *DIGIT
IP-literal = "[" ( IPv6address / IPvFuture ) "]"
IPvFuture = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" )
IPv6address = 6( h16 ":" ) ls32
/ "::" 5( h16 ":" ) ls32
/ [ h16 ] "::" 4( h16 ":" ) ls32
/ [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32
/ [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32
/ [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32
/ [ *4( h16 ":" ) h16 ] "::" ls32
/ [ *5( h16 ":" ) h16 ] "::" h16
/ [ *6( h16 ":" ) h16 ] "::"
h16 = 1*4HEXDIG
ls32 = ( h16 ":" h16 ) / IPv4address
IPv4address = dec-octet "." dec-octet "." dec-octet "." dec-octet */
#include <string>
#include <sstream>
#include <iostream>
#include "uri.h"
namespace kit_server
{
/* machine */
%%{
machine uri_parser;
sub_delims = ( "!" | "$" | "$" | "&" | "'" | "(" | ")" | "*"
| "+" | "," | ";" | "=") ;
gen_delims = ( ":" | "/" | "?" | "#" | "[" | "]" | "@" ) ;
reserved = ( gen_delims | sub_delims ) ;
unreserved = ( alpha | digit | "-" | "." | "_" | "~" ) ;
pct_encoded = ( "%" xdigit xdigit ) ;
action marku { mark = fpc; }
action markh { mark = fpc; }
action save_scheme
{
uri->setScheme(std::string(mark, fpc - mark));
mark = NULL;
}
scheme = (alpha (alpha | digit | "+" | "-" | ".")*) >marku %save_scheme;
action save_port
{
if(fpc != mark){
uri->setPort(atoi(mark));
}
mark = NULL;
}
action save_userinfo
{
if(mark) {
uri->setUserinfo(std::string(mark, fpc - mark));
}
mark = NULL;
}
action save_host
{
if(mark != NULL) {
uri->setHost(std::string(mark, fpc - mark));
}
}
userinfo = (unreserved | pct_encoded | sub_delims | ":")*;
dec_octet = digit | [1-9] digit | "l" digit{2} | 2 [0-4] digit | "25" [0-5];
IPv4address = dec_octet "." dec_octet "." dec_octet "." dec_octet;
h16 = xdigit{1,4};
ls32 = (h16 : h16) | IPv4address;
IPv6address = ( ( h16 ":" ) {6} ls32) |
( "::" ( h16 ":" ) {5} ls32) |
(( h16 )? "::" ( h16 ":" ) {4} ls32) |
((( h16 ":" ){1} h16 )? "::" ( h16 ":" ) {3} ls32) |
((( h16 ":" ){2} h16 )? "::" ( h16 ":" ) {2} ls32) |
((( h16 ":" ){3} h16 )? "::" ( h16 ":" ) {1} ls32) |
((( h16 ":" ){4} h16 )? "::" ls32) |
((( h16 ":" ){5} h16 )? "::" h16 ) |
((( h16 ":" ){6} h16 )? "::" );
IPvFuture = "v" xdigit+ "." (unreserved | sub_delims | ":")+;
IP_literal = "[" (IPv6address | IPvFuture) "]";
reg_name = (unreserved | pct_encoded | sub_delims)*;
host = IP_literal | IPv4address | reg_name;
port = digit*;
authority = ( (userinfo %save_userinfo "@")? host >markh %save_host (":" port >markh %save_port)? ) >markh;
action save_segment
{
uri->setFragment(std;:String(mark, fpc - mark));
mark = NULL;
}
action save_path
{
uri->setPath(std::string(mark, fpc - mark));
mark = NULL;
}
# pchar = unreserved | pct_encoded | sub_delims | ":" | "@";
# add (any -- ascii) support chinese
pchar = ( (any -- ascii) ) | unreserved | pct_encoded | sub_delims | ":" | "@";
segment = pchar*;
segment_nz = pchar+;
segment_nz_nc = (pchar - ":")+;
action clear_segments
{
}
path_abempty = (("/" segment))? ("/" segment)*;
path_absolute = ("/" (segment_nz ("/" segment)*)?);
path_noscheme = segment_nz_nc ("/" segment)*;
path_rootless = segment_nz ("/" segment)*;
path_empty = "";
path = (path_abempty | path_absolute | path_noscheme | path_rootless | path_empty);
action save_query
{
uri->setQuery(std::string(mark, fpc - mark));
mark = NULL;
}
action save_fragment
{
uri->setFragment(std::string(mark, fpc - mark));
mark = NULL;
}
query = (pchar | "/" | "?")* >marku %save_query;
fragment = (pchar | "/" | "?")* >marku %save_fragment;
hier_part = ("//" authority path_abempty> markh %save_path) | path_absolute | path_rootless | path_empty;
relative_part = ("//" authority path_abempty) | path_absolute | path_noscheme | path_empty;
relative_ref = relative_part ("?" query)? ("#" fragment)?;
absolute_URI = scheme "?" hier_part ("?" query)?;
# Obsolete, but referenced from HTTP, so we translate
relative_URI = relative_part ("?" query)?;
URI = scheme "?" hier_part ("?" query)? ("#" fragment)?;
URI_reference = URI | relative_ref;
# 状态机入口
main := URI_reference;
write data;
}%%
/* exec */
Uri::Uri()
:m_port(0)
{
}
std::ostream& Uri::dump(std::ostream& os)
{
os << m_scheme << "://"
<< m_userinfo
<< (m_userinfo.size() ? "@" : "")
<< m_host
<< (isDefaultPort() ? "" : ":" + std::string("" + m_port))
<< "/"
<< m_path
<< (m_query.size() ? "?" : "")
<< m_query
<< (m_fragment.size() ? "#" : "")
<< m_fragment;
return os;
}
std::string Uri::toString()
{
std::stringstream ss;
dump(ss);
return ss.str();
}
//通过URI创建一个地址
Address::ptr Uri::createAddress() const
{
auto addr = Address::LookUpAnyIPAddress(m_host);
if(addr)
addr->setPort(m_port);
return addr;
}
//传入一个uri字符串解析出对应URI对象
Uri::ptr Uri::Create(const std::string& u)
{
Uri::ptr uri(new Uri);
int cs = 0;
const char* mark = nullptr;
//初始化状态机所需的变量
%% write init;
//头指针
const char *p = u.c_str();
//尾指针
const char *pe = p + u.size();
//ragel指定尾指针的名字
const char *eof = pe;
//开始调用 自动状态机
%% write exec;
if(cs == uri_parser_error)
return nullptr;
else if(cs >= uri_parser_first_final)
return uri;
return nullptr;
}
bool Uri::isDefaultPort()
{
if(m_port == 0)
return true;
if(m_scheme == "http")
m_port = 80;
else if(m_scheme == "https")
m_port = 443;
return false;
}
}
5.3 接口
5.3.1 Create()
(核心)
功能:通过字符串URI解析并创建出URI对象
/**
* @brief 通过字符串URI创建URI对象
* @param[in] uri URI字符串
* @return Uri::ptr
*/
static Uri::ptr Create(const std::string& uri);
Uri::ptr Uri::Create(const std::string& u)
{
Uri::ptr uri(new Uri);
/*cs mark状态机中用到的变量 提前定义*/
int cs = 0;
const char* mark = NULL;
//初始化自动状态机
%% write init;
//指向u头指针
const char *p = u.c_str();
//指向u尾指针
const char *pe = p + u.size();
//ragel指定的一个尾指针的名字
const char *eof = pe;
//调用自动状态机
%% write exec;
//uri_parser_error 检查解析是否错误
if(cs == uri_parser_error)
return nullptr;
else if(cs >= uri_parser_first_final) //解析完成后的状态码是否合法
return uri;
return nullptr;
}
5.3.2 CreateAddress()
功能:通过URI中的域名创建通信地址对象
/**
* @brief 通过URI中的域名创建通信地址对象
* @return Address::ptr
*/
Address::ptr createAddress() const;
Address::ptr Uri::createAddress() const
{
auto addr = Address::LookUpAnyIPAddress(m_host);
if(addr)
addr->setPort(getPort());
return addr;
}
5.3.3 dump()
功能:将URI信息以流的形式组装
/**
* @brief 将URI信息以流的形式组装
* @param[in] os 标准输出流
* @return std::ostream&
*/
std::ostream& dump(std::ostream& os) const;
std::ostream& Uri::dump(std::ostream& os) const
{
os << m_scheme << "://"
<< m_userinfo
<< (m_userinfo.size() ? "@" : "")
<< m_host
<< (isDefaultPort() ? "" : ":" + std::string("" + m_port))
<< getPath()
<< (m_query.size() ? "?" : "")
<< m_query
<< (m_fragment.size() ? "#" : "")
<< m_fragment;
return os;
}
5.3.4 toString()
功能:将URI信息以字符串形式输出
/**
* @brief 将URI信息以字符串形式输出
* @return std::string
*/
std::string toString() const;
std::string Uri::toString() const
{
std::stringstream ss;
dump(ss);
return ss.str();
}
5.3.5 常用接口
/**
* @brief 设置scheme字段
* @param[in] v 具体scheme值 http https ftp等
*/
void setScheme(const std::string& v) {m_scheme = v;}
/**
* @brief 获取scheme字段
* @return const std::string&
*/
const std::string& getScheme() const {return m_scheme;}
/**
* @brief 设置用户认证信息
* @param[in] v 具体的用户认证信息值
*/
void setUserinfo(const std::string& v) {m_userinfo = v;}
/**
* @brief 获取用户认证信息
* @return const std::string&
*/
const std::string& getUserinfo() const {return m_userinfo;}
/**
* @brief 设置域名
* @param[in] v 具体域名值 www.xxxx.com
*/
void setHost(const std::string& v) {m_host = v;}
/**
* @brief 获取域名
* @return const std::string&
*/
const std::string& getHost() const {return m_host;}
/**
* @brief 设置端口号
* @param[in] v 具体端口号
*/
void setPort(int16_t v) {m_port = v;}
/**
* @brief 获取端口号 没有设置则根据scheme来设置http = 80 https = 443
* @return int16_t
*/
int16_t getPort() const;
int16_t Uri::getPort() const
{
if(m_port)
return m_port;
if(m_scheme == "http")
return 80;
else if(m_scheme == "https")
return 443;
return 0;
}
/**
* @brief 设置资源路径
* @param[in] v 具体资源路径
*/
void setPath(const std::string& v) {m_path = v;}
/**
* @brief 获取资源路径
* @return const std::string&
*/
const std::string& getPath() const;
const std::string& Uri::getPath() const
{
static std::string temp = "/";
return m_path.size() ? m_path : temp;
}
/**
* @brief 设置查询参数
* @param[in] v 具体查询参数 "?query"
*/
void setQuery(const std::string& v) {m_query = v;}
/**
* @brief 获取查询参数
* @return const std::string&
*/
const std::string& getQuery() const {return m_query;}
/**
* @brief 设置片段标识符
* @param[in] v 具体片段标识符的值
*/
void setFragment(const std::string& v) {m_fragment = v;}
/**
* @brief 获取片段标识符
* @return const std::string&
*/
const std::string& getFragment() const {return m_fragment;}
6. HttpConnectionPool 连接池封装
目的:利用”池化”技术将连接资源做一个统一管理,尤其对于”长连接”来讲,关注一个连接上的最大存活时间、最多能够处理的请求数量等,还要兼顾连接上的安全问题,规避一部分的攻击。并且,一个”池子”服务一个网址+一个端口,对于一个网址资源的请求连接数会很多。
· 类关系设计
6.1 成员变量
class HttpconnectionPool
{
....
....
private:
//要连接的主机域名
std::string m_host;
std::string m_vhost;
//端口号
uint16_t m_port;
//最大连接数
uint32_t m_maxSize;
//每条连接最大存活时间
uint32_t m_maxAliveTime;
//每条连接上最大请求数量
uint32_t m_maxRequest;
//互斥锁 读写频次相当 不用读写锁
MutexType m_mutex;
//连接存储容器 list增删方便
std::list<HttpConnection* > m_connections;
//当前连接数量 可以突破最大连接数 但是使用完毕放回后若超限就要马上释放
std::atomic<int32_t> m_tatol = {0};
};
6.2 接口
6.2.1 构造函数
/**
* @brief HTTP连接池类构造函数
* @param[in] host 域名
* @param[in] vhost 备用域名
* @param[in] port 端口号
* @param[in] max_size 最大连接数
* @param[in] maxAliveTime 每条连接最大存活时间
* @param[in] maxRequest 每条连接最大请求数
*/
HttpConnectionPool(const std::string& host, const std::string& vhost,
uint16_t port, uint32_t max_size, uint32_t maxAliveTime, uint32_t maxRequest);
HttpConnectionPool::HttpConnectionPool(const std::string& host, const std::string& vhost,
uint16_t port, uint32_t max_size, uint32_t maxAliveTime, uint32_t maxRequest)
:m_host(host)
,m_vhost(vhost)
,m_port(port)
,m_maxSize(max_size)
,m_maxAliveTime(maxAliveTime)
,m_maxRequest(maxRequest)
{
}
** 私有接口
1). ReleasePtr()
功能:释放连接资源。第二个参数HttpConnectionPool*
非常关键,因为该函数静态函数static
,被指定为HttpConnection对象
的析构函数,只能通过传参第二个参数去判断HTTP连接池中的一些状态去决定是否释放连接资源。
/**
* @brief 释放连接资源
* @param[in] ptr http连接指针
* @param[in] pool http连接池指针
*/
static void ReleasePtr(HttpConnection* ptr, HttpConnectionPool* pool);
void HttpConnectionPool::ReleasePtr(HttpConnection* ptr, HttpConnectionPool* pool)
{
//连接上的请求数+1
ptr->addRequest();
//判断当前连接是否过期:是否处于连接 || 是否超过存活时间 || 是否超出最大请求数 || 是否超出最大连接数
if(!ptr->isConnected() ||
(ptr->getCreateTime() + pool->m_maxAliveTime >= GetCurrentMs()) ||
(ptr->getRequest() > pool->m_maxRequest) ||
(pool->m_total > pool->m_maxSize))
{
delete ptr;
--pool->m_total;
return;
}
//连接未过期 把用完的连接放回连接池
MutexType::Lock lock(pool->m_mutex);
pool->m_connections.push_back(ptr);
}
6.2.2 getConnection()
(核心)
功能:从连接池中获取连接,同时检查连接池中的连接是否已经过期,将过期连接收集并释放资源;如果没有可用连接,新创建一条连接提供使用。
/**
* @brief 从连接池中获取连接,没有可用资源要创建后返回
* @return HttpConnection::ptr
*/
HttpConnection::ptr getConnection();
HttpConnection::ptr HttpConnectionPool::getConnection()
{
//收集过期连接vector容器
std::vector<HttpConnection*> invalid_connections;
//返回出去的可用连接
HttpConnection* con_ptr = nullptr;
//加锁
MutexType::Lock lock(m_mutex);
//从连接池里找出一个可用的资源
while(m_connections.size())
{
auto con = m_connections.front();
m_connections.pop_front();
//当前连接已经断开 视为过期
if(!con->isConnected())
{
invalid_connections.push_back(con);
continue;
}
//当前连接超过了最大存活时间 视为过期
if(con->getCreateTime() + m_maxAliveTime >= GetCurrentMs())
{
invalid_connections.push_back(con);
continue;
}
con_ptr = con;
break;
}
lock.unlock(); //解锁
//将过期连接资源都要释放
for(auto &x : invalid_connections)
delete x;
m_tatol -= invalid_connections.size();
//如果在连接池里没有拿到对应的资源 线程创建一个
if(!con_ptr)
{
IPAddress::ptr addr = Address::LookUpAnyIPAddress(m_host);
if(!addr)
{
KIT_LOG_ERROR(g_logger) << "HttpConnectionPool::getConnection get addr fail, host=" << m_host << ", port=" << m_port;
return nullptr;
}
addr->setPort(m_port);
Socket::ptr sock = Socket::CreateTCP(addr);
if(!sock)
{
KIT_LOG_ERROR(g_logger) << "HttpConnectionPool::getConnection create socket fail, addr=" << *addr;
return nullptr;
}
if(!sock->connect(addr))
{
KIT_LOG_ERROR(g_logger) << "HttpConnectionPool::getConnection connect fail, addr=" << *addr;
return nullptr;
}
con_ptr = new HttpConnection(sock);
++m_tatol;
}
//指定一个用于释放的函数HttpConnectionPool::ReleasePtr
return HttpConnection::ptr(con_ptr, std::bind(&HttpConnectionPool::ReleasePtr, std::placeholders::_1, this));
}
6.2.3 发起HTTP请求系列函数
6.2.3.1 doRequest(HttpRequest::ptr req, uint64_t timeout_ms);
(核心)
功能:负责将HTTP请求发给目标服务器。有多个以此函数为基础,扩展出来的重载函数/便捷接口
/**
* @brief 负责将HTTP请求发给目标服务器
* @param[in] req HTTP请求报文智能指针
* @param[in] timeout_ms 接收响应报文超时时间
* @return HttpResult::ptr 返回HTTP连接上操作结果
*/
HttpResult::ptr doRequest(HttpRequest::ptr req, uint64_t timeout_ms);
HttpResult::ptr HttpConnectionPool::doRequest(HttpRequest::ptr req, uint64_t timeout_ms)
{
//获取HTTP连接资源
auto con = getConnection();
if(!con)
{
KIT_LOG_ERROR(g_logger) << "HttpConnectionPool::doRequest get connection from pool fail";
return std::make_shared<HttpResult>((int)HttpResult::Error::POOL_GET_CONNECT_FAIL, nullptr, "get connection from pool fail, host=" + m_host + ", port=" + std::to_string(m_port));
}
//通过连接获取到套接字
auto sock = con->getSocket();
if(!sock)
{
KIT_LOG_ERROR(g_logger) << "HttpConnectionPool::doRequest get sock fail";
return std::make_shared<HttpResult>((int)HttpResult::Error::POOL_INVALID_SOCK, nullptr, "send request fail, host=" + m_host + ", port=" + std::to_string(m_port));
}
//设置接收超时时间
sock->setRecvTimeout(timeout_ms);
//通过连接发送HTTP请求报文
int ret = con->sendRequest(req);
if(ret < 0)
{
KIT_LOG_ERROR(g_logger) << "HttpConnectionPool::DoRequest write fail, errno=" << errno
<< ", is:" << strerror(errno);
return std::make_shared<HttpResult>((int)HttpResult::Error::SEND_REQ_FAIL, nullptr, "send request fail, addr=" + con->getSocket()->getRemoteAddress()->toString() + ", port=" + std::to_string(m_port) + ", errno=" + std::to_string(errno) + ",is:" + std::string(strerror(errno)));
}
//通过连接接收HTTP响应报文
auto rsp = con->recvResponse();
if(!rsp)
{
KIT_LOG_WARN(g_logger) << "HttpConnectionPool::doRequest recv response maybe timeout, time=" << timeout_ms;
return std::make_shared<HttpResult>((int)HttpResult::Error::RECV_RSP_TIMEOUT, nullptr, "recv response maybe timeout, ,time=" + std::to_string(timeout_ms) + ",addr=" + con->getSocket()->getRemoteAddress()->toString() + ", errno=" + std::to_string(errno) + ",is:" + std::string(strerror(errno)));
}
//返回连接上的收发结果
return std::make_shared<HttpResult>((int)HttpResult::Error::OK, rsp, "OK");
}
6.2.3.2 重载/便捷接口
/**
* @brief 使用GET方法将HTTP请求发给目标服务器
* @param[in] url 指定的目标网址
* @param[in] timeout_ms 接收超时时间
* @param[in] headers 指定的首部字段
* @param[in] body 指定的报文主体
* @return HttpResult::ptr 返回HTTP连接上操作结果
*/
HttpResult::ptr doGet(const std::string& url, uint64_t timeout_ms,
const std::map<std::string, std::string>& headers = {}, const std::string& body = "");
/**
* @brief 使用POST方法将HTTP请求发给目标服务器
* @param[in] url 指定的目标网址
* @param[in] timeout_ms 接收超时时间
* @param[in] headers 指定的首部字段
* @param[in] body 指定的报文主体
* @return HttpResult::ptr 返回HTTP连接上操作结果
*/
HttpResult::ptr doPost(const std::string& url, uint64_t timeout_ms,
const std::map<std::string, std::string>& headers = {}, const std::string& body = "");
/**
* @brief 使用GET方法将HTTP请求发给目标服务器
* @param[in] uri 指定的URI对象智能指针
* @param[in] timeout_ms 接收超时时间
* @param[in] headers 指定的首部字段
* @param[in] body 指定的报文主体
* @return HttpResult::ptr 返回HTTP连接上操作结果
*/
HttpResult::ptr doGet(Uri::ptr uri, uint64_t timeout_ms,
const std::map<std::string, std::string>& headers = {}, const std::string& body = "");
/**
* @brief 使用POST方法将HTTP请求发给目标服务器
* @param[in] uri 指定的URI对象智能指针
* @param[in] timeout_ms 接收超时时间
* @param[in] headers 指定的首部字段
* @param[in] body 指定的报文主体
* @return HttpResult::ptr 返回HTTP连接上操作结果
*/
HttpResult::ptr doPost(Uri::ptr uri, uint64_t timeout_ms,
const std::map<std::string, std::string>& headers = {}, const std::string& body = "");
/**
* @brief 负责将HTTP请求发给目标服务器
* @param[in] method 指定HTTP请求方法
* @param[in] url 指定的目标网址
* @param[in] timeout_ms 接收超时时间
* @param[in] headers 指定的首部字段
* @param[in] body 指定的报文主体
* @return HttpResult::ptr 返回HTTP连接上操作结果
*/
HttpResult::ptr doRequest(HttpMethod method, const std::string& url, uint64_t timeout_ms,
const std::map<std::string, std::string>& headers = {}, const std::string& body = "");
/**
* @brief 负责将HTTP请求发给目标服务器
* @param[in] method 指定HTTP请求方法
* @param[in] uri 指定的URI对象智能指针
* @param[in] timeout_ms 接收超时时间
* @param[in] headers 指定的首部字段
* @param[in] body 指定的报文主体
* @return HttpResult::ptr 返回HTTP连接上操作结果
*/
HttpResult::ptr doRequest(HttpMethod method, Uri::ptr uri, uint64_t timeout_ms,
const std::map<std::string, std::string>& headers = {}, const std::string& body = "");
HttpResult::ptr HttpConnectionPool::doGet(const std::string& url, uint64_t timeout_ms,
const std::map<std::string, std::string>& headers, const std::string& body)
{
return doRequest(HttpMethod::GET, url, timeout_ms, headers, body);
}
HttpResult::ptr HttpConnectionPool::doPost(const std::string& url,
uint64_t timeout_ms, const std::map<std::string, std::string>& headers, const std::string& body)
{
return doRequest(HttpMethod::POST, url, timeout_ms, headers, body);
}
HttpResult::ptr HttpConnectionPool::doGet(Uri::ptr uri,
uint64_t timeout_ms, const std::map<std::string, std::string>& headers, const std::string& body)
{
std::stringstream ss;
ss << uri->getPath()
<< (uri->getQuery().size() ? "?" : "")
<< uri->getQuery()
<< (uri->getFragment().size() ? "#" : "")
<< uri->getFragment();
return doGet(ss.str(), timeout_ms, headers, body);
}
HttpResult::ptr HttpConnectionPool::doPost(Uri::ptr uri,
uint64_t timeout_ms, const std::map<std::string, std::string>& headers, const std::string& body)
{
std::stringstream ss;
ss << uri->getPath()
<< (uri->getQuery().size() ? "?" : "")
<< uri->getQuery()
<< (uri->getFragment().size() ? "#" : "")
<< uri->getFragment();
return doPost(ss.str(), timeout_ms, headers, body);
}
HttpResult::ptr HttpConnectionPool::doRequest(HttpMethod method, const std::string& url,
uint64_t timeout_ms, const std::map<std::string, std::string>& headers, const std::string& body)
{
/*组装HTTP请求报文*/
HttpRequest::ptr req = std::make_shared<HttpRequest>();
req->setMethod(method);
req->setPath(url);
req->setClose(false);
bool has_host = false; //域名标志位
for(auto &x : headers)
{
if(strcasecmp(x.first.c_str(), "connection") == 0 &&
strcasecmp(x.second.c_str(), "keep-alive") == 0)
{
req->setClose(false);
}
//是否存在host域名字段
if(has_host && strcasecmp(x.first.c_str(), "host") == 0)
has_host = !x.second.empty();
req->setHeader(x.first, x.second);
}
//没有带域名字段 要使用备用域名m_vhost
if(!has_host)
{
if(!m_vhost.size())
req->setHeader("host", m_host);
else
req->setHeader("host", m_vhost);
}
req->setBody(body);
return doRequest(req, timeout_ms);
}
HttpResult::ptr HttpConnectionPool::doRequest(HttpMethod method, Uri::ptr uri,
uint64_t timeout_ms, const std::map<std::string, std::string>& headers, const std::string& body)
{
std::stringstream ss;
//组装URI字符串
ss << uri->getPath()
<< (uri->getQuery().size() ? "?" : "")
<< uri->getQuery()
<< (uri->getFragment().size() ? "#" : "")
<< uri->getFragment();
return doRequest(method, ss.str(), timeout_ms, headers, body);
}
C\C++知识点补充复习:fnmatch()函数
功能:检查字符串pattern
是否和shell通配符模式字符串string
相匹配。flags
可以对string
中要匹配的模式进行一些设置,具体可以查看一下手册。
返回值:成功匹配返回0, 如果有错返回一个非0值,并且设置errno
#include <fnmatch.h>
int fnmatch(const char *pattern, const char *string, int flags);