http模块
- http {.h & .cpp}: 将HTTP请求报文和响应报文的对应内容提取为两个类:HttpRequest 和 HttpResponse。
- parse {.h & .cpp}: 将HTTP请求报文和响应报文的解析封装为两个类:HttpRequestParser 和 HttpResponseParser。
- http_session {.h & .cpp}: 从HTTP Server的角度来封装HTTP连接(因为 HttpSession 有 recvRequest 和 sendResponse 这两个函数)。
- http_connection {.h & .cpp}:
- HttpConnection:从HTTP Client的角度来封装HTTP连接(因为 HttpConnection有 sendRequest 和 recvResponse 这两个函数)。
- HttpConnectionPool:对于RPC来讲,client发起的请求都是对同一个server发送的,所以可以复用HttpConnection,避免重复建立连接的开销。
http {.h & .cpp}
- 解析http请求报文后,填充HttpRequest对应的字段。
- (根据HttpRequest)去进行业务逻辑处理,依据处理填充HttpResponse。
- 然后根据HttpResponse返回http响应报文。
- HttpRequest
/// 请求方法
HttpMethod m_method;
/// HTTP版本
uint8_t m_version;
/// 是否主动关闭
bool m_close;
/// 是否为websocket
bool m_websocket;
/// 请求路径
std::string m_path;
/// 请求参数
std::string m_query;
/// 请求fragment
std::string m_fragment;
/// 请求体
std::string m_body;
/// 请求头map
MapType m_headers;
/// 请求参数map
MapType m_params;
/// 请求cookies map
MapType m_cookies;
- HttpResponse
/// 响应状态
HttpStatus m_status;
/// 版本
uint8_t m_version;
/// 是否自动关闭
bool m_close;
/// 是否为websocket
bool m_websocket;
/// 响应消息体
std::string m_body;
/// 响应原因
std::string m_reason;
/// 响应头部MAP
MapType m_headers;
std::vector<std::string> m_cookies;
parse {.h & .cpp}
由于HTTP请求报文和响应报文的结构相同,这里对两种报文的解析做一个抽象:HttpParser。然后HttpRequestParser和HttpResponseParser继承HttpParser。
将解析任务分为三个:解析(请求/响应)行、解析头部、解析报文主体。
具体流程就是调用execute,然后将m_parser依次赋值为parse_line、parse_header、parse_chunk(每个都是一个协程,返回类型的是Error,标志解析是否成功)。
这三个协程需要在HttpRequestParser和HttpResponseParser实现。
而execute在基类HttpParser实现,因为解析请求报文和响应报文的流程是一样的。
class HttpParser{
public:
/**
* @brief 解析协议
* @param data 协议文本内存
* @param len 协议文本内存长度
* @param chunk 标识是否解析 http chunk
* @return 返回实际解析的长度,并且将已解析的数据移除
*/
size_t execute(char* data, size_t len, bool chunk = false);
protected:
virtual Task<Error> parse_line() = 0;
virtual Task<Error> parse_header() = 0;
virtual Task<Error> parse_chunk() = 0;
protected:
int m_error = 0;
bool m_finish = false;
char* m_cur = nullptr;
Task<Error> m_parser{};
CheckState m_checkState = NO_CHECK; // 初始状态
};
/**
* @brief 解析协议
* @param data 协议文本内存
* @param len 协议文本内存长度
* @param chunk 标识是否解析 http chunk
* @return 返回实际解析的长度,并且将已解析的数据移除
*/
size_t HttpParser::execute(char *data, size_t len, bool chunk) {
if (chunk) {
if (m_checkState != CHECK_CHUNK) {
m_checkState = CHECK_CHUNK;
m_finish = false;
m_parser = parse_chunk();
}
}
size_t offset = 0;
size_t i = 0; // 标示解释到哪一个字符
switch (m_checkState) {
case NO_CHECK: {
m_checkState = CHECK_LINE;
m_parser = parse_line();
}
// 解析请求行
case CHECK_LINE: {
for(; i < len; ++i){ // 逐个字符解析
m_cur = data + i; // m_cur记录当前解析到哪个字符
m_parser.resume();
m_error = m_parser.get();
++offset;
if(isFinished()){
memmove(data, data + offset, (len - offset)); // 移除已经解析的部分
return offset;
}
if(m_checkState == CHECK_HEADER){ // 转到解析报文头部
++i;
m_parser = parse_header();
break;
}
}
if(m_checkState != CHECK_HEADER) {
memmove(data, data + offset, (len - offset));
return offset;
}
}
// 解析请求头
case CHECK_HEADER: {
for(; i < len; ++i){ // 逐个字符解析(这个流程和解析请求行一模一样)
m_cur = data + i;
m_parser.resume();
m_error = m_parser.get();
++offset;
if(isFinished()){
memmove(data, data + offset, (len - offset));
return offset;
}
}
break;
}
case CHECK_CHUNK: {
for(; i < len; ++i){ // 逐个字符解析(这个流程和解析请求行一模一样)
m_cur = data + i;
m_parser.resume();
m_error = m_parser.get();
++offset;
if(isFinished()){
memmove(data, data + offset, (len - offset));
return offset;
}
}
break;
}
}
memmove(data, data + offset, (len - offset));
return offset;
}
需包含头文件:cstring
函数原型:void *memmove(void *dest, const void *source, std::size_t size);
函数功能:将指针source所指向内存区域的前size个字节复制到指针dest所指向的内存区域。如果出现内存重叠,函数能够保证源区域被覆盖前将重叠的区域复制到目标区域,但源区域内容会被更改。
http_session {.h & .cpp}
class HttpSession : public SocketStream {
public:
using ptr = std::shared_ptr<HttpSession>;
HttpSession(Socket::ptr socket, bool owner = true);
HttpRequest::ptr recvRequest();
ssize_t sendResponse(HttpResponse::ptr response);
};
HttpSession只有两个功能:接收HTTP请求报文(构造HttpRequest)、(根据HttpResponse)发送HTTP响应报文
这里主要看recvRequest函数的实现:
HttpRequest::ptr HttpSession::recvRequest() {
HttpRequestParser::ptr parser(new HttpRequestParser);
uint64_t buff_size = HttpRequestParser::GetHttpRequestBufferSize();
// 1、初始化HttpRequestParser
// 2、申请接收HTTP请求报文的buffer
// 只要!parser->isFinished(),就一直循环解析
while (!parser->isFinished()) {
// 从Socket处接收数据(HTTP请求报文)
ssize_t len = read(data + offset, buff_size - offset);
...
// 解析报文
size_t nparse = parser->execute(data, len);
...
}
// 设置请求报文的body
....
return parser->getData();
}
http_connection {.h & .cpp}
http_connection:接收解析HTTP响应报文、发送HTTP请求报文。
http_session :接收解析HTTP请求报文、发送HTTP响应报文。
http_connection是从 HTTP client 的角度来看。
http_session是从 HTTP server 的角度来看。
HttpConnection
class HttpConnection : public SocketStream {
friend HttpConnectionPool;
public:
using ptr = std::shared_ptr<HttpConnection>;
~HttpConnection();
static HttpResult::ptr DoGet(...);
static HttpResult::ptr DoPost(...);
static HttpResult::ptr DoRequest(...);
HttpConnection(Socket::ptr socket, bool owner = true);
HttpResponse::ptr recvResponse();
ssize_t sendRequest(HttpRequest::ptr request);
private:
uint64_t m_createTime = 0;
uint64_t m_request = 0;
};
recvResponse:接收并解析HTTP响应报文,从而构造HttpResponse
HttpResponse::ptr HttpConnection::recvResponse() {
// 申请HttpResponseParser
// 申请buffer用来接收HTTP响应报文
while (!parser->isFinished()) {
// 接收报文
ssize_t len = read(data + offset, buff_size - offset);
....
// 解析报文
size_t nparse = parser->execute(data, len);
....
}
if (parser->isChunked()) { // 解析chunk块
do {
ssize_t len = read(data + offset, buff_size - offset);
...
size_t nparse = parser->execute(data, len, true);
...
} while (!parser->isFinished());
} else {
uint64_t length = parser->getContentLength();
...
parser->getData()->setBody(body);
}
return parser->getData();
}
sendRequest:发送HTTP请求报文
DoXXX系列函数:
这里只是实现了两个方法GET和POST(DoGet和DoPost)。但最终都是调用DoRequest来完成。
http头中的host字段详解_http host头-CSDN博客
/*
method: 请求方法
uri: 请求资源
timeout_ms:超时时间
header: 请求头
body: 请求体
*/
// 根据method、uri、header、body来构造HttpRequest对象
HttpResult::ptr HttpConnection::DoRequest(HttpMethod method,
Uri::ptr uri, uint64_t timeout_ms,
const std::map<std::string, std::string> &header,
const std::string &body) {
HttpRequest::ptr request = std::make_shared<HttpRequest>();
request->setMethod(method);
request->setPath(uri->getPath());
request->setQuery(uri->getQuery());
request->setFragment(uri->getFragment());
bool hasHost = false;
// 根据header设置HTTP请求报文的HOST字段
request->setBody(body);
return DoRequest(request,uri, timeout_ms);
}
/*
request:HttpRequest对象
uri:请求资源
timeout_ms:超时时间
*/
HttpResult::ptr HttpConnection::DoRequest(HttpRequest::ptr request, Uri::ptr uri, uint64_t timeout_ms) {
// 根据uri创建服务器Address
// 创建Socket并连到服务器Address
// 创建HttpConnection,然后sendRequest
// 接收HttpResponse,构造并返回HttpResult
}
HttpConnectionPool
成员变量
std::string m_host;
std::string m_vhost;
uint32_t m_port;
uint32_t m_maxSize;
uint32_t m_maxAliveTime;
uint32_t m_maxRequest;
bool m_isHttps;
MutexType m_mutex;
std::list<HttpConnection*> m_conns;
std::atomic<int32_t> m_total{0};
HttpConnectionPool也有doXXX系列函数。
只看DoRequest函数:
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) {
// 这个函数的处理流程和HttpConnection::doRequest的一样,就是uri换成url了
return doRequest(req, timeout_ms);
}
// 这个调用的是上面这个doRequest
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;
ss << uri->getPath()
<< (uri->getQuery().empty() ? "" : "?")
<< uri->getQuery()
<< (uri->getFragment().empty() ? "" : "#")
<< uri->getFragment();
return doRequest(method, ss.str(), timeout_ms, headers, body);
}
HttpResult::ptr HttpConnectionPool::doRequest(HttpRequest::ptr request, uint64_t timeout_ms) {
/*
HttpConnection::DoRequest的流程是这样的
// 根据uri创建服务器Address
// 创建Socket并连到服务器Address
// 创建HttpConnection
// 然后sendRequest
// 接收HttpResponse,构造并返回HttpResult
而HttpConnectionPool::doRequest的流程是这样的
// 获取一个 HttpConnection(达到复用HttpConnection的效果)
// sendRequest
// recvResponse
// 构造并返回HttpResult
*/
// 获取一个HttpConnection
HttpConnection::ptr conn = getConnection();
// sendRequest
ssize_t rt = conn->sendRequest(request);
// recvResponse
HttpResponse::ptr rsp = conn->recvResponse();
// 构造并返回HttpResult
return std::make_shared<HttpResult>(HttpResult::OK, rsp, "ok");
}
获取连接的函数getConnection
HttpConnection::ptr HttpConnectionPool::getConnection() {
uint64_t now_ms = acid::GetCurrentMS();
std::vector<HttpConnection*> invalid_conns;
HttpConnection* ptr = nullptr;
// 遍历list,获取一个有效连接,无效连接放到 invalid_conns
// 释放 invalid_conns
// 如果没有 有效连接,就新创一个
// 注意构造ptr时指定的 deleter
return HttpConnection::ptr(ptr, [this](HttpConnection* conn){
RelesePtr(conn, this);
});
}
http_server {.h & .cpp}
继承自TcpServer,主要是handleClient不同
void HttpServer::handleClient(Socket::ptr client) {
// 创建session
while (true) {
// recvRequest
// 构造 HttpResponse
HttpResponse::ptr response(new HttpResponse(request->getVersion(), request->isClose() || !m_isKeepalive));
response->setHeader("Server" ,getName());
// 找到对应的handler进行处理,然后sendResponse
if (m_dispatch->handle(request, response, session) == 0) {
session->sendResponse(response);
}
// 不是长连接就关闭
}
// close session
session->close();
}
Servlet {.h & .cpp}
摘录自:Servlet 简介 | 菜鸟教程 (runoob.com)
Java Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。使用 Servlet,您可以收集来自网页表单的用户输入,呈现来自数据库或者其他源的记录,还可以动态创建网页。
Servlet 执行以下主要任务:
- 读取客户端(浏览器)发送的显式的数据。这包括网页上的 HTML 表单,或者也可以是来自 applet 或自定义的 HTTP 客户端程序的表单。
- 读取客户端(浏览器)发送的隐式的 HTTP 请求数据。这包括 cookies、媒体类型和浏览器能理解的压缩格式等等。
- 处理数据并生成结果。这个过程可能需要访问数据库,执行 RMI 或 CORBA 调用,调用 Web 服务,或者直接计算得出对应的响应。
- 发送显式的数据(即文档)到客户端(浏览器)。该文档的格式可以是多种多样的,包括文本文件(HTML 或 XML)、二进制文件(GIF 图像)、Excel 等。
- 发送隐式的 HTTP 响应到客户端(浏览器)。这包括告诉浏览器或其他客户端被返回的文档类型(例如 HTML),设置 cookies 和缓存参数,以及其他类似的任务。
可以简单地理解为一个中间层(位于 接收HTTP请求报文 和 发送HTTP相应报文 之间)
Servlet
FunctionServlet
ServletDispatch
NotFoundServlet
代表没有找到对应的Servlet之后默认的Servlet