Skip to content

Commit 4201dea

Browse files
authored
common: introduce http.h for httplib-based client (ggml-org#16373)
* common: introduce http.h for httplib-based client This change moves cpp-httplib based URL parsing and client setup into a new header `common/http.h`, and integrates it in `arg.cpp` and `run.cpp`. It is an iteration towards removing libcurl, while intentionally minimizing changes to existing code to guarantee the same behavior when `LLAMA_CURL` is used. Signed-off-by: Adrien Gallouët <[email protected]> * tools : add missing WIN32_LEAN_AND_MEAN Signed-off-by: Adrien Gallouët <[email protected]> --------- Signed-off-by: Adrien Gallouët <[email protected]> Signed-off-by: Adrien Gallouët <[email protected]>
1 parent 7647992 commit 4201dea

File tree

5 files changed

+196
-88
lines changed

5 files changed

+196
-88
lines changed

CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
/common/build-info.* @ggerganov
1515
/common/common.* @ggerganov
1616
/common/console.* @ggerganov
17+
/common/http.* @angt
1718
/common/llguidance.* @ggerganov
1819
/common/log.* @ggerganov
1920
/common/sampling.* @ggerganov

common/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ add_library(${TARGET} STATIC
5656
common.h
5757
console.cpp
5858
console.h
59+
http.h
5960
json-partial.cpp
6061
json-partial.h
6162
json-schema-to-grammar.cpp

common/arg.cpp

Lines changed: 4 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,11 @@
3232
#include <thread>
3333
#include <vector>
3434

35-
//#define LLAMA_USE_CURL
36-
3735
#if defined(LLAMA_USE_CURL)
3836
#include <curl/curl.h>
3937
#include <curl/easy.h>
4038
#else
41-
#include <cpp-httplib/httplib.h>
39+
#include "http.h"
4240
#endif
4341

4442
#ifdef __linux__
@@ -596,77 +594,6 @@ std::pair<long, std::vector<char>> common_remote_get_content(const std::string &
596594

597595
#else
598596

599-
struct common_url {
600-
std::string scheme;
601-
std::string user;
602-
std::string password;
603-
std::string host;
604-
std::string path;
605-
};
606-
607-
static common_url parse_url(const std::string & url) {
608-
common_url parts;
609-
auto scheme_end = url.find("://");
610-
611-
if (scheme_end == std::string::npos) {
612-
throw std::runtime_error("invalid URL: no scheme");
613-
}
614-
parts.scheme = url.substr(0, scheme_end);
615-
616-
if (parts.scheme != "http" && parts.scheme != "https") {
617-
throw std::runtime_error("unsupported URL scheme: " + parts.scheme);
618-
}
619-
620-
auto rest = url.substr(scheme_end + 3);
621-
auto at_pos = rest.find('@');
622-
623-
if (at_pos != std::string::npos) {
624-
auto auth = rest.substr(0, at_pos);
625-
auto colon_pos = auth.find(':');
626-
if (colon_pos != std::string::npos) {
627-
parts.user = auth.substr(0, colon_pos);
628-
parts.password = auth.substr(colon_pos + 1);
629-
} else {
630-
parts.user = auth;
631-
}
632-
rest = rest.substr(at_pos + 1);
633-
}
634-
635-
auto slash_pos = rest.find('/');
636-
637-
if (slash_pos != std::string::npos) {
638-
parts.host = rest.substr(0, slash_pos);
639-
parts.path = rest.substr(slash_pos);
640-
} else {
641-
parts.host = rest;
642-
parts.path = "/";
643-
}
644-
return parts;
645-
}
646-
647-
static std::pair<httplib::Client, common_url> http_client(const std::string & url) {
648-
common_url parts = parse_url(url);
649-
650-
if (parts.host.empty()) {
651-
throw std::runtime_error("error: invalid URL format");
652-
}
653-
654-
if (!parts.user.empty()) {
655-
throw std::runtime_error("error: user:password@ not supported yet"); // TODO
656-
}
657-
658-
httplib::Client cli(parts.scheme + "://" + parts.host);
659-
cli.set_follow_location(true);
660-
661-
// TODO cert
662-
663-
return { std::move(cli), std::move(parts) };
664-
}
665-
666-
static std::string show_masked_url(const common_url & parts) {
667-
return parts.scheme + "://" + (parts.user.empty() ? "" : "****:****@") + parts.host + parts.path;
668-
}
669-
670597
static void print_progress(size_t current, size_t total) {
671598
if (!is_output_a_tty()) {
672599
return;
@@ -759,7 +686,7 @@ static bool common_download_file_single_online(const std::string & url,
759686
static const int max_attempts = 3;
760687
static const int retry_delay_seconds = 2;
761688

762-
auto [cli, parts] = http_client(url);
689+
auto [cli, parts] = common_http_client(url);
763690

764691
httplib::Headers default_headers = {{"User-Agent", "llama-cpp"}};
765692
if (!bearer_token.empty()) {
@@ -839,7 +766,7 @@ static bool common_download_file_single_online(const std::string & url,
839766

840767
// start the download
841768
LOG_INF("%s: trying to download model from %s to %s (etag:%s)...\n",
842-
__func__, show_masked_url(parts).c_str(), path_temporary.c_str(), etag.c_str());
769+
__func__, common_http_show_masked_url(parts).c_str(), path_temporary.c_str(), etag.c_str());
843770
const bool was_pull_successful = common_pull_file(cli, parts.path, path_temporary, supports_ranges, existing_size, total_size);
844771
if (!was_pull_successful) {
845772
if (i + 1 < max_attempts) {
@@ -867,7 +794,7 @@ static bool common_download_file_single_online(const std::string & url,
867794

868795
std::pair<long, std::vector<char>> common_remote_get_content(const std::string & url,
869796
const common_remote_params & params) {
870-
auto [cli, parts] = http_client(url);
797+
auto [cli, parts] = common_http_client(url);
871798

872799
httplib::Headers headers = {{"User-Agent", "llama-cpp"}};
873800
for (const auto & header : params.headers) {

common/http.h

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
#pragma once
2+
3+
#include <cpp-httplib/httplib.h>
4+
5+
struct common_http_url {
6+
std::string scheme;
7+
std::string user;
8+
std::string password;
9+
std::string host;
10+
std::string path;
11+
};
12+
13+
static common_http_url common_http_parse_url(const std::string & url) {
14+
common_http_url parts;
15+
auto scheme_end = url.find("://");
16+
17+
if (scheme_end == std::string::npos) {
18+
throw std::runtime_error("invalid URL: no scheme");
19+
}
20+
parts.scheme = url.substr(0, scheme_end);
21+
22+
if (parts.scheme != "http" && parts.scheme != "https") {
23+
throw std::runtime_error("unsupported URL scheme: " + parts.scheme);
24+
}
25+
26+
auto rest = url.substr(scheme_end + 3);
27+
auto at_pos = rest.find('@');
28+
29+
if (at_pos != std::string::npos) {
30+
auto auth = rest.substr(0, at_pos);
31+
auto colon_pos = auth.find(':');
32+
if (colon_pos != std::string::npos) {
33+
parts.user = auth.substr(0, colon_pos);
34+
parts.password = auth.substr(colon_pos + 1);
35+
} else {
36+
parts.user = auth;
37+
}
38+
rest = rest.substr(at_pos + 1);
39+
}
40+
41+
auto slash_pos = rest.find('/');
42+
43+
if (slash_pos != std::string::npos) {
44+
parts.host = rest.substr(0, slash_pos);
45+
parts.path = rest.substr(slash_pos);
46+
} else {
47+
parts.host = rest;
48+
parts.path = "/";
49+
}
50+
return parts;
51+
}
52+
53+
static std::pair<httplib::Client, common_http_url> common_http_client(const std::string & url) {
54+
common_http_url parts = common_http_parse_url(url);
55+
56+
if (parts.host.empty()) {
57+
throw std::runtime_error("error: invalid URL format");
58+
}
59+
60+
httplib::Client cli(parts.scheme + "://" + parts.host);
61+
62+
if (!parts.user.empty()) {
63+
cli.set_basic_auth(parts.user, parts.password);
64+
}
65+
66+
cli.set_follow_location(true);
67+
68+
return { std::move(cli), std::move(parts) };
69+
}
70+
71+
static std::string common_http_show_masked_url(const common_http_url & parts) {
72+
return parts.scheme + "://" + (parts.user.empty() ? "" : "****:****@") + parts.host + parts.path;
73+
}

tools/run/run.cpp

Lines changed: 117 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <nlohmann/json.hpp>
1010

1111
#if defined(_WIN32)
12+
# define WIN32_LEAN_AND_MEAN
1213
# ifndef NOMINMAX
1314
# define NOMINMAX
1415
# endif
@@ -22,6 +23,8 @@
2223

2324
#if defined(LLAMA_USE_CURL)
2425
# include <curl/curl.h>
26+
#else
27+
# include "http.h"
2528
#endif
2629

2730
#include <signal.h>
@@ -397,7 +400,6 @@ class File {
397400
# endif
398401
};
399402

400-
#ifdef LLAMA_USE_CURL
401403
class HttpClient {
402404
public:
403405
int init(const std::string & url, const std::vector<std::string> & headers, const std::string & output_file,
@@ -428,6 +430,8 @@ class HttpClient {
428430
return 0;
429431
}
430432

433+
#ifdef LLAMA_USE_CURL
434+
431435
~HttpClient() {
432436
if (chunk) {
433437
curl_slist_free_all(chunk);
@@ -532,6 +536,117 @@ class HttpClient {
532536
return curl_easy_perform(curl);
533537
}
534538

539+
#else // LLAMA_USE_CURL is not defined
540+
541+
#define curl_off_t long long // temporary hack
542+
543+
private:
544+
// this is a direct translation of the cURL download() above
545+
int download(const std::string & url, const std::vector<std::string> & headers_vec, const std::string & output_file,
546+
const bool progress, std::string * response_str = nullptr) {
547+
try {
548+
auto [cli, url_parts] = common_http_client(url);
549+
550+
httplib::Headers headers;
551+
for (const auto & h : headers_vec) {
552+
size_t pos = h.find(':');
553+
if (pos != std::string::npos) {
554+
headers.emplace(h.substr(0, pos), h.substr(pos + 2));
555+
}
556+
}
557+
558+
File out;
559+
if (!output_file.empty()) {
560+
if (!out.open(output_file, "ab")) {
561+
printe("Failed to open file for writing\n");
562+
return 1;
563+
}
564+
if (out.lock()) {
565+
printe("Failed to exclusively lock file\n");
566+
return 1;
567+
}
568+
}
569+
570+
size_t resume_offset = 0;
571+
if (!output_file.empty() && std::filesystem::exists(output_file)) {
572+
resume_offset = std::filesystem::file_size(output_file);
573+
if (resume_offset > 0) {
574+
headers.emplace("Range", "bytes=" + std::to_string(resume_offset) + "-");
575+
}
576+
}
577+
578+
progress_data data;
579+
data.file_size = resume_offset;
580+
581+
long long total_size = 0;
582+
long long received_this_session = 0;
583+
584+
auto response_handler =
585+
[&](const httplib::Response & response) {
586+
if (resume_offset > 0 && response.status != 206) {
587+
printe("\nServer does not support resuming. Restarting download.\n");
588+
out.file = freopen(output_file.c_str(), "wb", out.file);
589+
if (!out.file) {
590+
return false;
591+
}
592+
data.file_size = 0;
593+
}
594+
if (progress) {
595+
if (response.has_header("Content-Length")) {
596+
total_size = std::stoll(response.get_header_value("Content-Length"));
597+
} else if (response.has_header("Content-Range")) {
598+
auto range = response.get_header_value("Content-Range");
599+
auto slash = range.find('/');
600+
if (slash != std::string::npos) {
601+
total_size = std::stoll(range.substr(slash + 1));
602+
}
603+
}
604+
}
605+
return true;
606+
};
607+
608+
auto content_receiver =
609+
[&](const char * chunk, size_t length) {
610+
if (out.file && fwrite(chunk, 1, length, out.file) != length) {
611+
return false;
612+
}
613+
if (response_str) {
614+
response_str->append(chunk, length);
615+
}
616+
received_this_session += length;
617+
618+
if (progress && total_size > 0) {
619+
update_progress(&data, total_size, received_this_session, 0, 0);
620+
}
621+
return true;
622+
};
623+
624+
auto res = cli.Get(url_parts.path, headers, response_handler, content_receiver);
625+
626+
if (data.printed) {
627+
printe("\n");
628+
}
629+
630+
if (!res) {
631+
auto err = res.error();
632+
printe("Fetching resource '%s' failed: %s\n", url.c_str(), httplib::to_string(err).c_str());
633+
return 1;
634+
}
635+
636+
if (res->status >= 400) {
637+
printe("Fetching resource '%s' failed with status code: %d\n", url.c_str(), res->status);
638+
return 1;
639+
}
640+
641+
} catch (const std::exception & e) {
642+
printe("HTTP request failed: %s\n", e.what());
643+
return 1;
644+
}
645+
return 0;
646+
}
647+
648+
#endif // LLAMA_USE_CURL
649+
535650
static std::string human_readable_time(double seconds) {
536651
int hrs = static_cast<int>(seconds) / 3600;
537652
int mins = (static_cast<int>(seconds) % 3600) / 60;
@@ -644,8 +759,8 @@ class HttpClient {
644759
str->append(static_cast<char *>(ptr), size * nmemb);
645760
return size * nmemb;
646761
}
762+
647763
};
648-
#endif
649764

650765
class LlamaData {
651766
public:
@@ -673,7 +788,6 @@ class LlamaData {
673788
}
674789

675790
private:
676-
#ifdef LLAMA_USE_CURL
677791
int download(const std::string & url, const std::string & output_file, const bool progress,
678792
const std::vector<std::string> & headers = {}, std::string * response_str = nullptr) {
679793
HttpClient http;
@@ -683,14 +797,6 @@ class LlamaData {
683797

684798
return 0;
685799
}
686-
#else
687-
int download(const std::string &, const std::string &, const bool, const std::vector<std::string> & = {},
688-
std::string * = nullptr) {
689-
printe("%s: llama.cpp built without libcurl, downloading from an url not supported.\n", __func__);
690-
691-
return 1;
692-
}
693-
#endif
694800

695801
// Helper function to handle model tag extraction and URL construction
696802
std::pair<std::string, std::string> extract_model_and_tag(std::string & model, const std::string & base_url) {

0 commit comments

Comments
 (0)