支持 HTTP/1.1 API级别

暂不支持下载服务器

准备:HTTP协议

1. HTTP 请求报文/HTTP响应报文:

HTTP协议开发 - 图1
$ telnet www.baidu.com 80

  1. /*请求报文*/
  2. GET / HTTP/1.0
  3. host: www.baidu.com
  4. /*响应报文*/
  5. HTTP/1.0 200 OK
  6. Accept-Ranges: bytes
  7. Cache-Control: no-cache
  8. Content-Length: 9508
  9. Content-Type: text/html
  10. Date: Tue, 18 Jan 2022 12:27:43 GMT
  11. ....

2. 常用请求方法

  • GET获取资源

用于请求已经被URI统一资源标识符识别的资源。指定的资源被服务器解析后返回响应内容

  • POST发送实体主体

用于传输报文实体的内容,也能获取响应但这不是主要的目的。GET方法也能传输但是一般不这么使用。

  • HEAD获得报文首部

GET一样的功能,只是不会获得报文实体,只返回报文首部。用于确认URI的有效性以及资源的更新日期。

  • TRACE追踪路径

用于查询发出的请求是如何进行加工/篡改的,想要使得请求真正到达源目标服务器中途可能会存在代理转发,可以通过这个方法来获得路径。

  • PUT 传输文件

用于向目标服务器传输文件,类似FTP协议。本身不带身份验证功能,任何人都可以上传文件,存在安全性问题,一般Web网站不会开放该方法,除非配合Web的身份验证机制。

  • DELETE删除文件

用于删除目标服务器的文件,PUT方法一样,本身不带身份验证功能,存在安全问题。

3. 常见状态码

3.1 1XX 通知信息码 请求已经收到或请求正在处理。

  • 100 Continue

请求已经收到或请求正在处理。客户端可以继续发送请求或者直接忽略该响应

3.2 2XX 成功码 请求已经被正常处理

  • 200 OK

请求已经正常处理,如请求的是实体,响应报文中会包含报文实体

  • 204No Content

请求已经正常处理,但是响应报文中不会包含任何实体,也不允许携带实体。一般,用于客户端往服务器发送消息,而服务器无需对客户端发送任何新的消息

  • 206 Partial Content

客户端对服务器发起的时候范围请求,服务器成功返回了对于的范围实体。

3.3 3XX 重定向码 需要执行某些特殊处理以正确处理请求

  • 301Moved Permanently

永久性重定向。如果请求的资源已经被分配了新的URI,那么以后使用资源应该指向当前的这个新的URI,响应首部字段Location: 新URI保存有指向所需资源的新的URI。假如该资源被保存为书签,会将原来老的URI替换为当前新的。

  • 302Found

临时性重定向。和永久性重定向类似,希望客户端(在本次)使用新分配的URI来请求资源。和永久性重定向不同的是,之后该资源的标识符可能还会发生变化。假如该资源被保存为书签,那么不会去更新URI,仍然保留的是老的标识符。

  • 303 See Other

请求对应的资源存在着另外的URI, 需要使用GET方法定向重新获取资源。该状态码和
302类似,但是指定了必须使用GET方法。

  • 304Not Modified

客户端发送附带条件的请求报文(请求首部字段中包含:If-MatchIf-Modified-SinceIf-None-MatchIf-RangeIf-Unmodified-Since任意一个)时,服务器运行访问资源,但是发生请求条件不满足的情况。

3.4 4XX 客户端错误码 表明是客户端导致的错误

  • 400 Bad Request

客户端发出的请求报文中存在语法错误。

  • 401Unauthorized

发送的请求需要通过HTTP认证(BASIC认证、DIGEST认证)的认证信息。如果之前已经请求过,第二次接收到该状态码,表明认证不通过。响应首部字段WWW-Authenticate:xxxxx存储有所属的认证方案信息。

  • 402Forbidden

请求被服务器拒绝。如需列举拒绝原因可在实体中说明。

  • 404Not Found

服务器上不存在/已经删除所要请求的资源。也可以在拒绝请求但不想说明理由时使用。

3.5 5XX服务器错误码 表明是服务器端导致的错误

  • 500Internal Server Error

服务器端执行请求时候发生语法错误。

  • 502Bad Gateway

网关发生错误。指出代理服务器从远端服务器接收到错误响应。

  • 503Service Unavailable

服务器暂时处于超负载或者正在进行停机维护,无法处理请求。

  • 504Gateway Timeout

网关超时。指出代理服务器没有从远端服务器得到及时的响应。

  • 505HTTP Version Not Supported

服务器不支持当前的HTTP协议版本。

4. 统一资源标识符 URI

统一资源标识符 URI Uniform Resource Identififier 唯一标记互联网上的资源
统一资源定位符URL Uniform Resource Locator 俗称的网址
统一资源名称URN Uniform Resource Name
HTTP协议开发 - 图2

  • URI的标准格式规范如下:

