mirror of
https://github.com/ikawrakow/ik_llama.cpp.git
synced 2026-06-28 04:30:15 -05:00
server: enable mcp proxy (#1904)
* update http lib * Add cors proxy --------- Co-authored-by: firecoperana <firecoperana>
This commit is contained in:
parent
074fc7dafd
commit
6c0180d702
@ -71,6 +71,7 @@ add_library(${TARGET} STATIC
|
||||
train.cpp
|
||||
log.cpp
|
||||
log.h
|
||||
http.h
|
||||
ngram-cache.cpp
|
||||
ngram-cache.h
|
||||
ngram-map.cpp
|
||||
|
||||
@ -2397,6 +2397,10 @@ bool gpt_params_find_arg(int argc, char ** argv, const std::string & arg, gpt_pa
|
||||
params.webui = common_webui_from_name(std::string(argv[i]));
|
||||
return true;
|
||||
}
|
||||
if (arg == "--webui-mcp-proxy" || arg == "--ui-mcp-proxy") {
|
||||
params.webui_mcp_proxy = true;
|
||||
return true;
|
||||
}
|
||||
if (arg == "--api-key") {
|
||||
CHECK_ARG
|
||||
params.api_keys.push_back(argv[i]);
|
||||
@ -3234,6 +3238,7 @@ void gpt_params_print_usage(int /*argc*/, char ** argv, const gpt_params & param
|
||||
"- auto: default webui \n"
|
||||
"- llamacpp: llamacpp webui \n"
|
||||
"(default: auto)", });
|
||||
options.push_back({ "server", " --ui-mcp-proxy, --webui-mcp-proxy", "experimental: whether to enable MCP CORS proxy - do not enable in untrusted environments (default: disabled)" });
|
||||
options.push_back({ "server", " --api-key KEY", "API key to use for authentication (default: none)" });
|
||||
options.push_back({ "server", " --api-key-file FNAME", "path to file containing API keys (default: none)" });
|
||||
options.push_back({ "server", " --ssl-key-file FNAME", "path to file a PEM-encoded SSL private key" });
|
||||
|
||||
@ -501,6 +501,7 @@ struct gpt_params {
|
||||
|
||||
// "advanced" endpoints are disabled by default for better security
|
||||
common_webui webui = COMMON_WEBUI_AUTO;
|
||||
bool webui_mcp_proxy = false;
|
||||
bool endpoint_slots = true;
|
||||
bool endpoint_props = false; // only control POST requests, not GET
|
||||
bool endpoint_metrics = false;
|
||||
|
||||
99
common/http.h
Normal file
99
common/http.h
Normal file
@ -0,0 +1,99 @@
|
||||
#pragma once
|
||||
|
||||
#include <cpp-httplib/httplib.h>
|
||||
|
||||
struct common_http_url {
|
||||
std::string scheme;
|
||||
std::string user;
|
||||
std::string password;
|
||||
std::string host;
|
||||
int port;
|
||||
std::string path;
|
||||
};
|
||||
|
||||
static common_http_url common_http_parse_url(const std::string & url) {
|
||||
common_http_url parts;
|
||||
auto scheme_end = url.find("://");
|
||||
|
||||
if (scheme_end == std::string::npos) {
|
||||
throw std::runtime_error("invalid URL: no scheme");
|
||||
}
|
||||
parts.scheme = url.substr(0, scheme_end);
|
||||
|
||||
if (parts.scheme != "http" && parts.scheme != "https") {
|
||||
throw std::runtime_error("unsupported URL scheme: " + parts.scheme);
|
||||
}
|
||||
|
||||
auto rest = url.substr(scheme_end + 3);
|
||||
auto at_pos = rest.find('@');
|
||||
|
||||
if (at_pos != std::string::npos) {
|
||||
auto auth = rest.substr(0, at_pos);
|
||||
auto colon_pos = auth.find(':');
|
||||
if (colon_pos != std::string::npos) {
|
||||
parts.user = auth.substr(0, colon_pos);
|
||||
parts.password = auth.substr(colon_pos + 1);
|
||||
} else {
|
||||
parts.user = auth;
|
||||
}
|
||||
rest = rest.substr(at_pos + 1);
|
||||
}
|
||||
|
||||
auto slash_pos = rest.find('/');
|
||||
|
||||
if (slash_pos != std::string::npos) {
|
||||
parts.host = rest.substr(0, slash_pos);
|
||||
parts.path = rest.substr(slash_pos);
|
||||
} else {
|
||||
parts.host = rest;
|
||||
parts.path = "/";
|
||||
}
|
||||
|
||||
auto colon_pos = parts.host.find(':');
|
||||
|
||||
if (colon_pos != std::string::npos) {
|
||||
parts.port = std::stoi(parts.host.substr(colon_pos + 1));
|
||||
parts.host = parts.host.substr(0, colon_pos);
|
||||
} else if (parts.scheme == "http") {
|
||||
parts.port = 80;
|
||||
} else if (parts.scheme == "https") {
|
||||
parts.port = 443;
|
||||
} else {
|
||||
throw std::runtime_error("unsupported URL scheme: " + parts.scheme);
|
||||
}
|
||||
|
||||
return parts;
|
||||
}
|
||||
|
||||
static std::pair<httplib::Client, common_http_url> common_http_client(const std::string & url) {
|
||||
common_http_url parts = common_http_parse_url(url);
|
||||
|
||||
if (parts.host.empty()) {
|
||||
throw std::runtime_error("error: invalid URL format");
|
||||
}
|
||||
|
||||
#ifndef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
if (parts.scheme == "https") {
|
||||
throw std::runtime_error(
|
||||
"HTTPS is not supported. Please rebuild with one of:\n"
|
||||
" -DLLAMA_BUILD_BORINGSSL=ON\n"
|
||||
" -DLLAMA_BUILD_LIBRESSL=ON\n"
|
||||
" -DLLAMA_OPENSSL=ON (default, requires OpenSSL dev files installed)"
|
||||
);
|
||||
}
|
||||
#endif
|
||||
|
||||
httplib::Client cli(parts.scheme + "://" + parts.host + ":" + std::to_string(parts.port));
|
||||
|
||||
if (!parts.user.empty()) {
|
||||
cli.set_basic_auth(parts.user, parts.password);
|
||||
}
|
||||
|
||||
cli.set_follow_location(true);
|
||||
|
||||
return { std::move(cli), std::move(parts) };
|
||||
}
|
||||
|
||||
static std::string common_http_show_masked_url(const common_http_url & parts) {
|
||||
return parts.scheme + "://" + (parts.user.empty() ? "" : "****:****@") + parts.host + parts.path;
|
||||
}
|
||||
170
examples/server/server-cors-proxy.h
Normal file
170
examples/server/server-cors-proxy.h
Normal file
@ -0,0 +1,170 @@
|
||||
#pragma once
|
||||
|
||||
#include "common.h"
|
||||
#include "http.h"
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
#include <list>
|
||||
#include <map>
|
||||
|
||||
static std::string to_lower_copy(const std::string & value) {
|
||||
std::string lowered(value.size(), '\0');
|
||||
std::transform(value.begin(), value.end(), lowered.begin(), [](unsigned char c) { return std::tolower(c); });
|
||||
return lowered;
|
||||
}
|
||||
|
||||
static httplib::Request prepare_proxy_req_header(const std::string & method,
|
||||
const std::string & scheme,
|
||||
const std::string & host,
|
||||
int port,
|
||||
const std::string & path,
|
||||
const std::map<std::string, std::string> & headers,
|
||||
const std::string & body,
|
||||
const httplib::FormFiles & files) {
|
||||
httplib::Request req;
|
||||
bool has_files = !files.empty();
|
||||
req.form.files = files;
|
||||
std::string effective_body = body;
|
||||
std::string override_content_type;
|
||||
req.method = method;
|
||||
req.path = path;
|
||||
for (const auto & [key, value] : headers) {
|
||||
const auto lowered = to_lower_copy(key);
|
||||
if (lowered == "accept-encoding") {
|
||||
// disable Accept-Encoding to avoid compressed responses
|
||||
continue;
|
||||
}
|
||||
if (lowered == "transfer-encoding") {
|
||||
// the body is already decoded
|
||||
continue;
|
||||
}
|
||||
if (lowered == "content-length") {
|
||||
// let httplib calculate Content-Length from the actual body
|
||||
continue;
|
||||
}
|
||||
if (lowered == "content-type") {
|
||||
if (has_files) {
|
||||
// we set our own Content-Type with the new boundary
|
||||
continue;
|
||||
}
|
||||
// when no files but the original request was multipart,
|
||||
// the body is now JSON, so correct the Content-Type
|
||||
if (value.find("multipart/form-data") != std::string::npos) {
|
||||
override_content_type = "application/json; charset=utf-8";
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (lowered == "host") {
|
||||
bool is_default_port = (scheme == "https" && port == 443) || (scheme == "http" && port == 80);
|
||||
req.set_header(key, is_default_port ? host : host + ":" + std::to_string(port));
|
||||
} else {
|
||||
req.set_header(key, value);
|
||||
}
|
||||
}
|
||||
req.body = effective_body;
|
||||
if (!override_content_type.empty()) {
|
||||
req.set_header("Content-Type", override_content_type);
|
||||
}
|
||||
//req.response_handler = response_handler;
|
||||
//req.content_receiver = content_receiver;
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
static std::string get_param(httplib::Params params,const std::string & key, const std::string & def = "") {
|
||||
auto it = params.find("url");
|
||||
if (it != params.end()) {
|
||||
return it->second;
|
||||
}
|
||||
return def;
|
||||
}
|
||||
|
||||
static void proxy_request(const httplib::Request & req,
|
||||
httplib::Response & res,
|
||||
const std::string & method) {
|
||||
std::string target_url = get_param(req.params, "url");
|
||||
common_http_url parsed_url = common_http_parse_url(target_url);
|
||||
if (parsed_url.host.empty()) {
|
||||
throw std::runtime_error("invalid target URL: missing host");
|
||||
}
|
||||
|
||||
if (parsed_url.path.empty()) {
|
||||
parsed_url.path = "/";
|
||||
}
|
||||
|
||||
if (!parsed_url.password.empty()) {
|
||||
throw std::runtime_error("authentication in target URL is not supported");
|
||||
}
|
||||
|
||||
if (parsed_url.scheme != "http" && parsed_url.scheme != "https") {
|
||||
throw std::runtime_error("unsupported URL scheme in target URL: " + parsed_url.scheme);
|
||||
}
|
||||
|
||||
SRV_INF("proxying %s request to %s://%s:%i%s\n", method.c_str(), parsed_url.scheme.c_str(), parsed_url.host.c_str(), parsed_url.port, parsed_url.path.c_str());
|
||||
std::map<std::string, std::string> headers;
|
||||
for (auto [key, value] : req.headers) {
|
||||
auto new_key = key;
|
||||
if (string_starts_with(new_key, "x-proxy-header-")) {
|
||||
string_replace_all(new_key, "x-proxy-header-", "");
|
||||
}
|
||||
headers[new_key] = value;
|
||||
}
|
||||
|
||||
httplib::Request proxy_req = prepare_proxy_req_header(method,
|
||||
parsed_url.scheme,
|
||||
parsed_url.host,
|
||||
parsed_url.port,
|
||||
parsed_url.path,
|
||||
headers,
|
||||
req.body,
|
||||
req.form.files);
|
||||
|
||||
// Make the proxied request
|
||||
httplib::Result proxy_res;
|
||||
|
||||
if (parsed_url.scheme == "https") {
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
httplib::SSLClient cli(parsed_url.host, parsed_url.port);
|
||||
// set timeouts, follow redirects as needed
|
||||
cli.set_connection_timeout(600);
|
||||
cli.set_read_timeout(600);
|
||||
cli.set_write_timeout(600);
|
||||
cli.set_follow_location(true);
|
||||
proxy_res = cli.send(proxy_req);
|
||||
#else
|
||||
res.status = 501;
|
||||
res.set_content("HTTPS not supported (build with OpenSSL)", "text/plain");
|
||||
return;
|
||||
#endif
|
||||
} else {
|
||||
httplib::Client cli(parsed_url.host, parsed_url.port);
|
||||
cli.set_connection_timeout(600);
|
||||
cli.set_read_timeout(600);
|
||||
cli.set_write_timeout(600);
|
||||
proxy_res = cli.send(std::move(proxy_req));
|
||||
}
|
||||
|
||||
if (!proxy_res) {
|
||||
std::string error_data = "Proxy failed: " + httplib::to_string(proxy_res.error());
|
||||
json final_response{ {"error", error_data} };
|
||||
res.set_content(safe_json_to_str(final_response), "application/json; charset=utf-8");
|
||||
res.status = json_value(error_data, "code", 500);
|
||||
return;
|
||||
}
|
||||
|
||||
res.status = proxy_res->status;
|
||||
res.set_content(proxy_res->body, proxy_res->get_header_value("Content-Type"));
|
||||
for (const auto & h : proxy_res->headers) {
|
||||
// skip hop-by-hop headers
|
||||
if (h.first != "Transfer-Encoding" && h.first != "Connection")
|
||||
res.set_header(h.first, h.second);
|
||||
}
|
||||
}
|
||||
|
||||
static void proxy_handler_get(const httplib::Request & req, httplib::Response & res) {
|
||||
proxy_request(req, res, "GET");
|
||||
}
|
||||
|
||||
static void proxy_handler_post(const httplib::Request & req, httplib::Response & res) {
|
||||
proxy_request(req, res, "POST");
|
||||
}
|
||||
@ -2,6 +2,7 @@
|
||||
#include "server-context.h"
|
||||
#include "server-common.h"
|
||||
#include "server-chat.h"
|
||||
#include "server-cors-proxy.h"
|
||||
#include "chat.h"
|
||||
|
||||
#include "common.h"
|
||||
@ -1020,7 +1021,8 @@ int main(int argc, char ** argv) {
|
||||
{"vision", ctx_server.chat_params.allow_image},
|
||||
{"audio", ctx_server.chat_params.allow_audio},
|
||||
} },
|
||||
{ "n_ctx", ctx_server.n_ctx }
|
||||
{ "n_ctx", ctx_server.n_ctx },
|
||||
{ "cors_proxy_enabled", ctx_server.params_base.webui_mcp_proxy},
|
||||
|
||||
};
|
||||
|
||||
@ -2108,6 +2110,16 @@ int main(int argc, char ** argv) {
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// CORS proxy (EXPERIMENTAL, only used by the Web UI for MCP)
|
||||
if (params.webui_mcp_proxy) {
|
||||
SRV_WRN("%s", "-----------------\n");
|
||||
SRV_WRN("%s", "CORS proxy is enabled, do not expose server to untrusted environments\n");
|
||||
SRV_WRN("%s", "This feature is EXPERIMENTAL and may be removed or changed in future versions\n");
|
||||
SRV_WRN("%s", "-----------------\n");
|
||||
svr->Get("/cors-proxy", proxy_handler_get);
|
||||
svr->Post("/cors-proxy", proxy_handler_post);
|
||||
}
|
||||
//
|
||||
// Start the server
|
||||
//
|
||||
|
||||
138
vendor/cpp-httplib/CMakeLists.txt
vendored
138
vendor/cpp-httplib/CMakeLists.txt
vendored
@ -22,7 +22,93 @@ target_compile_definitions(${TARGET} PRIVATE
|
||||
CPPHTTPLIB_TCP_NODELAY=1
|
||||
)
|
||||
|
||||
if (LLAMA_OPENSSL)
|
||||
set(OPENSSL_NO_ASM ON CACHE BOOL "Disable OpenSSL ASM code when building BoringSSL or LibreSSL")
|
||||
|
||||
if (LLAMA_BUILD_BORINGSSL)
|
||||
set(FIPS OFF CACHE BOOL "Enable FIPS (BoringSSL)")
|
||||
|
||||
set(BORINGSSL_GIT "https://boringssl.googlesource.com/boringssl" CACHE STRING "BoringSSL git repository")
|
||||
set(BORINGSSL_VERSION "0.20260508.0" CACHE STRING "BoringSSL version")
|
||||
|
||||
message(STATUS "Fetching BoringSSL version ${BORINGSSL_VERSION}")
|
||||
|
||||
set(BORINGSSL_ARGS
|
||||
GIT_REPOSITORY ${BORINGSSL_GIT}
|
||||
GIT_TAG ${BORINGSSL_VERSION}
|
||||
)
|
||||
if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.28)
|
||||
list(APPEND BORINGSSL_ARGS EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(boringssl ${BORINGSSL_ARGS})
|
||||
|
||||
set(SAVED_BUILD_SHARED_LIBS ${BUILD_SHARED_LIBS})
|
||||
set(SAVED_BUILD_TESTING ${BUILD_TESTING})
|
||||
|
||||
set(BUILD_SHARED_LIBS OFF)
|
||||
set(BUILD_TESTING OFF)
|
||||
|
||||
if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.28)
|
||||
FetchContent_MakeAvailable(boringssl)
|
||||
else()
|
||||
FetchContent_GetProperties(boringssl)
|
||||
if(NOT boringssl_POPULATED)
|
||||
FetchContent_Populate(boringssl)
|
||||
add_subdirectory(${boringssl_SOURCE_DIR} ${boringssl_BINARY_DIR} EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(BUILD_SHARED_LIBS ${SAVED_BUILD_SHARED_LIBS})
|
||||
set(BUILD_TESTING ${SAVED_BUILD_TESTING})
|
||||
|
||||
|
||||
set(CPPHTTPLIB_OPENSSL_SUPPORT TRUE)
|
||||
target_link_libraries(${TARGET} PUBLIC ssl crypto)
|
||||
|
||||
elseif (LLAMA_BUILD_LIBRESSL)
|
||||
set(LIBRESSL_VERSION "4.3.1" CACHE STRING "LibreSSL version")
|
||||
|
||||
message(STATUS "Fetching LibreSSL version ${LIBRESSL_VERSION}")
|
||||
|
||||
set(LIBRESSL_ARGS
|
||||
URL "https://cdn.openbsd.org/pub/OpenBSD/LibreSSL/libressl-${LIBRESSL_VERSION}.tar.gz"
|
||||
)
|
||||
if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.24)
|
||||
list(APPEND LIBRESSL_ARGS DOWNLOAD_EXTRACT_TIMESTAMP TRUE)
|
||||
endif()
|
||||
|
||||
if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.28)
|
||||
list(APPEND LIBRESSL_ARGS EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(libressl ${LIBRESSL_ARGS})
|
||||
|
||||
set(SAVED_BUILD_SHARED_LIBS ${BUILD_SHARED_LIBS})
|
||||
set(SAVED_BUILD_TESTING ${BUILD_TESTING})
|
||||
|
||||
set(BUILD_SHARED_LIBS OFF)
|
||||
set(BUILD_TESTING OFF)
|
||||
|
||||
if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.28)
|
||||
FetchContent_MakeAvailable(libressl)
|
||||
else()
|
||||
FetchContent_GetProperties(libressl)
|
||||
if(NOT libressl_POPULATED)
|
||||
FetchContent_Populate(libressl)
|
||||
add_subdirectory(${libressl_SOURCE_DIR} ${libressl_BINARY_DIR} EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(BUILD_SHARED_LIBS ${SAVED_BUILD_SHARED_LIBS})
|
||||
set(BUILD_TESTING ${SAVED_BUILD_TESTING})
|
||||
|
||||
|
||||
set(CPPHTTPLIB_OPENSSL_SUPPORT TRUE)
|
||||
target_link_libraries(${TARGET} PUBLIC ssl crypto)
|
||||
|
||||
elseif (LLAMA_OPENSSL)
|
||||
find_package(OpenSSL)
|
||||
if (OpenSSL_FOUND)
|
||||
include(CheckCSourceCompiles)
|
||||
@ -44,17 +130,51 @@ if (LLAMA_OPENSSL)
|
||||
set(CMAKE_REQUIRED_INCLUDES ${SAVED_CMAKE_REQUIRED_INCLUDES})
|
||||
if (OPENSSL_VERSION_SUPPORTED)
|
||||
message(STATUS "OpenSSL found: ${OPENSSL_VERSION}")
|
||||
target_compile_definitions(${TARGET} PUBLIC CPPHTTPLIB_OPENSSL_SUPPORT)
|
||||
set(CPPHTTPLIB_OPENSSL_SUPPORT TRUE)
|
||||
target_link_libraries(${TARGET} PUBLIC OpenSSL::SSL OpenSSL::Crypto)
|
||||
if (APPLE AND CMAKE_SYSTEM_NAME STREQUAL "Darwin")
|
||||
target_compile_definitions(${TARGET} PUBLIC CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN)
|
||||
find_library(CORE_FOUNDATION_FRAMEWORK CoreFoundation REQUIRED)
|
||||
find_library(SECURITY_FRAMEWORK Security REQUIRED)
|
||||
target_link_libraries(${TARGET} PUBLIC ${CORE_FOUNDATION_FRAMEWORK} ${SECURITY_FRAMEWORK})
|
||||
endif()
|
||||
endif()
|
||||
else()
|
||||
message(STATUS "OpenSSL not found, SSL support disabled")
|
||||
message(WARNING "OpenSSL not found, HTTPS support disabled")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# disable warnings in 3rd party code
|
||||
if(LLAMA_BUILD_BORINGSSL OR LLAMA_BUILD_LIBRESSL)
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
|
||||
target_compile_options(ssl PRIVATE /w)
|
||||
target_compile_options(crypto PRIVATE /w)
|
||||
if(LLAMA_BUILD_BORINGSSL)
|
||||
target_compile_options(fipsmodule PRIVATE /w)
|
||||
endif()
|
||||
if(LLAMA_BUILD_LIBRESSL)
|
||||
target_compile_options(ssl_obj PRIVATE /w)
|
||||
target_compile_options(bs_obj PRIVATE /w)
|
||||
target_compile_options(compat_obj PRIVATE /w)
|
||||
target_compile_options(crypto_obj PRIVATE /w)
|
||||
endif()
|
||||
else()
|
||||
target_compile_options(ssl PRIVATE -w)
|
||||
target_compile_options(crypto PRIVATE -w)
|
||||
if(LLAMA_BUILD_BORINGSSL)
|
||||
target_compile_options(fipsmodule PRIVATE -w)
|
||||
endif()
|
||||
if(LLAMA_BUILD_LIBRESSL)
|
||||
target_compile_options(ssl_obj PRIVATE -w)
|
||||
target_compile_options(bs_obj PRIVATE -w)
|
||||
target_compile_options(compat_obj PRIVATE -w)
|
||||
target_compile_options(crypto_obj PRIVATE -w)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (CPPHTTPLIB_OPENSSL_SUPPORT)
|
||||
target_compile_definitions(${TARGET} PUBLIC CPPHTTPLIB_OPENSSL_SUPPORT) # used in server.cpp
|
||||
if (APPLE AND CMAKE_SYSTEM_NAME STREQUAL "Darwin")
|
||||
find_library(CORE_FOUNDATION_FRAMEWORK CoreFoundation REQUIRED)
|
||||
find_library(SECURITY_FRAMEWORK Security REQUIRED)
|
||||
target_link_libraries(${TARGET} PUBLIC ${CORE_FOUNDATION_FRAMEWORK} ${SECURITY_FRAMEWORK})
|
||||
endif()
|
||||
if (WIN32 AND NOT MSVC)
|
||||
target_link_libraries(${TARGET} PUBLIC crypt32)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
11467
vendor/cpp-httplib/httplib.cpp
vendored
11467
vendor/cpp-httplib/httplib.cpp
vendored
File diff suppressed because it is too large
Load Diff
2284
vendor/cpp-httplib/httplib.h
vendored
2284
vendor/cpp-httplib/httplib.h
vendored
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user