227 lines
6.3 KiB
C++
227 lines
6.3 KiB
C++
// Copyright © 2008-2021 Pioneer Developers. See AUTHORS.txt for details
|
|
// Licensed under the terms of the GPL v3. See licenses/GPL-3.txt
|
|
|
|
#ifdef ENABLE_SERVER_AGENT
|
|
|
|
#if defined(_MSC_VER) && !defined(NOMINMAX)
|
|
#define NOMINMAX
|
|
#endif
|
|
|
|
#ifdef WIN32
|
|
#pragma comment(lib, "libcurl.lib")
|
|
#endif
|
|
|
|
#include "ServerAgent.h"
|
|
#include "StringF.h"
|
|
#include <curl/curl.h>
|
|
|
|
void NullServerAgent::Call(const std::string &method, const Json &data, SuccessCallback onSuccess, FailCallback onFail, void *userdata)
|
|
{
|
|
m_queue.push(Response(onFail, userdata));
|
|
}
|
|
|
|
void NullServerAgent::ProcessResponses()
|
|
{
|
|
while (!m_queue.empty()) {
|
|
Response &resp(m_queue.front());
|
|
resp.onFail("ServerAgent not available", resp.userdata);
|
|
m_queue.pop();
|
|
}
|
|
}
|
|
|
|
bool HTTPServerAgent::s_initialised = false;
|
|
|
|
HTTPServerAgent::HTTPServerAgent(const std::string &endpoint) :
|
|
m_endpoint(endpoint)
|
|
{
|
|
if (!s_initialised)
|
|
curl_global_init(CURL_GLOBAL_ALL);
|
|
|
|
m_curl = curl_easy_init();
|
|
//curl_easy_setopt(m_curl, CURLOPT_VERBOSE, 1);
|
|
|
|
curl_easy_setopt(m_curl, CURLOPT_POST, 1);
|
|
|
|
curl_easy_setopt(m_curl, CURLOPT_READFUNCTION, HTTPServerAgent::FillRequestBuffer);
|
|
curl_easy_setopt(m_curl, CURLOPT_WRITEFUNCTION, HTTPServerAgent::FillResponseBuffer);
|
|
|
|
m_curlHeaders = 0;
|
|
m_curlHeaders = curl_slist_append(m_curlHeaders, ("User-agent: " + UserAgent()).c_str());
|
|
m_curlHeaders = curl_slist_append(m_curlHeaders, "Content-type: application/json");
|
|
curl_easy_setopt(m_curl, CURLOPT_HTTPHEADER, m_curlHeaders);
|
|
|
|
m_requestQueueLock = SDL_CreateMutex();
|
|
m_requestQueueCond = SDL_CreateCond();
|
|
|
|
m_responseQueueLock = SDL_CreateMutex();
|
|
|
|
m_thread = SDL_CreateThread(&HTTPServerAgent::ThreadEntry, "HTTPServerAgent", this);
|
|
}
|
|
|
|
HTTPServerAgent::~HTTPServerAgent()
|
|
{
|
|
// flush the queue
|
|
SDL_LockMutex(m_requestQueueLock);
|
|
while (m_requestQueue.size() > 0)
|
|
m_requestQueue.pop();
|
|
SDL_UnlockMutex(m_requestQueueLock);
|
|
|
|
// signal the thread. empty queue will cause it to exit
|
|
SDL_CondBroadcast(m_requestQueueCond);
|
|
|
|
// first thing the queue does is check the queue, so we must
|
|
// not shut down until its done that. it will release the queue
|
|
// lock before it dies, so try to take it
|
|
SDL_LockMutex(m_requestQueueLock);
|
|
|
|
// we have the lock, so we know the thread won't doing anything
|
|
// else. we can clean up now
|
|
SDL_DestroyMutex(m_responseQueueLock);
|
|
SDL_DestroyMutex(m_requestQueueLock);
|
|
SDL_DestroyCond(m_requestQueueCond);
|
|
|
|
curl_slist_free_all(m_curlHeaders);
|
|
|
|
curl_easy_cleanup(m_curl);
|
|
}
|
|
|
|
void HTTPServerAgent::Call(const std::string &method, const Json &data, SuccessCallback onSuccess, FailCallback onFail, void *userdata)
|
|
{
|
|
SDL_LockMutex(m_requestQueueLock);
|
|
m_requestQueue.push(Request(method, data, onSuccess, onFail, userdata));
|
|
SDL_UnlockMutex(m_requestQueueLock);
|
|
|
|
SDL_CondBroadcast(m_requestQueueCond);
|
|
}
|
|
|
|
void HTTPServerAgent::ProcessResponses()
|
|
{
|
|
std::queue<Response> responseQueue;
|
|
|
|
// make a copy of the response queue so we can process
|
|
// the response at our leisure
|
|
SDL_LockMutex(m_responseQueueLock);
|
|
responseQueue = m_responseQueue;
|
|
while (!m_responseQueue.empty())
|
|
m_responseQueue.pop();
|
|
SDL_UnlockMutex(m_responseQueueLock);
|
|
|
|
while (!responseQueue.empty()) {
|
|
Response &resp = responseQueue.front();
|
|
|
|
if (resp.success)
|
|
resp.onSuccess(resp.data, resp.userdata);
|
|
else
|
|
resp.onFail(resp.buffer, resp.userdata);
|
|
|
|
responseQueue.pop();
|
|
}
|
|
}
|
|
|
|
int HTTPServerAgent::ThreadEntry(void *data)
|
|
{
|
|
reinterpret_cast<HTTPServerAgent *>(data)->ThreadMain();
|
|
return 0;
|
|
}
|
|
|
|
void HTTPServerAgent::ThreadMain()
|
|
{
|
|
while (1) {
|
|
|
|
// look for requests
|
|
SDL_LockMutex(m_requestQueueLock);
|
|
|
|
// if there's no requests, wait until the main thread wakes us
|
|
if (m_requestQueue.empty())
|
|
SDL_CondWait(m_requestQueueCond, m_requestQueueLock);
|
|
|
|
// woken up but nothing on the queue means we're being destroyed
|
|
if (m_requestQueue.empty()) {
|
|
// main thread is waiting for this lock, and will start
|
|
// cleanup as soon as it has it
|
|
SDL_UnlockMutex(m_requestQueueLock);
|
|
|
|
// might be cleaned up already, so don't do anything else,
|
|
// just get out of here
|
|
return;
|
|
}
|
|
|
|
// grab a request
|
|
Request req = m_requestQueue.front();
|
|
m_requestQueue.pop();
|
|
|
|
// done with the queue
|
|
SDL_UnlockMutex(m_requestQueueLock);
|
|
|
|
Json::FastWriter writer;
|
|
req.buffer = writer.write(req.data);
|
|
|
|
Response resp(req.onSuccess, req.onFail, req.userdata);
|
|
|
|
curl_easy_setopt(m_curl, CURLOPT_URL, std::string(m_endpoint + "/" + req.method).c_str());
|
|
curl_easy_setopt(m_curl, CURLOPT_POSTFIELDSIZE, req.buffer.size());
|
|
curl_easy_setopt(m_curl, CURLOPT_READDATA, &req);
|
|
curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, &resp);
|
|
|
|
CURLcode rc = curl_easy_perform(m_curl);
|
|
resp.success = rc == CURLE_OK;
|
|
if (!resp.success)
|
|
resp.buffer = std::string("call failed: " + std::string(curl_easy_strerror(rc)));
|
|
|
|
if (resp.success) {
|
|
uint32_t code;
|
|
curl_easy_getinfo(m_curl, CURLINFO_RESPONSE_CODE, &code);
|
|
if (code != 200) {
|
|
resp.success = false;
|
|
resp.buffer = stringf("call returned HTTP status: %0{d}", code);
|
|
}
|
|
}
|
|
|
|
if (resp.success) {
|
|
Json::Reader reader;
|
|
resp.success = reader.parse(resp.buffer, resp.data, false);
|
|
if (!resp.success)
|
|
resp.buffer = std::string("JSON parse error: " + reader.getFormattedErrorMessages());
|
|
}
|
|
|
|
SDL_LockMutex(m_responseQueueLock);
|
|
m_responseQueue.push(resp);
|
|
SDL_UnlockMutex(m_responseQueueLock);
|
|
}
|
|
}
|
|
|
|
size_t HTTPServerAgent::FillRequestBuffer(char *ptr, size_t size, size_t nmemb, void *userdata)
|
|
{
|
|
HTTPServerAgent::Request *req = reinterpret_cast<HTTPServerAgent::Request *>(userdata);
|
|
size_t amount = std::max(size * nmemb, req->buffer.size());
|
|
memcpy(ptr, req->buffer.data(), amount);
|
|
req->buffer.erase(0, amount);
|
|
return amount;
|
|
}
|
|
|
|
size_t HTTPServerAgent::FillResponseBuffer(char *ptr, size_t size, size_t nmemb, void *userdata)
|
|
{
|
|
HTTPServerAgent::Response *resp = reinterpret_cast<HTTPServerAgent::Response *>(userdata);
|
|
resp->buffer.append(ptr, size * nmemb);
|
|
return size * nmemb;
|
|
}
|
|
|
|
const std::string &HTTPServerAgent::UserAgent()
|
|
{
|
|
static std::string userAgent;
|
|
if (userAgent.size() > 0) return userAgent;
|
|
|
|
userAgent = "PioneerServerAgent/" PIONEER_VERSION;
|
|
|
|
size_t pos = 0;
|
|
while ((pos = userAgent.find(' ', pos)) != std::string::npos)
|
|
userAgent[pos] = '.';
|
|
|
|
if (strlen(PIONEER_EXTRAVERSION))
|
|
userAgent += "." PIONEER_EXTRAVERSION;
|
|
|
|
return userAgent;
|
|
}
|
|
|
|
#endif //ENABLE_SERVER_AGENT
|