1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2026-01-15 19:13:07 +01:00

Merge pull request #6978 from tobiaskohlbau/refactor/network

HTTP: Refactor into modern C++
This commit is contained in:
Ted John
2018-06-12 21:50:46 +01:00
committed by GitHub
12 changed files with 364 additions and 472 deletions

View File

@@ -1,4 +1,4 @@
#pragma region Copyright (c) 2014-2017 OpenRCT2 Developers
#pragma region Copyright (c) 2014-2018 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
@@ -22,7 +22,7 @@
#include <openrct2/core/String.hpp>
#include <openrct2/Context.h>
#include <openrct2/localisation/Localisation.h>
#include <openrct2/network/http.h>
#include <openrct2/network/Http.h>
#include <openrct2/network/network.h>
#include <openrct2/network/ServerList.h>
#include <openrct2/sprites.h>
@@ -33,6 +33,10 @@
#include <openrct2/interface/Colour.h>
#include <openrct2/drawing/Drawing.h>
#ifndef DISABLE_HTTP
using namespace OpenRCT2::Network;
#endif
#define WWIDTH_MIN 500
#define WHEIGHT_MIN 300
#define WWIDTH_MAX 1200
@@ -134,9 +138,9 @@ static void dispose_server_entry_list();
static server_entry & add_server_entry(const std::string &address);
static void sort_servers();
static void join_server(std::string address);
static void fetch_servers();
#ifndef DISABLE_HTTP
static void fetch_servers_callback(http_response_t* response);
static void fetch_servers();
static void fetch_servers_callback(Http::Response & response);
#endif
static bool is_version_valid(const std::string &version);
@@ -179,7 +183,9 @@ rct_window * window_server_list_open()
server_list_load_server_entries();
window->no_list_items = (uint16)_serverEntries.size();
#ifndef DISABLE_HTTP
fetch_servers();
#endif
return window;
}
@@ -219,7 +225,9 @@ static void window_server_list_mouseup(rct_window *w, rct_widgetindex widgetInde
break;
}
case WIDX_FETCH_SERVERS:
#ifndef DISABLE_HTTP
fetch_servers();
#endif
break;
case WIDX_ADD_SERVER:
window_text_input_open(w, widgetIndex, STR_ADD_SERVER, STR_ENTER_HOSTNAME_OR_IP_ADDRESS, STR_NONE, 0, 128);
@@ -611,9 +619,9 @@ static void join_server(std::string address)
}
}
#ifndef DISABLE_HTTP
static void fetch_servers()
{
#ifndef DISABLE_HTTP
const char *masterServerUrl = OPENRCT2_MASTER_SERVER_URL;
if (!str_is_null_or_empty(gConfigNetwork.master_server_url)) {
masterServerUrl = gConfigNetwork.master_server_url;
@@ -633,17 +641,14 @@ static void fetch_servers()
sort_servers();
}
http_request_t request = {};
Http::Request request;
request.url = masterServerUrl;
request.method = HTTP_METHOD_GET;
request.body = nullptr;
request.type = HTTP_DATA_JSON;
request.method = Http::Method::GET;
request.header["Accept"] = "application/json";
status_text = STR_SERVER_LIST_CONNECTING;
http_request_async(&request, fetch_servers_callback);
#endif
Http::DoAsync(request, fetch_servers_callback);
}
#ifndef DISABLE_HTTP
static uint32 get_total_player_count()
{
return std::accumulate(
@@ -656,18 +661,19 @@ static uint32 get_total_player_count()
});
}
static void fetch_servers_callback(http_response_t* response)
static void fetch_servers_callback(Http::Response & response)
{
if (response == nullptr) {
if (response.status != Http::Status::OK)
{
status_text = STR_SERVER_LIST_NO_CONNECTION;
window_invalidate_by_class(WC_SERVER_LIST);
log_warning("Unable to connect to master server");
return;
}
json_t *jsonStatus = json_object_get(response->root, "status");
json_t * root = Json::FromString(response.body);
json_t * jsonStatus = json_object_get(root, "status");
if (!json_is_number(jsonStatus)) {
http_request_dispose(response);
status_text = STR_SERVER_LIST_INVALID_RESPONSE_JSON_NUMBER;
window_invalidate_by_class(WC_SERVER_LIST);
log_warning("Invalid response from master server");
@@ -676,16 +682,14 @@ static void fetch_servers_callback(http_response_t* response)
sint32 status = (sint32)json_integer_value(jsonStatus);
if (status != 200) {
http_request_dispose(response);
status_text = STR_SERVER_LIST_MASTER_SERVER_FAILED;
window_invalidate_by_class(WC_SERVER_LIST);
log_warning("Master server failed to return servers");
return;
}
json_t *jsonServers = json_object_get(response->root, "servers");
json_t * jsonServers = json_object_get(root, "servers");
if (!json_is_array(jsonServers)) {
http_request_dispose(response);
status_text = STR_SERVER_LIST_INVALID_RESPONSE_JSON_ARRAY;
window_invalidate_by_class(WC_SERVER_LIST);
log_warning("Invalid response from master server");
@@ -728,7 +732,6 @@ static void fetch_servers_callback(http_response_t* response)
newserver.maxplayers = (uint8)json_integer_value(maxPlayers);
}
}
http_request_dispose(response);
sort_servers();
_numPlayersOnline = get_total_player_count();

View File

@@ -1,4 +1,4 @@
#pragma region Copyright (c) 2014-2017 OpenRCT2 Developers
#pragma region Copyright (c) 2014-2018 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
@@ -56,12 +56,13 @@
#include "world/Park.h"
#include "Version.h"
#include "audio/audio.h"
#include "config/Config.h"
#include "drawing/LightFX.h"
#include "Editor.h"
#include "Game.h"
#include "Input.h"
#include "Intro.h"
#include "audio/audio.h"
#include "config/Config.h"
#include "drawing/LightFX.h"
#include "interface/Chat.h"
#include "interface/InteractiveConsole.h"
#include "interface/Viewport.h"
@@ -69,7 +70,7 @@
#include "localisation/Date.h"
#include "localisation/LocalisationService.h"
#include "network/DiscordService.h"
#include "network/http.h"
#include "network/Http.h"
#include "network/network.h"
#include "network/twitch.h"
#include "platform/platform.h"
@@ -102,6 +103,9 @@ namespace OpenRCT2
std::unique_ptr<DiscordService> _discordService;
#endif
StdInOutConsole _stdInOutConsole;
#ifndef DISABLE_HTTP
Network::Http::Http _http;
#endif
// Game states
std::unique_ptr<TitleScreen> _titleScreen;
@@ -143,7 +147,6 @@ namespace OpenRCT2
~Context() override
{
window_close_all();
http_dispose();
object_manager_unload_all_objects();
gfx_object_check_all_images_freed();
gfx_unload_g2();
@@ -420,7 +423,6 @@ namespace OpenRCT2
audio_init_ride_sounds_and_info();
}
http_init();
network_set_env(_env);
chat_init();
CopyOriginalUserFilesOver();
@@ -718,7 +720,7 @@ namespace OpenRCT2
#ifndef DISABLE_HTTP
// Download park and open it using its temporary filename
void * data;
size_t dataSize = http_download_park(gOpenRCT2StartupActionPath, &data);
size_t dataSize = Network::Http::DownloadPark(gOpenRCT2StartupActionPath, &data);
if (dataSize == 0)
{
title_load();

View File

@@ -1,4 +1,4 @@
#pragma region Copyright (c) 2014-2017 OpenRCT2 Developers
#pragma region Copyright (c) 2014-2018 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*

View File

@@ -1,4 +1,4 @@
#pragma region Copyright (c) 2014-2017 OpenRCT2 Developers
#pragma region Copyright (c) 2014-2018 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
@@ -58,4 +58,16 @@ namespace Json
size_t jsonOutputSize = String::SizeOf(jsonOutput);
fs.Write(jsonOutput, jsonOutputSize);
}
json_t * FromString(const std::string & raw)
{
json_t * root;
json_error_t error;
root = json_loads(raw.c_str(), 0, &error);
if (root == nullptr)
{
throw JsonException(&error);
}
return root;
}
} // namespace Json

View File

@@ -1,4 +1,4 @@
#pragma region Copyright (c) 2014-2017 OpenRCT2 Developers
#pragma region Copyright (c) 2014-2018 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
@@ -29,6 +29,8 @@ namespace Json
json_t * ReadFromFile(const utf8 * path, size_t maxSize = MAX_JSON_SIZE);
void WriteToFile(const utf8 * path, const json_t * json, size_t flags = 0);
json_t * FromString(const std::string & raw);
}
class JsonException final : public std::runtime_error

