acid--http模块

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}

  1. 解析http请求报文后,填充HttpRequest对应的字段。
  2. (根据HttpRequest)去进行业务逻辑处理,依据处理填充HttpResponse。
  3. 然后根据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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值