2023-02-12 12:07:31 +01:00
|
|
|
/*
|
|
|
|
* This file is part of OpenTTD.
|
|
|
|
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
|
|
|
|
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
|
|
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @file http_curl.cpp CURL-based implementation for HTTP requests.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "../../stdafx.h"
|
|
|
|
#include "../../debug.h"
|
|
|
|
#include "../../rev.h"
|
2023-02-15 21:56:19 +01:00
|
|
|
#include "../../thread.h"
|
2023-02-12 12:07:31 +01:00
|
|
|
#include "../network_internal.h"
|
|
|
|
|
|
|
|
#include "http.h"
|
|
|
|
|
2023-02-15 21:56:19 +01:00
|
|
|
#include <atomic>
|
|
|
|
#include <condition_variable>
|
2023-02-12 12:07:31 +01:00
|
|
|
#include <curl/curl.h>
|
2023-02-15 21:56:19 +01:00
|
|
|
#include <memory>
|
|
|
|
#include <mutex>
|
|
|
|
#include <queue>
|
2023-02-12 12:07:31 +01:00
|
|
|
|
|
|
|
#include "../../safeguards.h"
|
|
|
|
|
|
|
|
/** Single HTTP request. */
|
|
|
|
class NetworkHTTPRequest {
|
|
|
|
public:
|
2023-02-15 21:56:19 +01:00
|
|
|
/**
|
|
|
|
* Create a new HTTP request.
|
|
|
|
*
|
|
|
|
* @param uri the URI to connect to (https://.../..).
|
|
|
|
* @param callback the callback to send data back on.
|
|
|
|
* @param data optionally, the data we want to send. When set, this will be a POST request, otherwise a GET request.
|
|
|
|
*/
|
|
|
|
NetworkHTTPRequest(const std::string &uri, HTTPCallback *callback, const char *data = nullptr) :
|
|
|
|
uri(uri),
|
|
|
|
callback(callback),
|
|
|
|
data(data)
|
|
|
|
{
|
|
|
|
}
|
2023-02-12 12:07:31 +01:00
|
|
|
|
2023-02-15 21:56:19 +01:00
|
|
|
/**
|
|
|
|
* Destructor of the HTTP request.
|
|
|
|
*/
|
|
|
|
~NetworkHTTPRequest()
|
|
|
|
{
|
|
|
|
free(this->data);
|
|
|
|
}
|
2023-02-12 12:07:31 +01:00
|
|
|
|
2023-02-15 21:56:19 +01:00
|
|
|
std::string uri; ///< URI to connect to.
|
|
|
|
HTTPCallback *callback; ///< Callback to send data back on.
|
|
|
|
const char *data; ///< Data to send, if any.
|
2023-02-12 12:07:31 +01:00
|
|
|
};
|
|
|
|
|
2023-02-15 21:56:19 +01:00
|
|
|
static std::thread _http_thread;
|
|
|
|
static std::atomic<bool> _http_thread_exit = false;
|
|
|
|
static std::queue<std::unique_ptr<NetworkHTTPRequest>> _http_requests;
|
|
|
|
static std::mutex _http_mutex;
|
|
|
|
static std::condition_variable _http_cv;
|
2023-02-12 12:07:31 +01:00
|
|
|
|
2023-02-15 21:56:19 +01:00
|
|
|
/* static */ void NetworkHTTPSocketHandler::Connect(const std::string &uri, HTTPCallback *callback, const char *data)
|
2023-02-12 12:07:31 +01:00
|
|
|
{
|
2023-02-15 21:56:19 +01:00
|
|
|
std::lock_guard<std::mutex> lock(_http_mutex);
|
|
|
|
_http_requests.push(std::make_unique<NetworkHTTPRequest>(uri, callback, data));
|
|
|
|
_http_cv.notify_one();
|
2023-02-12 12:07:31 +01:00
|
|
|
}
|
|
|
|
|
2023-02-15 21:56:19 +01:00
|
|
|
/* static */ void NetworkHTTPSocketHandler::HTTPReceive()
|
2023-02-12 12:07:31 +01:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2023-02-15 21:56:19 +01:00
|
|
|
void HttpThread()
|
2023-02-12 12:07:31 +01:00
|
|
|
{
|
2023-02-15 21:56:19 +01:00
|
|
|
CURL *curl = curl_easy_init();
|
|
|
|
assert(curl != nullptr);
|
2023-02-12 12:07:31 +01:00
|
|
|
|
2023-02-15 21:56:19 +01:00
|
|
|
for (;;) {
|
|
|
|
std::unique_lock<std::mutex> lock(_http_mutex);
|
2023-02-12 12:07:31 +01:00
|
|
|
|
2023-02-15 21:56:19 +01:00
|
|
|
/* Wait for a new request. */
|
|
|
|
while (_http_requests.empty() && !_http_thread_exit) {
|
|
|
|
_http_cv.wait(lock);
|
|
|
|
}
|
|
|
|
if (_http_thread_exit) break;
|
2023-02-12 12:07:31 +01:00
|
|
|
|
2023-02-15 21:56:19 +01:00
|
|
|
std::unique_ptr<NetworkHTTPRequest> request = std::move(_http_requests.front());
|
|
|
|
_http_requests.pop();
|
2023-02-12 12:07:31 +01:00
|
|
|
|
2023-02-15 21:56:19 +01:00
|
|
|
/* Release the lock, as we will take a while to process the request. */
|
|
|
|
lock.unlock();
|
2023-02-12 12:07:31 +01:00
|
|
|
|
2023-02-15 21:56:19 +01:00
|
|
|
/* Reset to default settings. */
|
|
|
|
curl_easy_reset(curl);
|
2023-02-12 12:07:31 +01:00
|
|
|
|
2023-02-15 21:56:19 +01:00
|
|
|
if (_debug_net_level >= 5) {
|
|
|
|
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
|
|
|
|
}
|
2023-02-12 12:07:31 +01:00
|
|
|
|
2023-02-15 21:56:19 +01:00
|
|
|
/* Setup some default options. */
|
|
|
|
std::string user_agent = fmt::format("OpenTTD/{}", GetNetworkRevisionString());
|
|
|
|
curl_easy_setopt(curl, CURLOPT_USERAGENT, user_agent.c_str());
|
|
|
|
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
|
|
|
curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 5L);
|
2023-02-12 12:07:31 +01:00
|
|
|
|
2023-02-15 21:56:19 +01:00
|
|
|
/* Give the connection about 10 seconds to complete. */
|
|
|
|
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10L);
|
2023-02-12 12:07:31 +01:00
|
|
|
|
2023-02-15 21:56:19 +01:00
|
|
|
/* Set a buffer of 100KiB, as the default of 16KiB seems a bit small. */
|
|
|
|
curl_easy_setopt(curl, CURLOPT_BUFFERSIZE, 100L * 1024L);
|
2023-02-12 12:07:31 +01:00
|
|
|
|
2023-02-15 21:56:19 +01:00
|
|
|
/* Fail our call if we don't receive a 2XX return value. */
|
|
|
|
curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L);
|
2023-02-12 12:07:31 +01:00
|
|
|
|
2023-02-15 21:56:19 +01:00
|
|
|
/* Prepare POST body and URI. */
|
|
|
|
if (request->data != nullptr) {
|
|
|
|
curl_easy_setopt(curl, CURLOPT_POST, 1L);
|
|
|
|
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request->data);
|
|
|
|
}
|
|
|
|
curl_easy_setopt(curl, CURLOPT_URL, request->uri.c_str());
|
|
|
|
|
|
|
|
/* Setup our (C-style) callback function which we pipe back into the callback. */
|
|
|
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, +[](char *ptr, size_t size, size_t nmemb, void *userdata) -> size_t {
|
|
|
|
Debug(net, 4, "HTTP callback: {} bytes", size * nmemb);
|
|
|
|
HTTPCallback *callback = static_cast<HTTPCallback *>(userdata);
|
|
|
|
callback->OnReceiveData(ptr, size * nmemb);
|
|
|
|
return size * nmemb;
|
|
|
|
});
|
|
|
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, request->callback);
|
|
|
|
|
|
|
|
/* Create a callback from which we can cancel. Sadly, there is no other
|
|
|
|
* thread-safe way to do this. If the connection went idle, it can take
|
|
|
|
* up to a second before this callback is called. There is little we can
|
|
|
|
* do about this. */
|
|
|
|
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);
|
|
|
|
curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, +[](void *userdata, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) -> int {
|
|
|
|
return _http_thread_exit ? 1 : 0;
|
|
|
|
});
|
|
|
|
|
|
|
|
/* Perform the request. */
|
|
|
|
CURLcode res = curl_easy_perform(curl);
|
|
|
|
|
|
|
|
if (res == CURLE_OK) {
|
|
|
|
Debug(net, 1, "HTTP request succeeded");
|
|
|
|
request->callback->OnReceiveData(nullptr, 0);
|
|
|
|
} else {
|
|
|
|
Debug(net, 0, "HTTP request failed: {}", curl_easy_strerror(res));
|
|
|
|
request->callback->OnFailure();
|
|
|
|
}
|
2023-02-12 12:07:31 +01:00
|
|
|
}
|
2023-02-15 21:56:19 +01:00
|
|
|
|
|
|
|
curl_easy_cleanup(curl);
|
2023-02-12 12:07:31 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void NetworkHTTPInitialize()
|
|
|
|
{
|
|
|
|
curl_global_init(CURL_GLOBAL_DEFAULT);
|
|
|
|
|
2023-02-15 21:56:19 +01:00
|
|
|
_http_thread_exit = false;
|
|
|
|
StartNewThread(&_http_thread, "ottd:http", &HttpThread);
|
2023-02-12 12:07:31 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void NetworkHTTPUninitialize()
|
|
|
|
{
|
|
|
|
curl_global_cleanup();
|
2023-02-15 21:56:19 +01:00
|
|
|
|
|
|
|
_http_thread_exit = true;
|
|
|
|
|
|
|
|
{
|
|
|
|
std::lock_guard<std::mutex> lock(_http_mutex);
|
|
|
|
_http_cv.notify_one();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_http_thread.joinable()) {
|
|
|
|
_http_thread.join();
|
|
|
|
}
|
2023-02-12 12:07:31 +01:00
|
|
|
}
|