From 14929e9d156351221b6da192710437bb50409f29 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Wed, 7 Feb 2018 11:37:15 +0100 Subject: [PATCH] Http client via libcurl --- cmake/modules/FindCURL.cmake | 59 ++++++++ xs/CMakeLists.txt | 7 + xs/src/slic3r/Utils/Http.cpp | 261 +++++++++++++++++++++++++++++++++++ xs/src/slic3r/Utils/Http.hpp | 53 +++++++ 4 files changed, 380 insertions(+) create mode 100644 cmake/modules/FindCURL.cmake create mode 100644 xs/src/slic3r/Utils/Http.cpp create mode 100644 xs/src/slic3r/Utils/Http.hpp diff --git a/cmake/modules/FindCURL.cmake b/cmake/modules/FindCURL.cmake new file mode 100644 index 000000000..b8724858c --- /dev/null +++ b/cmake/modules/FindCURL.cmake @@ -0,0 +1,59 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +#.rst: +# FindCURL +# -------- +# +# Find curl +# +# Find the native CURL headers and libraries. +# +# :: +# +# CURL_INCLUDE_DIRS - where to find curl/curl.h, etc. +# CURL_LIBRARIES - List of libraries when using curl. +# CURL_FOUND - True if curl found. +# CURL_VERSION_STRING - the version of curl found (since CMake 2.8.8) + +# Look for the header file. +find_path(CURL_INCLUDE_DIR NAMES curl/curl.h) +mark_as_advanced(CURL_INCLUDE_DIR) + +# Look for the library (sorted from most current/relevant entry to least). +find_library(CURL_LIBRARY NAMES + curl + # Windows MSVC Makefile: + libcurl_a + # Windows MSVC prebuilts: + curllib + libcurl_imp + curllib_static + # Windows older "Win32 - MSVC" prebuilts (libcurl.lib, e.g. libcurl-7.15.5-win32-msvc.zip): + libcurl +) +mark_as_advanced(CURL_LIBRARY) + +if(CURL_INCLUDE_DIR) + foreach(_curl_version_header curlver.h curl.h) + if(EXISTS "${CURL_INCLUDE_DIR}/curl/${_curl_version_header}") + file(STRINGS "${CURL_INCLUDE_DIR}/curl/${_curl_version_header}" curl_version_str REGEX "^#define[\t ]+LIBCURL_VERSION[\t ]+\".*\"") + + string(REGEX REPLACE "^#define[\t ]+LIBCURL_VERSION[\t ]+\"([^\"]*)\".*" "\\1" CURL_VERSION_STRING "${curl_version_str}") + unset(curl_version_str) + break() + endif() + endforeach() +endif() + +find_package_handle_standard_args(CURL + REQUIRED_VARS CURL_LIBRARY CURL_INCLUDE_DIR + VERSION_VAR CURL_VERSION_STRING) + +if(CURL_FOUND) + set(CURL_LIBRARIES ${CURL_LIBRARY}) + set(CURL_INCLUDE_DIRS ${CURL_INCLUDE_DIR}) + + message(STATUS " Curl libraries: = ${CURL_LIBRARIES}") + message(STATUS " Curl include dirs: = ${CURL_INCLUDE_DIRS}") +endif() diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index caabeab6b..d2f252030 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -199,6 +199,8 @@ add_library(libslic3r_gui STATIC ${LIBDIR}/slic3r/GUI/2DBed.hpp ${LIBDIR}/slic3r/GUI/wxExtensions.cpp ${LIBDIR}/slic3r/GUI/wxExtensions.hpp + ${LIBDIR}/slic3r/Utils/Http.cpp + ${LIBDIR}/slic3r/Utils/Http.hpp ) add_library(admesh STATIC @@ -522,6 +524,11 @@ if (SLIC3R_PRUSACONTROL) target_link_libraries(XS ${wxWidgets_LIBRARIES}) endif() +find_package(CURL REQUIRED) +include_directories(${CURL_INCLUDE_DIRS}) +target_link_libraries(XS ${CURL_LIBRARIES}) + + ## OPTIONAL packages # Find eigen3 or use bundled version diff --git a/xs/src/slic3r/Utils/Http.cpp b/xs/src/slic3r/Utils/Http.cpp new file mode 100644 index 000000000..45a350a59 --- /dev/null +++ b/xs/src/slic3r/Utils/Http.cpp @@ -0,0 +1,261 @@ +#include "Http.hpp" + +#include +#include +#include +#include +#include +#include + +#include + +#include "../../libslic3r/libslic3r.h" + + +namespace Slic3r { + + +// Private + +class CurlGlobalInit +{ + static const CurlGlobalInit instance; + + CurlGlobalInit() { ::curl_global_init(CURL_GLOBAL_DEFAULT); } + ~CurlGlobalInit() { ::curl_global_cleanup(); } +}; + +struct Http::priv +{ + enum { + DEFAULT_SIZE_LIMIT = 5 * 1024 * 1024, + }; + + ::CURL *curl; + ::curl_httppost *form; + ::curl_httppost *form_end; + ::curl_slist *headerlist; + std::string buffer; + size_t limit; + + std::thread io_thread; + Http::CompleteFn completefn; + Http::ErrorFn errorfn; + + priv(const std::string &url); + ~priv(); + + static size_t writecb(void *data, size_t size, size_t nmemb, void *userp); + std::string body_size_error(); + void http_perform(); +}; + +Http::priv::priv(const std::string &url) : + curl(::curl_easy_init()), + form(nullptr), + form_end(nullptr), + headerlist(nullptr) +{ + if (curl == nullptr) { + throw std::runtime_error(std::string("Could not construct Curl object")); + } + + ::curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); // curl makes a copy internally + ::curl_easy_setopt(curl, CURLOPT_USERAGENT, SLIC3R_FORK_NAME "/" SLIC3R_VERSION); +} + +Http::priv::~priv() +{ + ::curl_easy_cleanup(curl); + ::curl_formfree(form); + ::curl_slist_free_all(headerlist); +} + +size_t Http::priv::writecb(void *data, size_t size, size_t nmemb, void *userp) +{ + auto self = static_cast(userp); + const char *cdata = static_cast(data); + const size_t realsize = size * nmemb; + + const size_t limit = self->limit > 0 ? self->limit : DEFAULT_SIZE_LIMIT; + if (self->buffer.size() + realsize > limit) { + // This makes curl_easy_perform return CURLE_WRITE_ERROR + return 0; + } + + self->buffer.append(cdata, realsize); + + return realsize; +} + +std::string Http::priv::body_size_error() +{ + return (boost::format("HTTP body data size exceeded limit (%1% bytes)") % limit).str(); +} + +void Http::priv::http_perform() +{ + ::curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L); + ::curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + ::curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writecb); + ::curl_easy_setopt(curl, CURLOPT_WRITEDATA, static_cast(this)); + +#ifndef NDEBUG + ::curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); +#endif + + if (headerlist != nullptr) { + ::curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerlist); + } + + if (form != nullptr) { + ::curl_easy_setopt(curl, CURLOPT_HTTPPOST, form); + } + + CURLcode res = ::curl_easy_perform(curl); + long http_status = 0; + ::curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_status); + + if (res != CURLE_OK) { + std::string error; + if (res == CURLE_WRITE_ERROR) { + error = std::move(body_size_error()); + } else { + error = ::curl_easy_strerror(res); + }; + + if (errorfn) { + errorfn(std::move(buffer), std::move(error), http_status); + } + } else { + if (completefn) { + completefn(std::move(buffer), http_status); + } + } +} + +Http::Http(const std::string &url) : p(new priv(url)) {} + + +// Public + +Http::Http(Http &&other) : p(std::move(other.p)) {} + +Http::~Http() +{ + if (p && p->io_thread.joinable()) { + p->io_thread.detach(); + } +} + + +Http& Http::size_limit(size_t sizeLimit) +{ + if (p) { p->limit = sizeLimit; } + return *this; +} + +Http& Http::header(std::string name, const std::string &value) +{ + if (!p) { return * this; } + + if (name.size() > 0) { + name.append(": ").append(value); + } else { + name.push_back(':'); + } + p->headerlist = curl_slist_append(p->headerlist, name.c_str()); + return *this; +} + +Http& Http::remove_header(std::string name) +{ + if (p) { + name.push_back(':'); + p->headerlist = curl_slist_append(p->headerlist, name.c_str()); + } + + return *this; +} + +Http& Http::ca_file(const std::string &name) +{ + if (p) { + ::curl_easy_setopt(p->curl, CURLOPT_CAINFO, name.c_str()); + } + + return *this; +} + +Http& Http::form_add(const std::string &name, const std::string &contents) +{ + if (p) { + ::curl_formadd(&p->form, &p->form_end, + CURLFORM_COPYNAME, name.c_str(), + CURLFORM_COPYCONTENTS, contents.c_str(), + CURLFORM_END + ); + } + + return *this; +} + +Http& Http::form_add_file(const std::string &name, const std::string &filename) +{ + if (p) { + ::curl_formadd(&p->form, &p->form_end, + CURLFORM_COPYNAME, name.c_str(), + CURLFORM_FILE, filename.c_str(), + CURLFORM_CONTENTTYPE, "application/octet-stream", + CURLFORM_END + ); + } + + return *this; +} + +Http& Http::on_complete(CompleteFn fn) +{ + if (p) { p->completefn = std::move(fn); } + return *this; +} + +Http& Http::on_error(ErrorFn fn) +{ + if (p) { p->errorfn = std::move(fn); } + return *this; +} + +Http::Ptr Http::perform() +{ + auto self = std::make_shared(std::move(*this)); + + if (self->p) { + auto io_thread = std::thread([self](){ + self->p->http_perform(); + }); + self->p->io_thread = std::move(io_thread); + } + + return self; +} + +void Http::perform_sync() +{ + if (p) { p->http_perform(); } +} + +Http Http::get(std::string url) +{ + return std::move(Http{std::move(url)}); +} + +Http Http::post(std::string url) +{ + Http http{std::move(url)}; + curl_easy_setopt(http.p->curl, CURLOPT_POST, 1L); + return http; +} + + +} diff --git a/xs/src/slic3r/Utils/Http.hpp b/xs/src/slic3r/Utils/Http.hpp new file mode 100644 index 000000000..c591e17c5 --- /dev/null +++ b/xs/src/slic3r/Utils/Http.hpp @@ -0,0 +1,53 @@ +#ifndef slic3r_Http_hpp_ +#define slic3r_Http_hpp_ + +#include +#include +#include + + +namespace Slic3r { + + +/// Represetns a Http request +class Http : public std::enable_shared_from_this { +private: + struct priv; +public: + typedef std::shared_ptr Ptr; + typedef std::function CompleteFn; + typedef std::function ErrorFn; + + Http(Http &&other); + + static Http get(std::string url); + static Http post(std::string url); + ~Http(); + + Http(const Http &) = delete; + Http& operator=(const Http &) = delete; + Http& operator=(Http &&) = delete; + + Http& size_limit(size_t sizeLimit); + Http& header(std::string name, const std::string &value); + Http& remove_header(std::string name); + Http& ca_file(const std::string &filename); + Http& form_add(const std::string &name, const std::string &contents); + Http& form_add_file(const std::string &name, const std::string &filename); + + Http& on_complete(CompleteFn fn); + Http& on_error(ErrorFn fn); + + Ptr perform(); + void perform_sync(); + +private: + Http(const std::string &url); + + std::unique_ptr p; +}; + + +} + +#endif