C++开发示例
更新时间:2023-06-07
我们在写下边的示例代码时,为了简单清楚,便于理解,忽略了一些错误处理,用户基于以下示例开发的时候,可以自行补齐。
一些公共函数
首先我们这里定义一个HttpRequest
结构,后续的 demo 中我们会使用到这个结构。
struct HttpRequest {
HttpRequest() {
// 只支持ContentType为json
headers["Content-Type"] = "application/json";
}
std::string method;
std::string url;
std::string path;
std::string body;
std::map<std::string, std::string> headers;
std::map<std::string, std::string> params;
};
接下来是一些公共的函数。
// 时间戳转为公有云utc时间串的函数
// 例如:1535904000 转换为 2018-09-02T16:00:00Z
inline std::string timestamp_to_utc(time_t timestamp) {
struct tm* timeinfo = gmtime(×tamp);
const int buf_size = 32;
char buf[buf_size];
strftime(buf, buf_size, "%FT%TZ", timeinfo);
return std::string(buf);
}
// curl回调方法,用于读出返回的content,即body内容
size_t content_data(void* buffer, size_t size, size_t nmemb, void* userp) {
*((std::string*)userp) = *((std::string*)userp) +
std::string((const char*)buffer, size * nmemb);
return size * nmemb;
}
// curl回调方法,用于读出返回的header
size_t header_data(void* buffer, size_t size, size_t nmemb, void* userp) {
*((std::string*)userp) = *((std::string*)userp) +
std::string((const char*)buffer, size * nmemb);
return size * nmemb;
}
// 发送http请求
void execute_curl_http_request(const HttpRequest& http_request) {
// 使用curl发送请求
CURL* curl = curl_easy_init();
struct curl_slist* curl_headers = NULL;
// 填充header
for (auto const& item : http_request.headers) {
std::string header = item.first + ":" + item.second;
curl_headers = curl_slist_append(curl_headers, header.c_str());
}
// 拼接Url
std::string url = http_request.url;
url.append(http_request.path);
// 设置Url
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
// 设置headers
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, curl_headers);
// 设置method
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, http_request.method.c_str());
if (!http_request.body.empty()) {
// 设置body
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, http_request.body.c_str());
}
// 设置返回的header,content
// 如果你不需要解析header和content,则不需要做此设置,返回的content也会直接打印到标准输出
std::string header;
std::string content;
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, content_data);
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_data);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &content);
curl_easy_setopt(curl, CURLOPT_HEADERDATA, &header);
// 发送请求
curl_easy_perform(curl);
// 解析http返回码
long http_code = -1;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
// 清理curl
curl_easy_cleanup(curl);
// 打印返回的header内容
printf("query response header: %s.\n", header.c_str());
// 打印返回的content内容,为json格式,具体可参考api文档
// 如果你需要对返回的内容做下一步处理,可以将此content按json格式解析后作为后续使用
printf("query result: %s.\n", content.c_str());
}
生成认证字符串
在访问云数据库 TableStorage 时,每个访问都需要使用用户的 AK/SK 生成认证字符串来进行身份认证,我们这里给出了 C++实现的 demo,其他语言的实现可以参考百度智能云鉴权认证机制和百度智能云认证字符串生成代码示例。
我们在这里定义了一个根据 HttpRequest 生成认证字符串的函数,具体实现见本页最下方的百度智能云认证字符串 C++ 实现。
std::string gen_authorization(const HttpRequest& request, int timestamp);
创建 Instance
当用户开通云数据库 TableStorage 服务后,需要先创建一个 instance。
void create_instance() {
// 将创建名为ins_demo的instance
const std::string instance_name = "ins_demo";
// 初始化HttpRequest
HttpRequest http_request;
http_request.url = "http://bts.bd.baidubce.com";
// 方法为PUT表示创建资源
http_request.method = "PUT";
http_request.path = "/v1/instance/" + instance_name;
// 获取当前时间戳
time_t timestamp = time(NULL);
// 注意Http请求中的x-bce-date与生成认证字符串的时间需一致
http_request.headers["x-bce-date"] = timestamp_to_utc(timestamp);
http_request.headers["Host"] = "bts.bd.baidubce.com";
http_request.headers["Authorization"] = gen_authorization(http_request, timestamp);
execute_curl_http_request(http_request);
}
创建 Table
用户在创建好 Instance 后,就可以在 Instance 中创建表了,下边的示例是创建一张默认结构的表,如果用户对于表结构有特别需求,可参考API 参考增加参数。
void create_table() {
// 将在ins_demo中创建名为table_demo的table
const std::string instance_name = "ins_demo";
const std::string table_name = "table_demo";
// 初始化HttpRequest
HttpRequest http_request;
http_request.url = "http://bts.bd.baidubce.com";
// 方法为PUT表示创建资源
http_request.method = "PUT";
http_request.path = "/v1/instance/" + instance_name + "/table/" + table_name;
// 获取当前时间戳
time_t timestamp = time(NULL);
// 注意Http请求中的x-bce-date与生成认证字符串的时间需一致
http_request.headers["x-bce-date"] = timestamp_to_utc(timestamp);
http_request.headers["Host"] = "bts.bd.baidubce.com";
http_request.headers["Authorization"] = gen_authorization(http_request, timestamp);
execute_curl_http_request(http_request);
}
向 Table 写入数据
用户创建好 Table 后,就可以向 Table 写入数据。
void insert() {
// 将向ins_demo中的表table_demo中写入一行数据
const std::string instance_name = "ins_demo";
const std::string table_name = "table_demo";
// 初始化HttpRequest
HttpRequest http_request;
http_request.url = "http://bts.bd.baidubce.com";
// 方法为PUT表示创建资源,即写入数据
http_request.method = "PUT";
http_request.path = "/v1/instance/" + instance_name + "/table/" + table_name + "/row";
// 拼接写入的数据,为json格式,并且数据要经过UrlEncode
// 写入rowkey(主键)为"www.baidu.com/0"的两列数据:col1,值为"val1",col2,值为"val2"
http_request.body =
"{"
"\"rowkey\":\"www.baidu.com%2F0\","
"\"cells\":["
"{"
"\"column\":\"col1\","
"\"value\":\"val1\""
"},"
"{"
"\"column\":\"col2\","
"\"value\":\"val2\""
"}"
"]}";
// 获取当前时间戳
time_t timestamp = time(NULL);
// 注意Http请求中的x-bce-date与生成认证字符串的时间需一致
http_request.headers["x-bce-date"] = timestamp_to_utc(timestamp);
http_request.headers["Host"] = "bts.bd.baidubce.com";
http_request.headers["Content-Length"] = std::to_string(http_request.body.size());
http_request.headers["Authorization"] = gen_authorization(http_request, timestamp);
execute_curl_http_request(http_request);
}
从 Table 读取数据
一般来说,读取数据时,用户需要解析所读取的内容,所以我们在读示例里增加了 curl 获取返回内容的代码。用户可以指定 rowkey 从 Table 中读取数据。
void query() {
// 将向ins_demo中的table_demo读取一条数据
const std::string instance_name = "ins_demo";
const std::string table_name = "table_demo";
// 初始化HttpRequest
HttpRequest http_request;
http_request.url = "http://bts.bd.baidubce.com";
// 方法为GET表示获取资源
http_request.method = "GET";
http_request.path = "/v1/instance/" + instance_name + "/table/" + table_name + "/row";
// 拼接单条读请求,为json格式,并且数据要经过UrlEncode
// 读取rowkey(主键)为"www.baidu.com/0"的两列数据:col1和col2
// 如果需要读整行数据,则只需要设置rowkey即可
http_request.body =
"{"
"\"rowkey\":\"www.baidu.com%2F0\","
"\"cells\":["
"{"
"\"column\":\"col1\""
"},"
"{"
"\"column\":\"col2\""
"}"
"]}";
// 获取当前时间戳
time_t timestamp = time(NULL);
// 注意Http请求中的x-bce-date与生成认证字符串的时间需一致
http_request.headers["x-bce-date"] = timestamp_to_utc(timestamp);
http_request.headers["Host"] = "bts.bd.baidubce.com";
http_request.headers["Content-Length"] = std::to_string(http_request.body.size());
http_request.headers["Authorization"] = gen_authorization(http_request, timestamp);
execute_curl_http_request(http_request);
}
用户可以范围扫描数据。
void scan() {
// 将向ins_demo中的table_demo读取数据
const std::string instance_name = "ins_demo";
const std::string table_name = "table_demo";
// 初始化HttpRequest
HttpRequest http_request;
http_request.url = "http://bts.bd.baidubce.com";
// 方法为GET表示获取资源
http_request.method = "GET";
http_request.path = "/v1/instance/" + instance_name + "/table/" + table_name + "/rows";
// 拼接范围读请求,为json格式,并且数据要经过UrlEncode
// 读取rowkey(主键)范围为[www.baidu.com/0, www.baidu.com/9)数据,并且读取其中两列:col1和col2
// 注意includeStart和includeStop为区间的开闭,本示例中,区间为前闭后开
// limit为1表示只返回第一行这个区间内的数据
// rowkey区间和其它参数,用户可以参考api文档,按需配置
http_request.body =
"{"
"\"startRowkey\":\"www.baidu.com%2F0\","
"\"includeStart\":true,"
"\"stopRowkey\":\"www.baidu.com%2F9\","
"\"includeStop\":false,"
"\"selector\":["
"{"
"\"column\":\"col1\""
"},"
"{"
"\"column\":\"col2\""
"}],"
"\"limit\":1"
"}";
// 获取当前时间戳
time_t timestamp = time(NULL);
// 注意Http请求中的x-bce-date与生成认证字符串的时间需一致
http_request.headers["x-bce-date"] = timestamp_to_utc(timestamp);
http_request.headers["Host"] = "bts.bd.baidubce.com";
http_request.headers["Content-Length"] = std::to_string(http_request.body.size());
http_request.headers["Authorization"] = gen_authorization(http_request, timestamp);
execute_curl_http_request(http_request);
}
删除 Table 中数据
用户也可以指定 rowkey 删除 Table 中的某行数据。
void remove() {
// 将删除ins_demo中的表table_demo中的一行数据
const std::string instance_name = "ins_demo";
const std::string table_name = "table_demo";
// 初始化HttpRequest
HttpRequest http_request;
http_request.url = "http://bts.bd.baidubce.com";
// 方法为DELETE表示删除资源
http_request.method = "DELETE";
http_request.path = "/v1/instance/" + instance_name + "/table/" + table_name + "/row";
// 拼接写入的数据,为json格式,并且数据要经过UrlEncode
// 删除rowkey(主键)为"www.baidu.com/0"的两列数据:col1,值为"val1",col2,值为"val2"
// 如果要将整行,则不需要设置cells,只需设置rowkey即可
http_request.body =
"{"
"\"rowkey\":\"www.baidu.com%2F0\","
"\"cells\":["
"{"
"\"column\":\"col1\","
"\"value\":\"val1\""
"},"
"{"
"\"column\":\"col2\","
"\"value\":\"val2\""
"}"
"]}";
// 获取当前时间戳
time_t timestamp = time(NULL);
// 注意Http请求中的x-bce-date与生成认证字符串的时间需一致
http_request.headers["x-bce-date"] = timestamp_to_utc(timestamp);
http_request.headers["Host"] = "bts.bd.baidubce.com";
http_request.headers["Content-Length"] = std::to_string(http_request.body.size());
http_request.headers["Authorization"] = gen_authorization(http_request, timestamp);
execute_curl_http_request(http_request);
}
删除 Table
用户如果不再使用某张表,则可将其删除,其中的数据也会清理。
void drop_table() {
// 将删除ins_demo中名为table_demo的table
const std::string instance_name = "ins_demo";
const std::string table_name = "table_demo";
// 初始化HttpRequest
HttpRequest http_request;
http_request.url = "http://bts.bd.baidubce.com";
// 方法为DELETE表示删除资源
http_request.method = "DELETE";
http_request.path = "/v1/instance/" + instance_name + "/table/" + table_name;
// 获取当前时间戳
time_t timestamp = time(NULL);
// 注意Http请求中的x-bce-date与生成认证字符串的时间需一致
http_request.headers["x-bce-date"] = timestamp_to_utc(timestamp);
http_request.headers["Host"] = "bts.bd.baidubce.com";
http_request.headers["Authorization"] = gen_authorization(http_request, timestamp);
execute_curl_http_request(http_request);
}
删除 Instance
当用户不再使用某个 Instance 时,可将其删除,但需要注意的是,被删除的 Instance 必需是空的,即其中没有表存在,否则会拒绝删除操作。
void drop_instance() {
// 将删除名为ins_demo的instance
const std::string instance_name = "ins_demo";
// 初始化HttpRequest
HttpRequest http_request;
http_request.url = "http://bts.bd.baidubce.com";
// 方法为DELETE表示删除资源
http_request.method = "DELETE";
http_request.path = "/v1/instance/" + instance_name;
// 获取当前时间戳
time_t timestamp = time(NULL);
// 注意Http请求中的x-bce-date与生成认证字符串的时间需一致
http_request.headers["x-bce-date"] = timestamp_to_utc(timestamp);
http_request.headers["Host"] = "bts.bd.baidubce.com";
http_request.headers["Authorization"] = gen_authorization(http_request, timestamp);
execute_curl_http_request(http_request);
}
百度智能云认证字符串 C++实现
// UriEncode实现,except_slash参数表示是否编码'/'
inline std::string uri_encode(const std::string& src, bool except_slash = false) {
static const char* hex = "0123456789ABCDEF";
std::string dst = "";
for (size_t i = 0; i < src.length(); i++) {
char c = src[i];
if (isalnum(c) || strchr("-_.~", c)) {
dst += c;
} else if (except_slash && c == '/') {
dst += c;
} else {
dst += '%';
dst += hex[(unsigned char)c >> 4];
dst += hex[(unsigned char)c & 0xf];
}
}
return dst;
}
// UriEncodeExceptSlash实现
inline std::string uri_encode_except_slash(const std::string& str) {
return uri_encode(str, true);
}
// HmacSha256 的实现
std::string hmac_sha256(const std::string& key, const std::string& message) {
unsigned char digest[SHA256_DIGEST_LENGTH];
unsigned int digestLength;
HMAC_CTX ctx;
HMAC_CTX_init(&ctx);
HMAC_Init_ex(&ctx, key.c_str(), key.length(), EVP_sha256(), NULL);
HMAC_Update(&ctx, reinterpret_cast<const unsigned char*>(message.c_str()), message.length());
HMAC_Final(&ctx, digest, &digestLength);
HMAC_CTX_cleanup(&ctx);
std::stringstream ss;
ss << std::hex << std::setfill('0');
for (unsigned int i = 0; i < digestLength; i++) {
ss << std::setw(2) << static_cast<unsigned int>(digest[i]);
}
return ss.str();
}
// 百度智能云认证字符串生成的C++实现
// 其中有一些加密函数由于有公开库,我们这里直接调用,用户可找相应的公开库(如openssl)
// 用户可以参考百度智能云生成认证字符串流程来阅读以下实现代码
std::string gen_authorization(const HttpRequest& request, time_t timestamp) {
// 1. 生成CanonicalRequest
std::string http_method = request.method;
// 1.1 生成CanonicalURI,
std::string canonical_uri = uri_encode_except_slash(request.path);
// 1.2 计算CanonicalQueryString
std::string canonical_query = "";
// 使用set结构暂存在后续遍历时可以保证天然字典序遍历,如果使用vector暂存需要配合sort按字典序排序
std::set<std::string> encoded_params;
// 对params遍历一次对kv做urlencode
auto params_it = request.params.begin();
for (; params_it != request.params.end(); ++params_it) {
if (params_it->first == "authorization" || params_it->first == "Authorization") {
continue;
}
std::string key = uri_encode(params_it->first);
std::string value = uri_encode(params_it->second);
encoded_params.insert(key + '=' + value); // 兼容value为空字符串的情形
}
// 对params遍历urlencode后的集合,拼接字符串
auto enc_params_it = encoded_params.begin();
for (; enc_params_it != encoded_params.end(); ++enc_params_it) {
if (enc_params_it != encoded_params.begin()) {
canonical_query += '&';
}
canonical_query += *enc_params_it;
}
// 1.3 计算CanonicalHeaders
std::string canonical_header = "";
std::string signed_headers = "";
// 使用set结构暂存在后续遍历时可以保证天然字典序遍历,如果使用vector暂存需要配合sort按字典序排序
std::set<std::string> encoded_headers;
// 对headers遍历一次,对key做转换小写、urlencoded操作,对value做去首尾空格、urlencoded操作
auto headers_it = request.headers.begin();
for (; headers_it != request.headers.end(); ++headers_it) {
// 将key部分转换为全小写
std::string key = headers_it->first;
for (size_t i = 0; i < key.length(); ++i) {
key[i] = ::tolower(key[i]);
}
// 将value部分去掉首尾的空白符
const char* blank_chars = " \n\r\t\v";
std::string value = headers_it->second;
value.erase(0, value.find_first_not_of(blank_chars));
value.erase(value.find_last_not_of(blank_chars) + 1);
if (value.empty()) {
continue;
}
std::string encoded_key = uri_encode(key);
std::string encoded_value = uri_encode(value);
encoded_headers.insert(encoded_key + ':' + encoded_value);
if (!signed_headers.empty()) {
signed_headers += ";";
}
signed_headers += encoded_key;
}
// 对headers遍历urlencode后的集合,拼接字符串
auto enc_headers_it = encoded_headers.begin();
for (; enc_headers_it != encoded_headers.end(); ++enc_headers_it) {
if (enc_headers_it != encoded_headers.begin()) {
canonical_header += '\n';
}
canonical_header += *enc_headers_it;
}
// 拼接生成CanonicalRequest
std::string canonical_request = http_method + '\n'
+ canonical_uri + '\n'
+ canonical_query + '\n'
+ canonical_header;
// 2. 生成SigningKey,这里我们直接调用了hmac_sha256函数,用户可以使用任意公开库中的HMAC-SHA256-HEX函数代替
// 注意使用hmac_sha256函数时参数的顺序
// 在生成鉴权串时的时间戳一定要和发送请求中Header域中的Date/x-bce-date相同
// 用户的ak/sk
const std::string ak = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
const std::string sk = "ssssssssssssssssssssssssssssssss";
// 拼接认证字符串前缀 : bce-auth-v1/{accessKeyId}/{timestamp}/{expirationPeriodInSeconds}
std::string auth_string_prefix = "bce-auth-v1/";
auth_string_prefix.append(ak + '/');
auth_string_prefix.append(timestamp_to_utc(timestamp));
// 认证字符串的超时时间(有效期)为1800秒,用户可以自行设置,时间长意味着效率高,但安全性会降低
auth_string_prefix.append("/1800");
std::string sign_key = hmac_sha256(sk, auth_string_prefix);
// 3. 生成Signature
std::string sign = hmac_sha256(sign_key, canonical_request);
// 4. 生成认证字符串,即做拼接
// 将前缀和Signature拼接得到完整的认证字符串
// 与签名结果之间为两个”/”,含义是使用默认签名方式
std::string auth = auth_string_prefix + "/" + signed_headers + "/" + sign;
return auth;
}