前置知识
作用
- HTTP 消息头用于描述资源或服务器或客户端的行为
分类
根据不同上下文,可将消息头分为:
- General headers(通用头): 同时适用于请求和响应消息,但与最终消息主体中传输的数据无关的消息头。
- Request headers(请求头): 包含更多有关要获取的资源或客户端本身信息的消息头。
- Response headers(响应头): 包含有关响应的补充信息,如其位置或服务器本身(名称和版本等)的消息头。
- Entity headers(实体头): 包含有关实体主体的更多信息,比如主体长(Content-Length)度或其MIME类型。
又因为:实体报头可能同时存在于 HTTP 请求和响应信息中。因此可以把实体头看成是通用头,所以它们可以分为:
- 通用头: 既可以用于客户端请求又可以用于客户端响应
- 请求头:仅用于客户端请求
- 响应头:仅用于服务端响应
一般实现:我们可以将通用头实现为一个基础类,然后请求头和响应头作为其子类,这样它们就可以自然而然的继承通用头的属性和方法了。 缺点是:
- 引入了多个类,对使用者不友好
- 增加一些成本(理由请参见第三点),个人对继承的使用机会一般在于当需要用到动态时,但是这里的通用头对请求头和响应头的方法是一样的,因此没有必要引入继承机制
- 更重要的是:一个header要么是请求头,要么是响应头;通用头只是请求头和响应头的组成部分(使用私有继承/成员变量实现);因此我们不能生成一个通用头的实例,也就是说它应该是一个纯虚类(这里会引入虚表,一定会增加成本),而纯虚类的使用场景一定是动态(个人理解),而这里无需用到多态。
继承机制的实现原理:函数指针和类型转换
因此这里用一个单独的类http_header来实现,通过一个布尔类型变量is_request_
来区分这是请求头还是响应头(一个类要么是请求头,要么是响应头,不可能是其他)
#ifndef OCEANSTAR_HTTP_HTTP_HEADER_H
#define OCEANSTAR_HTTP_HTTP_HEADER_H
namespace oceanstar{
class http_header{
public:
/**
* 判断是否是 HTTP 请求头
* @return {bool} 返回 false 表明是 HTTP 响应头
*/
bool is_request(void) const;
/**
* 设置 HTTP 头是客户端的请求头还是服务器的响应头
* @param onoff {bool} true 表示是请求头,否则表示响应头
* @return {http_header&} 返回本对象的引用,便于用户连续操作
*/
http_header& set_request_mode(bool onoff);
private:
bool is_request_; // 是请求头还是响应头
};
}
#endif //OCEANSTAR_HTTP_HTTP_HEADER_H
#include "http_header.h"
namespace oceanstar{
bool http_header::is_request() const
{
return is_request_;
}
http_header& http_header::set_request_mode(bool onoff)
{
is_request_ = onoff;
return *this;
}
}
格式
- 一个请求头由名称(不区分大小写)后跟一个冒号“:”,冒号后跟具体的值(不带换行符)组成。该值前面的引导空白会被忽略
例如: Content-Language: de-DE
。
- 一个请求头中有多个项
- 客户可以禁止或者开启某些项
因此,我们新建一个类来表示 【项】,该类应该有三个成员变量:name_、value_、off_ (表示禁用1或者开启0)
/* name-value 格式的条目 */
class HTTP_HDR_ENTRY {
public:
HTTP_HDR_ENTRY(const char *name, const char *value){
name_ = name;
value_ = value;
off_ = 0;
}
~HTTP_HDR_ENTRY(){
}
const char * get_name() const{
return name_.c_str();
}
const char * get_value() const{
return value_.c_str();
}
void set_name(const char *name){
name_ = name;
}
void set_value(const char *value){
value_ = value;
}
std::string name_;
std::string value_;
int off_;
};
inline HTTP_HDR_ENTRY * http_hdr_entry_new(const char *name, const char *value){
return new HTTP_HDR_ENTRY(name, value);
}
inline void http_hdr_entry_free(HTTP_HDR_ENTRY * hdrEntry){
delete hdrEntry;
}
同时在一个http_header类中用std::list来存储这些项:
//
// Created by oceanstar on 2021/8/11.
//
#ifndef OCEANSTAR_HTTP_HTTP_HEADER_H
#define OCEANSTAR_HTTP_HTTP_HEADER_H
#include <protocol/HTTP_HDR_ENTRY.h>
#include <list>
namespace oceanstar{
class http_header{
public:
/**
* 向 HTTP 头中添加字段
* @param name {const char*} 字段名,非空指针
* @param value {const char*} 字段值,非空指针
* @param replace {bool} 如果存在重复项时是否自动覆盖旧数据
* @return {http_header&} 返回本对象的引用,便于用户连续操作
*/
http_header& add_entry(const char* name, const char* value, bool replace = true);
/**
* 从 HTTP 头中获得指定的头部字段
* @param name {const char*} 字段名,非空指针
* @return {const char*} 返回值 NULL 表示不存在
*/
const char* get_entry(const char* name) const;
private:
bool is_request_; // 是请求头还是响应头
std::list<HTTP_HDR_ENTRY*> entries_;
};
}
#endif //OCEANSTAR_HTTP_HTTP_HEADER_H
//
// Created by oceanstar on 2021/8/11.
//
#include <string.h>
#include "http_header.h"
namespace oceanstar{
http_header& http_header::add_entry(const char* name, const char* value, bool replace /* = true */){
if (name == NULL || *name == 0 || value == NULL || *value == 0) {
return *this;
}
if (replace) {
std::list<HTTP_HDR_ENTRY*>::iterator it = entries_.begin();
for (; it != entries_.end(); ++it) {
if (strcasecmp((*it)->get_name(), name) == 0) {
(*it)->set_value(value) ;
return *this;
}
}
}
HTTP_HDR_ENTRY * entry = http_hdr_entry_new(name, value);
entries_.push_back(entry);
return *this;
}
const char* http_header::get_entry(const char* name) const{
for (std::list<HTTP_HDR_ENTRY*>::const_iterator cit = entries_.begin();
cit != entries_.end(); ++cit) {
if (strcasecmp((*cit)->get_name(), name) == 0) {
return (*cit)->get_value();
}
}
return NULL;
}
}
Http头中除了有k-v格式的头外,还有另外格式的,如下:
另外,有些项目用的很频繁,比如说Connection
,我们不能每次需要Connection时都去list中循环获取,因此,我们需要定义一些单独的变量
通用头
有些header既可以用于请求,又可以用于响应,我们先来实现这个
添加:HTTP版本
http_header(){
version_[0] = 0;
}
private:
char version_[8]; // HTTP 协议版本号
http_header& http_header::set_proto_version(const char* version)
{
if (version && *version) {
strncpy(version_, version, sizeof(version_));
}
return *this;
}
添加:用于分段请求的range字段
/**
* 设置 HTTP 请求头(响应头)中的 Range 字段,用于分段请求(响应)数据,
* 多用于支持断点续传的 WEB 服务器中
* @param from {http_off_t} 起始偏移位置,下标从 0 开始,该
* 值当 >= 0 时才有效
* @param to {http_off_t} 请求结束偏移位置,下标从 0 开始,
* 在请求头中当该值输入 < 0 时,则认为是请求从起始位置开始至最终长度位置
* @return {http_header&} 返回本对象的引用,便于用户连续操作
*/
http_header& set_range(long long from, long long to);
/**
* 对于响应头在分段传输前需要调用此函数设置数据体总长度
* @param total {http_off_t} 仅对于响应头,该参数需要设为数据总长度
* @return {http_header&}
*/
http_header& set_range_total(long long total);
/**
* 获得由 set_range 设置的分段请求位置值
* @param from {http_off_t*} 非空时存储起始位置偏移
* @param to {http_off_t*} 非空时存储结束位置偏移
*/
void get_range(long long int* from, long long int* to);
private:
long long int range_from_ = -1; // 请求头中,range 起始位置
long long int range_to_ = -1; // 请求头中,range 结束位置
long long int range_total_ = -1; // range 传输模式下记录数据总长度
http_header& http_header::set_range_total(http_off_t total)
{
range_total_ = total;
return *this;
}
http_header& http_header::set_range(http_off_t from, http_off_t to)
{
range_from_ = from;
if (to >= from) {
range_to_ = to;
} else {
range_to_ = -1;
}
return *this;
}
void http_header::get_range(http_off_t* from, http_off_t* to)
{
if (from) {
*from = range_from_;
}
if (to) {
*to = range_to_;
}
}
添加:content_length && Content-Type
content_length和Content-Type是一起作用的
content_length
- Content-Length是一个实体消息首部
private:
long long int content_length_ = -1; // HTTP 数据体长度
/**
* 设置 HTTP 头中的 Content-Length 字段
* @param n {int64} 设置值
* @return {http_header&} 返回本对象的引用,便于用户连续操作
*/
http_header& set_content_length(long long int n);
long long int get_content_length() const
{
return content_length_;
}
Content-Type
Content-Type也是一个实体报头
/**
* 设置 HTTP 头中的 Content-Type 字段
* @param value {const char*} 设置值
* @return {http_header&} 返回本对象的引用,便于用户连续操作
*/
http_header& set_content_type(const char* value);
http_header& http_header::set_content_type(const char* value)
{
add_entry("Content-Type", value);
return *this;
}
添加:Connection–keep_alive(功能尚未完成)
/**
* 设置 HTTP 头中的 Connection 字段,是否保持长连接
* @param on {bool} 是否保持长连接
* @return {http_header&} 返回本对象的引用,便于用户连续操作
*/
http_header& set_keep_alive(bool on);
/**
* 检查当前头是否设置了保持长连接选项
*/
bool get_keep_alive() const
{
return keep_alive_;
}
private:
bool keep_alive_; // 是否保持长连接
http_header& http_header::set_keep_alive(bool on)
{
keep_alive_ = on;
return *this;
}
添加:upgrade_
- HTTP 101 Switching Protocol(协议切换)状态码表示服务器应客户端升级协议的请求(Upgrade (en-US)请求头)正在切换协议。
- 服务器会发送一个Upgrade (en-US)响应头来表明其正在切换过去的协议。
http_header(){
upgrade_ = NULL;
}
http_header& set_upgrade(const char* value = "websocket");
const char* get_upgrade(void) const
{
return upgrade_;
}
private:
char* upgrade_;
// 这个函数一定是响应调用
http_header& http_header::set_upgrade(const char* value /* = "websocket" */)
{
if (value && *value) {
upgrade_ = strdup(value);
status_ = 101; // automatic set status_ to 101
} else {
upgrade_ = NULL;
}
return *this;
}
添加:cookie
/**
* 向 HTTP 头中添加 cookie
* @param name {const char*} cookie 名
* @param value {const char*} cookie 值
* @param domain {const char*} 所属域
* @param path {const char*} 存储路径
* @param expires {time_t} 过期时间,当该值为 0 时表示不过期,
* > 0 时,则从现在起再增加 expires 即为过期时间,单位为秒
* @return {http_header&} 返回本对象的引用,便于用户连续操作
*/
http_header& add_cookie(const char* name, const char* value,
const char* domain = NULL, const char* path = NULL,
time_t expires = 0);
/**
* 从 HTTP 头中获得对应名称的 cookie 对象
* @param name {const char*} cookie 名
* @return {const HttpCookie*}
*/
const http_cookie* get_cookie(const char* name) const;
private:
std::list<http_cookie*> cookies_; // cookies 集合
const http_cookie* http_header::get_cookie(const char* name) const{
if (name == NULL || *name == 0) {
return NULL;
}
for (std::list<http_cookie*>::const_iterator cit = cookies_.begin();
cit != cookies_.end(); ++cit) {
if (!strcasecmp((*cit)->getName(), name)) {
return *cit;
}
}
return NULL;
}
http_header& http_header::add_cookie(const char* name, const char* value,
const char* domain /* = NULL */, const char* path /* = NULL */,
time_t expires /* = 0 */){
if (name == NULL || *name == 0 || value == NULL) {
return *this;
}
http_cookie * cookie = http_cookie_new(name, value);
if (domain && *domain) {
cookie->setDomain(domain);
}
if (path && *path) {
cookie->setPath(path);
}
if (expires > 0) {
cookie->setExpires(expires);
}
cookies_.push_back(cookie);
return *this;
}
生成通用头
void http_header::build_common(std::string& buf) const{
if (!entries_.empty()) {
std::list<HTTP_HDR_ENTRY*>::const_iterator it = entries_.begin();
for (; it != entries_.end(); ++it) {
buf += format("%s: %s\r\n", (*it)->get_name(), (*it)->get_value());
}
}
if (chunked_transfer_) {
buf += format("Transfer-Encoding: chunked\r\n");
} else if (content_length_ >= 0) {
buf += format("Transfer-Encoding: %d\r\n", content_length_);
}
if (!is_request_ && cgi_mode_) { // 是响应而且是cgi响应就直接返回
return;
}
if (upgrade_ && *upgrade_) {
buf += format("Upgrade: %s\r\nConnection: Upgrade\r\n", upgrade_);
} else if (keep_alive_) {
buf += format("Connection: Keep-Alive\r\n");
} else {
buf += format("Connection: Close\r\n");
}
}
请求头
添加:请求方法
// HTTP 请求方法
typedef enum
{
HTTP_METHOD_UNKNOWN, // 未知方法
HTTP_METHOD_GET, // GET 方法
HTTP_METHOD_POST, // POST 方法
HTTP_METHOD_PUT, // PUT 方法
HTTP_METHOD_CONNECT, // CONNECT 方法
HTTP_METHOD_PURGE, // PURGE 方法
HTTP_METHOD_DELETE, // DELETE 方法
HTTP_METHOD_HEAD, // HEAD 方法
HTTP_METHOD_OPTION, // OPTION 方法
HTTP_METHOD_PROPFIND, // PROPFIND 方法
HTTP_METHOD_PATCH, // PATCH 方法
HTTP_METHOD_OTHER, // 其它的方法
} http_method_t;
//
// Created by oceanstar on 2021/8/11.
//
#ifndef OCEANSTAR_HTTP_HTTP_HEADER_H
#define OCEANSTAR_HTTP_HTTP_HEADER_H
#include <protocol/HTTP_HDR_ENTRY.h>
#include <list>
#include "http_type.h"
namespace oceanstar{
class http_header{
public:
/**
* 设置 HTTP 协议的请求方法,如果不调用此函数,则默认用 GET 方法
* @param method {http_method_t} HTTP 请求方法
* @return {http_header&} 返回本对象的引用,便于用户连续操作
*/
http_header& set_method(http_method_t method);
/**
* 设置 HTTP 协议的请求方法,本函数允许用户扩展 HTTP 请求方法,
* 通过该函数设置的请求方法仅影响 HTTP 请求过程
* @param method {const char*} 请求方法
* @return {http_header&} 返回本对象的引用,便于用户连续操作
*/
http_header& set_method(const char* method);
/**
* 当作为请求头时,本函数取得当前邮件头的请求方法
* @param buf {string*} 存储用字符串表示的请求方法
* @return {http_method_t}
*/
http_method_t get_method(std::string* buf = NULL) const;
private:
bool is_request_; // 是请求头还是响应头
std::list<HTTP_HDR_ENTRY*> entries_;
http_method_t method_; // HTTP 请求的方法
char method_s_[64]; // HTTP 请求方法以字符串表示
};
}
#endif //OCEANSTAR_HTTP_HTTP_HEADER_H
#define CP(x, y) strncpy(x, y, sizeof(x))
http_header& http_header::set_method(http_method_t method)
{
method_ = method;
switch(method_) {
case HTTP_METHOD_GET:
CP(method_s_, "GET");
break;
case HTTP_METHOD_POST:
CP(method_s_, "POST");
break;
case HTTP_METHOD_PUT:
CP(method_s_, "PUT");
break;
case HTTP_METHOD_CONNECT:
CP(method_s_, "CONNECT");
break;
case HTTP_METHOD_PURGE:
CP(method_s_, "PURGE");
break;
case HTTP_METHOD_DELETE:
CP(method_s_, "DELETE");
break;
case HTTP_METHOD_HEAD:
CP(method_s_, "HEAD");
break;
case HTTP_METHOD_OPTION:
CP(method_s_, "OPTIONS");
break;
case HTTP_METHOD_PROPFIND:
CP(method_s_, "PROPFIND");
break;
case HTTP_METHOD_PATCH:
CP(method_s_, "PATCH");
break;
default:
CP(method_s_, "UNKNOWN");
break;
}
return *this;
}
http_header& http_header::set_method(const char* method)
{
if (strcasecmp(method, "GET") == 0) {
method_ = HTTP_METHOD_GET;
} else if (strcasecmp(method, "POST") == 0) {
method_ = HTTP_METHOD_POST;
} else if (strcasecmp(method, "PUT") == 0) {
method_ = HTTP_METHOD_PUT;
} else if (strcasecmp(method, "CONNECT") == 0) {
method_ = HTTP_METHOD_CONNECT;
} else if (strcasecmp(method, "PURGE") == 0) {
method_ = HTTP_METHOD_PURGE;
} else if (strcasecmp(method, "DELETE") == 0) {
method_ = HTTP_METHOD_DELETE;
} else if (strcasecmp(method, "HEAD") == 0) {
method_ = HTTP_METHOD_HEAD;
} else if (strcasecmp(method, "OPTIONS") == 0) {
method_ = HTTP_METHOD_OPTION;
} else if (strcasecmp(method, "PROPFIND") == 0) {
method_ = HTTP_METHOD_PROPFIND;
} else if (strcasecmp(method, "PATCH") == 0) {
method_ = HTTP_METHOD_PATCH;
} else if (*method != 0) {
method_ = HTTP_METHOD_OTHER;
} else {
method_ = HTTP_METHOD_UNKNOWN;
}
CP(method_s_, method);
return *this;
}
http_method_t http_header::get_method(std::string* buf /* = NULL */) const
{
if (buf) {
*buf = method_s_;
}
return method_;
}
添加:请求路径
分析:一个请求URI中能够获取到的东西有:
- 请求参数
- 请求路径
- 请求主机
添加:请求参数
因为一个请求中可能有多个参数,因此用list存储
class http_header{
public:
/**
* 向请求的 URL 中添加参数对,当只有参数名没有参数值时则:
* 1、参数名非空串,但参数值为空指针,则 URL 参数中只有:{name}
* 2、参数名非空串,但参数值为空串,则 URL参数中为:{name}=
* @param name {const char*} 参数名,不能为空指针
* @param value {const char*} 参数值,当为空指针时,仅添加参数名,
* @return {http_header&} 返回本对象的引用,便于用户连续操作
*/
http_header& add_param(const char* name, const char* value);
private:
std::list<http_param*> params_; // 请求参数集合
http_header& http_header::add_param(const char* name, const char* value){
if (name == NULL || *name == 0) {
return *this;
}
std::list<http_param*>::iterator it = params_.begin();
for (; it != params_.end(); ++it) {
if (strcasecmp((*it)->name, name) == 0) {
if (value) {
(*it)->value = strdup(value);
} else {
(*it)->value = NULL;
}
return *this;
}
}
http_param* param = (http_param*) malloc(sizeof(http_param));
param->name = strdup(name);
if (value) {
param->value = strdup(value);
} else {
param->value = NULL;
}
params_.push_back(param);
return *this;
}
添加:请求路径+请求主机
/**
* 设置请求的 URL,url 格式示例如下:
* 1、http://www.test.com/
* 2、/cgi-bin/test.cgi
* 3、http://www.test.com/cgi-bin/test.cgi
* 3、http://www.test.com/cgi-bin/test.cgi?name=value
* 4、/cgi-bin/test.cgi?name=value
* 5、http://www.test.com
* 如果该 url 中有主机字段,则内部自动添加主机;
* 如果该 url 中有参数字段,则内部自动进行处理并调用 add_param 方法;
* 调用该函数后用户仍可以调用 add_param 等函数添加其它参数;
* 当参数字段只有参数名没有参数值时,该参数将会被忽略,所以如果想
* 单独添加参数名,应该调用 add_param 方法来添加
* @param url {const char*} 请求的 url,非空指针
* @param encoding {bool} 是否对存在于 url 中的参数进行 url 编码
* @return {http_header&} 返回本对象的引用,便于用户连续操作
*/
http_header& set_url(const char* url, bool encoding = true);
/**
* 设置 HTTP 请求头的 HOST 字段
* @param value {const char*} 请求头的 HOST 字段值
* @return {http_header&} 返回本对象的引用,便于用户连续操作
*/
http_header& set_host(const char* value);
/**
* 获得设置的 HTTP 请求头中的 HOST 字段
* @return {const char*} 返回空指针表示没有设置 HOST 字段
*/
const char* get_host() const
{
return host_[0] == 0 ? NULL : host_;
}
private:
char method_s_[64]; // HTTP 请求方法以字符串表示
char* url_; // HTTP 请求的 URL
char host_[256]; // HTTP 请求头中的 HOST 字段
http_header& http_header::set_host(const char* value)
{
if (value && *value) {
CP(host_, value);
}
return *this;
}
http_header& http_header::set_url(const char* url, bool encoding /* = true */)
{
assert(url && *url);
is_request_ = true;
size_t len = strlen(url);
// 多分配两个字节:'\0' 及可能添加的 '/'
url_ = (char*) malloc(len + 2);
memcpy(url_, url, len);
url_[len] = 0;
char* ptr;
if (strncasecmp(url_, "http://", sizeof("http://") - 1) == 0) {
ptr = url_ + sizeof("http://") - 1;
} else if (strncasecmp(url_, "https://", sizeof("https://") - 1) == 0) {
ptr = url_+ sizeof("https://") -1;
} else if (strncasecmp(url_, "ws://", sizeof("ws://") - 1) == 0) {
ptr = url_ + sizeof("ws://") - 1;
} else if (strncasecmp(url_, "wss://", sizeof("wss://") - 1) == 0) {
ptr = url_ + sizeof("wss://") - 1;
} else {
ptr = url_;
}
char* params, *slash;
// 开始提取 host 字段
// 当 url 中只有相对路径时
if (ptr == url_) {
if (encoding) {
params = strchr(ptr, '?');
} else {
params = NULL;
}
}
// 当 url 为绝对路径时
else if ((slash = strchr(ptr, '/')) != NULL && slash > ptr) {
size_t n = slash - ptr + 1;
if (n > sizeof(host_)) {
n = sizeof(host_);
}
// 添加主机地址
strncpy(host_, ptr, n);
if (encoding) {
params = strchr(slash, '?');
} else {
params = NULL;
}
}
// 当 url 为绝对路径且主机地址后没有 '/'
else {
// 这是安全的,因为在前面给 url_ 分配内存时多了一个字节
if (slash == NULL) {
url_[len] = '/';
url_[len + 1] = 0;
}
if (encoding) {
params = strchr(ptr, '?');
} else {
params = NULL;
}
}
if (params == NULL) {
return *this;
}
*params++ = 0;
if (*params == 0) {
return *this;
}
oceanstar::url_coder coder;
coder.decode(params);
const std::vector<URL_NV*>& tokens = coder.get_params();
std::vector<URL_NV*>::const_iterator cit = tokens.begin();
for (; cit != tokens.end(); ++cit) {
add_param((*cit)->name, (*cit)->value);
}
return *this;
}
添加: HTTP 请求头中是否允许接收压缩数据(暂缺)
/**
* 设置 HTTP 请求头中是否允许接收压缩数据,对应的 HTTP 头字段为:
* Accept-Encoding: gzip, deflate
* @param on {bool} 如果为 true 则自动添加 HTTP 压缩头请求
* @return {http_header&} 返回本对象的引用,便于用户连续操作
*/
http_header& accept_gzip(bool on){
accept_compress_ = false;
return *this;
}
private:
bool accept_compress_; // 是否接收压缩数据
添加:ws
- 定义:
const char* get_ws_origin(void) const
{
return ws_origin_;
}
const char* get_ws_key(void) const
{
return ws_sec_key_;
}
const char* get_ws_protocol(void) const
{
return ws_sec_proto_;
}
int get_ws_version(void) const
{
return ws_sec_ver_;
}
const char* get_ws_accept(void) const
{
return ws_sec_accept_;
}
private:
// just for websocket
char* ws_origin_ = NULL;
char* ws_sec_key_ = NULL;
char* ws_sec_proto_ = NULL;
int ws_sec_ver_ = NULL;
char* ws_sec_accept_ = NULL;
- 方法:
http_header& set_ws_origin(const char* url){
if (url && *url) {
ws_origin_ = strdup(url);
}
return *this;
}
http_header& set_ws_protocol(const char* proto){
if (proto && *proto) {
ws_sec_proto_ = strdup(proto);
}
return *this;
}
http_header& set_ws_version(int ver){
ws_sec_ver_ = ver;
return *this;
}
http_header& set_ws_accept(const char* key){
if (key && *key) {
ws_sec_key_ = strdup(key);
}
return *this;
}
http_header& set_ws_key(const void* key, size_t len){
if (key && len > 0) {
acl_uint64 n = acl_hash_crc64(key, len);
char buf[24];
snprintf(buf, sizeof(buf), "%llu", n);
char * hex_enc = (char *)acl_hex_encode(buf, strlen(buf));
char * base64_enc = (char *)acl_base64_encode(hex_enc, strlen(hex_enc));
ws_sec_key_ = strdup(base64_enc);
free(base64_enc);
free(hex_enc);
}
return *this;
}
http_header& set_ws_key(const char* key){
if (key && *key) {
return set_ws_key(key, strlen(key));
} else {
return *this;
}
}
- 生成请求头时:
添加:重定向
重定向时,本header的url_会变
public:
/**
* url 重定向
* @param url {const char*} 重定向的 URL,格式为:
* http://xxx.xxx.xxx/xxx 或 /xxx
* 如果是前者,则自动从中取出 HOST 字段,如果是后者,则
* 延用之前的 HOST
*/
bool redirect(const char* url);
/**
* 设置重定向次数,如果该值 == 0 则不主动进行重定向,否则
* 进行重定向且重定向的次数由该值决定
* @param n {int} 允许重定向的次数
* @return {http_header&} 返回本对象的引用,便于用户连续操作
*/
http_header& set_redirect(unsigned int n = 5);
/**
* 获取通过 set_redirect 设置的允许的最大重定向次数
* @return {unsigned int}
*/
unsigned int get_redirect(void) const;
/**
* 当需要重定向时,会主动调用此函数允许子类做一些重置工作
*/
virtual void redicrect_reset(void) {}
bool http_header::redirect(const char* url)
{
if (url == NULL || *url == 0) {
logger_error("url null");
return false;
}
size_t n = 0;
// url: http[s]://xxx.xxx.xxx/xxx or /xxx
if (strncasecmp(url, "http://", sizeof("http://") - 1) == 0) {
n = sizeof("http://") - 1;
} else if (strncasecmp(url, "https://", sizeof("https://") - 1) == 0) {
n = sizeof("https://") - 1;
}
if (url_) {
url_ = NULL;
}
if (n > 0) {
url += n;
char* ptr = strdup(url);
char* p = strchr(ptr, '/');
if (p) {
*p = 0;
}
if (*ptr == 0) {
logger_error("invalid url(%s)", url);
return false;
}
set_host(ptr);
if (*(p + 1)) {
*p = '/';
url_ = p;
} else {
url_ = strdup("/");
}
} else {
url_ = strdup(url);
}
return true;
}
http_header& http_header::set_redirect(unsigned int n /* = 5 */)
{
nredirect_ = n;
return *this;
}
unsigned int http_header::get_redirect() const
{
return nredirect_;
}
生成请求头
/**
* 创建 HTTP 请求头数据
* @param buf {string&} 存储结果数据
* @return {bool} 创建请求头中否成功
*/
bool build_request(std::string& buf) const;
响应头
添加:设置响应状态字
/**
* 设置 HTTP 响应头中的响应状态字
* @param status {int} 状态字如:1xx, 2xx, 3xx, 4xx, 5xx
* @return {http_header&} 返回本对象的引用,便于用户连续操作
*/
http_header& set_status(int status);
/**
* 获得响应头中的 HTTP 状态字
* @return {int} HTTP 响应状态码:1xx, 2xx, 3xx, 4xx, 5xx
*/
int get_status(void) const
{
return status_;
}
http_header& http_header::set_status(int status)
{
status_ = status;
is_request_ = false;
return *this;
}
添加:设置是否是分块传输
/**
* 设置 HTTP 响应头中的 chunked 传输标志
* @param on {bool}
* @return {http_header&}
*/
http_header& set_chunked(bool on);
/**
* 判断当前 HTTP 传输是否采用 chunked 传输方式
* @return {bool}
*/
bool chunked_transfer(void) const
{
return chunked_transfer_;
}
private:
bool chunked_transfer_; // 是否为 chunked 传输模式
http_header& http_header::set_chunked(bool on)
{
chunked_transfer_ = on;
return *this;
}
添加:设置cgi_mode(没有实现)
/**
* 设置是否用来生成 CGI 格式的响应头
* @param on {bool} 是否 CGI 格式响应头
* @return {http_header&} 返回本对象的引用,便于用户连续操作
*/
http_header& set_cgi_mode(bool on);
/**
* 是否设置了 CGI 模式
* @return {bool}
*/
bool is_cgi_mode() const
{
return cgi_mode_;
}
private:
bool cgi_mode_; // 是否 CGI 响应头
http_header& http_header::set_cgi_mode(bool on)
{
cgi_mode_ = on;
if (cgi_mode_) {
is_request_ = false;
}
return *this;
}
添加:设置是否压缩【没有实现】
/**
* 设置传输的数据是否采用 gzip 方式进行压缩
* @param on {bool}
* @return {http_header&}
*/
http_header& set_transfer_gzip(bool on);
/**
* 获得当前的数据传输是否设置了采用 gzip 压缩方式
* @return {bool}
*/
bool is_transfer_gzip() const
{
return transfer_gzip_;
}
private:
bool transfer_gzip_; // 数据是否采用 gzip 压缩