174 lines
5.3 KiB
C++
174 lines
5.3 KiB
C++
|
/*
|
||
|
* @build: make examples
|
||
|
* @server bin/httpd -s restart -d
|
||
|
* @client bin/wget http://127.0.0.1:8080/
|
||
|
*/
|
||
|
|
||
|
#include "HttpClient.h"
|
||
|
#include "htime.h"
|
||
|
using namespace hv;
|
||
|
|
||
|
typedef std::function<void(size_t received_bytes, size_t total_bytes)> wget_progress_cb;
|
||
|
|
||
|
static int wget(const char* url, const char* filepath, wget_progress_cb progress_cb = NULL, bool use_range = true) {
|
||
|
int ret = 0;
|
||
|
HttpClient cli;
|
||
|
HttpRequest req;
|
||
|
HttpResponse resp;
|
||
|
|
||
|
// HEAD
|
||
|
req.method = HTTP_HEAD;
|
||
|
req.url = url;
|
||
|
ret = cli.send(&req, &resp);
|
||
|
if (ret != 0) {
|
||
|
fprintf(stderr, "request error: %d\n", ret);
|
||
|
return ret;
|
||
|
}
|
||
|
printd("%s", resp.Dump(true, false).c_str());
|
||
|
if (resp.status_code == HTTP_STATUS_NOT_FOUND) {
|
||
|
fprintf(stderr, "404 Not Found\n");
|
||
|
return 404;
|
||
|
}
|
||
|
|
||
|
// use Range?
|
||
|
int range_bytes = 1 << 20; // 1M
|
||
|
long from = 0, to = 0;
|
||
|
size_t content_length = hv::from_string<size_t>(resp.GetHeader("Content-Length"));
|
||
|
if (use_range) {
|
||
|
use_range = false;
|
||
|
std::string accept_ranges = resp.GetHeader("Accept-Ranges");
|
||
|
// use Range if server accept_ranges and content_length > 1M
|
||
|
if (resp.status_code == 200 &&
|
||
|
accept_ranges == "bytes" &&
|
||
|
content_length > range_bytes) {
|
||
|
use_range = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// open file
|
||
|
std::string filepath_download(filepath);
|
||
|
filepath_download += ".download";
|
||
|
HFile file;
|
||
|
if (use_range) {
|
||
|
ret = file.open(filepath_download.c_str(), "ab");
|
||
|
from = file.size();
|
||
|
} else {
|
||
|
ret = file.open(filepath_download.c_str(), "wb");
|
||
|
}
|
||
|
if (ret != 0) {
|
||
|
fprintf(stderr, "Failed to open file %s\n", filepath_download.c_str());
|
||
|
return ret;
|
||
|
}
|
||
|
printf("Save file to %s ...\n", filepath);
|
||
|
|
||
|
// GET
|
||
|
req.method = HTTP_GET;
|
||
|
req.timeout = 3600; // 1h
|
||
|
if (!use_range) {
|
||
|
size_t received_bytes = 0;
|
||
|
req.http_cb = [&file, &content_length, &received_bytes, &progress_cb]
|
||
|
(HttpMessage* resp, http_parser_state state, const char* data, size_t size) {
|
||
|
if (!resp->headers["Location"].empty()) return;
|
||
|
if (state == HP_HEADERS_COMPLETE) {
|
||
|
content_length = hv::from_string<size_t>(resp->GetHeader("Content-Length"));
|
||
|
printd("%s", resp->Dump(true, false).c_str());
|
||
|
} else if (state == HP_BODY) {
|
||
|
if (data && size) {
|
||
|
file.write(data, size);
|
||
|
received_bytes += size;
|
||
|
|
||
|
if (progress_cb) {
|
||
|
progress_cb(received_bytes, content_length);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
ret = cli.send(&req, &resp);
|
||
|
if (ret != 0) {
|
||
|
fprintf(stderr, "request error: %d\n", ret);
|
||
|
goto error;
|
||
|
}
|
||
|
goto success;
|
||
|
}
|
||
|
|
||
|
// Range: bytes=from-to
|
||
|
while (from < content_length) {
|
||
|
to = from + range_bytes - 1;
|
||
|
if (to >= content_length) to = content_length - 1;
|
||
|
req.SetRange(from, to);
|
||
|
printd("%s", req.Dump(true, false).c_str());
|
||
|
ret = cli.send(&req, &resp);
|
||
|
if (ret != 0) {
|
||
|
fprintf(stderr, "request error: %d\n", ret);
|
||
|
goto error;
|
||
|
}
|
||
|
printd("%s", resp.Dump(true, false).c_str());
|
||
|
file.write(resp.body.data(), resp.body.size());
|
||
|
// fix: resp.body.size != range_bytes on some server
|
||
|
// from = to + 1;
|
||
|
from += resp.body.size();
|
||
|
|
||
|
if (progress_cb) {
|
||
|
progress_cb(from, content_length);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
success:
|
||
|
file.close();
|
||
|
ret = file.rename(filepath);
|
||
|
if (ret != 0) {
|
||
|
fprintf(stderr, "mv %s => %s failed: %s:%d\n", filepath_download.c_str(), filepath, strerror(ret), ret);
|
||
|
}
|
||
|
return ret;
|
||
|
error:
|
||
|
file.close();
|
||
|
// file.remove();
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
int main(int argc, char** argv) {
|
||
|
if (argc < 2) {
|
||
|
printf("Usage: %s [--use_range] url [filepath]\n", argv[0]);
|
||
|
return -10;
|
||
|
}
|
||
|
int idx = 1;
|
||
|
bool use_range = false;
|
||
|
if (strcmp(argv[idx], "--use_range") == 0) {
|
||
|
use_range = true;
|
||
|
++idx;
|
||
|
}
|
||
|
const char* url = argv[idx++];
|
||
|
const char* filepath = "index.html";
|
||
|
if (argv[idx]) {
|
||
|
filepath = argv[idx];
|
||
|
} else {
|
||
|
const char* path = strrchr(url, '/');
|
||
|
if (path && path[1]) {
|
||
|
filepath = path + 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
unsigned int start_time = gettick_ms();
|
||
|
int last_progress = 0;
|
||
|
wget(url, filepath, [&last_progress](size_t received_bytes, size_t total_bytes) {
|
||
|
// print progress
|
||
|
if (total_bytes == 0) {
|
||
|
printf("\rprogress: %lu/? = ?", (unsigned long)received_bytes);
|
||
|
} else {
|
||
|
int cur_progress = received_bytes * 100 / total_bytes;
|
||
|
if (cur_progress > last_progress) {
|
||
|
printf("\rprogress: %lu/%lu = %d%%", (unsigned long)received_bytes, (unsigned long)total_bytes, (int)cur_progress);
|
||
|
last_progress = cur_progress;
|
||
|
}
|
||
|
}
|
||
|
fflush(stdout);
|
||
|
}, use_range);
|
||
|
unsigned int end_time = gettick_ms();
|
||
|
unsigned int cost_time = end_time - start_time;
|
||
|
printf("\ncost time %u ms\n", cost_time);
|
||
|
// 1B/ms = 1KB/s = 8Kbps
|
||
|
printf("download rate = %lu KB/s\n", (unsigned long)hv_filesize(filepath) / cost_time);
|
||
|
|
||
|
return 0;
|
||
|
}
|