Replace cURL with WinHttp implementation on Windows

This reduces the number of third party dependencies for Windows builds. WinHttp is quite a nice straight forward API so doesn't involve too much extra code.
This commit is contained in:
Ted John 2020-02-03 23:11:38 +00:00
parent d16fbb0cd2
commit be9cb19df5
11 changed files with 304 additions and 130 deletions

View File

@ -53,7 +53,7 @@
C4549: 'operator': operator before comma has no effect; did you intend 'operator'?
C4555: expression has no effect; expected expression with side-effect
-->
<PreprocessorDefinitions>__AVX2__;__SSE4_1__;OPENGL_NO_LINK;_CRT_SECURE_NO_WARNINGS;_USE_MATH_DEFINES;CURL_STATICLIB;SDL_MAIN_HANDLED;_WINSOCK_DEPRECATED_NO_WARNINGS;NOMINMAX;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>__AVX2__;__SSE4_1__;OPENGL_NO_LINK;_CRT_SECURE_NO_WARNINGS;_USE_MATH_DEFINES;SDL_MAIN_HANDLED;_WINSOCK_DEPRECATED_NO_WARNINGS;NOMINMAX;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary Condition="'$(UseSharedLibs)'!='true'">MultiThreaded</RuntimeLibrary>
<RuntimeLibrary Condition="'$(UseSharedLibs)'=='true'">MultiThreadedDLL</RuntimeLibrary>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
@ -61,7 +61,7 @@
<AdditionalOptions>/utf-8 /std:c++17 /permissive- /Zc:externConstexpr</AdditionalOptions>
</ClCompile>
<Link>
<AdditionalDependencies>wininet.lib;imm32.lib;version.lib;winmm.lib;crypt32.lib;wldap32.lib;shlwapi.lib;setupapi.lib;bcrypt.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>wininet.lib;imm32.lib;version.lib;winmm.lib;crypt32.lib;wldap32.lib;shlwapi.lib;setupapi.lib;bcrypt.lib;winhttp.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalOptions>/OPT:NOLBR /ignore:4099 %(AdditionalOptions)</AdditionalOptions>
</Link>
</ItemDefinitionGroup>
@ -76,7 +76,7 @@
</ClCompile>
<Link>
<GenerateDebugInformation>DebugFull</GenerateDebugInformation>
<AdditionalDependencies>benchmarkd.lib;libbreakpadd.lib;libbreakpad_clientd.lib;bz2d.lib;discord-rpc.lib;freetyped.lib;jansson_d.lib;libcurl-d.lib;libpng16d.lib;libspeexdsp.lib;SDL2d.lib;zip.lib;zlibd.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>benchmarkd.lib;libbreakpadd.lib;libbreakpad_clientd.lib;bz2d.lib;discord-rpc.lib;freetyped.lib;jansson_d.lib;libpng16d.lib;libspeexdsp.lib;SDL2d.lib;zip.lib;zlibd.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
@ -94,7 +94,7 @@
<GenerateDebugInformation>DebugFull</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<AdditionalDependencies>benchmark.lib;libbreakpad.lib;libbreakpad_client.lib;bz2.lib;discord-rpc.lib;freetype.lib;jansson.lib;libcurl.lib;libpng16.lib;libspeexdsp.lib;SDL2.lib;zip.lib;zlib.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>benchmark.lib;libbreakpad.lib;libbreakpad_client.lib;bz2.lib;discord-rpc.lib;freetype.lib;jansson.lib;libpng16.lib;libspeexdsp.lib;SDL2.lib;zip.lib;zlib.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>

View File

