全部文档
当前文档

暂无内容

如果没有找到您期望的内容,请尝试其他搜索词

文档中心

使用预签名上传

最近更新时间:2025-04-25 15:42:55

预签名url简单上传

using namespace ks3::sdk;

void putObjectByUrlDemo() {
    std::string filename = "<your file path>";

    std::string ak = "<your access key>";
    std::string sk = "<your secret key>";
    std::string host = "<your endpoint>"; // 填写桶所在region对应的Endpoint
    // 初始化client
    KS3Client client(host, ak, sk);

    std::string bucketName = "<your bucket name>";
    std::string keyName = "<your key name>";

    auto req = GeneratePresignedUrlRequest(bucketName, keyName, MethodType::PUT_METHOD);
    // 设置过期时间为1小时
    req.SetExpires(std::time(nullptr) + 3600);
    std::string uploadUrl = client.GeneratePresignedUrl(req);

    long fileSize = getFileSize(filename);
    std::shared_ptr<std::iostream> content = std::make_shared<std::fstream>(filename, std::ios::in|std::ios::binary);

    Response resp = doRequest({0, fileSize, content, {}, "PUT", uploadUrl});
    std::cout << "status code: " << resp.status_code << std::endl;
}

预签名分块上传

生成预签名url

生成初始化分块上传的预签名url
using namespace ks3::sdk;

std::string generateInitMultipartUploadPreSignedUrl(const KS3Client& client, const std::string& bucketName, const std::string& keyName, const std::map<std::string, std::string>& headers) {
    auto req = GeneratePresignedUrlRequest(bucketName, keyName, MethodType::POST_METHOD);
    // 设置过期时间为1小时
    req.SetExpires(std::time(nullptr) + 3600);
    req.SetParameters({{"uploads", ""}});
    req.SetHeaders(headers);
    return client.GeneratePresignedUrl(req);
}
生成上传分块的预签名url
using namespace ks3::sdk;

std::string generateUploadPartPreSignedUrl(const KS3Client& client, const std::string& bucketName, const std::string& keyName, const std::string& uploadId, int partNum) {
     auto req = GeneratePresignedUrlRequest(bucketName, keyName, MethodType::PUT_METHOD);
    // 设置过期时间为1小时
     req.SetExpires(std::time(nullptr) + 3600);
     req.SetParameters({{"partNumber", std::to_string(partNum)}, {"uploadId", uploadId}});
     return client.GeneratePresignedUrl(req);
}
生成完成分块上传的预签名url
using namespace ks3::sdk;

std::string generateCompleteMultipartUploadPreSignedUrl(const KS3Client& client, const std::string& bucketName, const std::string& keyName, const std::string& uploadId) {
    auto req = GeneratePresignedUrlRequest(bucketName, keyName, MethodType::POST_METHOD);
    // 设置过期时间为1小时
    req.SetExpires(std::time(nullptr) + 3600);
    req.SetParameters({{"uploadId", uploadId}});
    req.SetHeaders({{"Content-Type", "application/xml"}});
    return client.GeneratePresignedUrl(req);
}

使用预签名url

