size_t HTTPCURLRequest::headerCallback(char *const buffer, const size_t size, const size_t nmemb, void *userp) { assert(userp); auto baton = reinterpret_cast<HTTPCURLRequest *>(userp); MBGL_VERIFY_THREAD(baton->tid); if (!baton->response) { baton->response = std::make_unique<Response>(); } const size_t length = size * nmemb; size_t begin = std::string::npos; if ((begin = headerMatches("last-modified: ", buffer, length)) != std::string::npos) { // Always overwrite the modification date; We might already have a value here from the // Date header, but this one is more accurate. const std::string value { buffer + begin, length - begin - 2 }; // remove \r\n baton->response->modified = curl_getdate(value.c_str(), nullptr); } else if ((begin = headerMatches("etag: ", buffer, length)) != std::string::npos) { baton->response->etag = { buffer + begin, length - begin - 2 }; // remove \r\n } else if ((begin = headerMatches("cache-control: ", buffer, length)) != std::string::npos) { const std::string value { buffer + begin, length - begin - 2 }; // remove \r\n baton->response->expires = parseCacheControl(value.c_str()); } else if ((begin = headerMatches("expires: ", buffer, length)) != std::string::npos) { const std::string value { buffer + begin, length - begin - 2 }; // remove \r\n baton->response->expires = curl_getdate(value.c_str(), nullptr); } return length; }
int HTTPCURLContext::handleSocket(CURL * /* handle */, curl_socket_t s, int action, void *userp, void *socketp) { auto socket = reinterpret_cast<Socket *>(socketp); assert(userp); auto context = reinterpret_cast<HTTPCURLContext *>(userp); MBGL_VERIFY_THREAD(context->tid); if (!socket && action != CURL_POLL_REMOVE) { socket = new Socket(context, s); curl_multi_assign(context->multi, s, (void *)socket); } switch (action) { case CURL_POLL_IN: socket->start(UV_READABLE, perform); break; case CURL_POLL_OUT: socket->start(UV_WRITABLE, perform); break; case CURL_POLL_REMOVE: if (socket) { socket->stop(); curl_multi_assign(context->multi, s, nullptr); } break; default: throw std::runtime_error("Unhandled CURL socket action"); } return 0; }
void RunLoop::addWatch(int fd, Event event, std::function<void(int, Event)>&& cb) { MBGL_VERIFY_THREAD(tid); if (event == Event::Read || event == Event::ReadWrite) { auto notifier = std::make_unique<QSocketNotifier>(fd, QSocketNotifier::Read); QObject::connect(notifier.get(), SIGNAL(activated(int)), impl.get(), SLOT(onReadEvent(int))); impl->readPoll[fd] = WatchPair(std::move(notifier), std::move(cb)); }
void RunLoop::run() { MBGL_VERIFY_THREAD(tid); if (impl->type == Type::Default) { QCoreApplication::instance()->exec(); } else { impl->loop->exec(); } }
void HTTPCURLContext::onTimeout(UV_TIMER_PARAMS(req)) { assert(req->data); auto context = reinterpret_cast<HTTPCURLContext *>(req->data); MBGL_VERIFY_THREAD(context->tid); int running_handles; CURLMcode error = curl_multi_socket_action(context->multi, CURL_SOCKET_TIMEOUT, 0, &running_handles); if (error != CURLM_OK) { throw std::runtime_error(std::string("CURL multi error: ") + curl_multi_strerror(error)); } context->checkMultiInfo(); }
int HTTPCURLContext::startTimeout(CURLM * /* multi */, long timeout_ms, void *userp) { assert(userp); auto context = reinterpret_cast<HTTPCURLContext *>(userp); MBGL_VERIFY_THREAD(context->tid); if (timeout_ms < 0) { // A timeout of 0 ms means that the timer will invoked in the next loop iteration. timeout_ms = 0; } uv_timer_stop(context->timeout); uv_timer_start(context->timeout, onTimeout, timeout_ms, 0); return 0; }
// 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 HTTPCURLRequest::writeCallback(void *const contents, const size_t size, const size_t nmemb, void *userp) { assert(userp); auto impl = reinterpret_cast<HTTPCURLRequest *>(userp); MBGL_VERIFY_THREAD(impl->tid); if (!impl->response) { impl->response = std::make_unique<Response>(); } impl->response->data.append((char *)contents, size * nmemb); return size * nmemb; }
void unsubscribe(Request *request) { MBGL_VERIFY_THREAD(tid); observers.erase(request); if (abandoned()) { // There are no observers anymore. We are initiating cancelation. if (source) { // First, remove this SharedRequestBase from the source. source->notify(this, observers, nullptr, FileCache::Hint::No); } // Then, initiate cancelation of this request cancel(); } }
std::vector<Request *> removeAllInEnvironment(const Environment &env) { MBGL_VERIFY_THREAD(tid); std::vector<Request *> result; // Removes all Requests in the supplied environment and returns a list // of them. util::erase_if(observers, [&](Request *req) -> bool { if (&req->env == &env) { result.push_back(req); return true; } else { return false; } }); return result; }
void RunLoop::addWatch(int fd, Event event, std::function<void(int, Event)>&& callback) { MBGL_VERIFY_THREAD(tid); Watch *watch = nullptr; auto watchPollIter = impl->watchPoll.find(fd); if (watchPollIter == impl->watchPoll.end()) { std::unique_ptr<Watch> watchPtr = std::make_unique<Watch>(); watch = watchPtr.get(); impl->watchPoll[fd] = std::move(watchPtr); if (uv_poll_init(impl->loop, &watch->poll, fd)) { throw std::runtime_error("Failed to init poll on file descriptor."); } } else { watch = watchPollIter->second.get(); } watch->poll.data = watch; watch->fd = fd; watch->eventCallback = std::move(callback); int pollEvent = 0; switch (event) { case Event::Read: pollEvent = UV_READABLE; break; case Event::Write: pollEvent = UV_WRITABLE; break; case Event::ReadWrite: pollEvent = UV_READABLE | UV_WRITABLE; break; default: throw std::runtime_error("Unhandled event."); } if (uv_poll_start(&watch->poll, pollEvent, &Watch::onEvent)) { throw std::runtime_error("Failed to start poll on file descriptor."); } }
void HTTPCURLContext::perform(uv_poll_t *req, int /* status */, int events) { assert(req->data); auto socket = reinterpret_cast<Socket *>(req->data); auto context = socket->context; MBGL_VERIFY_THREAD(context->tid); int flags = 0; if (events & UV_READABLE) { flags |= CURL_CSELECT_IN; } if (events & UV_WRITABLE) { flags |= CURL_CSELECT_OUT; } int running_handles = 0; curl_multi_socket_action(context->multi, socket->sockfd, flags, &running_handles); context->checkMultiInfo(); }
void HTTPCURLContext::checkMultiInfo() { MBGL_VERIFY_THREAD(tid); CURLMsg *message = nullptr; int pending = 0; while ((message = curl_multi_info_read(multi, &pending))) { switch (message->msg) { case CURLMSG_DONE: { HTTPCURLRequest *baton = nullptr; curl_easy_getinfo(message->easy_handle, CURLINFO_PRIVATE, (char *)&baton); assert(baton); baton->handleResult(message->data.result); } break; default: // This should never happen, because there are no other message types. throw std::runtime_error("CURLMsg returned unknown message type"); } } }
HTTPCURLRequest::~HTTPCURLRequest() { MBGL_VERIFY_THREAD(tid); context->removeRequest(this); handleError(curl_multi_remove_handle(context->multi, handle)); context->returnHandle(handle); handle = nullptr; if (timer) { // Stop the backoff timer to avoid re-triggering this request. uv_timer_stop(timer); uv::close(timer); timer = nullptr; } if (headers) { curl_slist_free_all(headers); headers = nullptr; } }
void RunLoop::removeWatch(int fd) { MBGL_VERIFY_THREAD(tid); auto watchPollIter = impl->watchPoll.find(fd); if (watchPollIter == impl->watchPoll.end()) { return; } Watch* watch = watchPollIter->second.release(); impl->watchPoll.erase(watchPollIter); watch->closeCallback = [watch] { delete watch; }; if (uv_poll_stop(&watch->poll)) { throw std::runtime_error("Failed to stop poll on file descriptor."); } uv_close(reinterpret_cast<uv_handle_t*>(&watch->poll), &Watch::onClose); }
DefaultFileSource::~DefaultFileSource() { MBGL_VERIFY_THREAD(tid); }
void HTTPCURLRequest::handleResult(CURLcode code) { MBGL_VERIFY_THREAD(tid); if (cancelled) { // In this case, it doesn't make sense to even process the response even further since // the request was canceled anyway. delete this; return; } // Make sure a response object exists in case we haven't got any headers // or content. if (!response) { response = std::make_unique<Response>(); } // Add human-readable error code if (code != CURLE_OK) { response->status = Response::Error; response->message = std::string { curl_easy_strerror(code) } + ": " + error; switch (code) { case CURLE_COULDNT_RESOLVE_PROXY: case CURLE_COULDNT_RESOLVE_HOST: case CURLE_COULDNT_CONNECT: return finish(ResponseStatus::ConnectionError); case CURLE_OPERATION_TIMEDOUT: return finish(ResponseStatus::TemporaryError); default: return finish(ResponseStatus::PermanentError); } } else { long responseCode = 0; curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &responseCode); if (responseCode == 304) { if (existingResponse) { // We're going to copy over the existing response's data. response->status = existingResponse->status; response->message = existingResponse->message; response->modified = existingResponse->modified; response->etag = existingResponse->etag; response->data = existingResponse->data; return finish(ResponseStatus::NotModified); } else { // This is an unsolicited 304 response and should only happen on malfunctioning // HTTP servers. It likely doesn't include any data, but we don't have much options. response->status = Response::Successful; return finish(ResponseStatus::Successful); } } else if (responseCode == 200) { response->status = Response::Successful; return finish(ResponseStatus::Successful); } else if (responseCode == 404) { response->status = Response::NotFound; return finish(ResponseStatus::Successful); } else if (responseCode >= 500 && responseCode < 600) { // Server errors may be temporary, so back off exponentially. response->status = Response::Error; response->message = "HTTP status code " + util::toString(responseCode); return finish(ResponseStatus::TemporaryError); } else { // We don't know how to handle any other errors, so declare them as permanently failing. response->status = Response::Error; response->message = "HTTP status code " + util::toString(responseCode); return finish(ResponseStatus::PermanentError); } } throw std::runtime_error("Response hasn't been handled"); }
// Called in the originating thread. void Request::cancel() { MBGL_VERIFY_THREAD(tid) assert(async); assert(!canceled); canceled = util::make_unique<Canceled>(); }
void subscribe(Request *request) { MBGL_VERIFY_THREAD(tid); observers.insert(request); }
RunLoop::~RunLoop() { MBGL_VERIFY_THREAD(tid); current.set(nullptr); }