@ -11,10 +11,10 @@
#include <openrct2-ui/interface/Widget.h>
#include <openrct2-ui/windows/Window.h>
#include <openrct2/Context.h>
#include <openrct2/core/Http.h>
#include <openrct2/core/Json.hpp>
#include <openrct2/core/String.hpp>
#include <openrct2/localisation/Localisation.h>
#include <openrct2/network/Http.h>
#include <openrct2/object/ObjectList.h>
#include <openrct2/object/ObjectManager.h>
#include <openrct2/object/ObjectRepository.h>
@ -151,7 +151,6 @@ private:
void DownloadObject(const rct_object_entry& entry, const std::string name, const std::string url)
{
using namespace OpenRCT2::Networking;
try
{
std::printf("Downloading %s\n", url.c_str());
@ -190,8 +189,6 @@ private:
void NextDownload()
{
using namespace OpenRCT2::Networking;
if (!_downloadingObjects || _currentDownloadIndex >= _entries.size())
{
// Finished...

View File

@ -21,7 +21,6 @@
# include <openrct2/drawing/Drawing.h>
# include <openrct2/interface/Colour.h>
# include <openrct2/localisation/Localisation.h>
# include <openrct2/network/Http.h>
# include <openrct2/network/ServerList.h>
# include <openrct2/network/network.h>
# include <openrct2/platform/platform.h>

View File

@ -50,19 +50,18 @@ if (NOT DISABLE_NETWORK AND WIN32)
endif ()
if (NOT DISABLE_HTTP)
if (MSVC)
find_package(curl REQUIRED)
set(LIBCURL_LIBRARIES ${CURL_LIBRARIES})
if (WIN32)
target_link_libraries(${PROJECT_NAME} winhttp)
else ()
PKG_CHECK_MODULES(LIBCURL REQUIRED libcurl)
endif ()
target_include_directories(${PROJECT_NAME} PRIVATE ${LIBCURL_INCLUDE_DIRS})
if (STATIC)
target_link_libraries(${PROJECT_NAME} ${LIBCURL_STATIC_LIBRARIES})
else ()
target_link_libraries(${PROJECT_NAME} ${LIBCURL_LIBRARIES})
target_include_directories(${PROJECT_NAME} PRIVATE ${LIBCURL_INCLUDE_DIRS})
if (STATIC)
target_link_libraries(${PROJECT_NAME} ${LIBCURL_STATIC_LIBRARIES})
else ()
target_link_libraries(${PROJECT_NAME} ${LIBCURL_LIBRARIES})
endif ()
endif ()
endif ()

View File

@ -33,6 +33,7 @@
#include "core/FileScanner.h"
#include "core/FileStream.hpp"
#include "core/Guard.hpp"
#include "core/Http.h"
#include "core/MemoryStream.h"
#include "core/Path.hpp"
#include "core/String.hpp"
@ -45,7 +46,6 @@
#include "localisation/Localisation.h"
#include "localisation/LocalisationService.h"
#include "network/DiscordService.h"
#include "network/Http.h"
#include "network/Twitch.h"
#include "network/network.h"
#include "object/ObjectManager.h"
@ -99,9 +99,6 @@ namespace OpenRCT2
std::unique_ptr<DiscordService> _discordService;
#endif
StdInOutConsole _stdInOutConsole;
#ifndef DISABLE_HTTP
Networking::Http::Http _http;
#endif
// Game states
std::unique_ptr<TitleScreen> _titleScreen;
@ -721,15 +718,14 @@ namespace OpenRCT2
{
#ifndef DISABLE_HTTP
// Download park and open it using its temporary filename
void* data;
size_t dataSize = Networking::Http::DownloadPark(gOpenRCT2StartupActionPath, &data);
if (dataSize == 0)
auto data = DownloadPark(gOpenRCT2StartupActionPath);
if (data.empty())
{
title_load();
break;
}
auto ms = MemoryStream(data, dataSize, MEMORY_ACCESS::OWNER);
auto ms = MemoryStream(data.data(), data.size(), MEMORY_ACCESS::READ);
if (!LoadParkFromStream(&ms, gOpenRCT2StartupActionPath, true))
{
Console::Error::WriteLine("Failed to load '%s'", gOpenRCT2StartupActionPath);
@ -1083,6 +1079,34 @@ namespace OpenRCT2
}
delete scanner;
}
#ifndef DISABLE_HTTP
std::vector<uint8_t> DownloadPark(const std::string& url)
{
// Download park to buffer in memory
Http::Request request;
request.url = url;
request.method = Http::Method::GET;
Http::Response res;
try
{
res = Do(request);
if (res.status != Http::Status::OK)
throw 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());
return {};
}
std::vector<uint8_t> parkData;
parkData.resize(res.body.size());
std::memcpy(parkData.data(), res.body.c_str(), parkData.size());
return parkData;
}
#endif
};
Context* Context::Instance = nullptr;

View File

@ -0,0 +1,223 @@
/*****************************************************************************
* Copyright (c) 2014-2020 OpenRCT2 developers
*
* For a complete list of all authors, please refer to contributors.md
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is licensed under the GNU General Public License version 3.
*****************************************************************************/
#if !defined(DISABLE_HTTP) && defined(_WIN32)
# include "Http.h"
# include "../Version.h"
# include "String.hpp"
# include <cstdio>
# include <stdexcept>
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
# include <winhttp.h>
namespace Http
{
static constexpr char OPENRCT2_USER_AGENT[] = "OpenRCT2/" OPENRCT2_VERSION;
static void ThrowWin32Exception(const char* methodName)
{
auto errorCode = GetLastError();
auto msg = String::StdFormat("%s failed, error: %d.", methodName, errorCode);
throw std::runtime_error(msg);
}
static const wchar_t* GetVerb(Method method)
{
switch (method)
{
case Method::GET:
return L"GET";
case Method::POST:
return L"POST";
case Method::PUT:
return L"PUT";
default:
throw std::runtime_error("Unsupported verb.");
}
}
static std::wstring ReadHeader(HINTERNET hRequest, DWORD infoLevel, const wchar_t* name)
{
wchar_t headerBuffer[256]{};
auto headerBufferLen = (DWORD)std::size(headerBuffer);
if (!WinHttpQueryHeaders(hRequest, infoLevel, name, headerBuffer, &headerBufferLen, WINHTTP_NO_HEADER_INDEX))
ThrowWin32Exception("WinHttpQueryHeaders");
return std::wstring(headerBuffer, headerBufferLen);
}
static int32_t ReadStatusCode(HINTERNET hRequest)
{
auto wStatusCode = ReadHeader(hRequest, WINHTTP_QUERY_STATUS_CODE, L"StatusCode");
return std::stoi(wStatusCode);
}
static std::map<std::string, std::string> ReadHeaders(HINTERNET hRequest)
{
DWORD dwSize{};
WinHttpQueryHeaders(
hRequest, WINHTTP_QUERY_RAW_HEADERS, WINHTTP_HEADER_NAME_BY_INDEX, NULL, &dwSize, WINHTTP_NO_HEADER_INDEX);
std::wstring buffer;
buffer.resize(dwSize);
if (!WinHttpQueryHeaders(
hRequest, WINHTTP_QUERY_RAW_HEADERS, WINHTTP_HEADER_NAME_BY_INDEX, buffer.data(), &dwSize,
WINHTTP_NO_HEADER_INDEX))
ThrowWin32Exception("WinHttpQueryHeaders");
std::map<std::string, std::string> headers;
std::wstring wKey, wValue;
int state = 0;
int index = 0;
for (auto c : buffer)
{
if (c == '\0')
{
state = 0;
if (index != 0 && wKey.size() != 0)
{
auto key = String::ToUtf8(wKey);
auto value = String::ToUtf8(wValue);
headers[key] = value;
}
wKey.clear();
wValue.clear();
index++;
continue;
}
if (state == 0 && c == ':')
{
state = 1;
}
else if (state == 1 && c == ' ')
{
state = 2;
}
else if (state == 0)
{
wKey.push_back(c);
}
else
{
state = 2;
wValue.push_back(c);
}
}
return headers;
}
static std::string ReadBody(HINTERNET hRequest)
{
DWORD dwSize{};
std::string body;
do
{
// Check for available data.
if (!WinHttpQueryDataAvailable(hRequest, &dwSize))
ThrowWin32Exception("WinHttpQueryDataAvailable");
// No more available data.
if (dwSize == 0)
break;
auto offset = body.size();
body.resize(offset + dwSize);
auto dst = (LPVOID)&body[offset];
DWORD dwDownloaded{};
if (!WinHttpReadData(hRequest, dst, dwSize, &dwDownloaded))
ThrowWin32Exception("WinHttpReadData");
// This condition should never be reached since WinHttpQueryDataAvailable
// reported that there are bits to read.
if (dwDownloaded == 0)
break;
} while (dwSize > 0);
return body;
}
Response Do(const Request& req)
{
HINTERNET hSession{}, hConnect{}, hRequest{};
try
{
URL_COMPONENTS url;
ZeroMemory(&url, sizeof(url));
url.dwStructSize = sizeof(url);
url.dwSchemeLength = (DWORD)-1;
url.dwHostNameLength = (DWORD)-1;
url.dwUrlPathLength = (DWORD)-1;
url.dwExtraInfoLength = (DWORD)-1;
auto wUrl = String::ToWideChar(req.url);
if (!WinHttpCrackUrl(wUrl.c_str(), 0, 0, &url))
throw std::invalid_argument("Unable to parse URI.");
auto userAgent = String::ToWideChar(OPENRCT2_USER_AGENT);
hSession = WinHttpOpen(
userAgent.c_str(), WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
if (hSession == nullptr)
ThrowWin32Exception("WinHttpOpen");
auto wHostName = std::wstring(url.lpszHostName, url.dwHostNameLength);
hConnect = WinHttpConnect(hSession, wHostName.c_str(), url.nPort, 0);
if (hConnect == nullptr)
ThrowWin32Exception("WinHttpConnect");
auto wVerb = GetVerb(req.method);
auto wQuery = std::wstring(url.lpszUrlPath, url.dwUrlPathLength);
hRequest = WinHttpOpenRequest(
hConnect, wVerb, wQuery.c_str(), NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, WINHTTP_FLAG_SECURE);
if (hRequest == nullptr)
ThrowWin32Exception("WinHttpOpenRequest");
for (auto header : req.header)
{
auto fullHeader = String::ToWideChar(header.first) + L": " + String::ToWideChar(header.second);
if (!WinHttpAddRequestHeaders(hRequest, fullHeader.c_str(), (ULONG)-1L, WINHTTP_ADDREQ_FLAG_ADD))
ThrowWin32Exception("WinHttpAddRequestHeaders");
}
auto reqBody = (LPVOID)req.body.data();
auto reqBodyLen = (DWORD)req.body.size();
if (!WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, reqBody, reqBodyLen, reqBodyLen, 0))
ThrowWin32Exception("WinHttpSendRequest");
if (!WinHttpReceiveResponse(hRequest, NULL))
ThrowWin32Exception("WinHttpReceiveResponse");
auto statusCode = ReadStatusCode(hRequest);
auto contentType = ReadHeader(hRequest, WINHTTP_QUERY_CONTENT_TYPE, L"Content-Type");
auto headers = ReadHeaders(hRequest);
auto body = ReadBody(hRequest);
Response response;
response.body = std::move(body);
response.status = (Status)statusCode;
response.content_type = String::ToUtf8(contentType);
response.header = headers;
return response;
}
catch ([[maybe_unused]] const std::exception& e)
{
# ifdef DEBUG
std::fprintf(stderr, "HTTP request failed: %s\n", e.what());
# endif
WinHttpCloseHandle(hSession);
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hRequest);
throw;
}
}
} // namespace Http
#endif

View File

@ -1,5 +1,5 @@
/*****************************************************************************
* Copyright (c) 2014-2019 OpenRCT2 developers
* Copyright (c) 2014-2020 OpenRCT2 developers
*
* For a complete list of all authors, please refer to contributors.md
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
@ -7,17 +7,16 @@
* OpenRCT2 is licensed under the GNU General Public License version 3.
*****************************************************************************/
#include "Http.h"
#include <cstring>
#include <memory>
#include <stdexcept>
#include <thread>
#ifndef DISABLE_HTTP
#if !defined(DISABLE_HTTP) && !defined(_WIN32)
# include "../Version.h"
# include "../core/Console.hpp"
# include "Http.h"
# include <cstring>
# include <memory>
# include <stdexcept>
# include <thread>
# ifdef _WIN32
// cURL includes windows.h, but we don't need all of it.
@ -27,18 +26,8 @@
# define OPENRCT2_USER_AGENT "OpenRCT2/" OPENRCT2_VERSION
namespace OpenRCT2::Networking::Http
namespace Http
{
Http::Http()
{
curl_global_init(CURL_GLOBAL_DEFAULT);
}
Http::~Http()
{
curl_global_cleanup();
}
static size_t writeData(const char* src, size_t size, size_t nmemb, void* userdata)
{
size_t realsize = size * nmemb;
@ -168,59 +157,6 @@ namespace OpenRCT2::Networking::Http
return res;
}
void DoAsync(const Request& req, std::function<void(Response& res)> fn)
{
auto thread = std::thread([=]() {
Response res;
try
{
res = Do(req);
}
catch (std::exception& e)
{
res.error = e.what();
return;
}
fn(res);
});
thread.detach();
}
size_t DownloadPark(const char* url, void** outData)
{
// Download park to buffer in memory
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 = res.body.size() - 1;
void* data = malloc(dataSize);
if (data == nullptr)
{
Console::Error::WriteLine("Failed to allocate memory for downloaded park.");
return 0;
}
std::memcpy(data, res.body.c_str(), dataSize);
*outData = data;
return dataSize;
}
} // namespace OpenRCT2::Networking::Http
} // namespace Http
#endif

