From 14929e9d156351221b6da192710437bb50409f29 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Wed, 7 Feb 2018 11:37:15 +0100 Subject: Http client via libcurl --- xs/src/slic3r/Utils/Http.cpp | 261 +++++++++++++++++++++++++++++++++++++++++++ xs/src/slic3r/Utils/Http.hpp | 53 +++++++++ 2 files changed, 314 insertions(+) create mode 100644 xs/src/slic3r/Utils/Http.cpp create mode 100644 xs/src/slic3r/Utils/Http.hpp (limited to 'xs/src/slic3r') 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 -- cgit v1.2.3