qicosmos / cinatra

modern c++(c++20), cross-platform, header-only, easy to use http framework
MIT License
1.81k stars 369 forks source link

async_download 没有写入内容到文件 #593

Closed kiraYuukiAsuna closed 1 month ago

kiraYuukiAsuna commented 1 month ago

image image 从result返回的结果可以看到code=206,body大小104857600,是符合预期的,但文件却是空的,没有任何内容写入到文件中,并且我看了下async_download的代码好像并没有相关写入文件的地方。

kiraYuukiAsuna commented 1 month ago

相关代码,目前我是手动写入文件的

  // Download the file in chunks of 100MB
        const size_t chunk_size = 100 * 1024 * 1024; // 100MB
        for (size_t start = 0; start < bytes_total; start += chunk_size) {
            size_t end = std::min(start + chunk_size - 1, bytes_total - 1);
            SEELE_INFO_TAG(__func__, "{}",
                           "Download Range: " + std::to_string(start) + "-" + std::to_string(end) + "/" + std::to_string
                           (bytes_total));
            std::string rangeFilePath = localSaveFilePath + "_range=" + std::to_string(start) + "-" + std::to_string(end);
            auto result = co_await client.async_download(uri, rangeFilePath, std::to_string(start) + "-" + std::to_string(end));
            if (!result.net_err && result.status == 206) {
                bytes_received += end - start + 1;

                std::ofstream ofstream(rangeFilePath, std::ios::binary);
                if(!ofstream.is_open()) {
                    SEELE_INFO_TAG(__func__, "{}", "Open File Error! File:" + rangeFilePath);
                    std::pair<ReturnWrapper, std::string> ret{
                        {
                            false, ErrorCode::AccessFileFailed,
                            "Open File Error! File:" + rangeFilePath
                        },
                        ""
                    };
                    co_return ret;
                }

                ofstream << result.resp_body;
                ofstream.close();

                rangeFiles.push_back(rangeFilePath);
            }
            else {
                SEELE_INFO_TAG(__func__, "{}", "Http Response Error!" + result.net_err.message());
                std::pair<ReturnWrapper, std::string> ret{
                    {
                        false, ErrorCode::HttpResponseError,
                        "Http Response Error!" + result.net_err.message()
                    },
                    ""
                };
                co_return ret;
            }
        }
qicosmos commented 1 month ago

把返回的http 头输出一下,看一下是什么格式返回的,如果是chunked或者multipart则会下载到文件,否则是直接放body里面的,如果在body里面,那你就取出来存文件吧。

另外,返回206有点奇怪,206是重定向,重定向的url在http header里面,cinatra不会自动根据重定向url去请求,需要你手动去连接新的url并发请求。

kiraYuukiAsuna commented 1 month ago

谢谢您的回复!

HTTP 206 Partial Content 成功状态响应代码表示请求已成功,并且主体包含所请求的数据区间,该数据区间是在请求的 Range 首部指定的。

如果只包含一个数据区间,那么整个响应的 Content-Type 首部的值为所请求的文件的类型,同时包含 Content-Range 首部。

如果包含多个数据区间,那么整个响应的 Content-Type 首部的值为 multipart/byteranges ,其中一个片段对应一个数据区间,并提供 Content-Range 和 Content-Type 描述信息

在我的这个例子中是只包含一个Range数据区间,响应code=206Partial Content,响应的Content-Type值为application/octet-stream,具体请求和响应的头部信息可参考下面Postman的截图: image

kiraYuukiAsuna commented 1 month ago

所以应该是Content-Type值为application/octet-stream的时候cinatra默认不会写到文件而是会写到返回的body中吧,我以为不管什么都会写到文件的呢,这样的话那我就手动写入到文件吧,其实还更方便了,我可以控制将多次请求的文件数据片段写到一个文件里,不然默认这个async_download如果写到文件里我还需要将多次下载得到的多个文件拼接起来,不过感觉我好像不应该用这个download函数,直接用get就好了哈哈。

感谢您的回复,解答了我的疑问!

qicosmos commented 4 weeks ago

所以应该是Content-Type值为application/octet-stream的时候cinatra默认不会写到文件而是会写到返回的body中吧,我以为不管什么都会写到文件的呢,这样的话那我就手动写入到文件吧,其实还更方便了,我可以控制将多次请求的文件数据片段写到一个文件里,不然默认这个async_download如果写到文件里我还需要将多次下载得到的多个文件拼接起来,不过感觉我好像不应该用这个download函数,直接用get就好了哈哈。

感谢您的回复,解答了我的疑问!

是的,只有chunked和multipart格式才会写文件,因为这两种格式不会有content-length字段(multipart 可以有但不是http协议要求的,即使有长度可能是几个G的大文件也不适合放内存),需要流式读取到结束符为止。

另外我记错了301/302才是重定向,206是部分内容。