From 3e8cc2c67002a2915d8994209dd31dd6249e1215 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Tue, 3 Jan 2017 07:33:00 +0100 Subject: [PATCH] input/curl: move class CurlGlobal to separate source file --- Makefile.am | 2 + src/input/plugins/CurlInputPlugin.cxx | 292 +------------------------- src/lib/curl/Global.cxx | 258 +++++++++++++++++++++++ src/lib/curl/Global.hxx | 84 ++++++++ src/lib/curl/Request.hxx | 43 ++++ 5 files changed, 398 insertions(+), 281 deletions(-) create mode 100644 src/lib/curl/Global.cxx create mode 100644 src/lib/curl/Global.hxx create mode 100644 src/lib/curl/Request.hxx diff --git a/Makefile.am b/Makefile.am index 78e30ffbe..8159c54e4 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1277,6 +1277,8 @@ if ENABLE_CURL libinput_a_SOURCES += \ src/input/IcyInputStream.cxx src/input/IcyInputStream.hxx \ src/input/plugins/CurlInputPlugin.cxx src/input/plugins/CurlInputPlugin.hxx \ + src/lib/curl/Global.cxx src/lib/curl/Global.hxx \ + src/lib/curl/Request.hxx \ src/lib/curl/Easy.hxx \ src/lib/curl/Multi.hxx \ src/IcyMetaDataParser.cxx src/IcyMetaDataParser.hxx diff --git a/src/input/plugins/CurlInputPlugin.cxx b/src/input/plugins/CurlInputPlugin.cxx index 3034f0794..437fba56a 100644 --- a/src/input/plugins/CurlInputPlugin.cxx +++ b/src/input/plugins/CurlInputPlugin.cxx @@ -20,15 +20,14 @@ #include "config.h" #include "CurlInputPlugin.hxx" #include "lib/curl/Easy.hxx" -#include "lib/curl/Multi.hxx" +#include "lib/curl/Global.hxx" +#include "lib/curl/Request.hxx" #include "../AsyncInputStream.hxx" #include "../IcyInputStream.hxx" #include "../InputPlugin.hxx" #include "config/ConfigGlobal.hxx" #include "config/Block.hxx" #include "tag/TagBuilder.hxx" -#include "event/SocketMonitor.hxx" -#include "event/TimeoutMonitor.hxx" #include "event/Call.hxx" #include "IOThread.hxx" #include "util/ASCII.hxx" @@ -60,7 +59,7 @@ static const size_t CURL_MAX_BUFFERED = 512 * 1024; */ static const size_t CURL_RESUME_AT = 384 * 1024; -struct CurlInputStream final : public AsyncInputStream { +struct CurlInputStream final : public AsyncInputStream, CurlRequest { /* some buffers which were passed to libcurl, which we have too free */ char range[32]; @@ -127,115 +126,14 @@ struct CurlInputStream final : public AsyncInputStream { */ void RequestDone(CURLcode result, long status); + /* virtual methods from CurlRequest */ + void Done(CURLcode result) override; + /* virtual methods from AsyncInputStream */ virtual void DoResume() override; virtual void DoSeek(offset_type new_offset) override; }; -class CurlGlobal; - -/** - * Monitor for one socket created by CURL. - */ -class CurlSocket final : SocketMonitor { - CurlGlobal &global; - -public: - CurlSocket(CurlGlobal &_global, EventLoop &_loop, int _fd) - :SocketMonitor(_fd, _loop), global(_global) {} - - ~CurlSocket() { - /* TODO: sometimes, CURL uses CURL_POLL_REMOVE after - closing the socket, and sometimes, it uses - CURL_POLL_REMOVE just to move the (still open) - connection to the pool; in the first case, - Abandon() would be most appropriate, but it breaks - the second case - is that a CURL bug? is there a - better solution? */ - } - - /** - * Callback function for CURLMOPT_SOCKETFUNCTION. - */ - static int SocketFunction(CURL *easy, - curl_socket_t s, int action, - void *userp, void *socketp); - - virtual bool OnSocketReady(unsigned flags) override; - -private: - static constexpr int FlagsToCurlCSelect(unsigned flags) { - return (flags & (READ | HANGUP) ? CURL_CSELECT_IN : 0) | - (flags & WRITE ? CURL_CSELECT_OUT : 0) | - (flags & ERROR ? CURL_CSELECT_ERR : 0); - } - - gcc_const - static unsigned CurlPollToFlags(int action) { - switch (action) { - case CURL_POLL_NONE: - return 0; - - case CURL_POLL_IN: - return READ; - - case CURL_POLL_OUT: - return WRITE; - - case CURL_POLL_INOUT: - return READ|WRITE; - } - - assert(false); - gcc_unreachable(); - } -}; - -/** - * Manager for the global CURLM object. - */ -class CurlGlobal final : private TimeoutMonitor { - CurlMulti multi; - -public: - explicit CurlGlobal(EventLoop &_loop); - - void Add(CurlInputStream *c); - void Remove(CurlInputStream *c); - - /** - * Check for finished HTTP responses. - * - * Runs in the I/O thread. The caller must not hold locks. - */ - void ReadInfo(); - - void Assign(curl_socket_t fd, CurlSocket &cs) { - curl_multi_assign(multi.Get(), fd, &cs); - } - - void SocketAction(curl_socket_t fd, int ev_bitmask); - - void InvalidateSockets() { - SocketAction(CURL_SOCKET_TIMEOUT, 0); - } - - /** - * This is a kludge to allow pausing/resuming a stream with - * libcurl < 7.32.0. Read the curl_easy_pause manpage for - * more information. - */ - void ResumeSockets() { - int running_handles; - curl_multi_socket_all(multi.Get(), &running_handles); - } - -private: - static int TimerFunction(CURLM *global, long timeout_ms, void *userp); - - virtual void OnTimeout() override; -}; - /** * libcurl version number encoded in a 24 bit integer. */ @@ -253,36 +151,6 @@ static bool verify_peer, verify_host; static CurlGlobal *curl_global; static constexpr Domain curl_domain("curl"); -static constexpr Domain curlm_domain("curlm"); - -CurlGlobal::CurlGlobal(EventLoop &_loop) - :TimeoutMonitor(_loop) -{ - multi.SetOption(CURLMOPT_SOCKETFUNCTION, CurlSocket::SocketFunction); - multi.SetOption(CURLMOPT_SOCKETDATA, this); - - multi.SetOption(CURLMOPT_TIMERFUNCTION, TimerFunction); - multi.SetOption(CURLMOPT_TIMERDATA, this); -} - -/** - * Find a request by its CURL "easy" handle. - * - * Runs in the I/O thread. No lock needed. - */ -gcc_pure -static CurlInputStream * -input_curl_find_request(CURL *easy) -{ - assert(io_thread_inside()); - - void *p; - CURLcode code = curl_easy_getinfo(easy, CURLINFO_PRIVATE, &p); - if (code != CURLE_OK) - return nullptr; - - return (CurlInputStream *)p; -} void CurlInputStream::DoResume() @@ -304,72 +172,6 @@ CurlInputStream::DoResume() mutex.lock(); } -int -CurlSocket::SocketFunction(gcc_unused CURL *easy, - curl_socket_t s, int action, - void *userp, void *socketp) { - auto &global = *(CurlGlobal *)userp; - CurlSocket *cs = (CurlSocket *)socketp; - - assert(io_thread_inside()); - - if (action == CURL_POLL_REMOVE) { - delete cs; - return 0; - } - - if (cs == nullptr) { - cs = new CurlSocket(global, io_thread_get(), s); - global.Assign(s, *cs); - } else { -#ifdef USE_EPOLL - /* when using epoll, we need to unregister the socket - each time this callback is invoked, because older - CURL versions may omit the CURL_POLL_REMOVE call - when the socket has been closed and recreated with - the same file number (bug found in CURL 7.26, CURL - 7.33 not affected); in that case, epoll refuses the - EPOLL_CTL_MOD because it does not know the new - socket yet */ - cs->Cancel(); -#endif - } - - unsigned flags = CurlPollToFlags(action); - if (flags != 0) - cs->Schedule(flags); - return 0; -} - -bool -CurlSocket::OnSocketReady(unsigned flags) -{ - assert(io_thread_inside()); - - global.SocketAction(Get(), FlagsToCurlCSelect(flags)); - return true; -} - -/** - * Runs in the I/O thread. No lock needed. - * - * Throws std::runtime_error on error. - */ -inline void -CurlGlobal::Add(CurlInputStream *c) -{ - assert(io_thread_inside()); - assert(c != nullptr); - assert(c->easy); - - CURLMcode mcode = curl_multi_add_handle(multi.Get(), c->easy.Get()); - if (mcode != CURLM_OK) - throw FormatRuntimeError("curl_multi_add_handle() failed: %s", - curl_multi_strerror(mcode)); - - InvalidateSockets(); -} - /** * Call input_curl_easy_add() in the I/O thread. May be called from * any thread. Caller must not hold a mutex. @@ -383,16 +185,10 @@ input_curl_easy_add_indirect(CurlInputStream *c) assert(c->easy); BlockingCall(io_thread_get(), [c](){ - curl_global->Add(c); + curl_global->Add(c->easy.Get(), *c); }); } -inline void -CurlGlobal::Remove(CurlInputStream *c) -{ - curl_multi_remove_handle(multi.Get(), c->easy.Get()); -} - void CurlInputStream::FreeEasy() { @@ -447,78 +243,13 @@ CurlInputStream::RequestDone(CURLcode result, long status) cond.broadcast(); } -static void -input_curl_handle_done(CURL *easy_handle, CURLcode result) +void +CurlInputStream::Done(CURLcode result) { - CurlInputStream *c = input_curl_find_request(easy_handle); - assert(c != nullptr); - long status = 0; - curl_easy_getinfo(easy_handle, CURLINFO_RESPONSE_CODE, &status); + curl_easy_getinfo(easy.Get(), CURLINFO_RESPONSE_CODE, &status); - c->RequestDone(result, status); -} - -void -CurlGlobal::SocketAction(curl_socket_t fd, int ev_bitmask) -{ - int running_handles; - CURLMcode mcode = curl_multi_socket_action(multi.Get(), fd, ev_bitmask, - &running_handles); - if (mcode != CURLM_OK) - FormatError(curlm_domain, - "curl_multi_socket_action() failed: %s", - curl_multi_strerror(mcode)); - - ReadInfo(); -} - -/** - * Check for finished HTTP responses. - * - * Runs in the I/O thread. The caller must not hold locks. - */ -inline void -CurlGlobal::ReadInfo() -{ - assert(io_thread_inside()); - - CURLMsg *msg; - int msgs_in_queue; - - while ((msg = curl_multi_info_read(multi.Get(), - &msgs_in_queue)) != nullptr) { - if (msg->msg == CURLMSG_DONE) - input_curl_handle_done(msg->easy_handle, msg->data.result); - } -} - -int -CurlGlobal::TimerFunction(gcc_unused CURLM *_global, long timeout_ms, void *userp) -{ - auto &global = *(CurlGlobal *)userp; - assert(_global == global.multi.Get()); - - if (timeout_ms < 0) { - global.Cancel(); - return 0; - } - - if (timeout_ms >= 0 && timeout_ms < 10) - /* CURL 7.21.1 likes to report "timeout=0", which - means we're running in a busy loop. Quite a bad - idea to waste so much CPU. Let's use a lower limit - of 10ms. */ - timeout_ms = 10; - - global.Schedule(std::chrono::milliseconds(timeout_ms)); - return 0; -} - -void -CurlGlobal::OnTimeout() -{ - SocketAction(CURL_SOCKET_TIMEOUT, 0); + RequestDone(result, status); } /* @@ -725,7 +456,6 @@ CurlInputStream::InitEasy() { easy = CurlEasy(); - easy.SetOption(CURLOPT_PRIVATE, (void *)this); easy.SetOption(CURLOPT_USERAGENT, "Music Player Daemon " VERSION); easy.SetOption(CURLOPT_HEADERFUNCTION, input_curl_headerfunction); easy.SetOption(CURLOPT_WRITEHEADER, this); diff --git a/src/lib/curl/Global.cxx b/src/lib/curl/Global.cxx new file mode 100644 index 000000000..eb25172b3 --- /dev/null +++ b/src/lib/curl/Global.cxx @@ -0,0 +1,258 @@ +/* + * Copyright (C) 2008-2016 Max Kellermann + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "Global.hxx" +#include "Request.hxx" +#include "IOThread.hxx" +#include "Log.hxx" +#include "event/SocketMonitor.hxx" +#include "util/RuntimeError.hxx" +#include "util/Domain.hxx" + +static constexpr Domain curlm_domain("curlm"); + +/** + * Monitor for one socket created by CURL. + */ +class CurlSocket final : SocketMonitor { + CurlGlobal &global; + +public: + CurlSocket(CurlGlobal &_global, EventLoop &_loop, int _fd) + :SocketMonitor(_fd, _loop), global(_global) {} + + ~CurlSocket() { + /* TODO: sometimes, CURL uses CURL_POLL_REMOVE after + closing the socket, and sometimes, it uses + CURL_POLL_REMOVE just to move the (still open) + connection to the pool; in the first case, + Abandon() would be most appropriate, but it breaks + the second case - is that a CURL bug? is there a + better solution? */ + } + + /** + * Callback function for CURLMOPT_SOCKETFUNCTION. + */ + static int SocketFunction(CURL *easy, + curl_socket_t s, int action, + void *userp, void *socketp); + + virtual bool OnSocketReady(unsigned flags) override; + +private: + static constexpr int FlagsToCurlCSelect(unsigned flags) { + return (flags & (READ | HANGUP) ? CURL_CSELECT_IN : 0) | + (flags & WRITE ? CURL_CSELECT_OUT : 0) | + (flags & ERROR ? CURL_CSELECT_ERR : 0); + } + + gcc_const + static unsigned CurlPollToFlags(int action) { + switch (action) { + case CURL_POLL_NONE: + return 0; + + case CURL_POLL_IN: + return READ; + + case CURL_POLL_OUT: + return WRITE; + + case CURL_POLL_INOUT: + return READ|WRITE; + } + + assert(false); + gcc_unreachable(); + } +}; + +CurlGlobal::CurlGlobal(EventLoop &_loop) + :TimeoutMonitor(_loop) +{ + multi.SetOption(CURLMOPT_SOCKETFUNCTION, CurlSocket::SocketFunction); + multi.SetOption(CURLMOPT_SOCKETDATA, this); + + multi.SetOption(CURLMOPT_TIMERFUNCTION, TimerFunction); + multi.SetOption(CURLMOPT_TIMERDATA, this); +} + +int +CurlSocket::SocketFunction(gcc_unused CURL *easy, + curl_socket_t s, int action, + void *userp, void *socketp) { + auto &global = *(CurlGlobal *)userp; + CurlSocket *cs = (CurlSocket *)socketp; + + assert(io_thread_inside()); + + if (action == CURL_POLL_REMOVE) { + delete cs; + return 0; + } + + if (cs == nullptr) { + cs = new CurlSocket(global, io_thread_get(), s); + global.Assign(s, *cs); + } else { +#ifdef USE_EPOLL + /* when using epoll, we need to unregister the socket + each time this callback is invoked, because older + CURL versions may omit the CURL_POLL_REMOVE call + when the socket has been closed and recreated with + the same file number (bug found in CURL 7.26, CURL + 7.33 not affected); in that case, epoll refuses the + EPOLL_CTL_MOD because it does not know the new + socket yet */ + cs->Cancel(); +#endif + } + + unsigned flags = CurlPollToFlags(action); + if (flags != 0) + cs->Schedule(flags); + return 0; +} + +bool +CurlSocket::OnSocketReady(unsigned flags) +{ + assert(io_thread_inside()); + + global.SocketAction(Get(), FlagsToCurlCSelect(flags)); + return true; +} + +/** + * Runs in the I/O thread. No lock needed. + * + * Throws std::runtime_error on error. + */ +void +CurlGlobal::Add(CURL *easy, CurlRequest &request) +{ + assert(io_thread_inside()); + assert(easy != nullptr); + + curl_easy_setopt(easy, CURLOPT_PRIVATE, &request); + + CURLMcode mcode = curl_multi_add_handle(multi.Get(), easy); + if (mcode != CURLM_OK) + throw FormatRuntimeError("curl_multi_add_handle() failed: %s", + curl_multi_strerror(mcode)); + + InvalidateSockets(); +} + +void +CurlGlobal::Remove(CURL *easy) +{ + assert(io_thread_inside()); + assert(easy != nullptr); + + curl_multi_remove_handle(multi.Get(), easy); +} + +static CurlRequest * +ToRequest(CURL *easy) +{ + void *p; + CURLcode code = curl_easy_getinfo(easy, CURLINFO_PRIVATE, &p); + if (code != CURLE_OK) + return nullptr; + + return (CurlRequest *)p; +} + +/** + * Check for finished HTTP responses. + * + * Runs in the I/O thread. The caller must not hold locks. + */ +inline void +CurlGlobal::ReadInfo() +{ + assert(io_thread_inside()); + + CURLMsg *msg; + int msgs_in_queue; + + while ((msg = curl_multi_info_read(multi.Get(), + &msgs_in_queue)) != nullptr) { + if (msg->msg == CURLMSG_DONE) { + auto *request = ToRequest(msg->easy_handle); + if (request != nullptr) + request->Done(msg->data.result); + } + } +} + +int +CurlGlobal::TimerFunction(gcc_unused CURLM *_global, long timeout_ms, void *userp) +{ + auto &global = *(CurlGlobal *)userp; + assert(_global == global.multi.Get()); + + if (timeout_ms < 0) { + global.Cancel(); + return 0; + } + + if (timeout_ms >= 0 && timeout_ms < 10) + /* CURL 7.21.1 likes to report "timeout=0", which + means we're running in a busy loop. Quite a bad + idea to waste so much CPU. Let's use a lower limit + of 10ms. */ + timeout_ms = 10; + + global.Schedule(std::chrono::milliseconds(timeout_ms)); + return 0; +} + +void +CurlGlobal::OnTimeout() +{ + SocketAction(CURL_SOCKET_TIMEOUT, 0); +} + +void +CurlGlobal::SocketAction(curl_socket_t fd, int ev_bitmask) +{ + int running_handles; + CURLMcode mcode = curl_multi_socket_action(multi.Get(), fd, ev_bitmask, + &running_handles); + if (mcode != CURLM_OK) + FormatError(curlm_domain, + "curl_multi_socket_action() failed: %s", + curl_multi_strerror(mcode)); + + ReadInfo(); +} diff --git a/src/lib/curl/Global.hxx b/src/lib/curl/Global.hxx new file mode 100644 index 000000000..08b747a63 --- /dev/null +++ b/src/lib/curl/Global.hxx @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2008-2016 Max Kellermann + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CURL_GLOBAL_HXX +#define CURL_GLOBAL_HXX + +#include "Multi.hxx" +#include "event/TimeoutMonitor.hxx" + +class CurlSocket; +class CurlRequest; + +/** + * Manager for the global CURLM object. + */ +class CurlGlobal final : private TimeoutMonitor { + CurlMulti multi; + +public: + explicit CurlGlobal(EventLoop &_loop); + + void Add(CURL *easy, CurlRequest &request); + void Remove(CURL *easy); + + /** + * Check for finished HTTP responses. + * + * Runs in the I/O thread. The caller must not hold locks. + */ + void ReadInfo(); + + void Assign(curl_socket_t fd, CurlSocket &cs) { + curl_multi_assign(multi.Get(), fd, &cs); + } + + void SocketAction(curl_socket_t fd, int ev_bitmask); + + void InvalidateSockets() { + SocketAction(CURL_SOCKET_TIMEOUT, 0); + } + + /** + * This is a kludge to allow pausing/resuming a stream with + * libcurl < 7.32.0. Read the curl_easy_pause manpage for + * more information. + */ + void ResumeSockets() { + int running_handles; + curl_multi_socket_all(multi.Get(), &running_handles); + } + +private: + static int TimerFunction(CURLM *global, long timeout_ms, void *userp); + + virtual void OnTimeout() override; +}; + +#endif diff --git a/src/lib/curl/Request.hxx b/src/lib/curl/Request.hxx new file mode 100644 index 000000000..65cb21da0 --- /dev/null +++ b/src/lib/curl/Request.hxx @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2008-2016 Max Kellermann + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CURL_REQUEST_HXX +#define CURL_REQUEST_HXX + +#include + +class CurlRequest { +public: + /** + * A HTTP request is finished. Called by #CurlGlobal. + */ + virtual void Done(CURLcode result) = 0; +}; + +#endif