View File

@@ -1,4 +1,4 @@
#pragma region Copyright (c) 2014-2017 OpenRCT2 Developers
#pragma region Copyright (c) 2014-2018 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
@@ -14,322 +14,217 @@
*****************************************************************************/
#pragma endregion
#include "http.h"
#ifdef DISABLE_HTTP
void http_init() { }
void http_dispose() { }
#else
#include "Http.h"
#include <cstring>
#include <exception>
#include <memory>
#include <thread>
#include "../core/Console.hpp"
#include "../core/Math.hpp"
#ifndef DISABLE_HTTP
#include "../Version.h"
#include "../core/Console.hpp"
#ifdef _WIN32
// cURL includes windows.h, but we don't need all of it.
#define WIN32_LEAN_AND_MEAN
// cURL includes windows.h, but we don't need all of it.
#define WIN32_LEAN_AND_MEAN
#endif
#include <curl/curl.h>
#define MIME_TYPE_APPLICATION_JSON "application/json"
#define OPENRCT2_USER_AGENT "OpenRCT2/" OPENRCT2_VERSION
struct HttpRequest2
namespace OpenRCT2::Network::Http
{
void * Tag = nullptr;
std::string Method;
std::string Url;
HTTP_DATA_TYPE Type;
bool ForceIPv4 = false;
size_t Size = 0;
union
{
char * Buffer = nullptr;
json_t * Json;
} Body;
HttpRequest2() { }
HttpRequest2(const HttpRequest2 &request)
{
Tag = request.Tag;
Method = request.Method;
Url = request.Url;
Type = request.Type;
ForceIPv4 = request.ForceIPv4;
Size = request.Size;
if (request.Type == HTTP_DATA_JSON)
{
Body.Json = json_deep_copy(request.Body.Json);
}
else
{
Body.Buffer = new char[request.Size];
memcpy(Body.Buffer, request.Body.Buffer, request.Size);
}
}
explicit HttpRequest2(const http_request_t * request)
{
Tag = request->tag;
Method = std::string(request->method);
Url = std::string(request->url);
Type = request->type;
ForceIPv4 = request->forceIPv4;
Size = request->size;
if (request->type == HTTP_DATA_JSON)
{
Body.Json = json_deep_copy(request->root);
}
else
{
Body.Buffer = new char[request->size];
memcpy(Body.Buffer, request->body, request->size);
}
}
~HttpRequest2()
{
if (Type == HTTP_DATA_JSON)
{
json_decref(Body.Json);
}
else
{
delete Body.Buffer;
}
}
};
struct read_buffer {
char *ptr;
size_t length;
size_t position;
};
struct write_buffer {
char *ptr;
size_t length;
size_t capacity;
};
void http_init()
Http::Http()
{
curl_global_init(CURL_GLOBAL_DEFAULT);
}
void http_dispose()
Http::~Http()
{
curl_global_cleanup();
}
static size_t http_request_write_func(void *ptr, size_t size, size_t nmemb, void *userdata)
static size_t writeData(const char * src, size_t size, size_t nmemb, void * userdata)
{
write_buffer *writeBuffer = (write_buffer*)userdata;
size_t realsize = size * nmemb;
Response * res = static_cast<Response *>(userdata);
res->body += std::string(src, src + realsize);
size_t newBytesLength = size * nmemb;
if (newBytesLength > 0) {
size_t newCapacity = writeBuffer->capacity;
size_t newLength = writeBuffer->length + newBytesLength;
while (newLength > newCapacity) {
newCapacity = Math::Max<size_t>(4096, newCapacity * 2);
}
if (newCapacity != writeBuffer->capacity) {
writeBuffer->ptr = (char*)realloc(writeBuffer->ptr, newCapacity);
writeBuffer->capacity = newCapacity;
}
memcpy(writeBuffer->ptr + writeBuffer->length, ptr, newBytesLength);
writeBuffer->length = newLength;
}
return newBytesLength;
return realsize;
}
static http_response_t *http_request(const HttpRequest2 &request)
static size_t header_callback(const char * src, size_t size, size_t nitems, void * userdata)
{
CURL *curl;
CURLcode curlResult;
http_response_t *response;
read_buffer readBuffer = {};
write_buffer writeBuffer;
size_t realsize = nitems * size;
Response * res = static_cast<Response *>(userdata);
curl = curl_easy_init();
if (curl == nullptr)
return nullptr;
if (request.Type == HTTP_DATA_JSON && request.Body.Json != nullptr) {
readBuffer.ptr = json_dumps(request.Body.Json, JSON_COMPACT);
readBuffer.length = strlen(readBuffer.ptr);
readBuffer.position = 0;
} else if (request.Type == HTTP_DATA_RAW && request.Body.Buffer != nullptr) {
readBuffer.ptr = request.Body.Buffer;
readBuffer.length = request.Size;
readBuffer.position = 0;
auto line = std::string(src, src + realsize);
auto pos = line.find(':');
if (pos != std::string::npos)
{
std::string key = line.substr(0, pos);
// substract 4 chars for ": " and "\r\n"
std::string value = line.substr(pos + 2, line.size() - pos - 4);
res->header[key] = value;
}
writeBuffer.ptr = nullptr;
writeBuffer.length = 0;
writeBuffer.capacity = 0;
return realsize;
}
curl_slist *headers = nullptr;
struct WriteThis
{
const char * readptr;
size_t sizeleft;
};
if (request.Type == HTTP_DATA_JSON) {
headers = curl_slist_append(headers, "Accept: " MIME_TYPE_APPLICATION_JSON);
static size_t read_callback(void * dst, size_t size, size_t nmemb, void * userp)
{
WriteThis * wt = static_cast<WriteThis *>(userp);
size_t buffer_size = size * nmemb;
if (request.Body.Json != nullptr) {
headers = curl_slist_append(headers, "Content-Type: " MIME_TYPE_APPLICATION_JSON);
}
if (wt->sizeleft)
{
size_t copy_this_much = wt->sizeleft;
if (copy_this_much > buffer_size)
copy_this_much = buffer_size;
memcpy(dst, wt->readptr, copy_this_much);
wt->readptr += copy_this_much;
wt->sizeleft -= copy_this_much;
return copy_this_much;
}
if (readBuffer.ptr != nullptr) {
char contentLengthHeaderValue[64];
snprintf(contentLengthHeaderValue, sizeof(contentLengthHeaderValue), "Content-Length: %zu", readBuffer.length);
headers = curl_slist_append(headers, contentLengthHeaderValue);
return 0;
}
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, readBuffer.ptr);
Response Do(const Request & req)
{
CURL * curl = curl_easy_init();
std::shared_ptr<void> _(nullptr, [curl](...) { curl_easy_cleanup(curl); });
if (!curl)
std::runtime_error("Failed to initialize curl");
Response res;
if (req.method == Method::POST || req.method == Method::PUT)
{
WriteThis wt;
wt.readptr = req.body.c_str();
wt.sizeleft = req.body.size();
curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_callback);
curl_easy_setopt(curl, CURLOPT_READDATA, &wt);
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, (long)wt.sizeleft);
}
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, request.Method.c_str());
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
if (req.forceIPv4)
curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
if (req.method == Method::POST)
curl_easy_setopt(curl, CURLOPT_POST, 1L);
if (req.method == Method::PUT)
curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
curl_easy_setopt(curl, CURLOPT_URL, req.url.c_str());
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, true);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeData);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&res);
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_callback);
curl_easy_setopt(curl, CURLOPT_HEADERDATA, (void *)&res);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, true);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, true);
curl_easy_setopt(curl, CURLOPT_USERAGENT, OPENRCT2_USER_AGENT);
curl_easy_setopt(curl, CURLOPT_URL, request.Url.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &writeBuffer);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, http_request_write_func);
if (request.ForceIPv4)
curl_slist * chunk = nullptr;
std::shared_ptr<void> __(nullptr, [chunk](...) { curl_slist_free_all(chunk); });
for (auto header : req.header)
{
// Force resolving to IPv4 to fix issues where advertising over IPv6 does not work
curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
std::string hs = header.first + ": " + header.second;
chunk = curl_slist_append(chunk, hs.c_str());
}
curlResult = curl_easy_perform(curl);
if (request.Type == HTTP_DATA_JSON && request.Body.Json != nullptr) {
free(readBuffer.ptr);
}
if (curlResult != CURLE_OK) {
log_error("HTTP request failed: %s.", curl_easy_strerror(curlResult));
if (writeBuffer.ptr != nullptr)
free(writeBuffer.ptr);
return nullptr;
}
long httpStatusCode;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpStatusCode);
char* contentType;
curl_easy_getinfo(curl, CURLINFO_CONTENT_TYPE, &contentType);
// Null terminate the response buffer
writeBuffer.length++;
writeBuffer.ptr = (char*)realloc(writeBuffer.ptr, writeBuffer.length);
writeBuffer.capacity = writeBuffer.length;
writeBuffer.ptr[writeBuffer.length - 1] = 0;
response = nullptr;
// Parse as JSON if response is JSON
if (contentType != nullptr && strstr(contentType, "json") != nullptr) {
json_t *root;
json_error_t error;
root = json_loads(writeBuffer.ptr, 0, &error);
if (root != nullptr) {
response = (http_response_t*) malloc(sizeof(http_response_t));
response->tag = request.Tag;
response->status_code = (sint32) httpStatusCode;
response->root = root;
response->type = HTTP_DATA_JSON;
response->size = writeBuffer.length;
if (req.header.size() != 0)
{
if (chunk == nullptr)
{
throw std::runtime_error("Failed to set headers");
}
free(writeBuffer.ptr);
} else {
response = (http_response_t*) malloc(sizeof(http_response_t));
response->tag = request.Tag;
response->status_code = (sint32) httpStatusCode;
response->body = writeBuffer.ptr;
response->type = HTTP_DATA_RAW;
response->size = writeBuffer.length;
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, chunk);
}
curl_easy_cleanup(curl);
CURLcode curl_code = curl_easy_perform(curl);
if (curl_code != CURLE_OK)
{
throw std::runtime_error("Failed to perform request");
}
return response;
// gets freed by curl_easy_cleanup
char * content_type;
long code;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code);
curl_easy_getinfo(curl, CURLINFO_CONTENT_TYPE, &content_type);
res.status = static_cast<Status>(code);
res.content_type = std::string(content_type);
return res;
}
void http_request_async(const http_request_t * request, void (*callback)(http_response_t*))
void DoAsync(const Request & req, std::function<void(Response & res)> fn)
{
auto request2 = HttpRequest2(request);
auto thread = std::thread([](const HttpRequest2 &req, void(*callback2)(http_response_t*)) -> void
{
http_response_t * response = http_request(req);
callback2(response);
}, std::move(request2), callback);
auto thread = std::thread([=]() {
Response res;
try
{
res = Do(req);
}
catch (std::exception & e)
{
res.error = e.what();
return;
}
fn(res);
});
thread.detach();
}
void http_request_dispose(http_response_t *response)
{
if (response->type == HTTP_DATA_JSON && response->root != nullptr)
json_decref(response->root);
else if (response->type == HTTP_DATA_RAW && response->body != nullptr)
free(response->body);
free(response);
}
const char *http_get_extension_from_url(const char *url, const char *fallback)
{
const char *extension = strrchr(url, '.');
// Assume a save file by default if no valid extension can be determined
if (extension == nullptr || strchr(extension, '/') != nullptr) {
return fallback;
} else {
return extension;
}
}
size_t http_download_park(const char * url, void * * outData)
size_t DownloadPark(const char * url, void ** outData)
{
// Download park to buffer in memory
HttpRequest2 request;
request.Url = url;
request.Method = "GET";
request.Type = HTTP_DATA_NONE;
http_response_t *response = http_request(request);
if (response == nullptr || response->status_code != 200) {
Console::Error::WriteLine("Failed to download '%s'", request.Url.c_str());
if (response != nullptr) {
http_request_dispose(response);
}
Request request;
request.url = url;
request.method = Method::GET;
Response res;
try
{
res = Do(request);
if (res.status != Status::OK)
std::runtime_error("bad http status");
}
catch (std::exception & e)
{
Console::Error::WriteLine("Failed to download '%s', cause %s", request.url.c_str(), e.what());
*outData = nullptr;
return 0;
}
size_t dataSize = response->size - 1;
void * data = malloc(dataSize);
if (data == nullptr) {
dataSize = 0;
size_t dataSize = res.body.size() - 1;
void * data = malloc(dataSize);
if (data == nullptr)
{
Console::Error::WriteLine("Failed to allocate memory for downloaded park.");
} else {
memcpy(data, response->body, dataSize);
return 0;
}
http_request_dispose(response);
memcpy(data, res.body.c_str(), dataSize);
*outData = data;
return dataSize;
}
} // namespace OpenRCT2::Network
#endif

View File

@@ -0,0 +1,80 @@
#pragma region Copyright (c) 2014-2018 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 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, either version 3 of the License, or
* (at your option) any later version.
*
* A full copy of the GNU General Public License can be found in licence.txt
*****************************************************************************/
#pragma endregion
#pragma once
#ifndef DISABLE_HTTP
#include <functional>
#include <map>
#include <string>
#include "../common.h"
namespace OpenRCT2::Network::Http
{
enum class Status
{
OK = 200
};
enum class Method
{
GET,
POST,
PUT
};
struct Response
{
Status status;
std::string content_type;
std::string body = "";
std::map<std::string, std::string> header = {};
std::string error = "";
};
struct Request
{
std::string url;
std::map<std::string, std::string> header = {};
Method method = Method::GET;
std::string body = "";
bool forceIPv4 = false;
};
struct Http
{
Http();
~Http();
};
Response Do(const Request & req);
void DoAsync(const Request & req, std::function<void(Response & res)> fn);
/**
* Download a park via HTTP/S from the given URL into a memory buffer. This is
* a blocking operation.
* @param url The URL to download the park from.
* @param outData The data returned.
* @returns The size of the data or 0 if the download failed.
*/
size_t DownloadPark(const char * url, void ** outData);
} /* namespace http */
#endif // DISABLE_HTTP

View File

@@ -1,4 +1,4 @@
#pragma region Copyright (c) 2014-2017 OpenRCT2 Developers
#pragma region Copyright (c) 2014-2018 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
@@ -31,7 +31,9 @@
#include "../util/Util.h"
#include "../world/Map.h"
#include "../world/Park.h"
#include "http.h"
#include "Http.h"
using namespace OpenRCT2::Network;
#ifndef DISABLE_HTTP
@@ -103,30 +105,26 @@ private:
_lastAdvertiseTime = platform_get_ticks();
// Send the registration request
http_request_t request = {};
request.tag = this;
Http::Request request;
request.url = GetMasterServerUrl();
request.method = HTTP_METHOD_POST;
request.method = Http::Method::POST;
request.forceIPv4 = forceIPv4;
json_t *body = json_object();
json_object_set_new(body, "key", json_string(_key.c_str()));
json_object_set_new(body, "port", json_integer(_port));
request.root = body;
request.type = HTTP_DATA_JSON;
request.body = json_dumps(body, JSON_COMPACT);
request.header["Content-Type"] = "application/json";
http_request_async(&request, [](http_response_t * response) -> void
{
if (response == nullptr)
Http::DoAsync(request, [&](Http::Response response) -> void {
if (response.status != Http::Status::OK)
{
Console::WriteLine("Unable to connect to master server");
return;
}
else
{
auto advertiser = static_cast<NetworkServerAdvertiser*>(response->tag);
advertiser->OnRegistrationResponse(response->root);
http_request_dispose(response);
}
json_t * root = Json::FromString(response.body);
this->OnRegistrationResponse(root);
});
json_decref(body);
@@ -134,31 +132,27 @@ private:
void SendHeartbeat()
{
http_request_t request = {};
request.tag = this;
Http::Request request;
request.url = GetMasterServerUrl();
request.method = HTTP_METHOD_PUT;
request.method = Http::Method::POST;
json_t * jsonBody = GetHeartbeatJson();
request.root = jsonBody;
request.type = HTTP_DATA_JSON;
json_t * body = GetHeartbeatJson();
request.body = json_dumps(body, JSON_COMPACT);
request.header["Content-Type"] = "application/json";
_lastHeartbeatTime = platform_get_ticks();
http_request_async(&request, [](http_response_t *response) -> void
{
if (response == nullptr)
Http::DoAsync(request, [&](Http::Response response) -> void {
if (response.status != Http::Status::OK)
{
log_warning("Unable to connect to master server");
}
else
{
auto advertiser = static_cast<NetworkServerAdvertiser*>(response->tag);
advertiser->OnHeartbeatResponse(response->root);
http_request_dispose(response);
Console::WriteLine("Unable to connect to master server");
return;
}
json_t * root = Json::FromString(response.body);
this->OnHeartbeatResponse(root);
});
json_decref(jsonBody);
json_decref(body);
}
void OnRegistrationResponse(json_t * jsonRoot)

View File

@@ -1,4 +1,4 @@
#pragma region Copyright (c) 2014-2017 OpenRCT2 Developers
#pragma region Copyright (c) 2014-2018 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*

View File

@@ -1,4 +1,4 @@
#pragma region Copyright (c) 2014-2017 OpenRCT2 Developers
#pragma region Copyright (c) 2014-2018 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
@@ -26,13 +26,17 @@
#error HTTP must be enabled to use the TWITCH functionality.
#endif
#include <jansson.h>
#include <memory>
#include <vector>
#include "../Context.h"
#include "../core/Math.hpp"
#include "../core/String.hpp"
#include "../OpenRCT2.h"
#include "../config/Config.h"
#include "../core/Json.hpp"
#include "../drawing/Drawing.h"
#include "../Game.h"
#include "../interface/InteractiveConsole.h"
@@ -42,10 +46,12 @@
#include "../platform/platform.h"
#include "../util/Util.h"
#include "../world/Sprite.h"
#include "http.h"
#include "Http.h"
#include "twitch.h"
using namespace OpenRCT2;
using namespace OpenRCT2::Network;
bool gTwitchEnable = false;
@@ -107,7 +113,7 @@ namespace Twitch
static bool _twitchIdle = true;
static uint32 _twitchLastPulseTick = 0;
static sint32 _twitchLastPulseOperation = 1;
static http_response_t * _twitchJsonResponse;
static Http::Response _twitchJsonResponse;
static void Join();
static void Leave();
@@ -203,37 +209,33 @@ namespace Twitch
_twitchState = TWITCH_STATE_JOINING;
_twitchIdle = false;
http_request_t request = {};
Http::Request request;
request.url = url;
request.method = HTTP_METHOD_GET;
request.body = nullptr;
request.type = HTTP_DATA_JSON;
http_request_async(&request, [](http_response_t *jsonResponse) -> void
{
auto context = GetContext();
if (jsonResponse == nullptr)
request.method = Http::Method::GET;
Http::DoAsync(request, [](Http::Response res) {
std::shared_ptr<void> _(nullptr, [&](...) { _twitchIdle = true; });
if (res.status != Http::Status::OK)
{
_twitchState = TWITCH_STATE_LEFT;
context->WriteLine("Unable to connect to twitch channel.");
GetContext()->WriteLine("Unable to connect to twitch channel.");
return;
}
auto root = Json::FromString(res.body);
json_t * jsonStatus = json_object_get(root, "status");
if (json_is_number(jsonStatus) && json_integer_value(jsonStatus) == TWITCH_STATUS_OK)
{
_twitchState = TWITCH_STATE_JOINED;
}
else
{
json_t * jsonStatus = json_object_get(jsonResponse->root, "status");
if (json_is_number(jsonStatus) && json_integer_value(jsonStatus) == TWITCH_STATUS_OK)
{
_twitchState = TWITCH_STATE_JOINED;
}
else
{
_twitchState = TWITCH_STATE_LEFT;
}
http_request_dispose(jsonResponse);
_twitchLastPulseTick = 0;
context->WriteLine("Connected to twitch channel.");
_twitchState = TWITCH_STATE_LEFT;
}
_twitchIdle = true;
_twitchLastPulseTick = 0;
GetContext()->WriteLine("Connected to twitch channel.");
});
}
@@ -242,13 +244,8 @@ namespace Twitch
*/
static void Leave()
{
if (_twitchJsonResponse != nullptr)
{
http_request_dispose(_twitchJsonResponse);
_twitchJsonResponse = nullptr;
}
GetContext()->WriteLine("Left twitch channel.");
_twitchJsonResponse = {};
_twitchState = TWITCH_STATE_LEFT;
_twitchLastPulseTick = 0;
gTwitchEnable = false;
@@ -288,23 +285,17 @@ namespace Twitch
_twitchState = TWITCH_STATE_WAITING;
_twitchIdle = false;
http_request_t request = {};
request.url = url;
request.method = HTTP_METHOD_GET;
request.body = nullptr;
request.type = HTTP_DATA_JSON;
http_request_async(&request, [](http_response_t * jsonResponse) -> void
{
if (jsonResponse == nullptr)
Http::DoAsync({ url }, [](Http::Response res) {
std::shared_ptr<void> _(nullptr, [&](...) { _twitchIdle = true; });
if (res.status != Http::Status::OK)
{
_twitchState = TWITCH_STATE_JOINED;
return;
}
else
{
_twitchJsonResponse = jsonResponse;
_twitchState = TWITCH_STATE_GET_FOLLOWERS;
}
_twitchIdle = true;
_twitchJsonResponse = res;
_twitchState = TWITCH_STATE_GET_FOLLOWERS;
});
}
@@ -326,37 +317,31 @@ namespace Twitch
_twitchState = TWITCH_STATE_WAITING;
_twitchIdle = false;
http_request_t request = {};
request.url = url;
request.method = HTTP_METHOD_GET;
request.body = nullptr;
request.type = HTTP_DATA_JSON;
http_request_async(&request, [](http_response_t * jsonResponse) -> void
{
if (jsonResponse == nullptr)
Http::DoAsync({ url }, [](Http::Response res) {
std::shared_ptr<void> _(nullptr, [&](...) { _twitchIdle = true; });
if (res.status != Http::Status::OK)
{
_twitchState = TWITCH_STATE_JOINED;
return;
}
else
{
_twitchJsonResponse = jsonResponse;
_twitchState = TWITCH_STATE_GET_MESSAGES;
}
_twitchIdle = true;
_twitchJsonResponse = res;
_twitchState = TWITCH_STATE_GET_MESSAGES;
});
}
static void ParseFollowers()
{
http_response_t *jsonResponse = _twitchJsonResponse;
if (json_is_array(jsonResponse->root))
json_t * root = Json::FromString(_twitchJsonResponse.body);
if (json_is_array(root))
{
std::vector<AudienceMember> members;
size_t audienceCount = json_array_size(jsonResponse->root);
size_t audienceCount = json_array_size(root);
for (size_t i = 0; i < audienceCount; i++)
{
json_t * jsonAudienceMember = json_array_get(jsonResponse->root, i);
json_t * jsonAudienceMember = json_array_get(root, i);
auto member = AudienceMember::FromJson(jsonAudienceMember);
if (!String::IsNullOrEmpty(member.Name))
{
@@ -371,8 +356,7 @@ namespace Twitch
ManageGuestNames(members);
}
http_request_dispose(_twitchJsonResponse);
_twitchJsonResponse = nullptr;
_twitchJsonResponse = {};
_twitchState = TWITCH_STATE_JOINED;
gfx_invalidate_screen();
@@ -380,12 +364,12 @@ namespace Twitch
static void ParseMessages()
{
http_response_t * jsonResponse = _twitchJsonResponse;
if (json_is_array(jsonResponse->root))
json_t * root = Json::FromString(_twitchJsonResponse.body);
if (json_is_array(root))
{
size_t messageCount = json_array_size(jsonResponse->root);
size_t messageCount = json_array_size(root);
for (size_t i = 0; i < messageCount; i++) {
json_t * jsonMessage = json_array_get(jsonResponse->root, i);
json_t * jsonMessage = json_array_get(root, i);
if (!json_is_object(jsonMessage))
{
continue;
@@ -397,8 +381,7 @@ namespace Twitch
}
}
http_request_dispose(_twitchJsonResponse);
_twitchJsonResponse = nullptr;
_twitchJsonResponse = {};
_twitchState = TWITCH_STATE_JOINED;
}
@@ -569,7 +552,7 @@ namespace Twitch
safe_strcpy(buffer + 1, message, sizeof(buffer) - 1);
utf8_remove_formatting(buffer, false);
// TODO Create a new news item type for twitch which has twitch icon
news_item_add_to_queue_raw(NEWS_ITEM_BLANK, buffer, 0);
}

View File

@@ -1,79 +0,0 @@
#pragma region Copyright (c) 2014-2017 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 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, either version 3 of the License, or
* (at your option) any later version.
*
* A full copy of the GNU General Public License can be found in licence.txt
*****************************************************************************/
#pragma endregion
#ifndef _HTTP_H_
#define _HTTP_H_
#ifndef DISABLE_HTTP
#include <string>
#include <jansson.h>
#include "../common.h"
enum HTTP_DATA_TYPE {
HTTP_DATA_NONE,
HTTP_DATA_RAW,
HTTP_DATA_JSON
};
struct http_request_t {
void *tag;
std::string method;
std::string url;
HTTP_DATA_TYPE type = HTTP_DATA_NONE;
bool forceIPv4;
size_t size;
union {
const json_t *root;
char* body;
};
};
struct http_response_t {
void *tag;
sint32 status_code;
HTTP_DATA_TYPE type;
size_t size;
union {
json_t *root;
char* body;
};
};
#define HTTP_METHOD_GET "GET"
#define HTTP_METHOD_POST "POST"
#define HTTP_METHOD_PUT "PUT"
#define HTTP_METHOD_DELETE "DELETE"
void http_request_async(const http_request_t *request, void (*callback)(http_response_t*));
void http_request_dispose(http_response_t *response);
const char *http_get_extension_from_url(const char *url, const char *fallback);
/**
* Download a park via HTTP/S from the given URL into a memory buffer. This is
* a blocking operation.
* @param url The URL to download the park from.
* @param outData The data returned.
* @returns The size of the data or 0 if the download failed.
*/
size_t http_download_park(const char * url, void * * outData);
#endif // DISABLE_HTTP
// These callbacks are defined anyway, but are dummy if HTTP is disabled
void http_init();
void http_dispose();
#endif

View File

@@ -1,4 +1,4 @@
#pragma region Copyright (c) 2014-2017 OpenRCT2 Developers
#pragma region Copyright (c) 2014-2018 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*