准备:调试工具POSTMAN

简介:用于调试B/S模式程序的一款产品,可以发送几乎所有类型的HTTP请求!Postman在发送网络HTTP请求方面可以说是Chrome插件类产品中的代表产品之一。

参考出处:

· 小坑:最好使用客户端版本,网页版要使用浏览器作为代理,默认强制使用SSL,导致无法访问资源。

HTTP Server关系图

HTTP Server 开发 - 图1

1. HTTP Session封装

目的:将服务器端accept生成出来的socket封装成一个session结构,是一个被动发起的连接,负责收取来自客户端的HTTP请求报文,并且给客户端回发响应报文。

HTTP Server 开发 - 图2

  • 核心接口:

    1.1 recvRequest()

    功能:接收HTTP请求报文

核心逻辑:先解析出报文头部,再解析报文实体

  1. 创建HttpRequestParser对象,获取当前所能接收的最大报文首部长度,根据这个长度创建一块缓冲区用于暂存接收到的请求报文

  2. 采取一边接收报文,一边解析报文头部的策略:

while(1)
{

  1. 尝试通过套接字Socket对象接收报文
  2. 根据实际接收到的报文长度ret,尝试调用HttpRequestParser::execute()执行报文解析动作,并且计算一个偏移量offset = 实际接收到的报文长度 - 实际解析的报文长度判断当前接收的却没有解析掉的报文是否超过最大限定值(4KB)。
  3. 判断解析是否已经全部完成:是,就执行break;否则,继续接收报文,继续解析。

}

  1. 存储报文实体内容。获取报文实体的长度大小,根据报文实体长度 和 2. 中得到的一个偏移量offset来判断一下当前的报文实体是否接收完整:
    1. 报文实体长度 > offset, 说明当前收到的报文实体不完整。设置一个循环,再一次接收客户端传输来的报文实体,直至报文实体完整。
    2. 报文实体长度 <= offset,说明当前收到的报文实体已经完整
  2. 将完整的报文实体放入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], {

    1. delete[] ptr;

    });

    char *data = buffer.get(); int offset = 0;

    //一边读一遍解析 while(1) {

    1. int ret = read(data + offset, buff_size - offset);
    2. if(ret <= 0)
    3. {
    4. KIT_LOG_ERROR(g_logger) << "HttpSession::recvRequest read error, errno=" << errno
    5. << ", is:" << strerror(errno);
    6. return nullptr;
    7. }
    8. ret += offset;
    9. size_t n = parser->execute(data, ret);
    10. if(parser->hasError())
    11. {
    12. KIT_LOG_ERROR(g_logger) << "HttpSession::recvRequest parser error";
    13. return nullptr;
    14. }
  1. offset = ret - n;
  2. if(offset == (int)buff_size)
  3. {
  4. KIT_LOG_WARN(g_logger) << "HttpSession::recvRequest http requestion out of buffer's range";
  5. return nullptr;
  6. }
  7. //如果解析已经结束
  8. if(parser->isFinished())
  9. break;
  10. }
  11. //获取实体长度
  12. int64_t len = parser->getContenLength();
  13. if(len > HttpRequestParser::GetHttpMaxBodySize())
  14. {
  15. KIT_LOG_WARN(g_logger) << "HttpSession::recvRequest body length too long!";
  16. return nullptr;
  17. }
  18. //将报文实体读出 并且设置到HttpRequest对象中去
  19. if(len > 0)
  20. {
  21. std::string body;
  22. body.resize(len);
  23. //这里offset 就是报文首部解析完之后 剩余的报文实体的字节数
  24. if(len > offset)
  25. body.append(data, offset);
  26. else
  27. body.append(data, len);
  28. len -= offset;
  29. //如果还有剩余的报文实体长度 说明报文实体没有在本次传输中全部被接收
  30. //设置一个循环继续接收报文实体
  31. while(len > 0)
  32. {
  33. if(readFixSize(&body[body.size()], len) <= 0)
  34. {
  35. KIT_LOG_ERROR(g_logger) << "HttpSession::recvRequest readFixSize error, errno=" << errno
  36. << ", is:" << strerror(errno);
  37. return nullptr;
  38. }
  39. }
  40. //等到报文实体完整之后 装入对象之中
  41. parser->getData()->setBody(body);
  42. }
  43. return parser->getData();

}

  1. <a name="GRvZz"></a>
  2. ## 1.2 `sendResponse()`
  3. 功能:传入已经组装好的报文,给客户端发送HTTP响应报文
  4. ```cpp
  5. //发回HTTP响应报文
  6. int HttpSession::sendResponse(HttpResponse::ptr rsp)
  7. {
  8. std::stringstream ss;
  9. ss << *rsp;
  10. std::string data = ss.str();
  11. return writeFixSize(data.c_str(), data.size());
  12. }

1.3 HTTP Server类封装

功能:利用已经封装好的HttpSession类,搭建一个简单的HTTP交互服务器。接收HTTP请求报文,正确解析后,给客户端回发一个简单的响应报文,封装原来的请求报文。

  1. namespace kit_server
  2. {
  3. namespace http
  4. {
  5. class HttpServer: public TcpServer
  6. {
  7. public:
  8. typedef std::shared_ptr<HttpServer> ptr;
  9. HttpServer(bool keepalive = false, IOManager* worker = IOManager::GetThis(),
  10. IOManager* accept_worker = IOManager::GetThis());
  11. bool getKeepalive() const {return m_isKeepalive;}
  12. protected:
  13. //处理已经连接的客户端 完成数据交互通信
  14. virtual void handleClient(Socket::ptr client) override;
  15. private:
  16. //是否支持长连接
  17. bool m_isKeepalive;
  18. };
  19. }
  20. }
  • **handleClient()**函数中负责请求和应答收发: ```cpp //处理已经连接的客户端 完成数据交互通信 void HttpServer::handleClient(Socket::ptr client) { KIT_LOG_INFO(g_logger) << “HttpServer::handleClient, client=” << *client; HttpSession::ptr session(new HttpSession(client));

    do {

    1. // 接收请求报文
    2. auto req = session->recvRequest();
    3. if(!req)
    4. {
    5. KIT_LOG_WARN(g_logger) << "recv http request fail, errno=" << errno
    6. << ", is:" << strerror(errno)
    7. << ", client=" << *client;
    8. break;
    9. }
  1. //回复响应报文
  2. HttpResponse::ptr rsp(new HttpResponse(req->getVersion(), req->isClose() || !m_isKeepalive));
  3. rsp->setBody(std::string("hello kit!"));
  4. session->sendResponse(rsp);
  5. } while (m_isKeepalive);
  6. session->close();

}

  1. <a name="KLhN2"></a>
  2. # 3. HTTP Servlet封装
  3. 概念:Servlet是Server Applet的一个简称,直译就是服务小程序的意思。该概念常见于Java中。一般而言,B/S模型(浏览器-服务器模型),通过浏览器发送访问请求,服务器接收请求,并对这些请求作出处理。而servlet就是专门对请求作出处理的组件。
  4. <a name="fVjx1"></a>
  5. ## 2.1 类关系设计
  6. ![](https://cdn.nlark.com/yuque/0/2022/jpeg/25460685/1646639953768-c45addb4-aa35-4637-8ee4-565f55dd75c5.jpeg)
  7. <a name="zGGz6"></a>
  8. ### 2.1.1 `Servlet`类
  9. ```cpp
  10. /**
  11. * @brief 服务类基类
  12. */
  13. class Servlet
  14. {
  15. public:
  16. typedef std::shared_ptr<Servlet> ptr;
  17. /**
  18. * @brief 服务类构造函数
  19. * @param[in] name 服务名称
  20. */
  21. Servlet(const std::string& name):m_name(name) { }
  22. /**
  23. * @brief 服务类析构函数
  24. */
  25. virtual ~Servlet() {}
  26. /**
  27. * @brief 执行服务(虚接口)
  28. * @param[in] request HTTP请求报文
  29. * @param[in] response HTTP响应报文
  30. * @param[in] session HTTP主动连接会话
  31. * @return int32_t
  32. */
  33. virtual int32_t handle(HttpRequest::ptr request, HttpResponse::ptr response, HttpSession::ptr session) = 0;
  34. /**
  35. * @brief 获取服务名称
  36. * @return const std::string&
  37. */
  38. const std::string& getName() const {return m_name;}
  39. protected:
  40. /// 服务名称
  41. std::string m_name;
  42. };

2.1.2 FunctionServlet

  1. /**
  2. * @brief 函数服务类
  3. */
  4. class FunctionServlet: public Servlet
  5. {
  6. public:
  7. typedef std::shared_ptr<FunctionServlet> ptr;
  8. typedef std::function<int32_t (HttpRequest::ptr request,
  9. HttpResponse::ptr response, HttpSession::ptr session)> callback;
  10. /**
  11. * @brief 函数服务类
  12. * @param[in] cb 服务回调函数
  13. * @param[in] name 服务名称
  14. */
  15. FunctionServlet(callback cb, const std::string& name = "FunctionServlet");
  16. /**
  17. * @brief 执行服务
  18. * @param[in] request HTTP请求报文
  19. * @param[in] response HTTP响应报文
  20. * @param[in] session HTTP主动连接会话
  21. * @return int32_t
  22. */
  23. int32_t handle(HttpRequest::ptr request, HttpResponse::ptr response, HttpSession::ptr session) override;
  24. private:
  25. /// 回调函数
  26. callback m_cb;
  27. };

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 m_datas; //uri—->servlet 模糊匹配 temp/* std::vector > m_globs; //默认Servlet 所有路径都没匹配到时使用 Servlet::ptr m_default; //读写锁 读多写少 MutexType m_mutex;

};

  1. <a name="ja5DC"></a>
  2. ## 2.2 核心接口
  3. <a name="VKHqb"></a>
  4. ### `handle()`
  5. 通过虚基类`Servlet`定义出一个接口`handle()`,负责去执行不同请求所要对应的服务。
  6. - `FunctionServlet`继承后,通过回调函数的形式,存储相应的请求所要做的处理动作,通过`handel()`去执行该回调函数。
  7. ```cpp
  8. int32_t FunctionServlet::handle(HttpRequest::ptr request, HttpResponse::ptr response, HttpSession::ptr session)
  9. {
  10. return m_cb(request, response, session);
  11. }
  • 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);

  1. return 0;

}

  1. <a name="l1mLT"></a>
  2. ## 2.3 HTTP Server类封装
  3. 功能:综合之前`HttpSession`类的功能,将`Servlet`类的功能加入,实现不同请求对应不同服务。
  4. - **添加相应的服务管理对象**`**Servlet**`
  5. ```cpp
  6. /**
  7. * @brief HTTP服务器类
  8. */
  9. class HttpServer: public TcpServer
  10. {
  11. public:
  12. typedef std::shared_ptr<HttpServer> ptr;
  13. /**
  14. * @brief HTTP服务器类构造函数
  15. * @param[in] keepalive 是否是长连接 默认否
  16. * @param[in] worker 服务执行调度器 默认是当前线程持有的调度器
  17. * @param[in] accept_worker 接收连接调度器 默认是当前线程持有的调度器
  18. */
  19. HttpServer(bool keepalive = false, IOManager* worker = IOManager::GetThis(),
  20. IOManager* accept_worker = IOManager::GetThis());
  21. /**
  22. * @brief 获取长连接状态
  23. * @return true 处于长连接
  24. * @return false 处于短连接
  25. */
  26. bool getKeepalive() const {return m_isKeepalive;}
  27. /**
  28. * @brief 获取服务分发器
  29. * @return ServletDispatch::ptr
  30. */
  31. ServletDispatch::ptr getServletDispatch() const {return m_dispatch;}
  32. /**
  33. * @brief 设置服务分发器
  34. * @param[in] v
  35. */
  36. void setServletDispatch(ServletDispatch::ptr v) {m_dispatch = v;}
  37. /**
  38. * @brief 设置服务器名称
  39. * @param[in] v
  40. */
  41. virtual void setName(const std::string& v) override;
  42. protected:
  43. /**
  44. * @brief 处理已经连接的客户端 完成数据交互通信
  45. * @param[in] client 和客户端通信的Socket对象智能指针
  46. */
  47. virtual void handleClient(Socket::ptr client) override;
  48. private:
  49. /// 是否支持长连接
  50. bool m_isKeepalive;
  51. /// 服务分发器
  52. ServletDispatch::ptr m_dispatch;
  53. };
  • **handleClient()**函数从原来的固定一收一答,转为根据不同请求获取不同服务,并设置相对应的响应报文。 ```cpp //处理已经连接的客户端 完成数据交互通信 void HttpServer::handleClient(Socket::ptr client) { KIT_LOG_INFO(g_logger) << “HttpServer::handleClient, client=” << *client; HttpSession::ptr session(new HttpSession(client));

    do {

    1. // 接收请求报文
    2. auto req = session->recvRequest();
    3. if(!req)
    4. {
    5. KIT_LOG_WARN(g_logger) << "recv http request fail, errno=" << errno
    6. << ", is:" << strerror(errno)
    7. << ", client=" << *client;
    8. break;
    9. }
  1. //回复响应报文
  2. HttpResponse::ptr rsp(new HttpResponse(req->getVersion(), req->isClose() || !m_isKeepalive));
  3. m_dispatch->handle(req, rsp, session);
  4. // rsp->setBody(std::string("hello kit!"));
  5. session->sendResponse(rsp);
  6. } while (m_isKeepalive);
  7. session->close();

}

  1. <a name="GAatT"></a>
  2. # 3. HTTP Result 结构体封装
  3. 目的:封装对远端服务器发起请求后的HTTP报文处理结果
  4. <a name="Ao3go"></a>
  5. ## 3.1 成员变量
  6. ```cpp
  7. struct HttpResult
  8. {
  9. ....
  10. ....
  11. /**
  12. * @brief 错误码枚举类型
  13. */
  14. enum class Error
  15. {
  16. //成功
  17. OK,
  18. //非法URL
  19. INVALID_URL,
  20. //非法地址
  21. INVALID_ADDR,
  22. //非法套接字
  23. INVALID_SOCK,
  24. //连接失败
  25. CONNECT_FAIL,
  26. //发送请求失败
  27. SEND_REQ_FAIL,
  28. //接收响应超时
  29. RECV_RSP_TIMEOUT,
  30. //连接池取出连接失败
  31. POOL_GET_CONNECT_FAIL,
  32. //连接池非法sokcet
  33. POOL_INVALID_SOCK,
  34. };
  35. //错误码
  36. int result;
  37. //HTTP响应报文对象智能指针
  38. HttpResponse::ptr response;
  39. //错误原因短语
  40. std::string error;
  41. };

3.2 接口

3.2.1 构造函数

  1. /**
  2. * @brief HTTP报文处理结果结构体构造函数
  3. * @param[in] _result 错误码
  4. * @param[in] _response HTTP响应报文对象智能指针
  5. * @param[in] _error 错误原因短语
  6. */
  7. HttpResult(int _result, HttpResponse::ptr _response, const std::string& _error)
  8. :result(_result)
  9. ,response(_response)
  10. ,error(_error)
  11. {
  12. }

3.2.2 toString()

功能:HTTP报文处理结果以字符串形式输出

  1. /**
  2. * @brief HTTP报文处理结果以字符串形式输出
  3. * @return std::string
  4. */
  5. std::string toString() const;
  6. std::string HttpResult::toString() const
  7. {
  8. std::stringstream ss;
  9. ss << "[HttpResult result=" << result
  10. << ", error_str=]" << error
  11. << ", response=\n" << (response ? response->toString() : "nullptr");
  12. return ss.str();
  13. }

4. HTTP Connection类封装

目的:将服务器创建出来发起主动连接的socket封装成一个Connection。和之前HttpSeesion类的功能一模一样,只是此时的服务器作为一个客户端对远端另外一台服务器发起资源请求。

**HttpSession**类接口的功能正好相反

· 类关系设计

HTTP Server 开发 - 图3

4.1 成员变量

  1. class HttpConnection
  2. {
  3. ....
  4. ....
  5. private:
  6. //连接创建时间
  7. uint64_t m_createTime;
  8. //连接上的请求数
  9. uint64_t m_requestCount;
  10. };


4.2 接口

4.2.1 recvResponse() (核心)

功能:接收HTTP响应报文。支持HTTP chunck发包形式。

  • 核心逻辑:先解析出报文头部,再解析报文实体
  1. 创建HttpRequestParser对象,获取当前所能接收的最大报文首部长度,根据这个长度创建一块缓冲区用于暂存接收到的请求报文

  2. 采取一边接收报文,一边解析报文头部的策略:

while(1)
{

  1. 尝试通过套接字Socket对象接收报文
  2. 根据实际接收到的报文长度ret,尝试调用HttpResponseParser::execute()执行报文解析动作,并且计算一个偏移量offset = 实际接收到的报文长度 - 实际解析的报文长度判断当前的报文是否超过最大报文首部限定值(4KB)。
  3. 判断解析是否已经全部完成:是,就执行break;否则,继续接收报文,继续解析。

}

  1. 根据解析出的报文首部,看首部字段Transfer-Encoding:chunked,对应到代码中是之前HTTP解析模块中httpclient_parser:chuncked是否被置1:是,就表明接下来的响应报文实体将以chunck形式发送;否则,就是首部报文后紧跟需要的报文实体。

    1. 报文实体是chunck形式:

do{
do{

  1. 1. 尝试接收一次`数据量=剩余缓存空间大小`的数据,尽可能的多读出报文主体
  2. 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()将缓冲区中的未填装好的数据挪到缓冲区前覆盖已经填装掉掉的数据区域
}
elseclient_parser.content_len > len){
如果当前解析包中的主体长度 > 当前已经接受到的并且解析好的报文主体长度
说明当前缓冲区中所有的数据都是当前分块的,并且还有一部分数据没有接受到,还需要等待接收。将len(当前缓冲区中所有的数据)长度的数据放入body,计算剩余待接收的数据量left,通过read直接读取缓冲区中,又放入body中。
}

} while (!client_parser.chunks_done); //所有分块是否全部接受完毕

最后将完整的报文实体放入HttpRequestParser::HttpRequest对象之中

  1. 报文实体非chunck形式:(做法就和**recvRequest()**中一样):
    • 存储报文实体内容。获取报文实体的长度大小,根据报文实体长度 和 2. 中得到的一个偏移量offset来判断一下当前的报文实体是否接收完整:
  2. 报文实体长度 > offset, 说明当前收到的报文实体不完整。设置一个循环,再一次接收客户端传输来的报文实体,直至报文实体完整。
  3. 报文实体长度 <= offset,说明当前收到的报文实体已经完整
  • 将完整的报文实体放入HttpRequestParser::HttpRequest对象之中
  1. /**
  2. * @brief 接收HTTP响应报文
  3. * @return HttpResponse::ptr
  4. */
  5. HttpResponse::ptr recvResponse();
  6. HttpResponse::ptr HttpConnection::recvResponse()
  7. {
  8. HttpResponseParser::ptr parser(new HttpResponseParser);
  9. //使用某一时刻的值即可 不需要实时的值
  10. uint64_t buff_size = HttpResponseParser::GetHttpResponseBufferSize();
  11. //创建一个接受响应报文的缓冲区 指定析构
  12. std::shared_ptr<char> buffer(new char[buff_size + 1], [](char *ptr){
  13. delete[] ptr;
  14. });
  15. char *data = buffer.get();
  16. int offset = 0;
  17. //一边读一遍解析
  18. while(1)
  19. {
  20. int ret = read(data + offset, buff_size - offset);
  21. if(ret <= 0)
  22. {
  23. KIT_LOG_ERROR(g_logger) << "HttpConnection::recvResponse read error, errno=" << errno
  24. << ", is:" << strerror(errno);
  25. close();
  26. return nullptr;
  27. }
  28. ret += offset;
  29. data[ret] = '\0';
  30. size_t n = parser->execute(data, ret, false);
  31. if(parser->hasError())
  32. {
  33. KIT_LOG_ERROR(g_logger) << "HttpConnection::recvResponse parser error";
  34. close();
  35. return nullptr;
  36. }
  37. offset = ret - n;
  38. if(offset == (int)buff_size)
  39. {
  40. KIT_LOG_WARN(g_logger) << "HttpConnection::recvResponse http response buffer out of range";
  41. close();
  42. return nullptr;
  43. }
  44. //如果解析已经结束
  45. if(parser->isFinished())
  46. break;
  47. }
  48. KIT_LOG_DEBUG(g_logger) << "已经解析得到的报文:\n" << parser->getData()->toString();
  49. auto& client_parser = parser->getParser();
  50. //如果发送方式为chunked 分块发送 就需要分块接收
  51. if(client_parser.chunked)
  52. {
  53. std::string body;
  54. //拿到剩余报文的长度
  55. int len = offset;
  56. do
  57. {
  58. // KIT_LOG_DEBUG(g_logger) << "报文正在 chuncked!!!!!!";
  59. do
  60. {
  61. //尝试读取一次 剩余的缓存空间大小的数据 尽可能的多读出主体
  62. int ret = read(data + len, buff_size - len);
  63. if(ret <= 0)
  64. {
  65. KIT_LOG_ERROR(g_logger) << "HttpConnection::recvResponse read error, errno=" << errno
  66. << ", is:" << strerror(errno);
  67. close();
  68. return nullptr;
  69. }
  70. len += ret;
  71. data[len] = '\0';
  72. //开启分块解析
  73. size_t n = parser->execute(data, len, true);
  74. if(parser->hasError())
  75. {
  76. KIT_LOG_ERROR(g_logger) << "HttpConnection::recvResponse parser error";
  77. close();
  78. return nullptr;
  79. }
  80. len -= n;
  81. if(len == (int)buff_size) //读了数据但是没有解析 直到自己定义的缓存已经满了
  82. {
  83. KIT_LOG_WARN(g_logger) << "HttpConnection::recvResponse http response buffer out of range";
  84. close();
  85. return nullptr;
  86. }
  87. } while (!parser->isFinished()); //当前分块是否已经解析完毕
  88. /*这个减2非常关键 因为报文首部和实体中间还有一个空行\r\n将它算作首部的长度 不应算在body的长度中*/
  89. len -= 2;
  90. //如果当前解析包中的主体长度小于 当前已经接受到的并且解析好的报文主体长度
  91. if(client_parser.content_len <= len)
  92. {
  93. body.append(data, client_parser.content_len);
  94. //将已经装入的部分的内存 移动覆盖前面的内存
  95. memmove(data, data + client_parser.content_len, len - client_parser.content_len);
  96. len -= client_parser.content_len;
  97. }
  98. else //否则当前解析包中的主体长度 大于 当前接收到的并且解析好的报文主体长度
  99. {
  100. body.append(data, len);
  101. //计算还有多少报文主体未接收到
  102. int left = client_parser.content_len - len;
  103. while(left > 0)
  104. {
  105. //继续接收剩余的报文主体
  106. int ret = read(data, left > (int)buff_size ? (int)buff_size : left);
  107. if(ret <= 0)
  108. {
  109. KIT_LOG_ERROR(g_logger) << "HttpConnection::recvResponse read error, errno=" << errno
  110. << ", is:" << strerror(errno);
  111. close();
  112. return nullptr;
  113. }
  114. body.append(data, ret);
  115. left -= ret;
  116. }
  117. len = 0;
  118. }
  119. } while (!client_parser.chunks_done); //所有分块是否全部接受完毕
  120. parser->getData()->setBody(body);
  121. }
  122. else
  123. {
  124. //获取实体长度
  125. int64_t len = parser->getContenLength();
  126. //将报文实体读出 并且设置到HttpRequest对象中去
  127. if(len > 0)
  128. {
  129. std::string body;
  130. body.resize(len);
  131. int real_len = 0;
  132. //这里offset 就是报文首部解析完之后 剩余的报文实体的字节数
  133. if(len >= offset)
  134. {
  135. memcpy(&body[0], data, offset);
  136. //body.append(data, offset);
  137. real_len = offset;
  138. }
  139. else
  140. {
  141. memcpy(&body[0], data, len);
  142. // body.append(data, len);
  143. real_len = len;
  144. }
  145. len -= offset;
  146. //如果还有剩余的报文实体长度 说明报文实体没有在本次传输中全部被接收
  147. //设置一个循环继续接收报文实体
  148. if(len > 0)
  149. {
  150. if(readFixSize(&body[real_len], real_len) <= 0)
  151. {
  152. KIT_LOG_ERROR(g_logger) << "HttpConnection::recvResponse readFixSize error, errno=" << errno
  153. << ", is:" << strerror(errno);
  154. close();
  155. return nullptr;
  156. }
  157. }
  158. //等到报文实体完整之后 装入对象之中
  159. parser->getData()->setBody(body);
  160. }
  161. }
  162. return parser->getData();
  163. }

4.2.2 sendRequest()

功能:传入已经组装好的报文,给远端服务器发送HTTP请求报文

  1. //发送HTTP请求报文
  2. int HttpConnection::sendRequest(HttpRequest::ptr rsp)
  3. {
  4. std::stringstream ss;
  5. ss << *rsp;
  6. std::string data = ss.str();
  7. return writeFixSize(data.c_str(), data.size());
  8. }


4.2.3 常用接口

  1. /**
  2. * @brief 设置连接创建时间
  3. * @param[in] v 具体创建时间 单位ms
  4. */
  5. void setCreateTime(uint64_t v) {m_createTime = v;}
  6. /**
  7. * @brief 获取连接创建时间
  8. * @return uint64_t 单位ms
  9. */
  10. uint64_t getCreateTime() const {return m_createTime;}
  11. /**
  12. * @brief HTTP连接上请求数+1
  13. */
  14. void addRequestCount() {++m_requestCount;}
  15. /**
  16. * @brief 获取HTTP连接上请求数
  17. * @return uint64_t
  18. */
  19. 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请求发给目标服务器。有多个以此函数为基础,扩展出来的重载函数/便捷接口

  1. /**
  2. * @brief 负责将HTTP请求发给目标服务器
  3. * @param[in] req 指定的HTTP请求智能指针
  4. * @param[in] uri 指定的URI智能指针
  5. * @param[in] timeout_ms 接收超时时间 单位ms
  6. * @return HttpResult::ptr
  7. */
  8. static HttpResult::ptr DoRequest(HttpRequest::ptr req, Uri::ptr uri, uint64_t timeout_ms);
  9. HttpResult::ptr HttpConnection::DoRequest(HttpRequest::ptr req, Uri::ptr uri, uint64_t timeout_ms)
  10. {
  11. //通过URI对象智能指针创建地址对象
  12. Address::ptr addr = uri->createAddress();
  13. KIT_LOG_DEBUG(g_logger) << "DoRequest addr=" << *addr;
  14. if(!addr)
  15. {
  16. KIT_LOG_ERROR(g_logger) << "HttpConnection::DoRequest get addr fail";
  17. return std::make_shared<HttpResult>((int)HttpResult::Error::INVALID_ADDR, nullptr, "invalid addr=" + uri->getHost());
  18. }
  19. //通过地址对象创建套接字对象
  20. Socket::ptr sock = Socket::CreateTCP(addr);
  21. if(!sock)
  22. {
  23. KIT_LOG_ERROR(g_logger) << "HttpConnection::DoRequest create socket fail";
  24. return std::make_shared<HttpResult>((int)HttpResult::Error::INVALID_SOCK, nullptr, "invalid sock addr=" + addr->toString());
  25. }
  26. //通过套接字连接目标服务器
  27. if(!sock->connect(addr))
  28. {
  29. KIT_LOG_ERROR(g_logger) << "HttpConnection::DoRequest socket connect fail";
  30. return std::make_shared<HttpResult>((int)HttpResult::Error::CONNECT_FAIL, nullptr, "invalid sock addr=" + addr->toString());
  31. }
  32. //为套接字设置接收超时时间
  33. sock->setRecvTimeout(timeout_ms);
  34. //通过连接成功的套接字创建HTTP连接
  35. HttpConnection::ptr con = std::make_shared<HttpConnection>(sock);
  36. //通过连接发送HTTP请求报文
  37. int ret = con->sendRequest(req);
  38. if(ret < 0)
  39. {
  40. KIT_LOG_ERROR(g_logger) << "HttpConnection::DoRequest write fail, errno=" << errno
  41. << ", is:" << strerror(errno);
  42. 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)));
  43. }
  44. //通过连接接收HTTP请求报文
  45. auto rsp = con->recvResponse();
  46. if(!rsp)
  47. {
  48. KIT_LOG_WARN(g_logger) << "HttpConnection::DoRequest recv response maybe timeout";
  49. 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));
  50. }
  51. //返回HTTP处理结果的结构体指针指针
  52. return std::make_shared<HttpResult>((int)HttpResult::Error::OK, rsp, "OK");
  53. }

4.2.4.2 重载/便捷接口

  1. /**
  2. * @brief 用GET方法将HTTP请求发给目标服务器
  3. * @param[in] url 指定访问的网址
  4. * @param[in] timeout_ms 接收超时时间 单位ms
  5. * @param[in] headers 指定的首部字段组
  6. * @param[in] body 指定的报文主体内容
  7. * @return HttpResult::ptr
  8. */
  9. static HttpResult::ptr DoGet(const std::string& url, uint64_t timeout_ms,
  10. const std::map<std::string, std::string>& headers = {}, const std::string& body = "");
  11. /**
  12. * @brief 用POST方法将HTTP请求发给目标服务器
  13. * @param[in] url 指定访问的网址
  14. * @param[in] timeout_ms 接收超时时间 单位ms
  15. * @param[in] headers 指定的首部字段组
  16. * @param[in] body 指定的报文主体内容
  17. * @return HttpResult::ptr
  18. */
  19. static HttpResult::ptr DoPost(const std::string& url, uint64_t timeout_ms,
  20. const std::map<std::string, std::string>& headers = {}, const std::string& body = "");
  21. /**
  22. * @brief 用GET方法将HTTP请求发给目标服务器
  23. * @param[in] uri 指定URI对象智能指针
  24. * @param[in] timeout_ms 接收超时时间 单位ms
  25. * @param[in] headers 指定的首部字段组
  26. * @param[in] body 指定的报文主体内容
  27. * @return HttpResult::ptr
  28. */
  29. static HttpResult::ptr DoGet(Uri::ptr uri, uint64_t timeout_ms,
  30. const std::map<std::string, std::string>& headers = {}, const std::string& body = "");
  31. /**
  32. * @brief 用POST方法将HTTP请求发给目标服务器
  33. * @param[in] uri 指定URI对象智能指针
  34. * @param[in] timeout_ms 接收超时时间 单位ms
  35. * @param[in] headers 指定的首部字段组
  36. * @param[in] body 指定的报文主体内容
  37. * @return HttpResult::ptr
  38. */
  39. static HttpResult::ptr DoPost(Uri::ptr uri, uint64_t timeout_ms,
  40. const std::map<std::string, std::string>& headers = {}, const std::string& body = "");
  41. /**
  42. * @brief 负责将HTTP请求发给目标服务器
  43. * @param[in] method HTTP请求方法
  44. * @param[in] url 指定访问的网址
  45. * @param[in] timeout_ms 接收超时时间 单位ms
  46. * @param[in] headers 指定的首部字段组
  47. * @param[in] body 指定的报文主体内容
  48. * @return HttpResult::ptr
  49. */
  50. static HttpResult::ptr DoRequest(HttpMethod method, const std::string& url, uint64_t timeout_ms,
  51. const std::map<std::string, std::string>& headers = {}, const std::string& body = "");
  52. /**
  53. * @brief 负责将HTTP请求发给目标服务器
  54. * @param[in] method HTTP请求方法
  55. * @param[in] uri 指定URI对象智能指针
  56. * @param[in] timeout_ms 接收超时时间 单位ms
  57. * @param[in] headers 指定的首部字段组
  58. * @param[in] body 指定的报文主体内容
  59. * @return HttpResult::ptr
  60. */
  61. static HttpResult::ptr DoRequest(HttpMethod method, Uri::ptr uri, uint64_t timeout_ms,
  62. const std::map<std::string, std::string>& headers = {}, const std::string& body = "");
  1. HttpResult::ptr HttpConnection::DoGet(const std::string& url,
  2. uint64_t timeout_ms, const std::map<std::string, std::string>& headers, const std::string& body)
  3. {
  4. //通过字符串解析并创建URI对象
  5. Uri::ptr uri = Uri::Create(url);
  6. if(!uri)
  7. {
  8. KIT_LOG_ERROR(g_logger) << "HttpConnection::DoGet Uri parser error";
  9. return std::make_shared<HttpResult>((int)HttpResult::Error::INVALID_URL, nullptr, "invalid url: " + url);
  10. }
  11. return DoGet(uri, timeout_ms, headers, body);
  12. }
  13. HttpResult::ptr HttpConnection::DoPost(const std::string& url,
  14. uint64_t timeout_ms, const std::map<std::string, std::string>& headers, const std::string& body)
  15. {
  16. //通过字符串解析并创建URI对象
  17. Uri::ptr uri = Uri::Create(url);
  18. if(!uri)
  19. {
  20. KIT_LOG_ERROR(g_logger) << "HttpConnection::DoPost Uri parser error";
  21. return std::make_shared<HttpResult>((int)HttpResult::Error::INVALID_URL, nullptr, "invalid url: " + url);
  22. }
  23. return DoPost(uri, timeout_ms, headers, body);
  24. }
  25. HttpResult::ptr HttpConnection::DoGet(Uri::ptr uri,
  26. uint64_t timeout_ms, const std::map<std::string, std::string>& headers, const std::string& body)
  27. {
  28. return DoRequest(HttpMethod::GET, uri, timeout_ms, headers, body);
  29. }
  30. HttpResult::ptr HttpConnection::DoPost(Uri::ptr uri,
  31. uint64_t timeout_ms, const std::map<std::string, std::string>& headers, const std::string& body)
  32. {
  33. return DoRequest(HttpMethod::POST, uri, timeout_ms, headers, body);
  34. }
  35. HttpResult::ptr HttpConnection::DoRequest(HttpMethod method, const std::string& url,
  36. uint64_t timeout_ms, const std::map<std::string, std::string>& headers, const std::string& body)
  37. {
  38. //通过字符串解析并创建URI对象
  39. Uri::ptr uri = Uri::Create(url);
  40. if(!uri)
  41. {
  42. KIT_LOG_ERROR(g_logger) << "HttpConnection::DoRequest Uri parser error";
  43. return std::make_shared<HttpResult>((int)HttpResult::Error::INVALID_URL, nullptr, "invalid url: " + url);
  44. }
  45. return DoRequest(method, uri, timeout_ms, headers, body);
  46. }
  47. HttpResult::ptr HttpConnection::DoRequest(HttpMethod method, Uri::ptr uri,
  48. uint64_t timeout_ms, const std::map<std::string, std::string>& headers, const std::string& body)
  49. {
  50. /*组装HTTP请求报文*/
  51. HttpRequest::ptr req = std::make_shared<HttpRequest>();
  52. req->setMethod(method); //设置请求方法
  53. req->setPath(uri->getPath()); //设置请求资源路径
  54. req->setQuery(uri->getQuery()); //设置查询参数
  55. req->setFragment(uri->getFragment()); //设置分段标识符
  56. bool has_host = false;
  57. for(auto &x : headers)
  58. {
  59. if(strcasecmp(x.first.c_str(), "connection") == 0 &&
  60. strcasecmp(x.second.c_str(), "keep-alive") == 0)
  61. {
  62. req->setClose(false);
  63. }
  64. if(has_host && strcasecmp(x.first.c_str(), "host") == 0)
  65. has_host = !x.second.empty();
  66. req->setHeader(x.first, x.second);
  67. }
  68. if(!has_host)
  69. req->setHeader("host", uri->getHost());
  70. req->setBody(body);
  71. return DoRequest(req, uri, timeout_ms);
  72. }

紧急:需要理解ragel的语法规则、正则表达式规则

5. URI 封装

参考出处:RFC 3986 文档
https://www.ietf.org/rfc/rfc3986

ragel官方文档
http://www.colm.net/files/ragel/ragel-guide-6.10.pdf

  • uri官方标准格式规范:

image.png

5.1 成员变量

  1. class Uri
  2. {
  3. ...
  4. ...
  5. private:
  6. //scheme字段
  7. std::string m_scheme;
  8. //用户认证信息字段
  9. std::string m_userinfo;
  10. //域名字段
  11. std::string m_host;
  12. //端口号字段
  13. int16_t m_port;
  14. //资源路径字段
  15. std::string m_path;
  16. //查询参数字段
  17. std::string m_query;
  18. //片段标识符字段
  19. std::string m_fragment;
  20. };

5.2 ragel文件编写uri.rl

功能:ragel状态机以及URI类的实现都放在这个文件中

通过ragel -C -G2 uri.rl -o uri.rl.cpp最终生成我们所需的.cpp文件,会在调用状态机的地方自动生成C/C++的代码嵌入到源代码中。

  1. /*
  2. see RFC 3986:
  3. Appendix A. Collected ABNF for URI
  4. URI = scheme ":" hier-part [ "?" query ] [ "" fragment ]
  5. hier-part = "//" authority path-abempty
  6. / path-absolute
  7. / path-rootless
  8. / path-empty
  9. URI-reference = URI / relative-ref
  10. absolute-URI = scheme ":" hier-part [ "?" query ]
  11. relative-ref = relative-part [ "?" query ] [ "" fragment ]
  12. relative-part = "//" authority path-abempty
  13. / path-absolute
  14. / path-noscheme
  15. / path-empty
  16. scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
  17. authority = [ userinfo "@" ] host [ ":" port ]
  18. userinfo = *( unreserved / pct-encoded / sub-delims / ":" )
  19. host = IP-literal / IPv4address / reg-name
  20. port = *DIGIT
  21. IP-literal = "[" ( IPv6address / IPvFuture ) "]"
  22. IPvFuture = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" )
  23. IPv6address = 6( h16 ":" ) ls32
  24. / "::" 5( h16 ":" ) ls32
  25. / [ h16 ] "::" 4( h16 ":" ) ls32
  26. / [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32
  27. / [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32
  28. / [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32
  29. / [ *4( h16 ":" ) h16 ] "::" ls32
  30. / [ *5( h16 ":" ) h16 ] "::" h16
  31. / [ *6( h16 ":" ) h16 ] "::"
  32. h16 = 1*4HEXDIG
  33. ls32 = ( h16 ":" h16 ) / IPv4address
  34. IPv4address = dec-octet "." dec-octet "." dec-octet "." dec-octet */
  35. #include <string>
  36. #include <sstream>
  37. #include <iostream>
  38. #include "uri.h"
  39. namespace kit_server
  40. {
  41. /* machine */
  42. %%{
  43. machine uri_parser;
  44. sub_delims = ( "!" | "$" | "$" | "&" | "'" | "(" | ")" | "*"
  45. | "+" | "," | ";" | "=") ;
  46. gen_delims = ( ":" | "/" | "?" | "#" | "[" | "]" | "@" ) ;
  47. reserved = ( gen_delims | sub_delims ) ;
  48. unreserved = ( alpha | digit | "-" | "." | "_" | "~" ) ;
  49. pct_encoded = ( "%" xdigit xdigit ) ;
  50. action marku { mark = fpc; }
  51. action markh { mark = fpc; }
  52. action save_scheme
  53. {
  54. uri->setScheme(std::string(mark, fpc - mark));
  55. mark = NULL;
  56. }
  57. scheme = (alpha (alpha | digit | "+" | "-" | ".")*) >marku %save_scheme;
  58. action save_port
  59. {
  60. if(fpc != mark){
  61. uri->setPort(atoi(mark));
  62. }
  63. mark = NULL;
  64. }
  65. action save_userinfo
  66. {
  67. if(mark) {
  68. uri->setUserinfo(std::string(mark, fpc - mark));
  69. }
  70. mark = NULL;
  71. }
  72. action save_host
  73. {
  74. if(mark != NULL) {
  75. uri->setHost(std::string(mark, fpc - mark));
  76. }
  77. }
  78. userinfo = (unreserved | pct_encoded | sub_delims | ":")*;
  79. dec_octet = digit | [1-9] digit | "l" digit{2} | 2 [0-4] digit | "25" [0-5];
  80. IPv4address = dec_octet "." dec_octet "." dec_octet "." dec_octet;
  81. h16 = xdigit{1,4};
  82. ls32 = (h16 : h16) | IPv4address;
  83. IPv6address = ( ( h16 ":" ) {6} ls32) |
  84. ( "::" ( h16 ":" ) {5} ls32) |
  85. (( h16 )? "::" ( h16 ":" ) {4} ls32) |
  86. ((( h16 ":" ){1} h16 )? "::" ( h16 ":" ) {3} ls32) |
  87. ((( h16 ":" ){2} h16 )? "::" ( h16 ":" ) {2} ls32) |
  88. ((( h16 ":" ){3} h16 )? "::" ( h16 ":" ) {1} ls32) |
  89. ((( h16 ":" ){4} h16 )? "::" ls32) |
  90. ((( h16 ":" ){5} h16 )? "::" h16 ) |
  91. ((( h16 ":" ){6} h16 )? "::" );
  92. IPvFuture = "v" xdigit+ "." (unreserved | sub_delims | ":")+;
  93. IP_literal = "[" (IPv6address | IPvFuture) "]";
  94. reg_name = (unreserved | pct_encoded | sub_delims)*;
  95. host = IP_literal | IPv4address | reg_name;
  96. port = digit*;
  97. authority = ( (userinfo %save_userinfo "@")? host >markh %save_host (":" port >markh %save_port)? ) >markh;
  98. action save_segment
  99. {
  100. uri->setFragment(std;:String(mark, fpc - mark));
  101. mark = NULL;
  102. }
  103. action save_path
  104. {
  105. uri->setPath(std::string(mark, fpc - mark));
  106. mark = NULL;
  107. }
  108. # pchar = unreserved | pct_encoded | sub_delims | ":" | "@";
  109. # add (any -- ascii) support chinese
  110. pchar = ( (any -- ascii) ) | unreserved | pct_encoded | sub_delims | ":" | "@";
  111. segment = pchar*;
  112. segment_nz = pchar+;
  113. segment_nz_nc = (pchar - ":")+;
  114. action clear_segments
  115. {
  116. }
  117. path_abempty = (("/" segment))? ("/" segment)*;
  118. path_absolute = ("/" (segment_nz ("/" segment)*)?);
  119. path_noscheme = segment_nz_nc ("/" segment)*;
  120. path_rootless = segment_nz ("/" segment)*;
  121. path_empty = "";
  122. path = (path_abempty | path_absolute | path_noscheme | path_rootless | path_empty);
  123. action save_query
  124. {
  125. uri->setQuery(std::string(mark, fpc - mark));
  126. mark = NULL;
  127. }
  128. action save_fragment
  129. {
  130. uri->setFragment(std::string(mark, fpc - mark));
  131. mark = NULL;
  132. }
  133. query = (pchar | "/" | "?")* >marku %save_query;
  134. fragment = (pchar | "/" | "?")* >marku %save_fragment;
  135. hier_part = ("//" authority path_abempty> markh %save_path) | path_absolute | path_rootless | path_empty;
  136. relative_part = ("//" authority path_abempty) | path_absolute | path_noscheme | path_empty;
  137. relative_ref = relative_part ("?" query)? ("#" fragment)?;
  138. absolute_URI = scheme "?" hier_part ("?" query)?;
  139. # Obsolete, but referenced from HTTP, so we translate
  140. relative_URI = relative_part ("?" query)?;
  141. URI = scheme "?" hier_part ("?" query)? ("#" fragment)?;
  142. URI_reference = URI | relative_ref;
  143. # 状态机入口
  144. main := URI_reference;
  145. write data;
  146. }%%
  147. /* exec */
  148. Uri::Uri()
  149. :m_port(0)
  150. {
  151. }
  152. std::ostream& Uri::dump(std::ostream& os)
  153. {
  154. os << m_scheme << "://"
  155. << m_userinfo
  156. << (m_userinfo.size() ? "@" : "")
  157. << m_host
  158. << (isDefaultPort() ? "" : ":" + std::string("" + m_port))
  159. << "/"
  160. << m_path
  161. << (m_query.size() ? "?" : "")
  162. << m_query
  163. << (m_fragment.size() ? "#" : "")
  164. << m_fragment;
  165. return os;
  166. }
  167. std::string Uri::toString()
  168. {
  169. std::stringstream ss;
  170. dump(ss);
  171. return ss.str();
  172. }
  173. //通过URI创建一个地址
  174. Address::ptr Uri::createAddress() const
  175. {
  176. auto addr = Address::LookUpAnyIPAddress(m_host);
  177. if(addr)
  178. addr->setPort(m_port);
  179. return addr;
  180. }
  181. //传入一个uri字符串解析出对应URI对象
  182. Uri::ptr Uri::Create(const std::string& u)
  183. {
  184. Uri::ptr uri(new Uri);
  185. int cs = 0;
  186. const char* mark = nullptr;
  187. //初始化状态机所需的变量
  188. %% write init;
  189. //头指针
  190. const char *p = u.c_str();
  191. //尾指针
  192. const char *pe = p + u.size();
  193. //ragel指定尾指针的名字
  194. const char *eof = pe;
  195. //开始调用 自动状态机
  196. %% write exec;
  197. if(cs == uri_parser_error)
  198. return nullptr;
  199. else if(cs >= uri_parser_first_final)
  200. return uri;
  201. return nullptr;
  202. }
  203. bool Uri::isDefaultPort()
  204. {
  205. if(m_port == 0)
  206. return true;
  207. if(m_scheme == "http")
  208. m_port = 80;
  209. else if(m_scheme == "https")
  210. m_port = 443;
  211. return false;
  212. }
  213. }


5.3 接口

5.3.1 Create()(核心)

功能:通过字符串URI解析并创建出URI对象

  1. /**
  2. * @brief 通过字符串URI创建URI对象
  3. * @param[in] uri URI字符串
  4. * @return Uri::ptr
  5. */
  6. static Uri::ptr Create(const std::string& uri);
  7. Uri::ptr Uri::Create(const std::string& u)
  8. {
  9. Uri::ptr uri(new Uri);
  10. /*cs mark状态机中用到的变量 提前定义*/
  11. int cs = 0;
  12. const char* mark = NULL;
  13. //初始化自动状态机
  14. %% write init;
  15. //指向u头指针
  16. const char *p = u.c_str();
  17. //指向u尾指针
  18. const char *pe = p + u.size();
  19. //ragel指定的一个尾指针的名字
  20. const char *eof = pe;
  21. //调用自动状态机
  22. %% write exec;
  23. //uri_parser_error 检查解析是否错误
  24. if(cs == uri_parser_error)
  25. return nullptr;
  26. else if(cs >= uri_parser_first_final) //解析完成后的状态码是否合法
  27. return uri;
  28. return nullptr;
  29. }


5.3.2 CreateAddress()

功能:通过URI中的域名创建通信地址对象

  1. /**
  2. * @brief 通过URI中的域名创建通信地址对象
  3. * @return Address::ptr
  4. */
  5. Address::ptr createAddress() const;
  6. Address::ptr Uri::createAddress() const
  7. {
  8. auto addr = Address::LookUpAnyIPAddress(m_host);
  9. if(addr)
  10. addr->setPort(getPort());
  11. return addr;
  12. }


5.3.3 dump()

功能:将URI信息以流的形式组装

  1. /**
  2. * @brief 将URI信息以流的形式组装
  3. * @param[in] os 标准输出流
  4. * @return std::ostream&
  5. */
  6. std::ostream& dump(std::ostream& os) const;
  7. std::ostream& Uri::dump(std::ostream& os) const
  8. {
  9. os << m_scheme << "://"
  10. << m_userinfo
  11. << (m_userinfo.size() ? "@" : "")
  12. << m_host
  13. << (isDefaultPort() ? "" : ":" + std::string("" + m_port))
  14. << getPath()
  15. << (m_query.size() ? "?" : "")
  16. << m_query
  17. << (m_fragment.size() ? "#" : "")
  18. << m_fragment;
  19. return os;
  20. }

5.3.4 toString()

功能:将URI信息以字符串形式输出

  1. /**
  2. * @brief 将URI信息以字符串形式输出
  3. * @return std::string
  4. */
  5. std::string toString() const;
  6. std::string Uri::toString() const
  7. {
  8. std::stringstream ss;
  9. dump(ss);
  10. return ss.str();
  11. }

5.3.5 常用接口

  1. /**
  2. * @brief 设置scheme字段
  3. * @param[in] v 具体scheme值 http https ftp等
  4. */
  5. void setScheme(const std::string& v) {m_scheme = v;}
  6. /**
  7. * @brief 获取scheme字段
  8. * @return const std::string&
  9. */
  10. const std::string& getScheme() const {return m_scheme;}
  11. /**
  12. * @brief 设置用户认证信息
  13. * @param[in] v 具体的用户认证信息值
  14. */
  15. void setUserinfo(const std::string& v) {m_userinfo = v;}
  16. /**
  17. * @brief 获取用户认证信息
  18. * @return const std::string&
  19. */
  20. const std::string& getUserinfo() const {return m_userinfo;}
  21. /**
  22. * @brief 设置域名
  23. * @param[in] v 具体域名值 www.xxxx.com
  24. */
  25. void setHost(const std::string& v) {m_host = v;}
  26. /**
  27. * @brief 获取域名
  28. * @return const std::string&
  29. */
  30. const std::string& getHost() const {return m_host;}
  31. /**
  32. * @brief 设置端口号
  33. * @param[in] v 具体端口号
  34. */
  35. void setPort(int16_t v) {m_port = v;}
  36. /**
  37. * @brief 获取端口号 没有设置则根据scheme来设置http = 80 https = 443
  38. * @return int16_t
  39. */
  40. int16_t getPort() const;
  41. int16_t Uri::getPort() const
  42. {
  43. if(m_port)
  44. return m_port;
  45. if(m_scheme == "http")
  46. return 80;
  47. else if(m_scheme == "https")
  48. return 443;
  49. return 0;
  50. }
  51. /**
  52. * @brief 设置资源路径
  53. * @param[in] v 具体资源路径
  54. */
  55. void setPath(const std::string& v) {m_path = v;}
  56. /**
  57. * @brief 获取资源路径
  58. * @return const std::string&
  59. */
  60. const std::string& getPath() const;
  61. const std::string& Uri::getPath() const
  62. {
  63. static std::string temp = "/";
  64. return m_path.size() ? m_path : temp;
  65. }
  66. /**
  67. * @brief 设置查询参数
  68. * @param[in] v 具体查询参数 "?query"
  69. */
  70. void setQuery(const std::string& v) {m_query = v;}
  71. /**
  72. * @brief 获取查询参数
  73. * @return const std::string&
  74. */
  75. const std::string& getQuery() const {return m_query;}
  76. /**
  77. * @brief 设置片段标识符
  78. * @param[in] v 具体片段标识符的值
  79. */
  80. void setFragment(const std::string& v) {m_fragment = v;}
  81. /**
  82. * @brief 获取片段标识符
  83. * @return const std::string&
  84. */
  85. const std::string& getFragment() const {return m_fragment;}

6. HttpConnectionPool 连接池封装

目的:利用”池化”技术将连接资源做一个统一管理,尤其对于”长连接”来讲,关注一个连接上的最大存活时间、最多能够处理的请求数量等,还要兼顾连接上的安全问题,规避一部分的攻击。并且,一个”池子”服务一个网址+一个端口,对于一个网址资源的请求连接数会很多。

· 类关系设计

HTTP Server 开发 - 图5

6.1 成员变量

  1. class HttpconnectionPool
  2. {
  3. ....
  4. ....
  5. private:
  6. //要连接的主机域名
  7. std::string m_host;
  8. std::string m_vhost;
  9. //端口号
  10. uint16_t m_port;
  11. //最大连接数
  12. uint32_t m_maxSize;
  13. //每条连接最大存活时间
  14. uint32_t m_maxAliveTime;
  15. //每条连接上最大请求数量
  16. uint32_t m_maxRequest;
  17. //互斥锁 读写频次相当 不用读写锁
  18. MutexType m_mutex;
  19. //连接存储容器 list增删方便
  20. std::list<HttpConnection* > m_connections;
  21. //当前连接数量 可以突破最大连接数 但是使用完毕放回后若超限就要马上释放
  22. std::atomic<int32_t> m_tatol = {0};
  23. };

6.2 接口

6.2.1 构造函数

  1. /**
  2. * @brief HTTP连接池类构造函数
  3. * @param[in] host 域名
  4. * @param[in] vhost 备用域名
  5. * @param[in] port 端口号
  6. * @param[in] max_size 最大连接数
  7. * @param[in] maxAliveTime 每条连接最大存活时间
  8. * @param[in] maxRequest 每条连接最大请求数
  9. */
  10. HttpConnectionPool(const std::string& host, const std::string& vhost,
  11. uint16_t port, uint32_t max_size, uint32_t maxAliveTime, uint32_t maxRequest);
  12. HttpConnectionPool::HttpConnectionPool(const std::string& host, const std::string& vhost,
  13. uint16_t port, uint32_t max_size, uint32_t maxAliveTime, uint32_t maxRequest)
  14. :m_host(host)
  15. ,m_vhost(vhost)
  16. ,m_port(port)
  17. ,m_maxSize(max_size)
  18. ,m_maxAliveTime(maxAliveTime)
  19. ,m_maxRequest(maxRequest)
  20. {
  21. }

** 私有接口

1). ReleasePtr()

功能:释放连接资源。第二个参数HttpConnectionPool*非常关键,因为该函数静态函数static,被指定为HttpConnection对象的析构函数,只能通过传参第二个参数去判断HTTP连接池中的一些状态去决定是否释放连接资源。

  1. /**
  2. * @brief 释放连接资源
  3. * @param[in] ptr http连接指针
  4. * @param[in] pool http连接池指针
  5. */
  6. static void ReleasePtr(HttpConnection* ptr, HttpConnectionPool* pool);
  7. void HttpConnectionPool::ReleasePtr(HttpConnection* ptr, HttpConnectionPool* pool)
  8. {
  9. //连接上的请求数+1
  10. ptr->addRequest();
  11. //判断当前连接是否过期:是否处于连接 || 是否超过存活时间 || 是否超出最大请求数 || 是否超出最大连接数
  12. if(!ptr->isConnected() ||
  13. (ptr->getCreateTime() + pool->m_maxAliveTime >= GetCurrentMs()) ||
  14. (ptr->getRequest() > pool->m_maxRequest) ||
  15. (pool->m_total > pool->m_maxSize))
  16. {
  17. delete ptr;
  18. --pool->m_total;
  19. return;
  20. }
  21. //连接未过期 把用完的连接放回连接池
  22. MutexType::Lock lock(pool->m_mutex);
  23. pool->m_connections.push_back(ptr);
  24. }

6.2.2 getConnection()(核心)

功能:从连接池中获取连接,同时检查连接池中的连接是否已经过期,将过期连接收集并释放资源;如果没有可用连接,新创建一条连接提供使用。

  1. /**
  2. * @brief 从连接池中获取连接,没有可用资源要创建后返回
  3. * @return HttpConnection::ptr
  4. */
  5. HttpConnection::ptr getConnection();
  6. HttpConnection::ptr HttpConnectionPool::getConnection()
  7. {
  8. //收集过期连接vector容器
  9. std::vector<HttpConnection*> invalid_connections;
  10. //返回出去的可用连接
  11. HttpConnection* con_ptr = nullptr;
  12. //加锁
  13. MutexType::Lock lock(m_mutex);
  14. //从连接池里找出一个可用的资源
  15. while(m_connections.size())
  16. {
  17. auto con = m_connections.front();
  18. m_connections.pop_front();
  19. //当前连接已经断开 视为过期
  20. if(!con->isConnected())
  21. {
  22. invalid_connections.push_back(con);
  23. continue;
  24. }
  25. //当前连接超过了最大存活时间 视为过期
  26. if(con->getCreateTime() + m_maxAliveTime >= GetCurrentMs())
  27. {
  28. invalid_connections.push_back(con);
  29. continue;
  30. }
  31. con_ptr = con;
  32. break;
  33. }
  34. lock.unlock(); //解锁
  35. //将过期连接资源都要释放
  36. for(auto &x : invalid_connections)
  37. delete x;
  38. m_tatol -= invalid_connections.size();
  39. //如果在连接池里没有拿到对应的资源 线程创建一个
  40. if(!con_ptr)
  41. {
  42. IPAddress::ptr addr = Address::LookUpAnyIPAddress(m_host);
  43. if(!addr)
  44. {
  45. KIT_LOG_ERROR(g_logger) << "HttpConnectionPool::getConnection get addr fail, host=" << m_host << ", port=" << m_port;
  46. return nullptr;
  47. }
  48. addr->setPort(m_port);
  49. Socket::ptr sock = Socket::CreateTCP(addr);
  50. if(!sock)
  51. {
  52. KIT_LOG_ERROR(g_logger) << "HttpConnectionPool::getConnection create socket fail, addr=" << *addr;
  53. return nullptr;
  54. }
  55. if(!sock->connect(addr))
  56. {
  57. KIT_LOG_ERROR(g_logger) << "HttpConnectionPool::getConnection connect fail, addr=" << *addr;
  58. return nullptr;
  59. }
  60. con_ptr = new HttpConnection(sock);
  61. ++m_tatol;
  62. }
  63. //指定一个用于释放的函数HttpConnectionPool::ReleasePtr
  64. return HttpConnection::ptr(con_ptr, std::bind(&HttpConnectionPool::ReleasePtr, std::placeholders::_1, this));
  65. }

6.2.3 发起HTTP请求系列函数

6.2.3.1 doRequest(HttpRequest::ptr req, uint64_t timeout_ms);(核心)

功能:负责将HTTP请求发给目标服务器。有多个以此函数为基础,扩展出来的重载函数/便捷接口

  1. /**
  2. * @brief 负责将HTTP请求发给目标服务器
  3. * @param[in] req HTTP请求报文智能指针
  4. * @param[in] timeout_ms 接收响应报文超时时间
  5. * @return HttpResult::ptr 返回HTTP连接上操作结果
  6. */
  7. HttpResult::ptr doRequest(HttpRequest::ptr req, uint64_t timeout_ms);
  8. HttpResult::ptr HttpConnectionPool::doRequest(HttpRequest::ptr req, uint64_t timeout_ms)
  9. {
  10. //获取HTTP连接资源
  11. auto con = getConnection();
  12. if(!con)
  13. {
  14. KIT_LOG_ERROR(g_logger) << "HttpConnectionPool::doRequest get connection from pool fail";
  15. 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));
  16. }
  17. //通过连接获取到套接字
  18. auto sock = con->getSocket();
  19. if(!sock)
  20. {
  21. KIT_LOG_ERROR(g_logger) << "HttpConnectionPool::doRequest get sock fail";
  22. return std::make_shared<HttpResult>((int)HttpResult::Error::POOL_INVALID_SOCK, nullptr, "send request fail, host=" + m_host + ", port=" + std::to_string(m_port));
  23. }
  24. //设置接收超时时间
  25. sock->setRecvTimeout(timeout_ms);
  26. //通过连接发送HTTP请求报文
  27. int ret = con->sendRequest(req);
  28. if(ret < 0)
  29. {
  30. KIT_LOG_ERROR(g_logger) << "HttpConnectionPool::DoRequest write fail, errno=" << errno
  31. << ", is:" << strerror(errno);
  32. 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)));
  33. }
  34. //通过连接接收HTTP响应报文
  35. auto rsp = con->recvResponse();
  36. if(!rsp)
  37. {
  38. KIT_LOG_WARN(g_logger) << "HttpConnectionPool::doRequest recv response maybe timeout, time=" << timeout_ms;
  39. 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)));
  40. }
  41. //返回连接上的收发结果
  42. return std::make_shared<HttpResult>((int)HttpResult::Error::OK, rsp, "OK");
  43. }

6.2.3.2 重载/便捷接口

  1. /**
  2. * @brief 使用GET方法将HTTP请求发给目标服务器
  3. * @param[in] url 指定的目标网址
  4. * @param[in] timeout_ms 接收超时时间
  5. * @param[in] headers 指定的首部字段
  6. * @param[in] body 指定的报文主体
  7. * @return HttpResult::ptr 返回HTTP连接上操作结果
  8. */
  9. HttpResult::ptr doGet(const std::string& url, uint64_t timeout_ms,
  10. const std::map<std::string, std::string>& headers = {}, const std::string& body = "");
  11. /**
  12. * @brief 使用POST方法将HTTP请求发给目标服务器
  13. * @param[in] url 指定的目标网址
  14. * @param[in] timeout_ms 接收超时时间
  15. * @param[in] headers 指定的首部字段
  16. * @param[in] body 指定的报文主体
  17. * @return HttpResult::ptr 返回HTTP连接上操作结果
  18. */
  19. HttpResult::ptr doPost(const std::string& url, uint64_t timeout_ms,
  20. const std::map<std::string, std::string>& headers = {}, const std::string& body = "");
  21. /**
  22. * @brief 使用GET方法将HTTP请求发给目标服务器
  23. * @param[in] uri 指定的URI对象智能指针
  24. * @param[in] timeout_ms 接收超时时间
  25. * @param[in] headers 指定的首部字段
  26. * @param[in] body 指定的报文主体
  27. * @return HttpResult::ptr 返回HTTP连接上操作结果
  28. */
  29. HttpResult::ptr doGet(Uri::ptr uri, uint64_t timeout_ms,
  30. const std::map<std::string, std::string>& headers = {}, const std::string& body = "");
  31. /**
  32. * @brief 使用POST方法将HTTP请求发给目标服务器
  33. * @param[in] uri 指定的URI对象智能指针
  34. * @param[in] timeout_ms 接收超时时间
  35. * @param[in] headers 指定的首部字段
  36. * @param[in] body 指定的报文主体
  37. * @return HttpResult::ptr 返回HTTP连接上操作结果
  38. */
  39. HttpResult::ptr doPost(Uri::ptr uri, uint64_t timeout_ms,
  40. const std::map<std::string, std::string>& headers = {}, const std::string& body = "");
  41. /**
  42. * @brief 负责将HTTP请求发给目标服务器
  43. * @param[in] method 指定HTTP请求方法
  44. * @param[in] url 指定的目标网址
  45. * @param[in] timeout_ms 接收超时时间
  46. * @param[in] headers 指定的首部字段
  47. * @param[in] body 指定的报文主体
  48. * @return HttpResult::ptr 返回HTTP连接上操作结果
  49. */
  50. HttpResult::ptr doRequest(HttpMethod method, const std::string& url, uint64_t timeout_ms,
  51. const std::map<std::string, std::string>& headers = {}, const std::string& body = "");
  52. /**
  53. * @brief 负责将HTTP请求发给目标服务器
  54. * @param[in] method 指定HTTP请求方法
  55. * @param[in] uri 指定的URI对象智能指针
  56. * @param[in] timeout_ms 接收超时时间
  57. * @param[in] headers 指定的首部字段
  58. * @param[in] body 指定的报文主体
  59. * @return HttpResult::ptr 返回HTTP连接上操作结果
  60. */
  61. HttpResult::ptr doRequest(HttpMethod method, Uri::ptr uri, uint64_t timeout_ms,
  62. const std::map<std::string, std::string>& headers = {}, const std::string& body = "");
  1. HttpResult::ptr HttpConnectionPool::doGet(const std::string& url, uint64_t timeout_ms,
  2. const std::map<std::string, std::string>& headers, const std::string& body)
  3. {
  4. return doRequest(HttpMethod::GET, url, timeout_ms, headers, body);
  5. }
  6. HttpResult::ptr HttpConnectionPool::doPost(const std::string& url,
  7. uint64_t timeout_ms, const std::map<std::string, std::string>& headers, const std::string& body)
  8. {
  9. return doRequest(HttpMethod::POST, url, timeout_ms, headers, body);
  10. }
  11. HttpResult::ptr HttpConnectionPool::doGet(Uri::ptr uri,
  12. uint64_t timeout_ms, const std::map<std::string, std::string>& headers, const std::string& body)
  13. {
  14. std::stringstream ss;
  15. ss << uri->getPath()
  16. << (uri->getQuery().size() ? "?" : "")
  17. << uri->getQuery()
  18. << (uri->getFragment().size() ? "#" : "")
  19. << uri->getFragment();
  20. return doGet(ss.str(), timeout_ms, headers, body);
  21. }
  22. HttpResult::ptr HttpConnectionPool::doPost(Uri::ptr uri,
  23. uint64_t timeout_ms, const std::map<std::string, std::string>& headers, const std::string& body)
  24. {
  25. std::stringstream ss;
  26. ss << uri->getPath()
  27. << (uri->getQuery().size() ? "?" : "")
  28. << uri->getQuery()
  29. << (uri->getFragment().size() ? "#" : "")
  30. << uri->getFragment();
  31. return doPost(ss.str(), timeout_ms, headers, body);
  32. }
  33. HttpResult::ptr HttpConnectionPool::doRequest(HttpMethod method, const std::string& url,
  34. uint64_t timeout_ms, const std::map<std::string, std::string>& headers, const std::string& body)
  35. {
  36. /*组装HTTP请求报文*/
  37. HttpRequest::ptr req = std::make_shared<HttpRequest>();
  38. req->setMethod(method);
  39. req->setPath(url);
  40. req->setClose(false);
  41. bool has_host = false; //域名标志位
  42. for(auto &x : headers)
  43. {
  44. if(strcasecmp(x.first.c_str(), "connection") == 0 &&
  45. strcasecmp(x.second.c_str(), "keep-alive") == 0)
  46. {
  47. req->setClose(false);
  48. }
  49. //是否存在host域名字段
  50. if(has_host && strcasecmp(x.first.c_str(), "host") == 0)
  51. has_host = !x.second.empty();
  52. req->setHeader(x.first, x.second);
  53. }
  54. //没有带域名字段 要使用备用域名m_vhost
  55. if(!has_host)
  56. {
  57. if(!m_vhost.size())
  58. req->setHeader("host", m_host);
  59. else
  60. req->setHeader("host", m_vhost);
  61. }
  62. req->setBody(body);
  63. return doRequest(req, timeout_ms);
  64. }
  65. HttpResult::ptr HttpConnectionPool::doRequest(HttpMethod method, Uri::ptr uri,
  66. uint64_t timeout_ms, const std::map<std::string, std::string>& headers, const std::string& body)
  67. {
  68. std::stringstream ss;
  69. //组装URI字符串
  70. ss << uri->getPath()
  71. << (uri->getQuery().size() ? "?" : "")
  72. << uri->getQuery()
  73. << (uri->getFragment().size() ? "#" : "")
  74. << uri->getFragment();
  75. return doRequest(method, ss.str(), timeout_ms, headers, body);
  76. }

C\C++知识点补充复习:fnmatch()函数

功能:检查字符串pattern是否和shell通配符模式字符串string相匹配。flags可以对string中要匹配的模式进行一些设置,具体可以查看一下手册。

返回值:成功匹配返回0, 如果有错返回一个非0值,并且设置errno

  1. #include <fnmatch.h>
  2. int fnmatch(const char *pattern, const char *string, int flags);