如何使用 gSOAP 在 C++ 中使用 Amazon Simple Storage Service (S3)






4.87/5 (16投票s)
如何使用 gSOAP 连接到 Amazon S3 以存储和检索数据
引言
本文将引导您完成使用 gSOAP 连接 Amazon Web Services 简单存储服务 (S3) 的过程。我们将创建一个存储桶,然后使用流式 DIME 附件将一个对象上传到该存储桶。
gSOAP(官网/SourceForge)是一个为 C 和 C++ 自动生成 SOAP 和 XML 数据绑定的工具包。它简化了 SOAP/XML Web 服务的开发,在我们的案例中,则是消费现有 Web 服务。
Amazon S3 是一项用于存储文件的 Web 服务。它们提供 SOAP API,因此我们可以轻松使用 gSOAP 工具包生成代码来与 AWS S3 进行交互。
gSOAP 用户指南目前没有关于使用 gSOAP 与 AWS S3 的信息。开发者中心(Developer Center)有一个关于 AWS S3 的小示例,但它并不全面,也没有涵盖常见的 AWS S3 SOAP API 函数。
安装和设置
gSOAP
有关如何在您的系统上安装 gSOAP 的说明,请参阅此页面上的相关部分。
OpenSSL
您需要安装 OpenSSL,因为 Amazon 要求所有 SOAP 请求都经过加密。您可以在他们的官网上了解更多关于 OpenSSL 的信息以及如何安装它。
AWS、访问密钥和配置文件
要使用该服务,您需要一个 AWS S3 帐户。您还需要创建访问密钥才能向 AWS API 发送任何请求。请妥善保管这些密钥。
强烈建议使用配置文件来安全地存储您的访问密钥。这样,您可以在需要时读取密钥,而不会意外地将它们上传到任何版本控制系统。要生成配置文件,我们可以使用一个名为 AWS 命令行界面(AWS Command Line Interface)的 AWS 工具。安装它并运行
aws configure
按照提示输入您的信息。此工具将在您的主目录(Unix 系统上的环境变量 ~
或 Windows 系统上的 %USERPROFILE%
)中创建一个名为 .aws
的文件夹,并在其中创建一个名为 credentials
的本地文件。有关使用 AWS CLI 工具创建本地配置文件的更多帮助,请参阅此页面。
如果您不想使用 AWS CLI,也可以手动创建一个配置文件。最好将其存储在不与任何版本控制关联的目录中。或者,您也可以使用环境变量来存储您的访问密钥。如果您选择这些选项之一,您将需要更改**从配置文件读取密钥**部分中的解析器,因为它假定文件格式由 AWS CLI 生成。
使用 gSOAP 从 AWS S3 WSDL 生成 C++ 数据绑定
建议为即将生成的文件创建一个新的、空的目录。
注意:在至少两种情况(CreateBucket
和 CopyObject
)下,Amazon 的 SOAP 响应与其承诺的模式不匹配(希望这很快会得到纠正)。因此,有必要编辑 typemap.dat 文件来修改 gSOAP 生成的类,以确保我们能够访问 Amazon 的所有响应。
我建议首先将 gsoap/ 目录下的 typemap.dat 文件复制到您的新目录中,然后将以下行添加到其中(任意位置)
_s3__CreateBucketResponse = $ s3__CreateBucketResult* CreateBucketResponse;
_s3__CopyObjectResponse = $ s3__CopyObjectResult* CopyObjectResponse;
现在,我们将生成 C++ 数据绑定。
步骤 1
使用 gSOAP 的 wsd2lh
工具处理 Amazon S3 WSDL 来生成一个头文件(我们将其命名为 aws-s3.h)。
wsdl2h -t typemap.dat -o aws-s3.h http://doc.s3.amazonaws.com/2006-03-01/AmazonS3.wsdl
第二步
gSOAP 代码生成的第二步是使用 soapcpp2
工具处理由 wsdl2h
工具创建的头文件。
soapcpp2 -C -j aws-s3.h
这会根据 aws-s3.h 头文件生成客户端代码(-C
选项),其中包含 C++ 服务代理和对象(-j
选项)。
生成的 aws-s3.h 头文件包含 Amazon S3 服务操作请求和响应的所有类声明,以及其他 Amazon S3 函数和 gSOAP 函数。
如果您感兴趣,可以查看 aws-s3.h 头文件的自动生成报告,其中列出了类和类型。有关 gSOAP 如何工作的更一般信息,请访问开发者中心或用户指南。
访问密钥和 AWS S3 签名
从配置文件读取密钥
在**安装和设置**部分,我们将访问密钥存储在主目录的 .aws
文件夹中的一个名为 credentials
的文件中。我们将定义此函数以从该文件读取密钥。
// Read access keys from file generated by AWS CLI bool getAWSKeys(std::string path, std::string user, std::string &accessKey, std::string &secretKey) { std::ifstream credentialsFile(path.c_str()); if (!credentialsFile.is_open()) return false; std::string line; while (std::getline(credentialsFile, line)) { // Keep going until we get to the desired user if (line.find(user) == std::string::npos) continue; while (std::getline(credentialsFile, line)) { // Keep going until we get to the access key lines if (line.find("aws_access_key_id") == std::string::npos) continue; // Grab keys and trim whitespace size_t first, last; accessKey = line.substr(line.find_first_of('=')+1); first = accessKey.find_first_not_of(' '); if (first == std::string::npos) return false; last = accessKey.find_last_not_of(' '); accessKey.substr(first, last-first+1).swap(accessKey); std::getline(credentialsFile, line); secretKey = line.substr(line.find_first_of('=')+1); first = secretKey.find_first_not_of(' '); if (first == std::string::npos) return false; last = secretKey.find_last_not_of(' '); secretKey.substr(first, last-first+1).swap(secretKey); return true; } } return false; }
我们可以调用此函数来设置局部变量,从而避免将硬编码的访问密钥包含在我们的源代码中。
// Load AWS keys from file std::string accessKey, secretKey; std::string credentialsFile = "path_to_aws_credentials_file"; std::string user = "default"; if (!getAWSKeys(credentialsFile, user, accessKey, secretKey)) { std::cout << "Couldn't read AWS keys for user " << user << " from file " << credentialsFile << '\n'; return 0; }
您必须提供包含访问密钥的文件的路径。如果您在运行 aws configure
时没有使用 --profile
选项,则可以将 user
变量保留为 default
。
如何构造签名
Amazon 需要一个特殊构造的 签名来处理每个 SOAP 请求,以进行身份验证和处理。我们将定义 soap_make_s3__signature
函数,以简化签名创建。它的输入是一个 soap 上下文、一个操作名称(如 "CreateBucket
")以及您的 AWS 密钥。它返回一个 string
,它是 HMAC-SHA1 哈希的 string
"AmazonS3
" + OPERATION_NAME
+ Timestamp
的 base64 编码版本。
// Make base64-encoded, HMAC-SHA1 hashed signature for AWS S3
std::string soap_make_s3__signature(struct soap *soap, char const *operation, char const *key) {
std::string signature = "AmazonS3";
signature += operation;
char UTCstamp[40]; //to hold ISO 8601 time format
time_t now;
time(&now);
strftime(UTCstamp, sizeof UTCstamp, "%Y-%m-%dT%H:%M:%S.000Z", gmtime(&now));
signature += UTCstamp;
// Get the HMAC-SHA1 digest of the signature string
unsigned char * digest;
digest = HMAC(EVP_sha1(), key, strlen(key),
(unsigned char*)(signature.c_str()),
signature.length(), NULL, NULL);
char signatureBase64[20];
// Convert the digest to base64
soap_s2base64(soap, digest, signatureBase64, sizeof signatureBase64);
return std::string(signatureBase64);
}
HMAC
和 EVP_sha1
函数来自 OpenSSL 库(我们将在**编译**部分进行链接)。
因此,我们可以简单地调用我们的新函数并提供必要的参数...
std::string signature = soap_make_s3__signature(aws.soap,
"...", // use an operation name
secretKey.c_str());
...从而在需要发出 API 请求时轻松创建签名。请注意,我们使用的是上一个部分中创建的 secretKey
变量,而不是硬编码的字符串。
使用 gSOAP 连接 AWS S3
注意:在这些示例中,我们将使用 gSOAP 的 wsdl2h
和 soapcpp2
工具在前面步骤中生成的类(AmazonS3SoapBindingProxy
、_s3__CreateBucket
等)。再次说明,如果您好奇有哪些生成的类可用或它们具有哪些成员数据,请查看生成的 aws-s3.h 文件或其自动生成报告。
第一个示例:创建存储桶
对于第一个示例,让我们创建一个新的存储桶。
为了让 gSOAP 生成的代码正常工作,我们需要包含生成的头文件和命名空间映射文件。
#include "soapAmazonS3SoapBindingProxy.h"
#include "AmazonS3SoapBinding.nsmap"
我们还将添加一个方便的模板函数,使用带上下文 soap
的 soap_malloc()
在 gSOAP 的托管堆上分配原始值。这并非必需,但会让代码更清晰、更易于管理。
// Make allocation of primitive values quick and easy:
template<class T>
T * soap_make(struct soap *soap, T val) {
T *p = (T*)soap_malloc(soap, sizeof(T));
*p = val;
return p;
}
现在,我们将编写连接到 S3 的代码。这都将在我们的 main
函数中。我们将创建一个自动生成类型的代理 AmazonS3SoapBindingProxy
来调用 AWS S3 服务。然后,我们将创建一个自动生成类型的 CreateBucket
请求 _s3__CreateBucket
并设置其参数。
// Create a proxy to invoke AWS S3 services
AmazonS3SoapBindingProxy aws(SOAP_XML_INDENT);
// Set the arguments of the CreateBucket service
_s3__CreateBucket createBucketReq;
std::string bucketName = "BucketName";
createBucketReq.Bucket = bucketName;
createBucketReq.AWSAccessKeyId = soap_new_std__string(aws.soap);
*createBucketReq.AWSAccessKeyId = accessKey;
createBucketReq.Timestamp = soap_make(aws.soap, time(0));
createBucketReq.Signature = soap_new_std__string(aws.soap);
*createBucketReq.Signature = soap_make_s3__signature(aws.soap,
"CreateBucket",
secretKey.c_str());
请务必将 bucketName string
更改为您想要的存储桶名称。对于 Signature
,我们使用在本文章**如何构造签名**部分创建的函数,操作名称为 "CreateBucket"
。
我们需要捕获请求的响应,因此我们将创建一个自动生成类 _s3__CreateBucketResponse
的实例。
// To store the result of the service
_s3__CreateBucketResponse createBucketRes;
现在,我们将实际调用服务。
// Create a bucket
if (aws.CreateBucket(&createBucketReq, createBucketRes)) {
aws.soap_stream_fault(std::cerr);
}
/*
NOTE: you must add the line:
_s3__CreateBucketResponse = $ s3__CreateBucketResult* CreateBucketResponse;
to the typemap.dat file because Amazon's response doesn't match
their promised schema. This adds the variable CreateBucketResponse
to the _s3__CreateBucketResponse class so we can access the response.
*/
else if (createBucketRes.CreateBucketResponse) {
s3__CreateBucketResult &result = *createBucketRes.CreateBucketResponse;
std::cout << "You are the owner of bucket'" <<
result.BucketName << "'." << std::endl;
}
这会将 createBucketReq
发送到 Amazon S3(借助 gSOAP,以正确的 SOAP 格式),并将响应放入 createBucketRes
。您可以在AWS 管理控制台中看到新创建的存储桶。
成功块上方的注释只是一个提醒,说明了我们在本文**从 AWS S3 WSDL 生成 C++ 数据绑定**部分的开头处的注意事项。
完成后,请务必调用代理的 destroy
函数。
// Delete all managed data
aws.destroy();
这就是创建存储桶所需的所有内容。最终的 .cpp 文件(我们称之为 createbucket.cpp)将如下所示
/*
createbucket.cpp
Example AWS S3 CreateBucket service invocation
*/
#include "soapAmazonS3SoapBindingProxy.h"
#include "AmazonS3SoapBinding.nsmap"
#include <fstream>
// Make allocation of primitive values quick and easy:
template<class T>
T * soap_make(struct soap *soap, T val) {
T *p = (T*)soap_malloc(soap, sizeof(T));
*p = val;
return p;
}
// Make base64-encoded, HMAC-SHA1 hashed signature for AWS S3
std::string soap_make_s3__signature(struct soap *soap, char const *operation, char const *key) {
std::string signature = "AmazonS3";
signature += operation;
char UTCstamp[40]; //to hold ISO 8601 time format
time_t now;
time(&now);
strftime(UTCstamp, sizeof UTCstamp, "%Y-%m-%dT%H:%M:%S.000Z", gmtime(&now));
signature += UTCstamp;
// Get the HMAC-SHA1 digest of the signature string
unsigned char * digest;
digest = HMAC(EVP_sha1(), key, strlen(key),
(unsigned char*)(signature.c_str()),
signature.length(), NULL, NULL);
char signatureBase64[20];
// Convert the digest to base64
soap_s2base64(soap, digest, signatureBase64, sizeof signatureBase64);
return std::string(signatureBase64);
}
// Read access keys from file generated by AWS CLI
bool getAWSKeys(std::string path, std::string user, std::string &accessKey, std::string &secretKey) {
std::ifstream credentialsFile(path.c_str());
if (!credentialsFile.is_open())
return false;
std::string line;
while (std::getline(credentialsFile, line)) {
// Keep going until we get to the desired user
if (line.find(user) == std::string::npos)
continue;
while (std::getline(credentialsFile, line)) {
// Keep going until we get to the access key lines
if (line.find("aws_access_key_id") == std::string::npos)
continue;
// Grab keys and trim whitespace
size_t first, last;
accessKey = line.substr(line.find_first_of('=')+1);
first = accessKey.find_first_not_of(' ');
if (first == std::string::npos)
return false;
last = accessKey.find_last_not_of(' ');
accessKey.substr(first, last-first+1).swap(accessKey);
std::getline(credentialsFile, line);
secretKey = line.substr(line.find_first_of('=')+1);
first = secretKey.find_first_not_of(' ');
if (first == std::string::npos)
return false;
last = secretKey.find_last_not_of(' ');
secretKey.substr(first, last-first+1).swap(secretKey);
return true;
}
}
return false;
}
int main(int argc, char **argv) {
// Load AWS keys from file
std::string accessKey, secretKey;
// Use the path to your AWS credentials file
std::string credentialsFile = (argc > 2 ? argv[2] : "path_to_aws_credentials_file");
std::string user = "default";
if (!getAWSKeys(credentialsFile, user, accessKey, secretKey)) {
std::cout << "Couldn't read AWS keys for user " << user
<< " from file " << credentialsFile << '\n';
return 0;
}
// Create a proxy to invoke AWS S3 services
AmazonS3SoapBindingProxy aws(SOAP_XML_INDENT);
// Create bucket
// Set the arguments of the CreateBucket service operation
_s3__CreateBucket createBucketReq;
std::string bucketName = (argc > 1 ? argv[1] : "BucketName");
createBucketReq.Bucket = bucketName;
createBucketReq.AWSAccessKeyId = soap_new_std__string(aws.soap);
*createBucketReq.AWSAccessKeyId = accessKey;
createBucketReq.Timestamp = soap_make(aws.soap, time(0));
createBucketReq.Signature = soap_new_std__string(aws.soap);
*createBucketReq.Signature = soap_make_s3__signature(aws.soap,
"CreateBucket",
secretKey.c_str());
// Store the result of the service
_s3__CreateBucketResponse createBucketRes;
// Create a bucket
if (aws.CreateBucket(&createBucketReq, createBucketRes)) {
aws.soap_stream_fault(std::cerr);
}
/*
NOTE: you must add the line:
_s3__CreateBucketResponse = $ s3__CreateBucketResult* CreateBucketResponse;
to the typemap.dat file because Amazon's response doesn't match
their promised schema. This adds the variable CreateBucketResponse
to the _s3__CreateBucketResponse class so we can access the response.
*/
else if (createBucketRes.CreateBucketResponse) {
s3__CreateBucketResult &result = *createBucketRes.CreateBucketResponse;
std::cout << "You are the owner of bucket '" << result.BucketName << "'." << std::endl;
}
// Delete all managed data
aws.destroy();
return 0;
}
编译 CreateBucket
示例
使用 g++
为了编译我们的程序,我们必须使用 OpenSSL,因为 AWS S3 服务需要 HTTPS 来确保凭证安全传输。这意味着我们必须链接 OpenSSL 和 OpenSSL crypto 库。命令如下
g++ -DWITH_OPENSSL -o createbucket createbucket.cpp soapAmazonS3SoapBindingProxy.cpp \
soapC.cpp stdsoap2.cpp -lssl -lcrypto
您可能需要将 stdsoap2.cpp 的路径修正为您安装目录中的 gsoap/ 目录下的 stdsoap2.cpp 文件的绝对路径。您可能还需要包含一个 -I
选项,指向 gsoap/import 文件夹的路径。
编译代码,运行 createbucket
,就会创建一个新的存储桶。
在最终的 .cpp 文件中,请注意我们在设置 credentialsFile
和 bucketName
时检查了命令行参数(argv
)。这允许程序通过参数调用
./createbucket BucketName path_to_credentials_file
第二个示例:使用 DIME 附件和流式传输上传文件(PutObject)
现在我们已经创建了一个存储桶,让我们将一个文件上传到其中。
AWS S3 的 SOAP API 有两个不同的操作用于上传对象:PutObjectInline
和 PutObject
。内联版本期望数据直接包含在 SOAP 消息的正文中,而非内联版本期望数据作为 DIME 附件提供。大于 1MB 的文件无法使用内联版本上传,因此本示例将演示如何使用 DIME 附件。示例(putobjectinline.cpp)用于 PutObjectInline
操作,也已包含在 aws-s3-gsoap-examples.zip 文件中。
gSOAP 支持 DIME 附件,无论是否使用流式传输。不使用流式传输时,整个文件会在内存中存储和检索,这对于大型文件/小型设备来说可能是一个问题。我们将展示如何使用 gSOAP 的 DIME 附件(带流式传输),尽管一个示例(putobject.cpp)不带流式传输,可以在 aws-s3-gsoap-examples.zip 文件中找到。
我们将使用与 CreateBucket
示例相同的辅助函数(soap_make
、getAWSKeys
和 soap_make_s3__signature
)。
我们的请求将是自动生成类 _s3__PutObject
。它需要存储桶名称、一个键(用于命名我们要上传的对象)、一个访问密钥、一个时间戳和一个签名。我们还可以向 Metadata
容器元素提供键值元数据对(类型为 s3__MetadataEntry
),这些元数据将与对象一起存储,但这(是可选的)。
// Create a proxy to invoke AWS S3 services AmazonS3SoapBindingProxy aws(SOAP_XML_INDENT); // Set the arguments of the PutObject service operation _s3__PutObject putObjectReq; std::string bucketName = "BucketName"; std::string keyName = "KeyName"; putObjectReq.Bucket = bucketName; putObjectReq.Key = keyName; putObjectReq.AWSAccessKeyId = soap_new_std__string(aws.soap); *putObjectReq.AWSAccessKeyId = accessKey; putObjectReq.Timestamp = soap_make(aws.soap, time(0)); putObjectReq.Signature = soap_new_std__string(aws.soap); *putObjectReq.Signature = soap_make_s3__signature(aws.soap, "PutObject", secretKey.c_str()); s3__MetadataEntry *metadataEntry = soap_new_req_s3__MetadataEntry(aws.soap, "Metadata Name", "Metadata Value"); putObjectReq.Metadata.push_back(metadataEntry);
最后一步是将我们要上传的数据包含为 DIME 附件,并进行 base64 编码。使用 gSOAP 进行流式 DIME 附件传输非常简单,但请注意,.zip 文件夹中的示例包含一个非流式版本。
有关流式 DIME 附件所需的 istream 回调函数的详细解释可以在 gSOAP 用户指南的此部分中阅读,但本示例将展示一个实现。dime_read_open
将简单地返回 handle
(我们将提前打开文件),dime_read
将从文件中读取最多 len
字节并将其存储在 gSOAP 管理的缓冲区中,而 dime_read_close
将关闭文件。
void *dime_read_open(struct soap *soap, void *handle, const char *id, const char *type, const char *options) { return handle; } size_t dime_read(struct soap *soap, void *handle, char* buf, size_t len) { return fread(buf, 1, len, (FILE*)handle); } void dime_read_close(struct soap *soap, void *handle) { fclose((FILE*)handle); }
好的。我们需要一个 xsd__base64Binary
对象来方便流式传输...
xsd__base64Binary *data = soap_new_xsd__base64Binary(aws.soap);
...以及一个指向我们文件的 FILE
指针,以便在回调函数之间传递作为 handle
FILE *fd = fopen("filename.jpg"), "rb"); if (fd == NULL) return 0;
只有在能够获取文件大小时(我们将使用 fstat
)才能流式传输数据。如果我们知道大小,那么我们就设置代理的 soap
结构的回调函数为我们刚才定义的函数,并赋值 data
对象的 __ptr
和 __size
字段。这些值将用于我们之前定义的 gSOAP 的流式回调函数(__ptr
字段作为 dime_read_open
函数的 handle
参数传递,__size
字段由回调函数用来知道何时完成)。
struct stat sb; if (!fstat(fileno(fd), &sb) && sb.st_size > 0) { // We can get the length of the file, so we can stream it // Set soap callbacks aws.soap->fdimereadopen = dime_read_open; aws.soap->fdimereadclose = dime_read_close; aws.soap->fdimeread = dime_read; //__ptr must be non-NULL. This is the handle in the callbacks data->__ptr = (unsigned char*)fd; data->__size = sb.st_size; }
如果我们无法读取文件大小,我们需要一个备用计划。gSOAP 支持 HTTP 分块传输(不需要预知大小),但我们不能使用此方法,因为 PutObject
API 调用需要 ContentLength
元素。因此,我们将 naively 读取并将数据设置到 data->__ptr
中,直到到达末尾,并在一个任意的大小限制处截断。
else { // We don't know the size, so buffer it int i; data->__ptr = (unsigned char*)soap_malloc(aws.soap, MAX_FILE_SIZE); for (i = 0; i < MAX_FILE_SIZE; i++) { int c; if ((c = fgetc(fd)) == EOF) break; data->__ptr[i] = c; } fclose(fd); data->__size = i; }
请务必定义 MAX_FILE_SIZE。
#define MAX_FILE_SIZE (1000000) // (1MB). Max size for when we must attach as non-streaming
好的!现在,要告诉 gSOAP 将 data
对象作为 DIME 附件放入请求中,我们需要设置其 type
或 id
字段。gSOAP 将知道要流式传输数据,因为我们之前设置了回调函数。我们还需要使用我们的 soap 上下文调用 gSOAP 函数 soap_set_dime
,并使用我们刚刚构建的信息调用 soap_set_dime_attachment
。然后,我们将设置请求的 ContentLength
参数。
// Set DIME attachment with options char type[] = "text/plain"; data->type = type; soap_set_dime(aws.soap); if (soap_set_dime_attachment(aws.soap, (char*)data->__ptr, data->__size, data->type, data->id, 0, data->options)) { aws.soap_stream_fault(std::cerr); return 0; } putObjectReq.ContentLength = data->__size;
请确保appropriately设置 data->type
。您也可以设置 data->id
,但如果未设置,gSOAP 会自动创建一个。data->options
控制 DIME 特定的 options 字段,该字段可以留空或使用 soap_dime_option
设置...
data->options = soap_dime_option(aws.soap, 0, "Optional text");
现在请求已完全构建!现在我们只需像 CreateBucket
示例一样,调用 API 并捕获响应。
// Store the result of the service _s3__PutObjectResponse putObjectRes; // Put object if (aws.PutObject(&putObjectReq, putObjectRes)) { aws.soap_stream_fault(std::cerr); } else if (putObjectRes.PutObjectResponse) { s3__PutObjectResult &result = *putObjectRes.PutObjectResponse; std::cout << "Object with key '" << putObjectReq.Key << "' put into bucket '" << putObjectReq.Bucket << "'" << std::endl; std::cout << "\tEtag: " << result.ETag << std::endl; std::cout << "\tLast modified: " << soap_dateTime2s(aws.soap, result.LastModified) << std::endl; }
同样,请务必在程序结束前调用 aws.destroy()
。
最终的 .cpp 文件(我们称之为 putobjectstreaming.cpp)将如下所示
/* putobjectstreaming.cpp Example AWS S3 PutObject service invocation with DIME attachment (streaming) */ #include "soapAmazonS3SoapBindingProxy.h" #include "AmazonS3SoapBinding.nsmap" #include <fstream> #include <sys/stat.h> #define MAX_FILE_SIZE (1000000) // (1MB). Max size for when we must attach as non-streaming // Make allocation of primitive values quick and easy: template<class T> T * soap_make(struct soap *soap, T val) { T *p = (T*)soap_malloc(soap, sizeof(T)); *p = val; return p; } // Make base64-encoded, HMAC-SHA1 hashed signature for AWS S3 std::string soap_make_s3__signature(struct soap *soap, char const *operation, char const *key) { std::string signature = "AmazonS3"; signature += operation; char UTCstamp[40]; //to hold ISO 8601 time format time_t now; time(&now); strftime(UTCstamp, sizeof UTCstamp, "%Y-%m-%dT%H:%M:%S.000Z", gmtime(&now)); signature += UTCstamp; // Get the HMAC-SHA1 digest of the signature string unsigned char * digest; digest = HMAC(EVP_sha1(), key, strlen(key), (unsigned char*)(signature.c_str()), signature.length(), NULL, NULL); char signatureBase64[20]; // Convert the digest to base64 soap_s2base64(soap, digest, signatureBase64, sizeof signatureBase64); return std::string(signatureBase64); } // Read access keys from file generated by AWS CLI bool getAWSKeys(std::string path, std::string user, std::string &accessKey, std::string &secretKey) { std::ifstream credentialsFile(path.c_str()); if (!credentialsFile.is_open()) return false; std::string line; while (std::getline(credentialsFile, line)) { // Keep going until we get to the desired user if (line.find(user) == std::string::npos) continue; while (std::getline(credentialsFile, line)) { // Keep going until we get to the access key lines if (line.find("aws_access_key_id") == std::string::npos) continue; // Grab keys and trim whitespace size_t first, last; accessKey = line.substr(line.find_first_of('=')+1); first = accessKey.find_first_not_of(' '); if (first == std::string::npos) return false; last = accessKey.find_last_not_of(' '); accessKey.substr(first, last-first+1).swap(accessKey); std::getline(credentialsFile, line); secretKey = line.substr(line.find_first_of('=')+1); first = secretKey.find_first_not_of(' '); if (first == std::string::npos) return false; last = secretKey.find_last_not_of(' '); secretKey.substr(first, last-first+1).swap(secretKey); return true; } } return false; } void *dime_read_open(struct soap *soap, void *handle, const char *id, const char *type, const char *options) { return handle; } size_t dime_read(struct soap *soap, void *handle, char* buf, size_t len) { return fread(buf, 1, len, (FILE*)handle); } void dime_read_close(struct soap *soap, void *handle) { fclose((FILE*)handle); } int main(int argc, char **argv) { // Load AWS keys from file std::string accessKey, secretKey; // Use the path to your AWS credentials file std::string credentialsFile = (argc > 4 ? argv[4] : "path_to_aws_credentials_file"); std::string user = "default"; if (!getAWSKeys(credentialsFile, user, accessKey, secretKey)) { std::cout << "Couldn't read AWS keys for user " << user << " from file " << credentialsFile << '\n'; return 0; } // Create a proxy to invoke AWS S3 services AmazonS3SoapBindingProxy aws(SOAP_XML_INDENT); // Put object in bucket (streaming) // Set the arguments of the PutObject service operation _s3__PutObject putObjectReq; std::string bucketName = (argc > 1 ? argv[1] : "BucketName"); std::string keyName = (argc > 2 ? argv[2] : "KeyName"); putObjectReq.Bucket = bucketName; putObjectReq.Key = keyName; putObjectReq.AWSAccessKeyId = soap_new_std__string(aws.soap); *putObjectReq.AWSAccessKeyId = accessKey; putObjectReq.Timestamp = soap_make(aws.soap, time(0)); putObjectReq.Signature = soap_new_std__string(aws.soap); *putObjectReq.Signature = soap_make_s3__signature(aws.soap, "PutObject", secretKey.c_str()); s3__MetadataEntry *metadataEntry = soap_new_req_s3__MetadataEntry(aws.soap, "Metadata Name", "Metadata Value"); putObjectReq.Metadata.push_back(metadataEntry); // Set up gSOAP DIME streaming xsd__base64Binary *data = soap_new_xsd__base64Binary(aws.soap); FILE *fd = fopen((argc > 3 ? argv[3] : "filename"), "rb"); if (fd == NULL) return 0; struct stat sb; if (!fstat(fileno(fd), &sb) && sb.st_size > 0) { // We can get the length of the file, so we can stream it // Set soap callbacks aws.soap->fdimereadopen = dime_read_open; aws.soap->fdimereadclose = dime_read_close; aws.soap->fdimeread = dime_read; //__ptr must be non-NULL. This is the handle in the callbacks data->__ptr = (unsigned char*)fd; data->__size = sb.st_size; } else { // We don't know the size, so buffer it int i; data->__ptr = (unsigned char*)soap_malloc(aws.soap, MAX_FILE_SIZE); for (i = 0; i < MAX_FILE_SIZE; i++) { int c; if ((c = fgetc(fd)) == EOF) break; data->__ptr[i] = c; } fclose(fd); data->__size = i; } // Set DIME attachment with options char type[] = "text/plain"; data->type = type; soap_set_dime(aws.soap); if (soap_set_dime_attachment(aws.soap, (char*)data->__ptr, data->__size, data->type, data->id, 0, data->options)) { aws.soap_stream_fault(std::cerr); return 0; } putObjectReq.ContentLength = data->__size; // Store the result of the service _s3__PutObjectResponse putObjectRes; // Put object if (aws.PutObject(&putObjectReq, putObjectRes)) { aws.soap_stream_fault(std::cerr); } else if (putObjectRes.PutObjectResponse) { s3__PutObjectResult &result = *putObjectRes.PutObjectResponse; std::cout << "Object with key '" << putObjectReq.Key << "' put into bucket '" << putObjectReq.Bucket << "'" << std::endl; std::cout << "\tEtag: " << result.ETag << std::endl; std::cout << "\tLast modified: " << soap_dateTime2s(aws.soap, result.LastModified) << std::endl; } // Delete all managed data aws.destroy(); return 0; }
编译 PutObject(流式)示例
使用 g++
g++ -DSOAP_MAXDIMESIZE=100000000 -DWITH_OPENSSL -o putobjectstreaming putobjectstreaming.cpp soapAmazonS3SoapBindingProxy.cpp \
soapC.cpp stdsoap2.cpp -lssl -lcrypto
有关命令的说明,请参阅**编译 CreateBucket 示例**部分。
请注意,我们添加了一个新的选项 -DSOAP_MAXDIMESIZE=100000000
来将默认 DIME 大小限制(在 stdsoap2.h 中设置)从 8MB 增加到约 100MB。如果需要,请增加此选项以允许更大的附件。
在最终的 .cpp 文件中,请注意我们在设置 bucketName
、keyName
、文件名和 credentialsFile
时检查了命令行参数(argv
)。这允许程序通过参数调用
./putobjectstreaming BucketName KeyName filename path_to_credentials_file
其他 AWS S3 API 函数及示例
调用不同的 API 函数只需使用不同的类并设置相应的参数。
AWS S3 的完整 SOAP API 文档可以在这里找到。它列出了可用的操作。您也可以查看生成的 aws-s3.h 文件或其自动生成报告,以获取可用 gSOAP 类和函数的列表(这些都是从 Amazon 的 WSDL/XSD 自动生成的)。
我已为以下 AWS S3 API 函数的用法编写了可用的示例程序
CreateBucket
(createbucket.cpp)(本文中已展示)ListBucket
(listbucket.cpp)DeleteBucket
(deletebucket.cpp)GetBucketAccessControlPolicy
(setacp.cpp)SetBucketAccessControlPolicy
(setacp.cpp)PutObjectInline
(putobjectinline.cpp)PutObject
(非流式:putobject.cpp,流式:putobjectstreaming.cpp)(本文中已展示)CopyObject
(copyobject.cpp)GetObject
(getobject.cpp)DeleteObject
(deleteobject.cpp)
这些都包含在 aws-s3-gsoap-examples.zip 文件中。
这应该足够创建一个功能齐全的客户端来与 AWS S3 进行交互,并找出如何使用 gSOAP 的其他自动生成类来调用其他 API 操作。也可以编写一个包装类来使与 AWS S3 的交互更加容易。
如果您有任何问题或建议,请留下评论!