支持 HTTP/1.1 API级别
暂不支持下载服务器
准备:HTTP协议
1. HTTP 请求报文/HTTP响应报文:
$ telnet www.baidu.com 80
/*请求报文*/
GET / HTTP/1.0
host: www.baidu.com
/*响应报文*/
HTTP/1.0 200 OK
Accept-Ranges: bytes
Cache-Control: no-cache
Content-Length: 9508
Content-Type: text/html
Date: Tue, 18 Jan 2022 12:27:43 GMT
....
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
请求已经正常处理,如请求的是实体,响应报文中会包含报文实体
204
No Content
请求已经正常处理,但是响应报文中不会包含任何实体,也不允许携带实体。一般,用于客户端往服务器发送消息,而服务器无需对客户端发送任何新的消息
206
Partial Content
客户端对服务器发起的时候范围请求,服务器成功返回了对于的范围实体。
3.3 3XX
重定向码 需要执行某些特殊处理以正确处理请求
301
Moved Permanently
永久性重定向。如果请求的资源已经被分配了新的URI
,那么以后使用资源应该指向当前的这个新的URI
,响应首部字段Location: 新URI
保存有指向所需资源的新的URI
。假如该资源被保存为书签,会将原来老的URI
替换为当前新的。
302
Found
临时性重定向。和永久性重定向类似,希望客户端(在本次)使用新分配的URI
来请求资源。和永久性重定向不同的是,之后该资源的标识符可能还会发生变化。假如该资源被保存为书签,那么不会去更新URI
,仍然保留的是老的标识符。
303
See Other
请求对应的资源存在着另外的URI
, 需要使用GET
方法定向重新获取资源。该状态码和302
类似,但是指定了必须使用GET
方法。
304
Not Modified
客户端发送附带条件的请求报文(请求首部字段中包含:If-Match
、If-Modified-Since
、If-None-Match
、If-Range
、If-Unmodified-Since
任意一个)时,服务器运行访问资源,但是发生请求条件不满足的情况。
3.4 4XX
客户端错误码 表明是客户端导致的错误
400
Bad Request
客户端发出的请求报文中存在语法错误。
401
Unauthorized
发送的请求需要通过HTTP认证(BASIC认证、DIGEST认证)的认证信息。如果之前已经请求过,第二次接收到该状态码,表明认证不通过。响应首部字段WWW-Authenticate:xxxxx
存储有所属的认证方案信息。
402
Forbidden
请求被服务器拒绝。如需列举拒绝原因可在实体中说明。
404
Not Found
服务器上不存在/已经删除所要请求的资源。也可以在拒绝请求但不想说明理由时使用。
3.5 5XX
服务器错误码 表明是服务器端导致的错误
500
Internal Server Error
服务器端执行请求时候发生语法错误。
502
Bad Gateway
网关发生错误。指出代理服务器从远端服务器接收到错误响应。
503
Service Unavailable
服务器暂时处于超负载或者正在进行停机维护,无法处理请求。
504
Gateway Timeout
网关超时。指出代理服务器没有从远端服务器得到及时的响应。
505
HTTP Version Not Supported
服务器不支持当前的HTTP协议版本。
4. 统一资源标识符 URI
统一资源标识符 URI Uniform Resource Identififier 唯一标记互联网上的资源
统一资源定位符URL Uniform Resource Locator 俗称的网址
统一资源名称URN Uniform Resource Name
- URI的标准格式规范如下:
scheme:[//[user:password@]host[:port]][/]path[?query][#fragment]
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协议的封装和解析的开源项目:
/* Request Methods 请求方法*/
#define HTTP_METHOD_MAP(XX) \
XX(0, DELETE, DELETE) \
XX(1, GET, GET) \
XX(2, HEAD, HEAD) \
XX(3, POST, POST) \
XX(4, PUT, PUT) \
/* pathological */ \
XX(5, CONNECT, CONNECT) \
XX(6, OPTIONS, OPTIONS) \
XX(7, TRACE, TRACE) \
/* WebDAV */ \
XX(8, COPY, COPY) \
XX(9, LOCK, LOCK) \
XX(10, MKCOL, MKCOL) \
XX(11, MOVE, MOVE) \
XX(12, PROPFIND, PROPFIND) \
XX(13, PROPPATCH, PROPPATCH) \
XX(14, SEARCH, SEARCH) \
XX(15, UNLOCK, UNLOCK) \
XX(16, BIND, BIND) \
XX(17, REBIND, REBIND) \
XX(18, UNBIND, UNBIND) \
XX(19, ACL, ACL) \
/* subversion */ \
XX(20, REPORT, REPORT) \
XX(21, MKACTIVITY, MKACTIVITY) \
XX(22, CHECKOUT, CHECKOUT) \
XX(23, MERGE, MERGE) \
/* upnp */ \
XX(24, MSEARCH, M-SEARCH) \
XX(25, NOTIFY, NOTIFY) \
XX(26, SUBSCRIBE, SUBSCRIBE) \
XX(27, UNSUBSCRIBE, UNSUBSCRIBE) \
/* RFC-5789 */ \
XX(28, PATCH, PATCH) \
XX(29, PURGE, PURGE) \
/* CalDAV */ \
XX(30, MKCALENDAR, MKCALENDAR) \
/* RFC-2068, section 19.6.1.2 */ \
XX(31, LINK, LINK) \
XX(32, UNLINK, UNLINK) \
/* icecast */ \
XX(33, SOURCE, SOURCE) \
/* Status Codes 响应状态码*/
#define HTTP_STATUS_MAP(XX) \
XX(100, CONTINUE, Continue) \
XX(101, SWITCHING_PROTOCOLS, Switching Protocols) \
XX(102, PROCESSING, Processing) \
XX(200, OK, OK) \
XX(201, CREATED, Created) \
XX(202, ACCEPTED, Accepted) \
XX(203, NON_AUTHORITATIVE_INFORMATION, Non-Authoritative Information) \
XX(204, NO_CONTENT, No Content) \
XX(205, RESET_CONTENT, Reset Content) \
XX(206, PARTIAL_CONTENT, Partial Content) \
XX(207, MULTI_STATUS, Multi-Status) \
XX(208, ALREADY_REPORTED, Already Reported) \
XX(226, IM_USED, IM Used) \
XX(300, MULTIPLE_CHOICES, Multiple Choices) \
XX(301, MOVED_PERMANENTLY, Moved Permanently) \
XX(302, FOUND, Found) \
XX(303, SEE_OTHER, See Other) \
XX(304, NOT_MODIFIED, Not Modified) \
XX(305, USE_PROXY, Use Proxy) \
XX(307, TEMPORARY_REDIRECT, Temporary Redirect) \
XX(308, PERMANENT_REDIRECT, Permanent Redirect) \
XX(400, BAD_REQUEST, Bad Request) \
XX(401, UNAUTHORIZED, Unauthorized) \
XX(402, PAYMENT_REQUIRED, Payment Required) \
XX(403, FORBIDDEN, Forbidden) \
XX(404, NOT_FOUND, Not Found) \
XX(405, METHOD_NOT_ALLOWED, Method Not Allowed) \
XX(406, NOT_ACCEPTABLE, Not Acceptable) \
XX(407, PROXY_AUTHENTICATION_REQUIRED, Proxy Authentication Required) \
XX(408, REQUEST_TIMEOUT, Request Timeout) \
XX(409, CONFLICT, Conflict) \
XX(410, GONE, Gone) \
XX(411, LENGTH_REQUIRED, Length Required) \
XX(412, PRECONDITION_FAILED, Precondition Failed) \
XX(413, PAYLOAD_TOO_LARGE, Payload Too Large) \
XX(414, URI_TOO_LONG, URI Too Long) \
XX(415, UNSUPPORTED_MEDIA_TYPE, Unsupported Media Type) \
XX(416, RANGE_NOT_SATISFIABLE, Range Not Satisfiable) \
XX(417, EXPECTATION_FAILED, Expectation Failed) \
XX(421, MISDIRECTED_REQUEST, Misdirected Request) \
XX(422, UNPROCESSABLE_ENTITY, Unprocessable Entity) \
XX(423, LOCKED, Locked) \
XX(424, FAILED_DEPENDENCY, Failed Dependency) \
XX(426, UPGRADE_REQUIRED, Upgrade Required) \
XX(428, PRECONDITION_REQUIRED, Precondition Required) \
XX(429, TOO_MANY_REQUESTS, Too Many Requests) \
XX(431, REQUEST_HEADER_FIELDS_TOO_LARGE, Request Header Fields Too Large) \
XX(451, UNAVAILABLE_FOR_LEGAL_REASONS, Unavailable For Legal Reasons) \
XX(500, INTERNAL_SERVER_ERROR, Internal Server Error) \
XX(501, NOT_IMPLEMENTED, Not Implemented) \
XX(502, BAD_GATEWAY, Bad Gateway) \
XX(503, SERVICE_UNAVAILABLE, Service Unavailable) \
XX(504, GATEWAY_TIMEOUT, Gateway Timeout) \
XX(505, HTTP_VERSION_NOT_SUPPORTED, HTTP Version Not Supported) \
XX(506, VARIANT_ALSO_NEGOTIATES, Variant Also Negotiates) \
XX(507, INSUFFICIENT_STORAGE, Insufficient Storage) \
XX(508, LOOP_DETECTED, Loop Detected) \
XX(510, NOT_EXTENDED, Not Extended) \
XX(511, NETWORK_AUTHENTICATION_REQUIRED, Network Authentication Required) \
** 辅助函数
1. checkGetAs()
功能:主要用于对存储字段的map容器进行查询和获取以及类型转换
/**
* @brief 用于对存储字段的map容器进行查询和获取以及类型转换
* @tparam 具体的map类型
* @tparam[in] T 期待转换的类型
* @param[in] key 查询的首部字段
* @param[out] val 转换后的传出值
* @param[in] def 默认转换类型
* @return true 存在并转换为T
* @return false 不存在并转换为默认类型
*/
template<class MapType, class T>
bool checkGetAs(const MapType& m, const std::string& key, T& val, const T& def = T())
{
std::string str;
auto it = m.find(key);
if(it == m.end())
{
val = def;
return false;
}
try
{
//万能转换
val = boost::lexical_cast<T>(it->second);
return true;
}
catch(...)
{
val = def;
}
return false;
}
2. getAs()
功能:主要用于对存储字段的map容器进行获取以及类型转换
/**
* @brief 主要用于对存储字段的map容器进行获取以及类型转换
* @tparam 具体的map类型
* @tparam[in] T 期待转换的类型
* @param[in] key 查询的字段
* @param[in] def 默认转换类型
* @return T
*/
template<class MapType, class T>
T getAs(const MapType& m, const std::string& key, const T& def = T())
{
auto it = m.find(key);
if(it == m.end())
return def;
try
{
return boost::lexical_cast<T>(it->second);
}
catch(...)
{
}
return def;
}
3. 字符串转为HTTP请求方法枚举型
功能:std::string --------> HttpMethod
/**
* @brief 字符串转为HTTP请求方法枚举型
* @param[in] method HTTP请求方法string
* @return HttpMethod
*/
HttpMethod StringToHttpMethod(const std::string &method);
HttpMethod StringToHttpMethod(const std::string &method)
{
#define XX(num, name, string)\
if(strcmp(#string, method.c_str()) == 0)\
return HttpMethod::name;\
HTTP_METHOD_MAP(XX);
#undef XX
return HttpMethod::INVALID_METHOD;
}
/**
* @brief 字符串转为HTTP请求方法枚举型
* @param[in] method HTTP请求方法 char*
* @return HttpMethod
*/
HttpMethod CharsToHttpMethod(const char *method);
HttpMethod CharsToHttpMethod(const char *method)
{
#define XX(num, name, string)\
if(strncmp(#string, method, strlen(#string)) == 0)\
return HttpMethod::name;\
HTTP_METHOD_MAP(XX)
#undef XX
return HttpMethod::INVALID_METHOD;
}
4. HTTP请求方法枚举型/HTTP状态码枚举型转为字符串
功能:HttpMethod --------> std::string
/ HttpStatus --------> std::string
/**
* @brief 辅助数组,将HTTP请求方法由枚举转为数组方便访问
*/
static const char* s_method_string[] = {
#define XX(num, name, string) #string,
HTTP_METHOD_MAP(XX)
#undef XX
};
/**
* @brief HTTP请求方法枚举型转为字符型
* @param[in] method HTTP请求方法枚举型具体值
* @return const char*
*/
const char* HttpMethodToString(const HttpMethod &method);
const char* HttpMethodToString(const HttpMethod &method)
{
uint32_t index = (uint32_t)method;
if(index >= sizeof(s_method_string) / sizeof(s_method_string[0]))
{
return "<unknown>";
}
return s_method_string[index];
}
/**
* @brief HTTP响应码枚举型转为字符型
* @param[in] status HTTP响应码枚举型具体值
* @return const char*
*/
const char* HttpStatusToString(const HttpStatus &status);
const char* HttpStatusToString(const HttpStatus &status)
{
switch(status)
{
#define XX(code, name, string)\
case HttpStatus::name: \
return #string;
HTTP_STATUS_MAP(XX);
#undef XX
default:
return "<unknown>";
}
}
1. HTTP请求报文类封装
/*请求报文*/
GET / HTTP/1.0
host: www.baidu.com
connection: close
.....
1.1 成员变量
成员分几部分看:请求方法、URI、协议版本、首部字段(请求、通用、主体)、报文主体、cookie处理
//HTTP请求类
class HttpRequest
{
....
private:
//请求方法
HttpMethod m_method;
//协议版本 0x11----http1.1 0x10-----http1.0
uint8_t m_version;
//是否处于长连接
bool m_close;
/*URI 资源定位符*/
//资源路径
std::string m_path;
//查询字符串
std::string m_query;
//片段标识
std::string m_fragment;
//报文主体
std::string m_body;
//首部字段
std::map<std::string, std::string, CaseInsensitiveLess> m_headers;
//参数字段
std::map<std::string, std::string, CaseInsensitiveLess> m_params;
//cookie字段
std::map<std::string, std::string, CaseInsensitiveLess> m_cookies;
};
1.1.1设置请求方法的小技巧
作为类成员变量存储时,使用枚举类型(即一个数字)来表示,真正组成报文的时候通过枚举类型将其转换为字符串类型使用即可。
利用宏展开,将之前已经定义的好的请求方法以及状态码的宏,转变为强类型枚举。
enum class HttpMethod
{
#define XX(num, name, string) name = num, //等价于从 HTTP_METHOD_MAP(XX)中取值
HTTP_METHOD_MAP(XX) //GET = 1,
#undef XX //HEAD = 2,
INVALID_METHOD //.....
};
同样利用宏展开,在枚举型和字符型之间转换所需的请求方法/状态码 ```cpp //字符串转枚举 请求方法 HttpMethod StringToHttpMethod(const std::string &method) {
define XX(num, name, string)\
if(strcmp(#string, method.c_str()) == 0)\
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,
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 “
return s_method_string[index];
}
<a name="GseLR"></a>
### 1.1.2 对于各种首部字段的封装采用KV形式`Key:Value`
利用`map`容器,存储KV形式的首部字段,理论上来说首部字段是可以一个key值对应多个value值的,简单起见这里认为他们是一对一的关系。指定容器的比较规则是忽视大小写的。
```cpp
//忽视字母大小写仿函数
struct CaseInsensitiveLess
{
bool operator()(const std::string& lhs, const std::string& rhs) const;
};
bool CaseInsensitiveLess::operator()(const std::string& lhs, const std::string& rhs) const
{
//忽视字母大小写比较
return strcasecmp(lhs.c_str(), rhs.c_str()) < 0;
}
//首部字段
std::map<std::string, std::string, CaseInsensitiveLess> m_headers;
//参数字段
std::map<std::string, std::string, CaseInsensitiveLess> m_params;
//cookie字段
std::map<std::string, std::string, CaseInsensitiveLess> m_cookies;
1.2 接口
1.2.1 构造函数
/**
* @brief HTTP请求类构造函数
* @param[in] version http协议版本,默认为1.1
* @param[in] close 是否支持长连接,默认不支持
*/
HttpRequest(uint8_t version = 0x11, bool close = true);
HttpRequest::HttpRequest(uint8_t version, bool close)
:m_method(HttpMethod::GET)
,m_version(version)
,m_close(close)
,m_path("/") //默认是根路径
{
}
1.2.2 常用接口
/**
* @brief 设置请求方法
* @param[in] m 具体请求方法
*/
void setMethod(HttpMethod m) {m_method = m;}
/**
* @brief 获取请求方法
* @return HttpMethod
*/
HttpMethod getMethod() const {return m_method;}
/**
* @brief 设置协议版本
* @param[in] v 具体版本号
*/
void setVersion(uint8_t v) {m_version = v;}
/**
* @brief 获取协议版本
* @return uint8_t
*/
uint8_t getVersion() const {return m_version;}
/**
* @brief 设置资源路径
* @param[in] s 具体资源路径
*/
void setPath(const std::string& s) {m_path = s;}
/**
* @brief 获取资源路径
* @return const std::string&
*/
const std::string& getPath() const {return m_path;}
/**
* @brief 设置查询字符串
* @param[in] s 具体查询字符串
*/
void setQuery(const std::string& s) {m_query = s;}
/**
* @brief 获取查询字符串
* @return const std::string&
*/
const std::string& getQuery() const {return m_query;}
/**
* @brief 设置片段标识符
* @param[in] s 具体片段标识
*/
void setFragment(const std::string& s) {m_fragment = s;}
/**
* @brief 获取片段标识符
* @return const std::string&
*/
const std::string& getFragment() const {return m_fragment;}
/**
* @brief 设置报文主体
* @param[in] s 设置具体主体内容
*/
void setBody(const std::string& s) {m_body = s;}
/**
* @brief 获取报文主体
* @return const std::string&
*/
const std::string& getBody() const {return m_body;}
/**
* @brief 设置报文首部字段组
* @param[in] v 具体map容器
*/
void setHeaders(const MapType& v) {m_headers = v;}
/**
* @brief 获取报文首部字段组
* @return const MapType&
*/
const MapType& getHeaders() const {return m_headers;}
/**
* @brief 设置参数组
* @param[in] v 具体map容器
*/
void setParams(const MapType& v) {m_params = v;}
/**
* @brief 获取参数组
* @return const MapType&
*/
const MapType& getParams() const {return m_params;}
/**
* @brief 设置cookie字段组
* @param[in] v 具体map容器
*/
void setCookies(const MapType& v) {m_cookies = v;}
/**
* @brief 获取cookie字段组
* @return const MapType&
*/
const MapType& getCookies() const {return m_cookies;}
/**
* @brief 获取连接状态
* @return true 属于短连接
* @return false 属于长连接
*/
bool isClose() const {return m_close;}
/**
* @brief 设置连接状态
* @param[in] v true = 短连接 false = 长连接
*/
void setClose(bool v) {m_close = v;}
//设置/获取/删除/查询具体首部字段
void setHeader(const std::string& key, const std::string& val);
std::string getHeadr(const std::string& key, const std::string& def = "") const;
void delHeader(const std::string& key);
bool hasHeader(const std::string& key, std::string * val = nullptr);
//设置/获取/删除/查询具体参数
void setParam(const std::string& key, const std::string& val);
std::string getParam(const std::string& key, const std::string& def = "") const;
void delParam(const std::string& key);
bool hasParam(const std::string& key, std::string * val = nullptr);
//设置/获取/删除/查询具体cookie
void setCookie(const std::string& key, const std::string& val);
std::string getCookie(const std::string& key, const std::string& def = "") const;
void delCookie(const std::string& key);
bool hasCookie(const std::string& key, std::string * val = nullptr);
/**
* @brief 查询并将存在的首部字段转换 sting---->T
* @tparam[in] T 期待转换的类型
* @param[in] key 查询的首部字段
* @param[out] val 转换后的传出值
* @param[in] def 默认转换类型
* @return true 存在并转换为T
* @return false 不存在并转换为默认类型
*/
template<class T>
bool checkGetHeaderAs(const std::string& key, T& val, const T& def = T())
{
return checkGetAs(m_headers, key, val, def);
}
/**
* @brief 将存在的首部字段转换 string---->T
* @tparam[in] T 期待转换的类型
* @param[in] key 查询的首部字符
* @param[in] def 默认转换类型
* @return T 返回转换为T类型的数据
*/
template<class T>
T GetHeaderAs(const std::string& key, const T& def = T())
{
return getAs(m_headers, key, def);
}
/**
* @brief 查询并将存在的参数字段转换 sting---->T
* @tparam[in] T 期待转换的类型
* @param[in] key 查询的参数字段
* @param[out] val 转换后的传出值
* @param[in] def 默认转换类型
* @return true 存在并转换为T
* @return false 不存在并转换为默认类型
*/
template<class T>
bool checkGetParamAs(const std::string& key, T& val, const T& def = T())
{
return checkGetAs(m_params, key, val, def);
}
/**
* @brief 将存在的参数字段转换 string---->T
* @tparam[in] T 期待转换的类型
* @param[in] key 查询的参数字段
* @param[in] def 默认转换类型
* @return T 返回转换为T类型的数据
*/
template<class T>
T GetParamAs(const std::string& key, const T& def = T())
{
return getAs(m_params, key, def);
}
/**
* @brief 查询并将存在的cookie字段转换 sting---->T
* @tparam[in] T 期待转换的类型
* @param[in] key 查询的cookie字段
* @param[out] val 转换后的传出值
* @param[in] def 默认转换类型
* @return true 存在并转换为T
* @return false 不存在并转换为默认类型
*/
template<class T>
bool checkGetCookieAs(const std::string& key, T& val, const T& def = T())
{
return checkGetAs(m_params, key, val, def);
}
/**
* @brief 将存在的cookie字段段转换 string---->T
* @tparam[in] T 期待转换的类型
* @param[in] key 查询的cookie字段
* @param[in] def 默认转换类型
* @return T 返回转换为T类型的数据
*/
template<class T>
T GetCookieAs(const std::string& key, const T& def = T())
{
return getAs(m_params, key, def);
}
1.2.3 dump()
功能:将变量信息以流的形式重新组装为HTTP请求报文
/**
* @brief 将变量信息以流的形式重新组装为HTTP请求报文
* @param[in] os 标准输出流
* @return std::ostream&
*/
std::ostream& dump(std::ostream& os) const;
std::ostream& HttpRequest::dump(std::ostream& os)
{
// GET /uri HTTP/1.1
//Host: www.baidu.com
//空行CR+LF
//实体
//请求行封装
os << HttpMethodToString(m_method) << " "
<< m_path
<< (m_query.size() ? "?" : "")
<< m_query
<< (m_fragment.size() ? "#" : "")
<< m_fragment
<< " HTTP/"
<< ((uint32_t)(m_version >> 4)) //把低4位移走 只保留高4位
<< "."
<< ((uint32_t)(m_version & 0x0F)) //把高4位置0 只保留低4位
<< "\r\n";
//连接状态首部字段单独列举
os << "connection:" << (m_close ? "close" : "keep-alive") << "\r\n";
//首部字段封装
for(auto &x : m_headers)
{
if(strcasecmp(x.first.c_str(), "connection") == 0)
continue;
os << x.first << ":" << x.second << "\r\n";
}
//空行封装
os << "\r\n";
//报文实体封装
if(m_body.size())
os << "content-length: " << m_body.size() << "\r\n\r\n"
<< m_body;
else
os << "\r\n";
return os;
}
1.2.4 toString()
功能:将HTTP请求报文以字符串形式输出
/**
* @brief 将HTTP请求报文以字符串形式输出
* @return std::string
*/
std::string toString() const;
std::string HttpRequest::toString() const
{
std::stringstream ss;
dump(ss);
return ss.str();
}
2. HTTP响应报文类封装
和HTTP请求报文类的封装类似。
/*响应报文*/
HTTP/1.0 200 OK
Accept-Ranges: bytes
Cache-Control: no-cache
Content-Length: 9508
Content-Type: text/html
Date: Tue, 18 Jan 2022 12:27:43 GMT
....
2.1 成员变量
成员分几部分看:协议版本、状态码、原因短语、首部字段(响应、通用、主体)、报文主体
//HTTP响应类
class HttpResponse
{
.....
private:
//响应状态码
HttpStatus m_status;
//协议版本 0x10----HTTP1.0 0x11-----HTTP1.1
uint8_t m_version;
//是否为长连接
bool m_close;
//响应报文主体
std::string m_body;
//响应状态原因短语
std::string m_reason;
//响应首部字段
std::map<std::string, std::string, CaseInsensitiveLess> m_headers;
};
2.1.1 设置状态码的小技巧
和请求方法的处理一样。作为类成员变量存储时,使用枚举类型(即一个数字)来表示,真正组成报文的时候通过枚举类型将其转换为字符串类型使用即可。
利用宏展开,将之前已经定义的好的请求方法以及状态码的宏,转变为强类型枚举。
enum class HttpStatus
{
#define XX(code, name, string) name = code,
HTTP_STATUS_MAP(XX)
#undef XX
};
同样利用宏展开,在枚举型和字符型之间转换所需的请求方法/状态码
const char* HttpStatusToString(const HttpStatus &status)
{
switch(status)
{
#define XX(code, name, string)\
case HttpStatus::name: \
return #string;
HTTP_STATUS_MAP(XX);
#undef XX
default:
return "<unknown>";
}
}
2.1.2 对于各种首部字段的封装采用KV形式Key:Value
和HTTP请求报文类处理一样。利用map
容器,存储KV形式的首部字段,理论上来说首部字段是可以一个key值对应多个value值的,简单起见这里认为他们是一对一的关系。指定容器的比较规则是忽视大小写的。
2. 2 接口
2.2.1 构造函数
/**
* @brief HTTP响应类构造函数
* @param[in] version http协议版本,默认为1.1
* @param[in] close 是否支持长连接,默认不支持
*/
HttpResponse(uint8_t version = 0x11, bool close = true);
HttpResponse::HttpResponse(uint8_t version, bool close)
:m_status(HttpStatus::OK)
,m_version(version)
,m_close(close)
{
}
2.2.2 常用接口
/**
* @brief 设置响应状态码
* @param[in] v 具体响应状态码
*/
void setStatus(HttpStatus v) {m_status = v;}
/**
* @brief 获取响应状态码
* @return HttpStatus
*/
HttpStatus getStatus() const {return m_status;}
/**
* @brief 设置HTTP协议版本
* @param[in] v 具体版本号
*/
void setVersion(uint8_t v) {m_version = v;}
/**
* @brief 获取HTTP协议版本
* @return uint8_t
*/
uint8_t getVersion() const {return m_version;}
/**
* @brief 设置响应报文主体
* @param[in] v 具体报文内容
*/
void setBody(const std::string& v) {m_body = v;}
/**
* @brief 获取响应报文主体
* @return const std::string&
*/
const std::string& getBody() const {return m_body;}
/**
* @brief 设置响应原因短语
* @param[in] v 具体短语内容
*/
void setReason(const std::string& v) {m_reason = v;}
/**
* @brief 获取响应原因短语
* @return const std::string&
*/
const std::string& getReason() const {return m_reason;}
/**
* @brief 设置响应首部字段组
* @param[in] v 具体map容器
*/
void setHeaders(const MapType& v) {m_headers = v;}
/**
* @brief 获取响应首部字段组
* @return const MapType&
*/
const MapType& getHeaders() const {return m_headers;}
/**
* @brief 获取连接状态
* @return true 属于短连接
* @return false 属于长连接
*/
bool isClose() const {return m_close;}
/**
* @brief 设置连接状态
* @param[in] v true = 短连接 false = 长连接
*/
void setClose(bool v) {m_close = v;}
void setHeader(const std::string& key, const std::string& val);
std::string getHeader(const std::string& key, const std::string& def = "") const;
void delHeader(const std::string& key);
/**
* @brief 查询并将存在的首部字段转换 sting---->T
* @tparam[in] T 期待转换的类型
* @param[in] key 查询的首部字段
* @param[out] val 转换后的传出值
* @param[in] def 默认转换类型
* @return true 存在并转换为T
* @return false 不存在并转换为默认类型
*/
template<class T>
bool checkGetHeaderAs(const std::string& key, T& val, const T& def = T())
{
return checkGetAs(m_headers, key, val, def);
}
/**
* @brief 将存在的首部字段转换 string---->T
* @tparam[in] T 期待转换的类型
* @param[in] key 查询的首部字符
* @param[in] def 默认转换类型
* @return T 返回转换为T类型的数据
*/
template<class T>
T GetHeaderAs(const std::string& key, T& val, const T& def = T())
{
return getAs(m_headers, key, def);
}
2.2.3 dump()
功能:将变量信息以流的形式重新组装为HTTP响应报文
/**
* @brief 将变量信息以流的形式重新组装为HTTP响应报文
* @param[in] os 标准输出流
* @return std::ostream&
*/
std::ostream& dump(std::ostream& os) const;
std::ostream& HttpResponse::dump(std::ostream& os)
{
// HTTP/1.1 200 OK
//响应行封装
os << "HTTP/"
<< ((uint32_t)(m_version >> 4))
<< "."
<< ((uint32_t)(m_version & 0x0F))
<< " "
<< (uint32_t)m_status
<< " "
<< (m_reason.size() ? m_reason : HttpStatusToString(m_status))
<< "\r\n";
//响应首部封装
for(auto &x : m_headers)
{
if(strcasecmp(x.first.c_str(), "connection") == 0)
continue;
os << x.first << ":" << x.second << "\r\n";
}
//单独封装连接状态
os << "connection: " << (m_close ? "close" : "keep-alive") << "\r\n";
//报文主体封装
if(m_body.size())
os << "content-length: " << m_body.size() << "\r\n"
<< m_body;
else
os << "\r\n";
return os;
}
2.2.4 toString()
功能:将HTTP响应报文以字符串形式输出
/**
* @brief 将HTTP响应报文以字符串形式输出
* @return std::string
*/
std::string toString() const;
std::string HttpResponse::toString() const
{
std::stringstream ss;
dump(ss);
return ss.str();
}
C\C++知识点补充复习:强类型枚举(枚举类)
解决问题:C++11引入新特性。对于C++来说,成员的访问往往伴随着”具名”的访问方式限定(作用域::成员
)。C语言对于枚举类型的设定是”非强类型”,即:enum
类型的成员名字对于全局都是可见的,导致不同的枚举类型中会有重名的风险。另外,C语言中枚举型变量被设计为常量数值,在比较时会被隐式转换为int
整型。
优点:
- 强作用域,成员名称不会被输出到其父作用域空间中
- 转换限制,成员的值不可以与
int
隐式地相互转换 - 可以指定底层数据类型
```cpp enum A {AA, BB}; enum B {DD, CC};/*无法通过编译 因为存在两个AA 发生重定义*/
enum A {AA, BB, CC};
enum B {AA, DD};
class Test { public: Test(A a, B b):a(a), b(b){} A a; B b;
};
int main() { Test t(AA, DD);
//会被隐式转换为int
if(t.a >= B::DD)
cout << "被转换为int" << endl;
return 0;
}
```cpp
enum class A {AA, BB};
enum class B {DD, CC};
class Test
{
public:
Test(A a, B b):a(a), b(b){}
A a;
B b;
};
int main()
{
//必须加上作用域符
Test t(A::AA, B::DD);
//无法通过编译 因为已经不会将其隐式视为int
if(t.a >= B::DD)
cout << "被转换为int" << endl;
return 0;
}
C\C++知识点补充复习:strcasecmp()函数
功能:执行字符串 s1 和 s2 的逐字节比较,忽略字符的大小写。和strcmp()
函数功能类似,只是比较时候不关心字符大小写带来的ACII值的影响。
#include <strings.h>
int strcasecmp(const char *s1, const char *s2);