如何使用 GSOAP for C 和 C++ 访问 Amazon AWS S3?
第 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
使用选项-c 生成C 源代码而不是默认的C++ 源代码。 typemap.dat 文件位于 gSOAP 发行版的 gsoap 目录中。
第 2 步
对从 wsdl2h 工具创建的头文件使用 soapcpp2 工具。
soapcpp2 -C -j aws-s3.h
这会从 aws-s3.h 标头生成带有 C++ 服务代理和对象(-j 选项)的客户端代码(-C 选项)。 C 代码省略-j。
第 3 步
使用自动生成的 AmazonS3SoapBindingProxy 代理方法访问 AWS S3 并为 AWS S3 创建 base64 编码的 HMAC-SHA1 哈希签名。签名是一个带有 HMAC-SHA1 哈希字符串 "AmazonS3" + OPERATION_NAME + Timestamp 的 base64 编码版本的字符串:
/*
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;
}
C 代码看起来很相似,主要区别在于使用函数调用而不是方法调用,即soap_call___s3__CreateBucket(&createBucketReq, &createBucketRes)。所有这些都在生成的aws-s4.h 文件中进行了解释。
使用您的源代码编译生成的文件:
c++ -DSOAP_MAXDIMESIZE=104857600 -DWITH_OPENSSL -o createbucket createbucket.cpp soapAmazonS3SoapBindingProxy.cpp soapC.cpp stdsoap2.cpp -lssl -lcrypto
SOAP_MAXDIMESIZE=104857600 确保 DIME 附件大小可以足够大,同时防止使用 DIME 的拒绝服务攻击。 DIME 标头具有附件大小,因此攻击者可以将其设置为任意大以耗尽内存资源。其他帖子没有提到这一点。
运行createbucket,会创建一个新的bucket。
在最终的 .cpp 文件中,请注意我们在设置 credentialsFile 和 bucketName 时会检查命令行参数 (argv)。这允许使用参数调用程序:
./createbucket BucketName path_to_credentials_file
有关所有这些的更多详细信息,我建议阅读 Chris Moutsos 在How to use AWS S3 in C++ with gSOAP 上撰写的优秀 CodeProject 文章,该解释的部分内容来自该文章。