使用预签名url初始化分块上传
/**
* 初始化分块上传,并得到uploadId
* 
* @param initUrl 初始化分块上传的预签名URL
* @param headers 请求头, 必须和生成预签名URL时的请求头一致
* @return uploadId 分块上传的uploadId, 后续环节生成预签名url时需要用到
*/
std::string initMultipartUploadByUrl(const std::string& initUrl, const std::map<std::string, std::string>& headers) {
    Response resp = doRequest({0, 0, nullptr, headers, "POST", initUrl});

    std::string uploadId;

    // 解析响应XML,得到uploadId
    xmlKeepBlanksDefault(0);
    xmlDocPtr doc = xmlReadMemory(resp.body.c_str(), resp.body.size(), nullptr, "UTF-8", XML_PARSE_RECOVER);
    if (doc != nullptr) {
        if (xmlNodePtr proot = xmlDocGetRootElement(doc); proot != nullptr) {
            xmlNodePtr cur = proot->xmlChildrenNode;
            while (cur != nullptr) {
                if (xmlStrcmp(cur->name, reinterpret_cast<const xmlChar*>("UploadId")) == 0) {
                    xmlChar* tmp_content = xmlNodeGetContent(cur);
                    uploadId.assign(reinterpret_cast<char*>(tmp_content));
                    xmlFree(tmp_content);
                    break;
                }

                cur = cur->next;
            }
        }
    }

    xmlFreeDoc(doc);

    return uploadId;
}
使用预签名url上传分块
std::map<std::string, std::string> uploadPartByUrl(const std::string& uploadUrl, const std::string& filename, int partNum, long partSize) {
    long fileSize = getFileSize(filename);
    long offset = (partNum - 1) * partSize;
    long uploadSize = offset + partSize > fileSize ? fileSize - offset : partSize;

    std::shared_ptr<std::iostream> content = std::make_shared<std::fstream>(filename, std::ios::in|std::ios::binary);
    // 移动到指定offset
    content->seekg(offset, std::ios::beg);

    Response resp = doRequest({0, uploadSize, content, {}, "PUT", uploadUrl});

    // 提取响应头中的信息,构造partInfo
    auto etag = resp.headers["ETag"];
    auto crc = resp.headers["x-kss-checksum-crc64ecma"];

    std::map<std::string, std::string> partInfo = {
        {"PartNumber", std::to_string(partNum)},
        {"ETag", etag},
        {"ChecksumCRC64ECMA", crc}
    };

    return partInfo;
}
使用预签名url完成分块上传
void completeUploadByUrl(const std::string& completeUrl, const std::vector<std::map<std::string, std::string>>& partInfos) {
    // 通过partInfos构造XML,作为请求体
    xmlDocPtr doc = xmlNewDoc(nullptr);
    xmlNodePtr root = xmlNewNode(nullptr, reinterpret_cast<const xmlChar*>("CompleteMultipartUpload"));
    xmlDocSetRootElement(doc, root);

    for (const auto& partInfo : partInfos) {
        xmlNodePtr part = xmlNewNode(nullptr, reinterpret_cast<const xmlChar*>("Part"));
        xmlNodePtr partNumber = xmlNewNode(nullptr, reinterpret_cast<const xmlChar*>("PartNumber"));
        xmlNodePtr crc = xmlNewNode(nullptr, reinterpret_cast<const xmlChar*>("ChecksumCRC64ECMA"));
        xmlNodePtr etag = xmlNewNode(nullptr, reinterpret_cast<const xmlChar*>("ETag"));

        xmlNodeAddContent(partNumber, reinterpret_cast<const xmlChar*>(partInfo.at("PartNumber").c_str()));
        xmlNodeAddContent(crc, reinterpret_cast<const xmlChar*>(partInfo.at("ChecksumCRC64ECMA").c_str()));
        xmlNodeAddContent(etag, reinterpret_cast<const xmlChar*>(partInfo.at("ETag").c_str()));

        xmlAddChild(part, partNumber);
        xmlAddChild(part, crc);
        xmlAddChild(part, etag);
        xmlAddChild(root, part);
    }

    xmlChar* out = nullptr;
    int len = 0;
    xmlDocDumpFormatMemory(doc, &out, &len, 1);

    std::string content;
    content.assign(reinterpret_cast<char*>(out));
    xmlFree(out);
    xmlFreeDoc(doc);
    xmlMemoryDump();

    std::shared_ptr<std::stringstream> ss = std::make_shared<std::stringstream>(content);
    const Response resp = doRequest({0, len, ss, {{"Content-Type", "application/xml"}}, "POST", completeUrl});
}

生成预签名url并进行分块上传的完整流程

using namespace ks3::sdk;

void multiuploadByPreSignUrlDemo() {
    std::string filename = "<your file path>";

    std::string ak = "<your access key>";
    std::string sk = "<your secret key>";
    std::string host = "<your endpoint>"; // 填写桶所在region对应的Endpoint
    // 初始化client
    KS3Client client(host, ak, sk);

    std::string bucketName = "<your bucket name>";
    std::string keyName = "<your key name>";
    // 获取初始化分块上传的预签名URL
    // 设置请求头
    std::map<std::string, std::string> headers = {{"Content-Type", "text/plain"}};
    std::string initUrl = generateInitMultipartUploadPreSignedUrl(client, bucketName, keyName, headers);
    // 使用预签名URL初始化分块上传,并得到uploadId
    // 请求头必须和生成预签名URL时的请求头一致

    auto uploadId = initMultipartUploadByUrl(initUrl, headers);


    long partSize = 100 * 1024 * 1024;
    long fileSize = getFileSize(filename);
    int partCount = static_cast<int>((fileSize + partSize - 1) / partSize);

    std::vector<std::map<std::string, std::string>> partInfos;
    for (int partNum = 1; partNum <= partCount; partNum++) {
        // 获取分块上传的预签名URL
        std::string partUploadUrl = generateUploadPartPreSignedUrl(client, bucketName, keyName, uploadId, partNum);
        // 使用预签名URL上传分块
        auto partInfo = uploadPartByUrl(partUploadUrl, filename, partNum, partSize);
        partInfos.push_back(partInfo);
    }

    // 获取完成分块上传的预签名URL
    std::string completeUrl = generateCompleteMultipartUploadPreSignedUrl(client, bucketName, keyName, uploadId);
    // 使用预签名URL完成分块上传
    completeUploadByUrl(completeUrl, partInfos);
}

