diff options
author | Konstantin Käfer <mail@kkaefer.com> | 2014-09-24 16:09:26 +0200 |
---|---|---|
committer | Konstantin Käfer <mail@kkaefer.com> | 2014-09-24 16:19:47 +0200 |
commit | 992d9ff051ecd45f83160930c43c9d5a2da04048 (patch) | |
tree | b89bc59a887b336ddada4b5dd2d22a34eaa3b033 /common | |
parent | 0fc1f6686886e1122757d5cee17e401ece8178bb (diff) | |
download | qtlocation-mapboxgl-992d9ff051ecd45f83160930c43c9d5a2da04048.tar.gz |
add back CURL requesting
Diffstat (limited to 'common')
-rw-r--r-- | common/curl_request.cpp | 828 | ||||
-rw-r--r-- | common/http_request_baton_cocoa.mm | 81 | ||||
-rw-r--r-- | common/http_request_baton_curl.cpp | 421 |
3 files changed, 871 insertions, 459 deletions
diff --git a/common/curl_request.cpp b/common/curl_request.cpp index 0957481226..456bbfd6d1 100644 --- a/common/curl_request.cpp +++ b/common/curl_request.cpp @@ -1,414 +1,414 @@ - -#include <mbgl/platform/platform.hpp> -#include <mbgl/platform/request.hpp> -#include <mbgl/util/uv_detail.hpp> -#include <mbgl/util/std.hpp> - -#include <queue> -#include <boost/lockfree/queue.hpp> - -#include <curl/curl.h> - -// This file contains code from http://curl.haxx.se/libcurl/c/multi-uv.html: - -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2013, Daniel Stenberg, <daniel@haxx.se>, et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at http://curl.haxx.se/docs/copyright.html. - * - * You may opt to use, copy, modify, merge, publish, distribute and/or sell - * copies of the Software, and permit persons to whom the Software is - * furnished to do so, under the terms of the COPYING file. - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY - * KIND, either express or implied. - * - ***************************************************************************/ - -/* Example application code using the multi socket interface to download - multiple files at once, but instead of using curl_multi_perform and - curl_multi_wait, which uses select(), we use libuv. - It supports epoll, kqueue, etc. on unixes and fast IO completion ports on - Windows, which means, it should be very fast on all platforms.. - - Written by Clemens Gruber, based on an outdated example from uvbook and - some tests from libuv. - - Requires libuv and (of course) libcurl. - - See http://nikhilm.github.com/uvbook/ for more information on libuv. -*/ - -namespace mbgl { -namespace platform { -namespace request { - -struct curl_context { - uv_poll_t poll_handle; - curl_socket_t sockfd; -}; - -// Handles the request thread + messaging to the thread. -static uv_thread_t thread; -static uv::once init_thread_once; -static uv_loop_t *loop = nullptr; -static uv_async_t async_add; -static uv_async_t async_cancel; - -// Stores pointers (!) to shared_ptrs. We use shared_ptrs so that request objects don't get -// auto-destructed while they're in progress. The TileData object retains a weak_ptr to this -// request, so we have to use a shared_ptr here to ensure that this object stays alive. -static boost::lockfree::queue<std::shared_ptr<mbgl::platform::Request> *> add_queue(8); -static boost::lockfree::queue<std::shared_ptr<mbgl::platform::Request> *> cancel_queue(8); - -// Used as the CURL timer function to periodically check for socket updates. -static uv_timer_t timeout; - -// CURL multi handle that we use to request multiple URLs at the same time, without having to block -// and spawn threads. -static CURLM *curl_multi = nullptr; - -// CURL share handles are used for sharing session state (e.g.) -static uv::mutex curl_share_mutex; -static CURLSH *curl_share = nullptr; - -// A queue that we use for storing resuable CURL easy handles to avoid creating and destroying them -// all the time. -static std::queue<CURL *> curl_handle_cache; - - -class CURLRequest : public mbgl::platform::Request { -public: - CURLRequest(const std::string &url, - std::function<void(mbgl::platform::Response *)> callback, - std::shared_ptr<uv::loop> loop) - : Request(url, callback, loop) {} - - CURL *curl = nullptr; -}; - - -// Implementation starts here. - -// Locks the CURL share handle -void curl_share_lock(CURL *, curl_lock_data, curl_lock_access, void *) { curl_share_mutex.lock(); } - -// Unlocks the CURL share handle -void curl_share_unlock(CURL *, curl_lock_data, void *) { curl_share_mutex.unlock(); } - -curl_context *create_curl_context(curl_socket_t sockfd) { - curl_context *context = new curl_context; - context->sockfd = sockfd; - - uv_poll_init_socket(loop, &context->poll_handle, sockfd); - context->poll_handle.data = context; - - return context; -} - -void curl_close_cb(uv_handle_t *handle) { - curl_context *context = (curl_context *)handle->data; - free(context); -} - -void destroy_curl_context(curl_context *context) { - uv_close((uv_handle_t *)&context->poll_handle, curl_close_cb); -} - -void remove_curl_handle(CURL *handle) { - CURLMcode error = curl_multi_remove_handle(curl_multi, handle); - if (error != CURLM_OK) { - throw std::runtime_error(std::string("CURL multi error: ") + curl_multi_strerror(error)); - } - - curl_easy_reset(handle); - curl_handle_cache.push(handle); -} - -void curl_perform(uv_poll_t *req, int /*status*/, int events) { - int running_handles; - int flags = 0; - curl_context *context; - CURLMsg *message; - int pending; - - uv_timer_stop(&timeout); - - if (events & UV_READABLE) - flags |= CURL_CSELECT_IN; - if (events & UV_WRITABLE) - flags |= CURL_CSELECT_OUT; - - context = (curl_context *)req; - - curl_multi_socket_action(curl_multi, context->sockfd, flags, &running_handles); - - while ((message = curl_multi_info_read(curl_multi, &pending))) { - switch (message->msg) { - case CURLMSG_DONE: { - std::shared_ptr<Request> *req = nullptr; - curl_easy_getinfo(message->easy_handle, CURLINFO_PRIVATE, (char *)&req); - - // Add human-readable error code - if (message->data.result != CURLE_OK) { - (*req)->res->error_message = curl_easy_strerror(message->data.result); - (*req)->res->code = -1; - } else { - curl_easy_getinfo(message->easy_handle, CURLINFO_RESPONSE_CODE, &(*req)->res->code); - } - - // We're currently in the CURL request thread. We're going to schedule a uv_work request - // that executes the background function in a threadpool, and tell it to call the - // after callback back in the main uv loop. - (*req)->complete(); - - CURL *handle = message->easy_handle; - remove_curl_handle(handle); - - // We're setting this to NULL because there might still be shared_ptrs around that could - // be cancelled. - ((CURLRequest *)req->get())->curl = nullptr; - - // Delete the shared_ptr pointer we created earlier. - delete req; - break; - } - - default: - // This should never happen, because there are no other message types. - throw std::runtime_error("CURLMSG returned unknown message type"); - } - } -} - -int handle_socket(CURL * /*easy*/, curl_socket_t s, int action, void * /*userp*/, void *socketp) { - curl_context *context = nullptr; - - if (socketp) { - context = (curl_context *)socketp; - } else if (action != CURL_POLL_REMOVE) { - context = create_curl_context(s); - } - - if (context) { - curl_multi_assign(curl_multi, s, (void *)context); - if (action == CURL_POLL_IN || action == CURL_POLL_INOUT) { - uv_poll_start(&context->poll_handle, UV_READABLE, curl_perform); - } - if (action == CURL_POLL_OUT || action == CURL_POLL_INOUT) { - uv_poll_start(&context->poll_handle, UV_WRITABLE, curl_perform); - } - if (action == CURL_POLL_REMOVE && socketp) { - uv_poll_stop(&context->poll_handle); - destroy_curl_context(context); - curl_multi_assign(curl_multi, s, NULL); - } - } - - return 0; -} - -void on_timeout(uv_timer_t * /*req*/) { - int running_handles; - CURLMcode error = - curl_multi_socket_action(curl_multi, CURL_SOCKET_TIMEOUT, 0, &running_handles); - if (error != CURLM_OK) { - throw std::runtime_error(std::string("CURL multi error: ") + curl_multi_strerror(error)); - } -} - -void start_timeout(CURLM * /*multi*/, long timeout_ms, void * /*userp*/) { - if (timeout_ms <= 0) { - on_timeout(&timeout); - } else { - uv_timer_start(&timeout, on_timeout, timeout_ms, 0); - } -} - -// This function is the first function called in the request thread. It sets up the CURL share/multi -// handles and runs the thread loop. -void thread_init(void * /*ptr*/) { - uv_timer_init(loop, &timeout); - - CURLSHcode share_error; - curl_share = curl_share_init(); - - share_error = curl_share_setopt(curl_share, CURLSHOPT_LOCKFUNC, curl_share_lock); - if (share_error != CURLSHE_OK) { - throw std::runtime_error(std::string("CURL share error: ") + curl_share_strerror(share_error)); - } - - share_error = curl_share_setopt(curl_share, CURLSHOPT_UNLOCKFUNC, curl_share_unlock); - if (share_error != CURLSHE_OK) { - throw std::runtime_error(std::string("CURL share error: ") + curl_share_strerror(share_error)); - } - - CURLMcode multi_error; - curl_multi = curl_multi_init(); - - multi_error = curl_multi_setopt(curl_multi, CURLMOPT_SOCKETFUNCTION, handle_socket); - if (multi_error != CURLM_OK) { - throw std::runtime_error(std::string("CURL multi error: ") + curl_multi_strerror(multi_error)); - } - multi_error = curl_multi_setopt(curl_multi, CURLMOPT_TIMERFUNCTION, start_timeout); - if (multi_error != CURLM_OK) { - throw std::runtime_error(std::string("CURL multi error: ") + curl_multi_strerror(multi_error)); - - } - - // Main event loop. This will not return until the request loop is terminated. - uv_run(loop, UV_RUN_DEFAULT); - - curl_multi_cleanup(curl_multi); - curl_multi = nullptr; - curl_share_cleanup(curl_share); - curl_share = nullptr; - - // Clean up all the CURL easy handles that we kept around for potential future reuse. - while (!curl_handle_cache.empty()) { - curl_easy_cleanup(curl_handle_cache.front()); - curl_handle_cache.pop(); - } -} - -// This function is called when we have new data for a request. We just append it to the string -// containing the previous data. -size_t curl_write_cb(void *contents, size_t size, size_t nmemb, void *userp) { - ((std::string *)userp)->append((char *)contents, size * nmemb); - return size * nmemb; -} - -// Compares the beginning of the (non-zero-terminated!) data buffer with the (zero-terminated!) -// header string. If the data buffer contains the header string at the beginning, it returns -// the length of the header string == begin of the value, otherwise it returns npos. -// The comparison of the header is ASCII-case-insensitive. -size_t header_matches(const char *header, const char *buffer, size_t length) { - const size_t header_length = strlen(header); - if (length < header_length) return std::string::npos; - size_t i = 0; - while (i < length && i < header_length && std::tolower(buffer[i]) == header[i]) { - i++; - } - return i == header_length ? i : std::string::npos; -} - -size_t curl_header_cb(char *buffer, size_t size, size_t nmemb, void *userp) { - const size_t length = size * nmemb; - - size_t begin = std::string::npos; - if ((begin = header_matches("last-modified: ", buffer, length)) != std::string::npos) { - const std::string value { buffer + begin, length - begin - 2 /* remove \r\n */ }; - static_cast<Response *>(userp)->setLastModified(value.c_str()); - } else if ((begin = header_matches("cache-control: ", buffer, length)) != std::string::npos) { - const std::string value { buffer + begin, length - begin - 2 /* remove \r\n */ }; - static_cast<Response *>(userp)->setCacheControl(value.c_str()); - } - - return length; -} - -// This callback is called in the request event loop (on the request thread). -// It initializes newly queued up download requests and adds them to the CURL -// multi handle. -void async_add_cb(uv_async_t * /*async*/) { - std::shared_ptr<Request> *req = nullptr; - while (add_queue.pop(req)) { - // Make sure that we're not starting requests that have been cancelled - // already by async_cancel_cb. - if ((*req)->cancelled) { - delete req; - continue; - } - - // Obtain a curl handle (and try to reuse existing handles before creating new ones). - CURL *handle = nullptr; - if (!curl_handle_cache.empty()) { - handle = curl_handle_cache.front(); - curl_handle_cache.pop(); - } else { - handle = curl_easy_init(); - } - - ((CURLRequest *)req->get())->curl = handle; - - curl_easy_setopt(handle, CURLOPT_PRIVATE, req); - curl_easy_setopt(handle, CURLOPT_CAINFO, "ca-bundle.crt"); - curl_easy_setopt(handle, CURLOPT_URL, (*req)->url.c_str()); - curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, curl_write_cb); - curl_easy_setopt(handle, CURLOPT_WRITEDATA, &(*req)->res->body); - curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, curl_header_cb); - curl_easy_setopt(handle, CURLOPT_HEADERDATA, (*req)->res.get()); - curl_easy_setopt(handle, CURLOPT_ACCEPT_ENCODING, "gzip, deflate"); - curl_easy_setopt(handle, CURLOPT_SHARE, curl_share); - curl_multi_add_handle(curl_multi, handle); - } -} - -void async_cancel_cb(uv_async_t * /*async*/) { - std::shared_ptr<Request> *req = nullptr; - while (cancel_queue.pop(req)) { - // It is possible that the request has not yet been started, but that it already has been - // added to the queue for scheduling new requests. In this case, the CURL handle is invalid - // and we manually mark the Request as cancelled. - CURL *handle = ((CURLRequest *)req->get())->curl; - if (handle && !(*req)->cancelled) { - remove_curl_handle(handle); - ((CURLRequest *)req->get())->curl = nullptr; - } - (*req)->cancelled = true; - - delete req; - req = nullptr; - } -} - -void thread_init_cb() { - curl_global_init(CURL_GLOBAL_ALL); - - loop = uv_loop_new(); - uv_async_init(loop, &async_add, async_add_cb); - uv_async_init(loop, &async_cancel, async_cancel_cb); - uv_thread_create(&thread, thread_init, nullptr); -} -} // end namespace request -} // end namespace platform - - -std::shared_ptr<platform::Request> -platform::request_http(const std::string &url, - std::function<void(Response *)> callback, - std::shared_ptr<uv::loop> loop) { - using namespace request; - init_thread_once(thread_init_cb); - std::shared_ptr<CURLRequest> req = std::make_shared<CURLRequest>(url, callback, loop); - - // Note that we are creating a new shared_ptr pointer(!) because the lockless queue can't store - // objects with nontrivial destructors. We have to make absolutely sure that we manually delete - // the shared_ptr when we pop it from the queue. - add_queue.push(new std::shared_ptr<Request>(req)); - uv_async_send(&async_add); - - return req; -} - -// Cancels an HTTP request. -void platform::cancel_request_http(const std::shared_ptr<Request> &req) { - if (req) { - using namespace request; - - // Note that we are creating a new shared_ptr pointer(!) because the lockless queue can't - // store objects with nontrivial destructors. We have to make absolutely shure that we - // manually delete the shared_ptr when we pop it from the queue. - cancel_queue.push(new std::shared_ptr<Request>(req)); - uv_async_send(&async_cancel); - } -} -} // end namespace mbgl +// +//#include <mbgl/platform/platform.hpp> +//#include <mbgl/platform/request.hpp> +//#include <mbgl/util/uv_detail.hpp> +//#include <mbgl/util/std.hpp> +// +//#include <queue> +//#include <boost/lockfree/queue.hpp> +// +//#include <curl/curl.h> +// +//// This file contains code from http://curl.haxx.se/libcurl/c/multi-uv.html: +// +///*************************************************************************** +// * _ _ ____ _ +// * Project ___| | | | _ \| | +// * / __| | | | |_) | | +// * | (__| |_| | _ <| |___ +// * \___|\___/|_| \_\_____| +// * +// * Copyright (C) 1998 - 2013, Daniel Stenberg, <daniel@haxx.se>, et al. +// * +// * This software is licensed as described in the file COPYING, which +// * you should have received as part of this distribution. The terms +// * are also available at http://curl.haxx.se/docs/copyright.html. +// * +// * You may opt to use, copy, modify, merge, publish, distribute and/or sell +// * copies of the Software, and permit persons to whom the Software is +// * furnished to do so, under the terms of the COPYING file. +// * +// * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +// * KIND, either express or implied. +// * +// ***************************************************************************/ +// +///* Example application code using the multi socket interface to download +// multiple files at once, but instead of using curl_multi_perform and +// curl_multi_wait, which uses select(), we use libuv. +// It supports epoll, kqueue, etc. on unixes and fast IO completion ports on +// Windows, which means, it should be very fast on all platforms.. +// +// Written by Clemens Gruber, based on an outdated example from uvbook and +// some tests from libuv. +// +// Requires libuv and (of course) libcurl. +// +// See http://nikhilm.github.com/uvbook/ for more information on libuv. +//*/ +// +//namespace mbgl { +//namespace platform { +//namespace request { +// +//struct curl_context { +// uv_poll_t poll_handle; +// curl_socket_t sockfd; +//}; +// +//// Handles the request thread + messaging to the thread. +//static uv_thread_t thread; +//static uv::once init_thread_once; +//static uv_loop_t *loop = nullptr; +//static uv_async_t async_add; +//static uv_async_t async_cancel; +// +//// Stores pointers (!) to shared_ptrs. We use shared_ptrs so that request objects don't get +//// auto-destructed while they're in progress. The TileData object retains a weak_ptr to this +//// request, so we have to use a shared_ptr here to ensure that this object stays alive. +//static boost::lockfree::queue<std::shared_ptr<mbgl::platform::Request> *> add_queue(8); +//static boost::lockfree::queue<std::shared_ptr<mbgl::platform::Request> *> cancel_queue(8); +// +//// Used as the CURL timer function to periodically check for socket updates. +//static uv_timer_t timeout; +// +//// CURL multi handle that we use to request multiple URLs at the same time, without having to block +//// and spawn threads. +//static CURLM *curl_multi = nullptr; +// +//// CURL share handles are used for sharing session state (e.g.) +//static uv::mutex curl_share_mutex; +//static CURLSH *curl_share = nullptr; +// +//// A queue that we use for storing resuable CURL easy handles to avoid creating and destroying them +//// all the time. +//static std::queue<CURL *> curl_handle_cache; +// +// +//class CURLRequest : public mbgl::platform::Request { +//public: +// CURLRequest(const std::string &url, +// std::function<void(mbgl::platform::Response *)> callback, +// std::shared_ptr<uv::loop> loop) +// : Request(url, callback, loop) {} +// +// CURL *curl = nullptr; +//}; +// +// +//// Implementation starts here. +// +//// Locks the CURL share handle +//void curl_share_lock(CURL *, curl_lock_data, curl_lock_access, void *) { curl_share_mutex.lock(); } +// +//// Unlocks the CURL share handle +//void curl_share_unlock(CURL *, curl_lock_data, void *) { curl_share_mutex.unlock(); } +// +//curl_context *create_curl_context(curl_socket_t sockfd) { +// curl_context *context = new curl_context; +// context->sockfd = sockfd; +// +// uv_poll_init_socket(loop, &context->poll_handle, sockfd); +// context->poll_handle.data = context; +// +// return context; +//} +// +//void curl_close_cb(uv_handle_t *handle) { +// curl_context *context = (curl_context *)handle->data; +// free(context); +//} +// +//void destroy_curl_context(curl_context *context) { +// uv_close((uv_handle_t *)&context->poll_handle, curl_close_cb); +//} +// +//void remove_curl_handle(CURL *handle) { +// CURLMcode error = curl_multi_remove_handle(curl_multi, handle); +// if (error != CURLM_OK) { +// throw std::runtime_error(std::string("CURL multi error: ") + curl_multi_strerror(error)); +// } +// +// curl_easy_reset(handle); +// curl_handle_cache.push(handle); +//} +// +//void curl_perform(uv_poll_t *req, int /*status*/, int events) { +// int running_handles; +// int flags = 0; +// curl_context *context; +// CURLMsg *message; +// int pending; +// +// uv_timer_stop(&timeout); +// +// if (events & UV_READABLE) +// flags |= CURL_CSELECT_IN; +// if (events & UV_WRITABLE) +// flags |= CURL_CSELECT_OUT; +// +// context = (curl_context *)req; +// +// curl_multi_socket_action(curl_multi, context->sockfd, flags, &running_handles); +// +// while ((message = curl_multi_info_read(curl_multi, &pending))) { +// switch (message->msg) { +// case CURLMSG_DONE: { +// std::shared_ptr<Request> *req = nullptr; +// curl_easy_getinfo(message->easy_handle, CURLINFO_PRIVATE, (char *)&req); +// +// // Add human-readable error code +// if (message->data.result != CURLE_OK) { +// (*req)->res->error_message = curl_easy_strerror(message->data.result); +// (*req)->res->code = -1; +// } else { +// curl_easy_getinfo(message->easy_handle, CURLINFO_RESPONSE_CODE, &(*req)->res->code); +// } +// +// // We're currently in the CURL request thread. We're going to schedule a uv_work request +// // that executes the background function in a threadpool, and tell it to call the +// // after callback back in the main uv loop. +// (*req)->complete(); +// +// CURL *handle = message->easy_handle; +// remove_curl_handle(handle); +// +// // We're setting this to NULL because there might still be shared_ptrs around that could +// // be cancelled. +// ((CURLRequest *)req->get())->curl = nullptr; +// +// // Delete the shared_ptr pointer we created earlier. +// delete req; +// break; +// } +// +// default: +// // This should never happen, because there are no other message types. +// throw std::runtime_error("CURLMSG returned unknown message type"); +// } +// } +//} +// +//int handle_socket(CURL * /*easy*/, curl_socket_t s, int action, void * /*userp*/, void *socketp) { +// curl_context *context = nullptr; +// +// if (socketp) { +// context = (curl_context *)socketp; +// } else if (action != CURL_POLL_REMOVE) { +// context = create_curl_context(s); +// } +// +// if (context) { +// curl_multi_assign(curl_multi, s, (void *)context); +// if (action == CURL_POLL_IN || action == CURL_POLL_INOUT) { +// uv_poll_start(&context->poll_handle, UV_READABLE, curl_perform); +// } +// if (action == CURL_POLL_OUT || action == CURL_POLL_INOUT) { +// uv_poll_start(&context->poll_handle, UV_WRITABLE, curl_perform); +// } +// if (action == CURL_POLL_REMOVE && socketp) { +// uv_poll_stop(&context->poll_handle); +// destroy_curl_context(context); +// curl_multi_assign(curl_multi, s, NULL); +// } +// } +// +// return 0; +//} +// +//void on_timeout(uv_timer_t * /*req*/) { +// int running_handles; +// CURLMcode error = +// curl_multi_socket_action(curl_multi, CURL_SOCKET_TIMEOUT, 0, &running_handles); +// if (error != CURLM_OK) { +// throw std::runtime_error(std::string("CURL multi error: ") + curl_multi_strerror(error)); +// } +//} +// +//void start_timeout(CURLM * /*multi*/, long timeout_ms, void * /*userp*/) { +// if (timeout_ms <= 0) { +// on_timeout(&timeout); +// } else { +// uv_timer_start(&timeout, on_timeout, timeout_ms, 0); +// } +//} +// +//// This function is the first function called in the request thread. It sets up the CURL share/multi +//// handles and runs the thread loop. +//void thread_init(void * /*ptr*/) { +// uv_timer_init(loop, &timeout); +// +// CURLSHcode share_error; +// curl_share = curl_share_init(); +// +// share_error = curl_share_setopt(curl_share, CURLSHOPT_LOCKFUNC, curl_share_lock); +// if (share_error != CURLSHE_OK) { +// throw std::runtime_error(std::string("CURL share error: ") + curl_share_strerror(share_error)); +// } +// +// share_error = curl_share_setopt(curl_share, CURLSHOPT_UNLOCKFUNC, curl_share_unlock); +// if (share_error != CURLSHE_OK) { +// throw std::runtime_error(std::string("CURL share error: ") + curl_share_strerror(share_error)); +// } +// +// CURLMcode multi_error; +// curl_multi = curl_multi_init(); +// +// multi_error = curl_multi_setopt(curl_multi, CURLMOPT_SOCKETFUNCTION, handle_socket); +// if (multi_error != CURLM_OK) { +// throw std::runtime_error(std::string("CURL multi error: ") + curl_multi_strerror(multi_error)); +// } +// multi_error = curl_multi_setopt(curl_multi, CURLMOPT_TIMERFUNCTION, start_timeout); +// if (multi_error != CURLM_OK) { +// throw std::runtime_error(std::string("CURL multi error: ") + curl_multi_strerror(multi_error)); +// +// } +// +// // Main event loop. This will not return until the request loop is terminated. +// uv_run(loop, UV_RUN_DEFAULT); +// +// curl_multi_cleanup(curl_multi); +// curl_multi = nullptr; +// curl_share_cleanup(curl_share); +// curl_share = nullptr; +// +// // Clean up all the CURL easy handles that we kept around for potential future reuse. +// while (!curl_handle_cache.empty()) { +// curl_easy_cleanup(curl_handle_cache.front()); +// curl_handle_cache.pop(); +// } +//} +// +//// This function is called when we have new data for a request. We just append it to the string +//// containing the previous data. +//size_t curl_write_cb(void *contents, size_t size, size_t nmemb, void *userp) { +// ((std::string *)userp)->append((char *)contents, size * nmemb); +// return size * nmemb; +//} +// +//// Compares the beginning of the (non-zero-terminated!) data buffer with the (zero-terminated!) +//// header string. If the data buffer contains the header string at the beginning, it returns +//// the length of the header string == begin of the value, otherwise it returns npos. +//// The comparison of the header is ASCII-case-insensitive. +//size_t header_matches(const char *header, const char *buffer, size_t length) { +// const size_t header_length = strlen(header); +// if (length < header_length) return std::string::npos; +// size_t i = 0; +// while (i < length && i < header_length && std::tolower(buffer[i]) == header[i]) { +// i++; +// } +// return i == header_length ? i : std::string::npos; +//} +// +//size_t curl_header_cb(char *buffer, size_t size, size_t nmemb, void *userp) { +// const size_t length = size * nmemb; +// +// size_t begin = std::string::npos; +// if ((begin = header_matches("last-modified: ", buffer, length)) != std::string::npos) { +// const std::string value { buffer + begin, length - begin - 2 /* remove \r\n */ }; +// static_cast<Response *>(userp)->setLastModified(value.c_str()); +// } else if ((begin = header_matches("cache-control: ", buffer, length)) != std::string::npos) { +// const std::string value { buffer + begin, length - begin - 2 /* remove \r\n */ }; +// static_cast<Response *>(userp)->setCacheControl(value.c_str()); +// } +// +// return length; +//} +// +//// This callback is called in the request event loop (on the request thread). +//// It initializes newly queued up download requests and adds them to the CURL +//// multi handle. +//void async_add_cb(uv_async_t * /*async*/) { +// std::shared_ptr<Request> *req = nullptr; +// while (add_queue.pop(req)) { +// // Make sure that we're not starting requests that have been cancelled +// // already by async_cancel_cb. +// if ((*req)->cancelled) { +// delete req; +// continue; +// } +// +// // Obtain a curl handle (and try to reuse existing handles before creating new ones). +// CURL *handle = nullptr; +// if (!curl_handle_cache.empty()) { +// handle = curl_handle_cache.front(); +// curl_handle_cache.pop(); +// } else { +// handle = curl_easy_init(); +// } +// +// ((CURLRequest *)req->get())->curl = handle; +// +// curl_easy_setopt(handle, CURLOPT_PRIVATE, req); +// curl_easy_setopt(handle, CURLOPT_CAINFO, "ca-bundle.crt"); +// curl_easy_setopt(handle, CURLOPT_URL, (*req)->url.c_str()); +// curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, curl_write_cb); +// curl_easy_setopt(handle, CURLOPT_WRITEDATA, &(*req)->res->body); +// curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, curl_header_cb); +// curl_easy_setopt(handle, CURLOPT_HEADERDATA, (*req)->res.get()); +// curl_easy_setopt(handle, CURLOPT_ACCEPT_ENCODING, "gzip, deflate"); +// curl_easy_setopt(handle, CURLOPT_SHARE, curl_share); +// curl_multi_add_handle(curl_multi, handle); +// } +//} +// +//void async_cancel_cb(uv_async_t * /*async*/) { +// std::shared_ptr<Request> *req = nullptr; +// while (cancel_queue.pop(req)) { +// // It is possible that the request has not yet been started, but that it already has been +// // added to the queue for scheduling new requests. In this case, the CURL handle is invalid +// // and we manually mark the Request as cancelled. +// CURL *handle = ((CURLRequest *)req->get())->curl; +// if (handle && !(*req)->cancelled) { +// remove_curl_handle(handle); +// ((CURLRequest *)req->get())->curl = nullptr; +// } +// (*req)->cancelled = true; +// +// delete req; +// req = nullptr; +// } +//} +// +//void thread_init_cb() { +// curl_global_init(CURL_GLOBAL_ALL); +// +// loop = uv_loop_new(); +// uv_async_init(loop, &async_add, async_add_cb); +// uv_async_init(loop, &async_cancel, async_cancel_cb); +// uv_thread_create(&thread, thread_init, nullptr); +//} +//} // end namespace request +//} // end namespace platform +// +// +//std::shared_ptr<platform::Request> +//platform::request_http(const std::string &url, +// std::function<void(Response *)> callback, +// std::shared_ptr<uv::loop> loop) { +// using namespace request; +// init_thread_once(thread_init_cb); +// std::shared_ptr<CURLRequest> req = std::make_shared<CURLRequest>(url, callback, loop); +// +// // Note that we are creating a new shared_ptr pointer(!) because the lockless queue can't store +// // objects with nontrivial destructors. We have to make absolutely sure that we manually delete +// // the shared_ptr when we pop it from the queue. +// add_queue.push(new std::shared_ptr<Request>(req)); +// uv_async_send(&async_add); +// +// return req; +//} +// +//// Cancels an HTTP request. +//void platform::cancel_request_http(const std::shared_ptr<Request> &req) { +// if (req) { +// using namespace request; +// +// // Note that we are creating a new shared_ptr pointer(!) because the lockless queue can't +// // store objects with nontrivial destructors. We have to make absolutely shure that we +// // manually delete the shared_ptr when we pop it from the queue. +// cancel_queue.push(new std::shared_ptr<Request>(req)); +// uv_async_send(&async_cancel); +// } +//} +//} // end namespace mbgl diff --git a/common/http_request_baton_cocoa.mm b/common/http_request_baton_cocoa.mm index 32b824b7f4..497d55653a 100644 --- a/common/http_request_baton_cocoa.mm +++ b/common/http_request_baton_cocoa.mm @@ -15,9 +15,11 @@ namespace mbgl { dispatch_once_t request_initialize = 0; NSURLSession *session = nullptr; -void HTTPRequestBaton::start() { +void HTTPRequestBaton::start(const util::ptr<HTTPRequestBaton> &ptr) { + assert(uv_thread_self() == ptr->thread_id); + // Starts the request. - assert(!ptr); + util::ptr<HTTPRequestBaton> baton = ptr; // Create a C locale static locale_t locale = newlocale(LC_ALL_MASK, nullptr, nullptr); @@ -32,11 +34,11 @@ void HTTPRequestBaton::start() { // TODO: add a delegate to the session that prohibits caching, since we handle this ourselves. }); - NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@(path.c_str())]]; - if (response && response->modified) { + NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@(baton->path.c_str())]]; + if (baton->response && baton->response->modified) { struct tm *timeinfo; char buffer[32]; - const time_t modified = response->modified; + const time_t modified = baton->response->modified; timeinfo = std::gmtime(&modified); strftime_l(buffer, 32, "%a, %d %b %Y %H:%M:%S GMT", timeinfo, locale); [request addValue:@(buffer) forHTTPHeaderField:@"If-Modified-Since"]; @@ -48,23 +50,23 @@ void HTTPRequestBaton::start() { if ([error code] == NSURLErrorCancelled) { // The response code remains at 0 to indicate cancelation. // In addition, we don't need any response object. - response.reset(); - type = HTTPResponseType::Canceled; + baton->response.reset(); + baton->type = HTTPResponseType::Canceled; } else { // TODO: Use different codes for host not found, timeout, invalid URL etc. // These can be categorized in temporary and permanent errors. - response = std::make_unique<Response>(); - response->code = [(NSHTTPURLResponse *)res statusCode]; - response->message = [[error localizedDescription] UTF8String]; + baton->response = std::make_unique<Response>(); + baton->response->code = [(NSHTTPURLResponse *)res statusCode]; + baton->response->message = [[error localizedDescription] UTF8String]; switch ([error code]) { case NSURLErrorBadServerResponse: // 5xx errors - type = HTTPResponseType::TemporaryError; + baton->type = HTTPResponseType::TemporaryError; break; case NSURLErrorTimedOut: case NSURLErrorUserCancelledAuthentication: - type = HTTPResponseType::SingularError; // retry immediately + baton->type = HTTPResponseType::SingularError; // retry immediately break; case NSURLErrorNetworkConnectionLost: @@ -75,28 +77,28 @@ void HTTPRequestBaton::start() { case NSURLErrorInternationalRoamingOff: case NSURLErrorCallIsActive: case NSURLErrorDataNotAllowed: - type = HTTPResponseType::ConnectionError; + baton->type = HTTPResponseType::ConnectionError; break; default: - type = HTTPResponseType::PermanentError; + baton->type = HTTPResponseType::PermanentError; } } } else if ([res isKindOfClass:[NSHTTPURLResponse class]]) { const long code = [(NSHTTPURLResponse *)res statusCode]; if (code == 304) { // Assume a Response object already exists. - assert(response); + assert(baton->response); } else { - response = std::make_unique<Response>(); - response->code = code; - response->data = {(const char *)[data bytes], [data length]}; + baton->response = std::make_unique<Response>(); + baton->response->code = code; + baton->response->data = {(const char *)[data bytes], [data length]}; } if (code == 304) { - type = HTTPResponseType::NotModified; + baton->type = HTTPResponseType::NotModified; } else if (code == 200) { - type = HTTPResponseType::Successful; + baton->type = HTTPResponseType::Successful; } else { assert(!"code must be either 200 or 304"); } @@ -104,47 +106,36 @@ void HTTPRequestBaton::start() { NSDictionary *headers = [(NSHTTPURLResponse *)res allHeaderFields]; NSString *cache_control = [headers objectForKey:@"Cache-Control"]; if (cache_control) { - response->expires = Response::parseCacheControl([cache_control UTF8String]); + baton->response->expires = Response::parseCacheControl([cache_control UTF8String]); } NSString *last_modified = [headers objectForKey:@"Last-Modified"]; if (last_modified) { - response->modified = parse_date([last_modified UTF8String]); + baton->response->modified = parse_date([last_modified UTF8String]); } } else { // This should never happen. - type = HTTPResponseType::PermanentError; - response = std::make_unique<Response>(); - response->code = -1; - response->message = "response class is not NSHTTPURLResponse"; + baton->type = HTTPResponseType::PermanentError; + baton->response = std::make_unique<Response>(); + baton->response->code = -1; + baton->response->message = "response class is not NSHTTPURLResponse"; } - uv_async_send(async); + uv_async_send(baton->async); }]; [task resume]; - ptr = const_cast<void *>(CFBridgingRetain(task)); + baton->ptr = const_cast<void *>(CFBridgingRetain(task)); } -void HTTPRequestBaton::cleanup() { - if (ptr) { - CFBridgingRelease(ptr); - ptr = nullptr; - } -} +void HTTPRequestBaton::stop(const util::ptr<HTTPRequestBaton> &ptr) { + assert(uv_thread_self() == ptr->thread_id); + assert(ptr->ptr); -void HTTPRequestBaton::cancel() { - // After this function returns, the HTTPRequestBaton object may cease to exist at any time. - // try to stop the request - if (ptr) { - NSURLSessionDataTask *task = CFBridgingRelease(ptr); - ptr = nullptr; - [task cancel]; - } else { - // Currently, there is no request in progress. We can delete the async right away. - uv_async_send(async); - } + NSURLSessionDataTask *task = CFBridgingRelease(ptr->ptr); + ptr->ptr = nullptr; + [task cancel]; } } diff --git a/common/http_request_baton_curl.cpp b/common/http_request_baton_curl.cpp new file mode 100644 index 0000000000..55147fbfc7 --- /dev/null +++ b/common/http_request_baton_curl.cpp @@ -0,0 +1,421 @@ +#include <mbgl/storage/http_request_baton.hpp> +#include <mbgl/util/uv-messenger.h> +#include <mbgl/util/std.hpp> +#include <mbgl/util/ptr.hpp> + +#include <uv.h> +#include <curl/curl.h> + +#include <queue> +#include <cassert> + +// This file contains code from http://curl.haxx.se/libcurl/c/multi-uv.html: + +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2013, Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ***************************************************************************/ + +/* Example application code using the multi socket interface to download + multiple files at once, but instead of using curl_multi_perform and + curl_multi_wait, which uses select(), we use libuv. + It supports epoll, kqueue, etc. on unixes and fast IO completion ports on + Windows, which means, it should be very fast on all platforms.. + + Written by Clemens Gruber, based on an outdated example from uvbook and + some tests from libuv. + + Requires libuv and (of course) libcurl. + + See http://nikhilm.github.com/uvbook/ for more information on libuv. +*/ + +// Handles the request thread + messaging to the thread. +static uv_once_t once; +static uv_loop_t loop; +static uv_messenger_t start_messenger; +static uv_messenger_t stop_messenger; +static uv_thread_t thread; +static unsigned long thread_id; + +// Used as the CURL timer function to periodically check for socket updates. +static uv_timer_t timeout; + +// CURL multi handle that we use to request multiple URLs at the same time, without having to block +// and spawn threads. +static CURLM *multi = nullptr; + +// CURL share handles are used for sharing session state (e.g.) +static uv_mutex_t share_mutex; +static CURLSH *share = nullptr; + +// A queue that we use for storing resuable CURL easy handles to avoid creating and destroying them +// all the time. +static std::queue<CURL *> handles; + +namespace mbgl { + +struct curl_context { + uv_poll_t poll_handle; + curl_socket_t sockfd; +}; + +// Locks the CURL share handle +void curl_share_lock(CURL *, curl_lock_data, curl_lock_access, void *) { + uv_mutex_lock(&share_mutex); +} + +// Unlocks the CURL share handle +void curl_share_unlock(CURL *, curl_lock_data, void *) { + uv_mutex_unlock(&share_mutex); +} + +curl_context *create_curl_context(curl_socket_t sockfd) { + curl_context *context = new curl_context; + context->sockfd = sockfd; + + uv_poll_init_socket(&loop, &context->poll_handle, sockfd); + context->poll_handle.data = context; + + return context; +} + +void destroy_curl_context(curl_context *context) { + uv_close((uv_handle_t *)&context->poll_handle, [](uv_handle_t *handle) { + delete (curl_context *)handle->data; + }); +} + +// This function must run in the CURL thread. +// It is either called when the request is completed, or when we try to cancel the request. +void finish_request(const util::ptr<HTTPRequestBaton> &baton) { + assert(uv_thread_self() == thread_id); + if (baton->ptr) { + CURL *handle = (CURL *)baton->ptr; + CURLMcode error = curl_multi_remove_handle(multi, handle); + if (error != CURLM_OK) { + baton->response = std::make_unique<Response>(); + baton->response->code = -1; + baton->response->message = curl_multi_strerror(error); + } + + // Destroy the shared pointer. We still have one pointing to it + util::ptr<HTTPRequestBaton> *baton_ptr = nullptr; + curl_easy_getinfo(handle, CURLINFO_PRIVATE, (char *)&baton_ptr); + curl_easy_setopt(handle, CURLOPT_PRIVATE, nullptr); + delete baton_ptr; + + curl_easy_reset(handle); + handles.push(handle); + baton->ptr = nullptr; + } +} + +void curl_perform(uv_poll_t *req, int status, int events) { + int running_handles; + int flags = 0; + curl_context *context = (curl_context *)req; + CURLMsg *message; + int pending; + + uv_timer_stop(&timeout); + + if (events & UV_READABLE) { + flags |= CURL_CSELECT_IN; + } + if (events & UV_WRITABLE) { + flags |= CURL_CSELECT_OUT; + } + + curl_multi_socket_action(multi, context->sockfd, flags, &running_handles); + + while ((message = curl_multi_info_read(multi, &pending))) { + switch (message->msg) { + case CURLMSG_DONE: { + util::ptr<HTTPRequestBaton> *baton_ptr = nullptr; + curl_easy_getinfo(message->easy_handle, CURLINFO_PRIVATE, (char *)&baton_ptr); + util::ptr<HTTPRequestBaton> baton = *baton_ptr; + + // Add human-readable error code + if (message->data.result != CURLE_OK) { + baton->response->message = curl_easy_strerror(message->data.result); + baton->response->code = -1; + + switch (message->data.result) { + case CURLE_COULDNT_RESOLVE_PROXY: + case CURLE_COULDNT_RESOLVE_HOST: + case CURLE_COULDNT_CONNECT: + baton->type = HTTPResponseType::ConnectionError; + break; + + case CURLE_OPERATION_TIMEDOUT: + baton->type = HTTPResponseType::TemporaryError; + break; + + default: + baton->type = HTTPResponseType::PermanentError; + } + } else { + long code = 0; + curl_easy_getinfo(message->easy_handle, CURLINFO_RESPONSE_CODE, &code); + + if (code != 304) { + baton->response->code = code; + } + + if (code == 304) { + baton->type = HTTPResponseType::NotModified; + } else if (code == 200) { + baton->type = HTTPResponseType::Successful; + } else if (code >= 500 && code < 600) { + baton->type = HTTPResponseType::TemporaryError; + } else if (code >= 400 && code < 500) { + baton->type = HTTPResponseType::PermanentError; + } else { + assert(!"code must be either 200 or 304"); + } + } + + // We're currently in the CURL request thread. + finish_request(baton); + + if (baton->async) { + uv_async_send(baton->async); + baton->async = nullptr; + } + + break; + } + + default: + // This should never happen, because there are no other message types. + throw std::runtime_error("CURLMSG returned unknown message type"); + } + } +} + +int handle_socket(CURL *easy, curl_socket_t s, int action, void * userp, void *socketp) { + curl_context *context = nullptr; + + if (socketp) { + context = (curl_context *)socketp; + } else if (action != CURL_POLL_REMOVE) { + context = create_curl_context(s); + } + + if (context) { + curl_multi_assign(multi, s, (void *)context); + if (action == CURL_POLL_IN || action == CURL_POLL_INOUT) { + uv_poll_start(&context->poll_handle, UV_READABLE, curl_perform); + } + if (action == CURL_POLL_OUT || action == CURL_POLL_INOUT) { + uv_poll_start(&context->poll_handle, UV_WRITABLE, curl_perform); + } + if (action == CURL_POLL_REMOVE && socketp) { + uv_poll_stop(&context->poll_handle); + destroy_curl_context(context); + curl_multi_assign(multi, s, NULL); + } + } + + return 0; +} + +void on_timeout(uv_timer_t *req) { + int running_handles; + CURLMcode error = curl_multi_socket_action(multi, CURL_SOCKET_TIMEOUT, 0, &running_handles); + if (error != CURLM_OK) { + throw std::runtime_error(std::string("CURL multi error: ") + curl_multi_strerror(error)); + } +} + +void start_timeout(CURLM * multi, long timeout_ms, void * userp) { + if (timeout_ms <= 0) { + on_timeout(&timeout); + } else { + uv_timer_start(&timeout, on_timeout, timeout_ms, 0); + } +} + +void thread_init(void *) { +#ifdef __APPLE__ + pthread_setname_np("CURL"); +#endif + thread_id = uv_thread_self(); + + uv_timer_init(&loop, &timeout); + + CURLSHcode share_error; + share = curl_share_init(); + + share_error = curl_share_setopt(share, CURLSHOPT_LOCKFUNC, curl_share_lock); + if (share_error != CURLSHE_OK) { + throw std::runtime_error(std::string("CURL share error: ") + curl_share_strerror(share_error)); + } + + share_error = curl_share_setopt(share, CURLSHOPT_UNLOCKFUNC, curl_share_unlock); + if (share_error != CURLSHE_OK) { + throw std::runtime_error(std::string("CURL share error: ") + curl_share_strerror(share_error)); + } + + + CURLMcode multi_error; + multi = curl_multi_init(); + + multi_error = curl_multi_setopt(multi, CURLMOPT_SOCKETFUNCTION, handle_socket); + if (multi_error != CURLM_OK) { + throw std::runtime_error(std::string("CURL multi error: ") + curl_multi_strerror(multi_error)); + } + multi_error = curl_multi_setopt(multi, CURLMOPT_TIMERFUNCTION, start_timeout); + if (multi_error != CURLM_OK) { + throw std::runtime_error(std::string("CURL multi error: ") + curl_multi_strerror(multi_error)); + + } + + // Main event loop. This will not return until the request loop is terminated. + uv_run(&loop, UV_RUN_DEFAULT); + + curl_multi_cleanup(multi); + multi = nullptr; + + curl_share_cleanup(share); + share = nullptr; + + thread_id = -1; +} + +// This function is called when we have new data for a request. We just append it to the string +// containing the previous data. +size_t curl_write_cb(void *const contents, const size_t size, const size_t nmemb, void *const userp) { + ((std::string *)userp)->append((char *)contents, size * nmemb); + return size * nmemb; +} + +// Compares the beginning of the (non-zero-terminated!) data buffer with the (zero-terminated!) +// header string. If the data buffer contains the header string at the beginning, it returns +// the length of the header string == begin of the value, otherwise it returns npos. +// The comparison of the header is ASCII-case-insensitive. +size_t header_matches(const char *const header, const char *const buffer, const size_t length) { + const size_t header_length = strlen(header); + if (length < header_length) return std::string::npos; + size_t i = 0; + while (i < length && i < header_length && std::tolower(buffer[i]) == header[i]) { + i++; + } + return i == header_length ? i : std::string::npos; +} + +size_t curl_header_cb(char * const buffer, const size_t size, const size_t nmemb, void *const userp) { + const size_t length = size * nmemb; + + size_t begin = std::string::npos; + if ((begin = header_matches("last-modified: ", buffer, length)) != std::string::npos) { + const std::string value { buffer + begin, length - begin - 2 }; // remove \r\n + static_cast<Response *>(userp)->modified = curl_getdate(value.c_str(), nullptr); + } else if ((begin = header_matches("cache-control: ", buffer, length)) != std::string::npos) { + const std::string value { buffer + begin, length - begin - 2 }; // remove \r\n + static_cast<Response *>(userp)->expires = Response::parseCacheControl(value.c_str()); + } + + return length; +} + +// This function must run in the CURL thread. +void start_request(void *const ptr) { + assert(uv_thread_self() == thread_id); + util::ptr<HTTPRequestBaton> &baton = *(util::ptr<HTTPRequestBaton> *)ptr; + assert(baton); + + CURL *handle = nullptr; + if (!handles.empty()) { + handle = handles.front(); + handles.pop(); + } else { + handle = curl_easy_init(); + } + + baton->ptr = handle; + + if (!baton->response) { + baton->response = std::make_unique<Response>(); + } + + // Carry on the shared pointer in the private information of the CURL handle. + curl_easy_setopt(handle, CURLOPT_PRIVATE, ptr); + curl_easy_setopt(handle, CURLOPT_CAINFO, "ca-bundle.crt"); + curl_easy_setopt(handle, CURLOPT_URL, baton->path.c_str()); + curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, curl_write_cb); + curl_easy_setopt(handle, CURLOPT_WRITEDATA, &baton->response->data); + curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, curl_header_cb); + curl_easy_setopt(handle, CURLOPT_HEADERDATA, baton->response.get()); + curl_easy_setopt(handle, CURLOPT_ACCEPT_ENCODING, "gzip, deflate"); + curl_easy_setopt(handle, CURLOPT_SHARE, share); + + // Start requesting the information. + curl_multi_add_handle(multi, handle); +} + +// This function must run in the CURL thread. +void stop_request(void *const ptr) { + assert(uv_thread_self() == thread_id); + util::ptr<HTTPRequestBaton> &baton = *(util::ptr<HTTPRequestBaton> *)ptr; + assert(baton); + + if (baton->async) { + baton->type = HTTPResponseType::Canceled; + + // We can still stop the request because it is still in progress. + finish_request(baton); + + uv_async_send(baton->async); + baton->async = nullptr; + } else { + // If the async handle is gone, it means that the actual request has been completed before + // we got a chance to cancel it. In this case, this is a no-op. It is likely that + // the pointer below is the last lifeline of the HTTPRequestBaton. This means we're going + // to delete the HTTPRequestBaton in the current (CURL) thread. + } + + delete (util::ptr<HTTPRequestBaton> *)ptr; +} + +void create_thread() { + uv_mutex_init(&share_mutex); + uv_loop_init(&loop); + uv_messenger_init(&loop, &start_messenger, start_request); + uv_messenger_init(&loop, &stop_messenger, stop_request); + uv_thread_create(&thread, thread_init, nullptr); +} + +// This function must be run from the main thread (== where the HTTPRequestBaton was created) +void HTTPRequestBaton::start(const util::ptr<HTTPRequestBaton> &ptr) { + assert(uv_thread_self() == ptr->thread_id); + uv_once(&once, create_thread); + uv_messenger_send(&start_messenger, new util::ptr<HTTPRequestBaton>(ptr)); +} + +// This function must be run from the main thread (== where the HTTPRequestBaton was created) +void HTTPRequestBaton::stop(const util::ptr<HTTPRequestBaton> &ptr) { + assert(uv_thread_self() == ptr->thread_id); + uv_once(&once, create_thread); + uv_messenger_send(&stop_messenger, new util::ptr<HTTPRequestBaton>(ptr)); +} + +} |