10#include "../../stdafx.h"
11#include "../../debug.h"
13#include "../network_internal.h"
21#include "../../safeguards.h"
23static HINTERNET _winhttp_session =
nullptr;
28 const std::wstring
uri;
30 const std::string
data;
47static std::vector<NetworkHTTPRequest *> _http_requests;
48static std::vector<NetworkHTTPRequest *> _new_http_requests;
49static std::mutex _new_http_requests_mutex;
51static std::vector<HTTPThreadSafeCallback *> _http_callbacks;
52static std::vector<HTTPThreadSafeCallback *> _new_http_callbacks;
53static std::mutex _http_callback_mutex;
54static std::mutex _new_http_callback_mutex;
68 std::lock_guard<std::mutex>
lock(_new_http_callback_mutex);
69 _new_http_callbacks.push_back(&this->callback);
72static std::string GetLastErrorAsString()
76 DWORD error_code = GetLastError();
78 if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_IGNORE_INSERTS, GetModuleHandle(L
"winhttp.dll"), error_code,
79 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buffer,
static_cast<DWORD
>(std::size(buffer)),
nullptr) == 0) {
80 return fmt::format(
"unknown error {}", error_code);
103 case WINHTTP_CALLBACK_STATUS_RESOLVING_NAME:
104 case WINHTTP_CALLBACK_STATUS_NAME_RESOLVED:
105 case WINHTTP_CALLBACK_STATUS_CONNECTING_TO_SERVER:
106 case WINHTTP_CALLBACK_STATUS_CONNECTED_TO_SERVER:
107 case WINHTTP_CALLBACK_STATUS_SENDING_REQUEST:
108 case WINHTTP_CALLBACK_STATUS_REQUEST_SENT:
109 case WINHTTP_CALLBACK_STATUS_RECEIVING_RESPONSE:
110 case WINHTTP_CALLBACK_STATUS_RESPONSE_RECEIVED:
111 case WINHTTP_CALLBACK_STATUS_CLOSING_CONNECTION:
112 case WINHTTP_CALLBACK_STATUS_CONNECTION_CLOSED:
113 case WINHTTP_CALLBACK_STATUS_HANDLE_CREATED:
114 case WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING:
118 case WINHTTP_CALLBACK_STATUS_REDIRECT:
120 if (this->
depth++ > 5) {
121 Debug(net, 0,
"HTTP request failed: too many redirects");
128 case WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE:
130 WinHttpReceiveResponse(this->
request,
nullptr);
133 case WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE:
136 DWORD status_code = 0;
137 DWORD status_code_size =
sizeof(status_code);
138 WinHttpQueryHeaders(this->
request, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, WINHTTP_HEADER_NAME_BY_INDEX, &status_code, &status_code_size, WINHTTP_NO_HEADER_INDEX);
139 Debug(net, 3,
"HTTP request status code: {}", status_code);
142 if (status_code >= 400) {
144 Debug(net, status_code == HTTP_429_TOO_MANY_REQUESTS ? 1 : 0,
"HTTP request failed: status-code {}", status_code);
151 WinHttpQueryDataAvailable(this->
request,
nullptr);
154 case WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE:
157 DWORD size = *(DWORD *)info;
161 char *buffer = size == 0 ? nullptr :
new char[size];
162 WinHttpReadData(this->
request, buffer, size, 0);
165 case WINHTTP_CALLBACK_STATUS_READ_COMPLETE:
166 Debug(net, 6,
"HTTP callback: {} bytes", length);
173 Debug(net, 1,
"HTTP request succeeded");
176 WinHttpQueryDataAvailable(this->
request,
nullptr);
181 case WINHTTP_CALLBACK_STATUS_SECURE_FAILURE:
182 case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR:
183 Debug(net, 0,
"HTTP request failed: {}", GetLastErrorAsString());
189 Debug(net, 0,
"HTTP request failed: unexpected callback code 0x{:x}", code);
196static void CALLBACK StaticWinHttpCallback(HINTERNET, DWORD_PTR context, DWORD code,
void *info, DWORD length)
198 if (context == 0)
return;
213 Debug(net, 1,
"HTTP request to {}", std::string(
uri.begin(),
uri.end()));
215 URL_COMPONENTS url_components = {};
217 wchar_t hostname[128];
218 wchar_t url_path[4096];
221 url_components.dwStructSize =
sizeof(url_components);
222 url_components.lpszScheme = scheme;
223 url_components.dwSchemeLength =
static_cast<DWORD
>(std::size(scheme));
224 url_components.lpszHostName = hostname;
225 url_components.dwHostNameLength =
static_cast<DWORD
>(std::size(hostname));
226 url_components.lpszUrlPath = url_path;
227 url_components.dwUrlPathLength =
static_cast<DWORD
>(std::size(url_path));
228 WinHttpCrackUrl(this->
uri.c_str(), 0, 0, &url_components);
231 this->
connection = WinHttpConnect(_winhttp_session, url_components.lpszHostName, url_components.nPort, 0);
233 Debug(net, 0,
"HTTP request failed: {}", GetLastErrorAsString());
239 this->request = WinHttpOpenRequest(
connection,
data.empty() ? L
"GET" : L
"POST", url_components.lpszUrlPath,
nullptr, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, url_components.nScheme == INTERNET_SCHEME_HTTPS ? WINHTTP_FLAG_SECURE : 0);
240 if (this->request ==
nullptr) {
243 Debug(net, 0,
"HTTP request failed: {}", GetLastErrorAsString());
251 WinHttpSendRequest(this->request, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0,
reinterpret_cast<DWORD_PTR
>(
this));
254 LPCWSTR content_type =
data.starts_with(
"{") ? L
"Content-Type: application/json\r\n" : L
"Content-Type: application/x-www-form-urlencoded\r\n";
255 WinHttpSendRequest(this->request, content_type, -1,
const_cast<char *
>(
data.data()),
static_cast<DWORD
>(
data.size()),
static_cast<DWORD
>(
data.size()),
reinterpret_cast<DWORD_PTR
>(
this));
266 if (this->
callback.cancelled && !this->finished) {
267 Debug(net, 1,
"HTTP request failed: cancelled by user");
284 WinHttpCloseHandle(this->request);
288 std::lock_guard<std::mutex>
lock(_http_callback_mutex);
289 _http_callbacks.erase(std::remove(_http_callbacks.begin(), _http_callbacks.end(), &this->callback), _http_callbacks.end());
294 auto request =
new NetworkHTTPRequest(std::wstring(uri.begin(), uri.end()), callback, std::move(data));
297 std::lock_guard<std::mutex>
lock(_new_http_requests_mutex);
298 _new_http_requests.push_back(request);
305 std::lock_guard<std::mutex>
lock(_http_callback_mutex);
308 std::lock_guard<std::mutex>
lock(_new_http_callback_mutex);
309 if (!_new_http_callbacks.empty()) {
311 _http_callbacks.insert(_http_callbacks.end(), _new_http_callbacks.begin(), _new_http_callbacks.end());
312 _new_http_callbacks.clear();
316 for (
auto &callback : _http_callbacks) {
317 callback->HandleQueue();
323 std::lock_guard<std::mutex>
lock(_new_http_requests_mutex);
324 if (!_new_http_requests.empty()) {
326 _http_requests.insert(_http_requests.end(), _new_http_requests.begin(), _new_http_requests.end());
327 _new_http_requests.clear();
331 if (_http_requests.empty())
return;
333 for (
auto it = _http_requests.begin(); it != _http_requests.end(); ) {
337 it = _http_requests.erase(it);
350 _winhttp_session = WinHttpOpen(std::wstring(user_agent.begin(), user_agent.end()).c_str(), WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, WINHTTP_FLAG_ASYNC);
353 WinHttpSetStatusCallback(_winhttp_session, StaticWinHttpCallback, WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS, 0);
356 WinHttpSetTimeouts(_winhttp_session, 10000, 10000, 10000, 10000);
361 WinHttpCloseHandle(_winhttp_session);
Converts a HTTPCallback to a Thread-Safe variant.
void OnFailure()
Similar to HTTPCallback::OnFailure, but thread-safe.
void OnReceiveData(std::unique_ptr< char[]> data, size_t length)
Similar to HTTPCallback::OnReceiveData, but thread-safe.
bool IsQueueEmpty()
Check if the queue is empty.
bool Receive()
Poll and process the HTTP request/response.
std::atomic< bool > finished
Whether we are finished with the request.
HINTERNET connection
Current connection object.
const std::string data
Data to send, if any.
~NetworkHTTPRequest()
Destructor of the HTTP request.
const std::wstring uri
URI to connect to.
NetworkHTTPRequest(std::string_view uri, HTTPCallback *callback, std::string &&data)
Create a new HTTP request.
HINTERNET request
Current request object.
void Connect()
Start the HTTP request handling.
HTTPThreadSafeCallback callback
Callback to send data back on.
void WinHttpCallback(DWORD code, void *info, DWORD length)
Callback from the WinHTTP library, called when-ever something changes about the HTTP request status.
const std::string uri
URI to connect to.
int depth
Current redirect depth we are in.
static void Connect(std::string_view uri, HTTPCallback *callback, std::string &&data="")
Connect to the given URI.
static void HTTPReceive()
Do the receiving for all HTTP connections.
#define Debug(category, level, format_string,...)
Output a line of debugging information.
Basic functions to send and receive HTTP packets.
Shared functions for implementations of HTTP requests.
void NetworkHTTPInitialize()
Initialize the HTTP socket handler.
void NetworkHTTPUninitialize()
Uninitialize the HTTP socket handler.
std::string_view GetNetworkRevisionString()
Get the network version string used by this build.
Callback for when the HTTP handler has something to tell us.
std::string FS2OTTD(std::wstring_view name)
Convert to OpenTTD's encoding from a wide string.
std::mutex lock
synchronization for playback status fields