相关工具方法与结构体实现

发送请求相关方法

// 用于保存响应信息
struct Response {
    int status_code;
    std::string status_msg;
    std::map<std::string, std::string> headers;
    std::string body;

    Response() : status_code(0) {}
};

// 用于保存请求信息
struct ResourceManager {
    long send;   // request body send
    long total;  // request body content-length
    std::shared_ptr<std::iostream> body;
    std::map<std::string, std::string> headers;
    std::string method;
    std::string url;
};

// 读取响应体的方法
size_t ReadResponseBody(void* buffer, size_t size, size_t nmemb, void* userdata) {
    const size_t num_bytes = size * nmemb;
    auto* res = static_cast<Response*>(userdata);
    res->body.append(static_cast<char*>(buffer), num_bytes);
    return num_bytes;
}

// 发送请求体的方法
size_t SendData(char* buffer, size_t size, size_t nmemb, void* userdata) {
    auto* rm = static_cast<ResourceManager*>(userdata);
    std::shared_ptr<std::iostream>& content = rm->body;
    const size_t wanted = size * nmemb;
    size_t got = 0;
    if (content != nullptr && wanted > 0) {
        size_t read = wanted;
        if (rm->total > 0) {
            int64_t remains = rm->total - rm->send;
            if (remains < static_cast<int64_t>(wanted)) {
                read = static_cast<size_t>(remains);
            }
        }
        content->read(buffer, read);
        got = static_cast<size_t>(content->gcount());
    }

    rm->send += got;
    return got;
}

// 读取响应头的方法
size_t ReadHeaders(char* buffer, size_t size, size_t nmemb, void* userdata) {
    const size_t num_bytes = size * nmemb;
    if (num_bytes <= 0) {
        return 0;
    }

    auto* res = static_cast<Response*>(userdata);
    std::string line(buffer);

    if (const auto key_len = line.find(':'); key_len != std::string::npos) {
        auto value_len = line.rfind('\r');
        if (value_len != std::string::npos) {
            value_len = value_len - key_len - 2;
        }
        // header格式为:key: value\r
        std::string key = line.substr(0, key_len);
        std::string value = line.substr(key_len + 2, value_len);
        res->headers.insert(std::pair(key, value));
    } else if (line.find("HTTP") == 0) {
        // 获取status_msg HTTP/1.1 200 OK\r\n
        const auto first_space_pos = line.find(' ');
        if (first_space_pos == std::string::npos) return num_bytes;
        const auto second_space_pos = line.find(' ', first_space_pos + 1);
        if (second_space_pos == std::string::npos) return num_bytes;
        const auto end_pos = line.rfind('\r');
        if (end_pos == std::string::npos) return num_bytes;
        res->status_msg.append(line.substr(second_space_pos + 1, end_pos - second_space_pos - 1));
    }

    return num_bytes;
}

Response doRequest(const ResourceManager& rm) {
    CURL* curl = curl_easy_init();

    curl_easy_setopt(curl, CURLOPT_URL, rm.url.c_str());
    if (rm.method == "POST") {
        curl_easy_setopt(curl, CURLOPT_POST, 1L);
        curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, rm.total);
        curl_easy_setopt(curl, CURLOPT_TRANSFER_ENCODING, 0L);
    } else if (rm.method == "PUT") {
        curl_easy_setopt(curl, CURLOPT_PUT, 1L);
        curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
        curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, rm.total);
    }

    // add headers
    curl_slist* list = nullptr;

    for (const auto& p : rm.headers) {
        if (p.second.empty())
            continue;
        std::string str(p.first);
        str.append(":").append(p.second);
        list = curl_slist_append(list, str.c_str());
    }
    // Disable Expect: 100-continue
    list = curl_slist_append(list, "Expect:");
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);

    Response response;
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, ReadResponseBody);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
    curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, ReadHeaders);
    curl_easy_setopt(curl, CURLOPT_HEADERDATA, &response);
    curl_easy_setopt(curl, CURLOPT_READFUNCTION, SendData);
    curl_easy_setopt(curl, CURLOPT_READDATA, rm);

    if(const CURLcode res = curl_easy_perform(curl); res != CURLE_OK)
        std::cerr << "curl_easy_perform() failed: " << curl_easy_strerror(res) << std::endl;
    else
        curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, & response.status_code);

    return response;
}

获取文件大小

long getFileSize(const std::string &filename) {
    std::ifstream file(filename, std::ios::binary | std::ios::ate);
    if (!file) {
        std::cerr << "[ERROR] open file=" << filename << " failed" << std::endl;
        return -1;
    }
    long len = file.tellg();
    file.close();
    return len;
}

文档导读
纯净模式常规模式

纯净模式

点击可全屏预览文档内容
文档反馈