scheme:[//[user:password@]host[:port]][/]path[?query][#fragment]
HTTP协议开发 - 图3
uri: http://www.xxxx.com:80/page/xxx?id=10&v=20#fr

  • http协议类型
  • www.xxxx.com 为服务器地址(主机名)host
  • 80 为服务器端口port
  • /page/xxx 为文件路径path
  • id=10&v=20 为查询字符串,传入任意参数query
  • fr为片段标识符,标记已获取资源中的子资源fragment

· HTTP报文类封装参考

参考github上对HTTP协议的封装和解析的开源项目:

  1. /* Request Methods 请求方法*/
  2. #define HTTP_METHOD_MAP(XX) \
  3. XX(0, DELETE, DELETE) \
  4. XX(1, GET, GET) \
  5. XX(2, HEAD, HEAD) \
  6. XX(3, POST, POST) \
  7. XX(4, PUT, PUT) \
  8. /* pathological */ \
  9. XX(5, CONNECT, CONNECT) \
  10. XX(6, OPTIONS, OPTIONS) \
  11. XX(7, TRACE, TRACE) \
  12. /* WebDAV */ \
  13. XX(8, COPY, COPY) \
  14. XX(9, LOCK, LOCK) \
  15. XX(10, MKCOL, MKCOL) \
  16. XX(11, MOVE, MOVE) \
  17. XX(12, PROPFIND, PROPFIND) \
  18. XX(13, PROPPATCH, PROPPATCH) \
  19. XX(14, SEARCH, SEARCH) \
  20. XX(15, UNLOCK, UNLOCK) \
  21. XX(16, BIND, BIND) \
  22. XX(17, REBIND, REBIND) \
  23. XX(18, UNBIND, UNBIND) \
  24. XX(19, ACL, ACL) \
  25. /* subversion */ \
  26. XX(20, REPORT, REPORT) \
  27. XX(21, MKACTIVITY, MKACTIVITY) \
  28. XX(22, CHECKOUT, CHECKOUT) \
  29. XX(23, MERGE, MERGE) \
  30. /* upnp */ \
  31. XX(24, MSEARCH, M-SEARCH) \
  32. XX(25, NOTIFY, NOTIFY) \
  33. XX(26, SUBSCRIBE, SUBSCRIBE) \
  34. XX(27, UNSUBSCRIBE, UNSUBSCRIBE) \
  35. /* RFC-5789 */ \
  36. XX(28, PATCH, PATCH) \
  37. XX(29, PURGE, PURGE) \
  38. /* CalDAV */ \
  39. XX(30, MKCALENDAR, MKCALENDAR) \
  40. /* RFC-2068, section 19.6.1.2 */ \
  41. XX(31, LINK, LINK) \
  42. XX(32, UNLINK, UNLINK) \
  43. /* icecast */ \
  44. XX(33, SOURCE, SOURCE) \
  45. /* Status Codes 响应状态码*/
  46. #define HTTP_STATUS_MAP(XX) \
  47. XX(100, CONTINUE, Continue) \
  48. XX(101, SWITCHING_PROTOCOLS, Switching Protocols) \
  49. XX(102, PROCESSING, Processing) \
  50. XX(200, OK, OK) \
  51. XX(201, CREATED, Created) \
  52. XX(202, ACCEPTED, Accepted) \
  53. XX(203, NON_AUTHORITATIVE_INFORMATION, Non-Authoritative Information) \
  54. XX(204, NO_CONTENT, No Content) \
  55. XX(205, RESET_CONTENT, Reset Content) \
  56. XX(206, PARTIAL_CONTENT, Partial Content) \
  57. XX(207, MULTI_STATUS, Multi-Status) \
  58. XX(208, ALREADY_REPORTED, Already Reported) \
  59. XX(226, IM_USED, IM Used) \
  60. XX(300, MULTIPLE_CHOICES, Multiple Choices) \
  61. XX(301, MOVED_PERMANENTLY, Moved Permanently) \
  62. XX(302, FOUND, Found) \
  63. XX(303, SEE_OTHER, See Other) \
  64. XX(304, NOT_MODIFIED, Not Modified) \
  65. XX(305, USE_PROXY, Use Proxy) \
  66. XX(307, TEMPORARY_REDIRECT, Temporary Redirect) \
  67. XX(308, PERMANENT_REDIRECT, Permanent Redirect) \
  68. XX(400, BAD_REQUEST, Bad Request) \
  69. XX(401, UNAUTHORIZED, Unauthorized) \
  70. XX(402, PAYMENT_REQUIRED, Payment Required) \
  71. XX(403, FORBIDDEN, Forbidden) \
  72. XX(404, NOT_FOUND, Not Found) \
  73. XX(405, METHOD_NOT_ALLOWED, Method Not Allowed) \
  74. XX(406, NOT_ACCEPTABLE, Not Acceptable) \
  75. XX(407, PROXY_AUTHENTICATION_REQUIRED, Proxy Authentication Required) \
  76. XX(408, REQUEST_TIMEOUT, Request Timeout) \
  77. XX(409, CONFLICT, Conflict) \
  78. XX(410, GONE, Gone) \
  79. XX(411, LENGTH_REQUIRED, Length Required) \
  80. XX(412, PRECONDITION_FAILED, Precondition Failed) \
  81. XX(413, PAYLOAD_TOO_LARGE, Payload Too Large) \
  82. XX(414, URI_TOO_LONG, URI Too Long) \
  83. XX(415, UNSUPPORTED_MEDIA_TYPE, Unsupported Media Type) \
  84. XX(416, RANGE_NOT_SATISFIABLE, Range Not Satisfiable) \
  85. XX(417, EXPECTATION_FAILED, Expectation Failed) \
  86. XX(421, MISDIRECTED_REQUEST, Misdirected Request) \
  87. XX(422, UNPROCESSABLE_ENTITY, Unprocessable Entity) \
  88. XX(423, LOCKED, Locked) \
  89. XX(424, FAILED_DEPENDENCY, Failed Dependency) \
  90. XX(426, UPGRADE_REQUIRED, Upgrade Required) \
  91. XX(428, PRECONDITION_REQUIRED, Precondition Required) \
  92. XX(429, TOO_MANY_REQUESTS, Too Many Requests) \
  93. XX(431, REQUEST_HEADER_FIELDS_TOO_LARGE, Request Header Fields Too Large) \
  94. XX(451, UNAVAILABLE_FOR_LEGAL_REASONS, Unavailable For Legal Reasons) \
  95. XX(500, INTERNAL_SERVER_ERROR, Internal Server Error) \
  96. XX(501, NOT_IMPLEMENTED, Not Implemented) \
  97. XX(502, BAD_GATEWAY, Bad Gateway) \
  98. XX(503, SERVICE_UNAVAILABLE, Service Unavailable) \
  99. XX(504, GATEWAY_TIMEOUT, Gateway Timeout) \
  100. XX(505, HTTP_VERSION_NOT_SUPPORTED, HTTP Version Not Supported) \
  101. XX(506, VARIANT_ALSO_NEGOTIATES, Variant Also Negotiates) \
  102. XX(507, INSUFFICIENT_STORAGE, Insufficient Storage) \
  103. XX(508, LOOP_DETECTED, Loop Detected) \
  104. XX(510, NOT_EXTENDED, Not Extended) \
  105. XX(511, NETWORK_AUTHENTICATION_REQUIRED, Network Authentication Required) \

** 辅助函数

1. checkGetAs()

功能:主要用于对存储字段的map容器进行查询和获取以及类型转换

  1. /**
  2. * @brief 用于对存储字段的map容器进行查询和获取以及类型转换
  3. * @tparam 具体的map类型
  4. * @tparam[in] T 期待转换的类型
  5. * @param[in] key 查询的首部字段
  6. * @param[out] val 转换后的传出值
  7. * @param[in] def 默认转换类型
  8. * @return true 存在并转换为T
  9. * @return false 不存在并转换为默认类型
  10. */
  11. template<class MapType, class T>
  12. bool checkGetAs(const MapType& m, const std::string& key, T& val, const T& def = T())
  13. {
  14. std::string str;
  15. auto it = m.find(key);
  16. if(it == m.end())
  17. {
  18. val = def;
  19. return false;
  20. }
  21. try
  22. {
  23. //万能转换
  24. val = boost::lexical_cast<T>(it->second);
  25. return true;
  26. }
  27. catch(...)
  28. {
  29. val = def;
  30. }
  31. return false;
  32. }

2. getAs()

功能:主要用于对存储字段的map容器进行获取以及类型转换

  1. /**
  2. * @brief 主要用于对存储字段的map容器进行获取以及类型转换
  3. * @tparam 具体的map类型
  4. * @tparam[in] T 期待转换的类型
  5. * @param[in] key 查询的字段
  6. * @param[in] def 默认转换类型
  7. * @return T
  8. */
  9. template<class MapType, class T>
  10. T getAs(const MapType& m, const std::string& key, const T& def = T())
  11. {
  12. auto it = m.find(key);
  13. if(it == m.end())
  14. return def;
  15. try
  16. {
  17. return boost::lexical_cast<T>(it->second);
  18. }
  19. catch(...)
  20. {
  21. }
  22. return def;
  23. }

3. 字符串转为HTTP请求方法枚举型

功能:std::string --------> HttpMethod

  1. /**
  2. * @brief 字符串转为HTTP请求方法枚举型
  3. * @param[in] method HTTP请求方法string
  4. * @return HttpMethod
  5. */
  6. HttpMethod StringToHttpMethod(const std::string &method);
  7. HttpMethod StringToHttpMethod(const std::string &method)
  8. {
  9. #define XX(num, name, string)\
  10. if(strcmp(#string, method.c_str()) == 0)\
  11. return HttpMethod::name;\
  12. HTTP_METHOD_MAP(XX);
  13. #undef XX
  14. return HttpMethod::INVALID_METHOD;
  15. }
  16. /**
  17. * @brief 字符串转为HTTP请求方法枚举型
  18. * @param[in] method HTTP请求方法 char*
  19. * @return HttpMethod
  20. */
  21. HttpMethod CharsToHttpMethod(const char *method);
  22. HttpMethod CharsToHttpMethod(const char *method)
  23. {
  24. #define XX(num, name, string)\
  25. if(strncmp(#string, method, strlen(#string)) == 0)\
  26. return HttpMethod::name;\
  27. HTTP_METHOD_MAP(XX)
  28. #undef XX
  29. return HttpMethod::INVALID_METHOD;
  30. }

4. HTTP请求方法枚举型/HTTP状态码枚举型转为字符串

功能:HttpMethod --------> std::string / HttpStatus --------> std::string

  1. /**
  2. * @brief 辅助数组,将HTTP请求方法由枚举转为数组方便访问
  3. */
  4. static const char* s_method_string[] = {
  5. #define XX(num, name, string) #string,
  6. HTTP_METHOD_MAP(XX)
  7. #undef XX
  8. };
  9. /**
  10. * @brief HTTP请求方法枚举型转为字符型
  11. * @param[in] method HTTP请求方法枚举型具体值
  12. * @return const char*
  13. */
  14. const char* HttpMethodToString(const HttpMethod &method);
  15. const char* HttpMethodToString(const HttpMethod &method)
  16. {
  17. uint32_t index = (uint32_t)method;
  18. if(index >= sizeof(s_method_string) / sizeof(s_method_string[0]))
  19. {
  20. return "<unknown>";
  21. }
  22. return s_method_string[index];
  23. }
  24. /**
  25. * @brief HTTP响应码枚举型转为字符型
  26. * @param[in] status HTTP响应码枚举型具体值
  27. * @return const char*
  28. */
  29. const char* HttpStatusToString(const HttpStatus &status);
  30. const char* HttpStatusToString(const HttpStatus &status)
  31. {
  32. switch(status)
  33. {
  34. #define XX(code, name, string)\
  35. case HttpStatus::name: \
  36. return #string;
  37. HTTP_STATUS_MAP(XX);
  38. #undef XX
  39. default:
  40. return "<unknown>";
  41. }
  42. }

1. HTTP请求报文类封装

  1. /*请求报文*/
  2. GET / HTTP/1.0
  3. host: www.baidu.com
  4. connection: close
  5. .....

1.1 成员变量

成员分几部分看:请求方法、URI、协议版本、首部字段(请求、通用、主体)、报文主体、cookie处理

  1. //HTTP请求类
  2. class HttpRequest
  3. {
  4. ....
  5. private:
  6. //请求方法
  7. HttpMethod m_method;
  8. //协议版本 0x11----http1.1 0x10-----http1.0
  9. uint8_t m_version;
  10. //是否处于长连接
  11. bool m_close;
  12. /*URI 资源定位符*/
  13. //资源路径
  14. std::string m_path;
  15. //查询字符串
  16. std::string m_query;
  17. //片段标识
  18. std::string m_fragment;
  19. //报文主体
  20. std::string m_body;
  21. //首部字段
  22. std::map<std::string, std::string, CaseInsensitiveLess> m_headers;
  23. //参数字段
  24. std::map<std::string, std::string, CaseInsensitiveLess> m_params;
  25. //cookie字段
  26. std::map<std::string, std::string, CaseInsensitiveLess> m_cookies;
  27. };

1.1.1设置请求方法的小技巧

作为类成员变量存储时,使用枚举类型(即一个数字)来表示,真正组成报文的时候通过枚举类型将其转换为字符串类型使用即可。

  • 利用宏展开,将之前已经定义的好的请求方法以及状态码的宏,转变为强类型枚举。

    1. enum class HttpMethod
    2. {
    3. #define XX(num, name, string) name = num, //等价于从 HTTP_METHOD_MAP(XX)中取值
    4. HTTP_METHOD_MAP(XX) //GET = 1,
    5. #undef XX //HEAD = 2,
    6. INVALID_METHOD //.....
    7. };
  • 同样利用宏展开,在枚举型和字符型之间转换所需的请求方法/状态码 ```cpp //字符串转枚举 请求方法 HttpMethod StringToHttpMethod(const std::string &method) {

    define XX(num, name, string)\

    if(strcmp(#string, method.c_str()) == 0)\

    1. return HttpMethod::name;\

    HTTP_METHOD_MAP(XX);

    undef XX

    return HttpMethod::INVALID_METHOD;

}

//请求方法 数字从0开始定义 转为数组实现 static const char* s_method_string[] = {

define XX(num, name, string) #string,

  1. HTTP_METHOD_MAP(XX)

undef XX

};

//枚举转字符串 请求方法 const char* HttpMethodToString(const HttpMethod &method) { uint32_t index = (uint32_t)method; if(index >= sizeof(s_method_string) / sizeof(s_method_string[0])) { return ““; }

  1. return s_method_string[index];

}

  1. <a name="GseLR"></a>
  2. ### 1.1.2 对于各种首部字段的封装采用KV形式`Key:Value`
  3. 利用`map`容器,存储KV形式的首部字段,理论上来说首部字段是可以一个key值对应多个value值的,简单起见这里认为他们是一对一的关系。指定容器的比较规则是忽视大小写的。
  4. ```cpp
  5. //忽视字母大小写仿函数
  6. struct CaseInsensitiveLess
  7. {
  8. bool operator()(const std::string& lhs, const std::string& rhs) const;
  9. };
  10. bool CaseInsensitiveLess::operator()(const std::string& lhs, const std::string& rhs) const
  11. {
  12. //忽视字母大小写比较
  13. return strcasecmp(lhs.c_str(), rhs.c_str()) < 0;
  14. }
  15. //首部字段
  16. std::map<std::string, std::string, CaseInsensitiveLess> m_headers;
  17. //参数字段
  18. std::map<std::string, std::string, CaseInsensitiveLess> m_params;
  19. //cookie字段
  20. std::map<std::string, std::string, CaseInsensitiveLess> m_cookies;

1.2 接口

1.2.1 构造函数

  1. /**
  2. * @brief HTTP请求类构造函数
  3. * @param[in] version http协议版本,默认为1.1
  4. * @param[in] close 是否支持长连接,默认不支持
  5. */
  6. HttpRequest(uint8_t version = 0x11, bool close = true);
  7. HttpRequest::HttpRequest(uint8_t version, bool close)
  8. :m_method(HttpMethod::GET)
  9. ,m_version(version)
  10. ,m_close(close)
  11. ,m_path("/") //默认是根路径
  12. {
  13. }

1.2.2 常用接口

  1. /**
  2. * @brief 设置请求方法
  3. * @param[in] m 具体请求方法
  4. */
  5. void setMethod(HttpMethod m) {m_method = m;}
  6. /**
  7. * @brief 获取请求方法
  8. * @return HttpMethod
  9. */
  10. HttpMethod getMethod() const {return m_method;}
  11. /**
  12. * @brief 设置协议版本
  13. * @param[in] v 具体版本号
  14. */
  15. void setVersion(uint8_t v) {m_version = v;}
  16. /**
  17. * @brief 获取协议版本
  18. * @return uint8_t
  19. */
  20. uint8_t getVersion() const {return m_version;}
  21. /**
  22. * @brief 设置资源路径
  23. * @param[in] s 具体资源路径
  24. */
  25. void setPath(const std::string& s) {m_path = s;}
  26. /**
  27. * @brief 获取资源路径
  28. * @return const std::string&
  29. */
  30. const std::string& getPath() const {return m_path;}
  31. /**
  32. * @brief 设置查询字符串
  33. * @param[in] s 具体查询字符串
  34. */
  35. void setQuery(const std::string& s) {m_query = s;}
  36. /**
  37. * @brief 获取查询字符串
  38. * @return const std::string&
  39. */
  40. const std::string& getQuery() const {return m_query;}
  41. /**
  42. * @brief 设置片段标识符
  43. * @param[in] s 具体片段标识
  44. */
  45. void setFragment(const std::string& s) {m_fragment = s;}
  46. /**
  47. * @brief 获取片段标识符
  48. * @return const std::string&
  49. */
  50. const std::string& getFragment() const {return m_fragment;}
  51. /**
  52. * @brief 设置报文主体
  53. * @param[in] s 设置具体主体内容
  54. */
  55. void setBody(const std::string& s) {m_body = s;}
  56. /**
  57. * @brief 获取报文主体
  58. * @return const std::string&
  59. */
  60. const std::string& getBody() const {return m_body;}
  61. /**
  62. * @brief 设置报文首部字段组
  63. * @param[in] v 具体map容器
  64. */
  65. void setHeaders(const MapType& v) {m_headers = v;}
  66. /**
  67. * @brief 获取报文首部字段组
  68. * @return const MapType&
  69. */
  70. const MapType& getHeaders() const {return m_headers;}
  71. /**
  72. * @brief 设置参数组
  73. * @param[in] v 具体map容器
  74. */
  75. void setParams(const MapType& v) {m_params = v;}
  76. /**
  77. * @brief 获取参数组
  78. * @return const MapType&
  79. */
  80. const MapType& getParams() const {return m_params;}
  81. /**
  82. * @brief 设置cookie字段组
  83. * @param[in] v 具体map容器
  84. */
  85. void setCookies(const MapType& v) {m_cookies = v;}
  86. /**
  87. * @brief 获取cookie字段组
  88. * @return const MapType&
  89. */
  90. const MapType& getCookies() const {return m_cookies;}
  91. /**
  92. * @brief 获取连接状态
  93. * @return true 属于短连接
  94. * @return false 属于长连接
  95. */
  96. bool isClose() const {return m_close;}
  97. /**
  98. * @brief 设置连接状态
  99. * @param[in] v true = 短连接 false = 长连接
  100. */
  101. void setClose(bool v) {m_close = v;}
  102. //设置/获取/删除/查询具体首部字段
  103. void setHeader(const std::string& key, const std::string& val);
  104. std::string getHeadr(const std::string& key, const std::string& def = "") const;
  105. void delHeader(const std::string& key);
  106. bool hasHeader(const std::string& key, std::string * val = nullptr);
  107. //设置/获取/删除/查询具体参数
  108. void setParam(const std::string& key, const std::string& val);
  109. std::string getParam(const std::string& key, const std::string& def = "") const;
  110. void delParam(const std::string& key);
  111. bool hasParam(const std::string& key, std::string * val = nullptr);
  112. //设置/获取/删除/查询具体cookie
  113. void setCookie(const std::string& key, const std::string& val);
  114. std::string getCookie(const std::string& key, const std::string& def = "") const;
  115. void delCookie(const std::string& key);
  116. bool hasCookie(const std::string& key, std::string * val = nullptr);
  117. /**
  118. * @brief 查询并将存在的首部字段转换 sting---->T
  119. * @tparam[in] T 期待转换的类型
  120. * @param[in] key 查询的首部字段
  121. * @param[out] val 转换后的传出值
  122. * @param[in] def 默认转换类型
  123. * @return true 存在并转换为T
  124. * @return false 不存在并转换为默认类型
  125. */
  126. template<class T>
  127. bool checkGetHeaderAs(const std::string& key, T& val, const T& def = T())
  128. {
  129. return checkGetAs(m_headers, key, val, def);
  130. }
  131. /**
  132. * @brief 将存在的首部字段转换 string---->T
  133. * @tparam[in] T 期待转换的类型
  134. * @param[in] key 查询的首部字符
  135. * @param[in] def 默认转换类型
  136. * @return T 返回转换为T类型的数据
  137. */
  138. template<class T>
  139. T GetHeaderAs(const std::string& key, const T& def = T())
  140. {
  141. return getAs(m_headers, key, def);
  142. }
  143. /**
  144. * @brief 查询并将存在的参数字段转换 sting---->T
  145. * @tparam[in] T 期待转换的类型
  146. * @param[in] key 查询的参数字段
  147. * @param[out] val 转换后的传出值
  148. * @param[in] def 默认转换类型
  149. * @return true 存在并转换为T
  150. * @return false 不存在并转换为默认类型
  151. */
  152. template<class T>
  153. bool checkGetParamAs(const std::string& key, T& val, const T& def = T())
  154. {
  155. return checkGetAs(m_params, key, val, def);
  156. }
  157. /**
  158. * @brief 将存在的参数字段转换 string---->T
  159. * @tparam[in] T 期待转换的类型
  160. * @param[in] key 查询的参数字段
  161. * @param[in] def 默认转换类型
  162. * @return T 返回转换为T类型的数据
  163. */
  164. template<class T>
  165. T GetParamAs(const std::string& key, const T& def = T())
  166. {
  167. return getAs(m_params, key, def);
  168. }
  169. /**
  170. * @brief 查询并将存在的cookie字段转换 sting---->T
  171. * @tparam[in] T 期待转换的类型
  172. * @param[in] key 查询的cookie字段
  173. * @param[out] val 转换后的传出值
  174. * @param[in] def 默认转换类型
  175. * @return true 存在并转换为T
  176. * @return false 不存在并转换为默认类型
  177. */
  178. template<class T>
  179. bool checkGetCookieAs(const std::string& key, T& val, const T& def = T())
  180. {
  181. return checkGetAs(m_params, key, val, def);
  182. }
  183. /**
  184. * @brief 将存在的cookie字段段转换 string---->T
  185. * @tparam[in] T 期待转换的类型
  186. * @param[in] key 查询的cookie字段
  187. * @param[in] def 默认转换类型
  188. * @return T 返回转换为T类型的数据
  189. */
  190. template<class T>
  191. T GetCookieAs(const std::string& key, const T& def = T())
  192. {
  193. return getAs(m_params, key, def);
  194. }

1.2.3 dump()

功能:将变量信息以流的形式重新组装为HTTP请求报文

  1. /**
  2. * @brief 将变量信息以流的形式重新组装为HTTP请求报文
  3. * @param[in] os 标准输出流
  4. * @return std::ostream&
  5. */
  6. std::ostream& dump(std::ostream& os) const;
  7. std::ostream& HttpRequest::dump(std::ostream& os)
  8. {
  9. // GET /uri HTTP/1.1
  10. //Host: www.baidu.com
  11. //空行CR+LF
  12. //实体
  13. //请求行封装
  14. os << HttpMethodToString(m_method) << " "
  15. << m_path
  16. << (m_query.size() ? "?" : "")
  17. << m_query
  18. << (m_fragment.size() ? "#" : "")
  19. << m_fragment
  20. << " HTTP/"
  21. << ((uint32_t)(m_version >> 4)) //把低4位移走 只保留高4位
  22. << "."
  23. << ((uint32_t)(m_version & 0x0F)) //把高4位置0 只保留低4位
  24. << "\r\n";
  25. //连接状态首部字段单独列举
  26. os << "connection:" << (m_close ? "close" : "keep-alive") << "\r\n";
  27. //首部字段封装
  28. for(auto &x : m_headers)
  29. {
  30. if(strcasecmp(x.first.c_str(), "connection") == 0)
  31. continue;
  32. os << x.first << ":" << x.second << "\r\n";
  33. }
  34. //空行封装
  35. os << "\r\n";
  36. //报文实体封装
  37. if(m_body.size())
  38. os << "content-length: " << m_body.size() << "\r\n\r\n"
  39. << m_body;
  40. else
  41. os << "\r\n";
  42. return os;
  43. }

1.2.4 toString()

功能:将HTTP请求报文以字符串形式输出

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

2. HTTP响应报文类封装

和HTTP请求报文类的封装类似。

  1. /*响应报文*/
  2. HTTP/1.0 200 OK
  3. Accept-Ranges: bytes
  4. Cache-Control: no-cache
  5. Content-Length: 9508
  6. Content-Type: text/html
  7. Date: Tue, 18 Jan 2022 12:27:43 GMT
  8. ....

2.1 成员变量

成员分几部分看:协议版本、状态码、原因短语、首部字段(响应、通用、主体)、报文主体

  1. //HTTP响应类
  2. class HttpResponse
  3. {
  4. .....
  5. private:
  6. //响应状态码
  7. HttpStatus m_status;
  8. //协议版本 0x10----HTTP1.0 0x11-----HTTP1.1
  9. uint8_t m_version;
  10. //是否为长连接
  11. bool m_close;
  12. //响应报文主体
  13. std::string m_body;
  14. //响应状态原因短语
  15. std::string m_reason;
  16. //响应首部字段
  17. std::map<std::string, std::string, CaseInsensitiveLess> m_headers;
  18. };

2.1.1 设置状态码的小技巧

和请求方法的处理一样。作为类成员变量存储时,使用枚举类型(即一个数字)来表示,真正组成报文的时候通过枚举类型将其转换为字符串类型使用即可。

  • 利用宏展开,将之前已经定义的好的请求方法以及状态码的宏,转变为强类型枚举。

    1. enum class HttpStatus
    2. {
    3. #define XX(code, name, string) name = code,
    4. HTTP_STATUS_MAP(XX)
    5. #undef XX
    6. };
  • 同样利用宏展开,在枚举型和字符型之间转换所需的请求方法/状态码

    1. const char* HttpStatusToString(const HttpStatus &status)
    2. {
    3. switch(status)
    4. {
    5. #define XX(code, name, string)\
    6. case HttpStatus::name: \
    7. return #string;
    8. HTTP_STATUS_MAP(XX);
    9. #undef XX
    10. default:
    11. return "<unknown>";
    12. }
    13. }

2.1.2 对于各种首部字段的封装采用KV形式Key:Value

和HTTP请求报文类处理一样。利用map容器,存储KV形式的首部字段,理论上来说首部字段是可以一个key值对应多个value值的,简单起见这里认为他们是一对一的关系。指定容器的比较规则是忽视大小写的。

2. 2 接口

2.2.1 构造函数

  1. /**
  2. * @brief HTTP响应类构造函数
  3. * @param[in] version http协议版本,默认为1.1
  4. * @param[in] close 是否支持长连接,默认不支持
  5. */
  6. HttpResponse(uint8_t version = 0x11, bool close = true);
  7. HttpResponse::HttpResponse(uint8_t version, bool close)
  8. :m_status(HttpStatus::OK)
  9. ,m_version(version)
  10. ,m_close(close)
  11. {
  12. }

2.2.2 常用接口

  1. /**
  2. * @brief 设置响应状态码
  3. * @param[in] v 具体响应状态码
  4. */
  5. void setStatus(HttpStatus v) {m_status = v;}
  6. /**
  7. * @brief 获取响应状态码
  8. * @return HttpStatus
  9. */
  10. HttpStatus getStatus() const {return m_status;}
  11. /**
  12. * @brief 设置HTTP协议版本
  13. * @param[in] v 具体版本号
  14. */
  15. void setVersion(uint8_t v) {m_version = v;}
  16. /**
  17. * @brief 获取HTTP协议版本
  18. * @return uint8_t
  19. */
  20. uint8_t getVersion() const {return m_version;}
  21. /**
  22. * @brief 设置响应报文主体
  23. * @param[in] v 具体报文内容
  24. */
  25. void setBody(const std::string& v) {m_body = v;}
  26. /**
  27. * @brief 获取响应报文主体
  28. * @return const std::string&
  29. */
  30. const std::string& getBody() const {return m_body;}
  31. /**
  32. * @brief 设置响应原因短语
  33. * @param[in] v 具体短语内容
  34. */
  35. void setReason(const std::string& v) {m_reason = v;}
  36. /**
  37. * @brief 获取响应原因短语
  38. * @return const std::string&
  39. */
  40. const std::string& getReason() const {return m_reason;}
  41. /**
  42. * @brief 设置响应首部字段组
  43. * @param[in] v 具体map容器
  44. */
  45. void setHeaders(const MapType& v) {m_headers = v;}
  46. /**
  47. * @brief 获取响应首部字段组
  48. * @return const MapType&
  49. */
  50. const MapType& getHeaders() const {return m_headers;}
  51. /**
  52. * @brief 获取连接状态
  53. * @return true 属于短连接
  54. * @return false 属于长连接
  55. */
  56. bool isClose() const {return m_close;}
  57. /**
  58. * @brief 设置连接状态
  59. * @param[in] v true = 短连接 false = 长连接
  60. */
  61. void setClose(bool v) {m_close = v;}
  62. void setHeader(const std::string& key, const std::string& val);
  63. std::string getHeader(const std::string& key, const std::string& def = "") const;
  64. void delHeader(const std::string& key);
  65. /**
  66. * @brief 查询并将存在的首部字段转换 sting---->T
  67. * @tparam[in] T 期待转换的类型
  68. * @param[in] key 查询的首部字段
  69. * @param[out] val 转换后的传出值
  70. * @param[in] def 默认转换类型
  71. * @return true 存在并转换为T
  72. * @return false 不存在并转换为默认类型
  73. */
  74. template<class T>
  75. bool checkGetHeaderAs(const std::string& key, T& val, const T& def = T())
  76. {
  77. return checkGetAs(m_headers, key, val, def);
  78. }
  79. /**
  80. * @brief 将存在的首部字段转换 string---->T
  81. * @tparam[in] T 期待转换的类型
  82. * @param[in] key 查询的首部字符
  83. * @param[in] def 默认转换类型
  84. * @return T 返回转换为T类型的数据
  85. */
  86. template<class T>
  87. T GetHeaderAs(const std::string& key, T& val, const T& def = T())
  88. {
  89. return getAs(m_headers, key, def);
  90. }

2.2.3 dump()

功能:将变量信息以流的形式重新组装为HTTP响应报文

  1. /**
  2. * @brief 将变量信息以流的形式重新组装为HTTP响应报文
  3. * @param[in] os 标准输出流
  4. * @return std::ostream&
  5. */
  6. std::ostream& dump(std::ostream& os) const;
  7. std::ostream& HttpResponse::dump(std::ostream& os)
  8. {
  9. // HTTP/1.1 200 OK
  10. //响应行封装
  11. os << "HTTP/"
  12. << ((uint32_t)(m_version >> 4))
  13. << "."
  14. << ((uint32_t)(m_version & 0x0F))
  15. << " "
  16. << (uint32_t)m_status
  17. << " "
  18. << (m_reason.size() ? m_reason : HttpStatusToString(m_status))
  19. << "\r\n";
  20. //响应首部封装
  21. for(auto &x : m_headers)
  22. {
  23. if(strcasecmp(x.first.c_str(), "connection") == 0)
  24. continue;
  25. os << x.first << ":" << x.second << "\r\n";
  26. }
  27. //单独封装连接状态
  28. os << "connection: " << (m_close ? "close" : "keep-alive") << "\r\n";
  29. //报文主体封装
  30. if(m_body.size())
  31. os << "content-length: " << m_body.size() << "\r\n"
  32. << m_body;
  33. else
  34. os << "\r\n";
  35. return os;
  36. }

2.2.4 toString()

功能:将HTTP响应报文以字符串形式输出

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

C\C++知识点补充复习:强类型枚举(枚举类)

解决问题:C++11引入新特性。对于C++来说,成员的访问往往伴随着”具名”的访问方式限定(作用域::成员)。C语言对于枚举类型的设定是”非强类型”,即:enum类型的成员名字对于全局都是可见的,导致不同的枚举类型中会有重名的风险。另外,C语言中枚举型变量被设计为常量数值,在比较时会被隐式转换为int整型。

优点:

  1. 强作用域,成员名称不会被输出到其父作用域空间中
  2. 转换限制,成员的值不可以与int隐式地相互转换
  3. 可以指定底层数据类型
    1. /*无法通过编译 因为存在两个AA 发生重定义*/
    2. enum A {AA, BB, CC};
    3. enum B {AA, DD};
    ```cpp enum A {AA, BB}; enum B {DD, CC};

class Test { public: Test(A a, B b):a(a), b(b){} A a; B b;

};

int main() { Test t(AA, DD);

  1. //会被隐式转换为int
  2. if(t.a >= B::DD)
  3. cout << "被转换为int" << endl;
  4. return 0;

}

  1. ```cpp
  2. enum class A {AA, BB};
  3. enum class B {DD, CC};
  4. class Test
  5. {
  6. public:
  7. Test(A a, B b):a(a), b(b){}
  8. A a;
  9. B b;
  10. };
  11. int main()
  12. {
  13. //必须加上作用域符
  14. Test t(A::AA, B::DD);
  15. //无法通过编译 因为已经不会将其隐式视为int
  16. if(t.a >= B::DD)
  17. cout << "被转换为int" << endl;
  18. return 0;
  19. }

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

功能:执行字符串 s1 和 s2 的逐字节比较,忽略字符的大小写。和strcmp()函数功能类似,只是比较时候不关心字符大小写带来的ACII值的影响。

  1. #include <strings.h>
  2. int strcasecmp(const char *s1, const char *s2);