View File

@ -1,5 +1,5 @@
/*****************************************************************************
* Copyright (c) 2014-2019 OpenRCT2 developers
* Copyright (c) 2014-2020 OpenRCT2 developers
*
* For a complete list of all authors, please refer to contributors.md
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
@ -16,8 +16,9 @@
# include <functional>
# include <map>
# include <string>
# include <thread>
namespace OpenRCT2::Networking::Http
namespace Http
{
enum class Status
{
@ -50,24 +51,25 @@ namespace OpenRCT2::Networking::Http
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 OpenRCT2::Networking::Http
inline void DoAsync(const Request& req, std::function<void(Response& res)> fn)
{
auto thread = std::thread([=]() {
Response res;
try
{
res = Do(req);
}
catch (std::exception& e)
{
res.error = e.what();
return;
}
fn(res);
});
thread.detach();
}
} // namespace Http
#endif // DISABLE_HTTP

View File

@ -13,6 +13,7 @@
# include "../config/Config.h"
# include "../core/Console.hpp"
# include "../core/Http.h"
# include "../core/Json.hpp"
# include "../core/String.hpp"
# include "../localisation/Date.h"
@ -23,7 +24,6 @@
# include "../util/Util.h"
# include "../world/Map.h"
# include "../world/Park.h"
# include "Http.h"
# include "Socket.h"
# include "network.h"
@ -164,8 +164,6 @@ private:
void SendRegistration(bool forceIPv4)
{
using namespace OpenRCT2::Networking;
_lastAdvertiseTime = platform_get_ticks();
// Send the registration request
@ -199,8 +197,6 @@ private:
void SendHeartbeat()
{
using namespace OpenRCT2::Networking;
Http::Request request;
request.url = GetMasterServerUrl();
request.method = Http::Method::PUT;

View File

@ -15,11 +15,11 @@
# include "../PlatformEnvironment.h"
# include "../config/Config.h"
# include "../core/FileStream.hpp"
# include "../core/Http.h"
# include "../core/Json.hpp"
# include "../core/Memory.hpp"
# include "../core/Path.hpp"
# include "../core/String.hpp"
# include "../network/Http.h"
# include "../platform/platform.h"
# include "Socket.h"
# include "network.h"
@ -331,7 +331,6 @@ std::future<std::vector<ServerListEntry>> ServerList::FetchOnlineServerListAsync
# ifdef DISABLE_HTTP
return {};
# else
using namespace OpenRCT2::Networking;
auto p = std::make_shared<std::promise<std::vector<ServerListEntry>>>();
auto f = p->get_future();

View File

@ -29,6 +29,7 @@ namespace Twitch
# include "../OpenRCT2.h"
# include "../actions/GuestSetFlagsAction.hpp"
# include "../config/Config.h"
# include "../core/Http.h"
# include "../core/Json.hpp"
# include "../core/String.hpp"
# include "../drawing/Drawing.h"
@ -39,7 +40,6 @@ namespace Twitch
# include "../platform/platform.h"
# include "../util/Util.h"
# include "../world/Sprite.h"
# include "Http.h"
# include "Twitch.h"
# include <jansson.h>
@ -47,7 +47,6 @@ namespace Twitch
# include <vector>
using namespace OpenRCT2;
using namespace OpenRCT2::Networking;
bool gTwitchEnable = false;