12#include "../../stdafx.h"
13#include "../../debug.h"
15#include "../network_internal.h"
23#include "../../safeguards.h"
25static HINTERNET _winhttp_session =
nullptr;
30 const std::wstring
uri;
32 const std::string
data;
49static std::vector<NetworkHTTPRequest *> _http_requests;
50static std::vector<NetworkHTTPRequest *> _new_http_requests;
51static std::mutex _new_http_requests_mutex;
53static std::vector<HTTPThreadSafeCallback *> _http_callbacks;
54static std::vector<HTTPThreadSafeCallback *> _new_http_callbacks;
55static std::mutex _http_callback_mutex;
56static std::mutex _new_http_callback_mutex;
70 std::lock_guard<std::mutex>
lock(_new_http_callback_mutex);
71 _new_http_callbacks.push_back(&this->callback);
74static std::string GetLastErrorAsString()
78 DWORD error_code = GetLastError();
80 if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_IGNORE_INSERTS, GetModuleHandle(L
"winhttp.dll"), error_code,
81 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buffer,
static_cast<DWORD
>(std::size(buffer)),
nullptr) == 0) {
82 return fmt::format(
"unknown error {}", error_code);
105 case WINHTTP_CALLBACK_STATUS_RESOLVING_NAME:
106 case WINHTTP_CALLBACK_STATUS_NAME_RESOLVED:
107 case WINHTTP_CALLBACK_STATUS_CONNECTING_TO_SERVER:
108 case WINHTTP_CALLBACK_STATUS_CONNECTED_TO_SERVER:
109 case WINHTTP_CALLBACK_STATUS_SENDING_REQUEST:
110 case WINHTTP_CALLBACK_STATUS_REQUEST_SENT:
111 case WINHTTP_CALLBACK_STATUS_RECEIVING_RESPONSE:
112 case WINHTTP_CALLBACK_STATUS_RESPONSE_RECEIVED:
113 case WINHTTP_CALLBACK_STATUS_CLOSING_CONNECTION:
114 case WINHTTP_CALLBACK_STATUS_CONNECTION_CLOSED:
115 case WINHTTP_CALLBACK_STATUS_HANDLE_CREATED:
116 case WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING:
120 case WINHTTP_CALLBACK_STATUS_REDIRECT:
122 if (this->
depth++ > 5) {
123 Debug(net, 0,
"HTTP request failed: too many redirects");
130 case WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE:
132 WinHttpReceiveResponse(this->
request,
nullptr);
135 case WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE:
138 DWORD status_code = 0;
139 DWORD status_code_size =
sizeof(status_code);
140 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);
141 Debug(net, 3,
"HTTP request status code: {}", status_code);
144 if (status_code >= 400) {
146 Debug(net, status_code == HTTP_429_TOO_MANY_REQUESTS ? 1 : 0,
"HTTP request failed: status-code {}", status_code);
153 WinHttpQueryDataAvailable(this->
request,
nullptr);
156 case WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE:
159 DWORD size = *(DWORD *)info;
163 char *buffer = size == 0 ? nullptr :
new char[size];
164 WinHttpReadData(this->
request, buffer, size, 0);
167 case WINHTTP_CALLBACK_STATUS_READ_COMPLETE:
168 Debug(net, 6,
"HTTP callback: {} bytes", length);
175 Debug(net, 1,
"HTTP request succeeded");
178 WinHttpQueryDataAvailable(this->
request,
nullptr);
183 case WINHTTP_CALLBACK_STATUS_SECURE_FAILURE:
184 case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR:
185 Debug(net, 0,
"HTTP request failed: {}", GetLastErrorAsString());
191 Debug(net, 0,
"HTTP request failed: unexepected callback code 0x{:x}", code);
198static void CALLBACK StaticWinHttpCallback(HINTERNET, DWORD_PTR context, DWORD code,
void *info, DWORD length)
200 if (context == 0)
return;
215 Debug(net, 1,
"HTTP request to {}", std::string(
uri.begin(),
uri.end()));
217 URL_COMPONENTS url_components = {};
219 wchar_t hostname[128];
220 wchar_t url_path[4096];
223 url_components.dwStructSize =
sizeof(url_components);
224 url_components.lpszScheme = scheme;
225 url_components.dwSchemeLength =
static_cast<DWORD
>(std::size(scheme));
226 url_components.lpszHostName = hostname;
227 url_components.dwHostNameLength =
static_cast<DWORD
>(std::size(hostname));
228 url_components.lpszUrlPath = url_path;
229 url_components.dwUrlPathLength =
static_cast<DWORD
>(std::size(url_path));
230 WinHttpCrackUrl(this->
uri.c_str(), 0, 0, &url_components);
233 this->
connection = WinHttpConnect(_winhttp_session, url_components.lpszHostName, url_components.nPort, 0);
235 Debug(net, 0,
"HTTP request failed: {}", GetLastErrorAsString());
241 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);
242 if (this->request ==
nullptr) {
245 Debug(net, 0,
"HTTP request failed: {}", GetLastErrorAsString());
253 WinHttpSendRequest(this->request, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0,
reinterpret_cast<DWORD_PTR
>(
this));
256 LPCWSTR content_type =
data.starts_with(
"{") ? L
"Content-Type: application/json\r\n" : L
"Content-Type: application/x-www-form-urlencoded\r\n";
257 WinHttpSendRequest(this->request, content_type, -1,
const_cast<char *
>(
data.c_str()),
static_cast<DWORD
>(
data.size()),
static_cast<DWORD
>(
data.size()),
reinterpret_cast<DWORD_PTR
>(
this));
268 if (this->
callback.cancelled && !this->finished) {
269 Debug(net, 1,
"HTTP request failed: cancelled by user");
286 WinHttpCloseHandle(this->request);
290 std::lock_guard<std::mutex>
lock(_http_callback_mutex);
291 _http_callbacks.erase(std::remove(_http_callbacks.begin(), _http_callbacks.end(), &this->callback), _http_callbacks.end());
296 auto request =
new NetworkHTTPRequest(std::wstring(uri.begin(), uri.end()), callback, data);
299 std::lock_guard<std::mutex>
lock(_new_http_requests_mutex);
300 _new_http_requests.push_back(request);
307 std::lock_guard<std::mutex>
lock(_http_callback_mutex);
310 std::lock_guard<std::mutex>
lock(_new_http_callback_mutex);
311 if (!_new_http_callbacks.empty()) {
313 _http_callbacks.insert(_http_callbacks.end(), _new_http_callbacks.begin(), _new_http_callbacks.end());
314 _new_http_callbacks.clear();
318 for (
auto &callback : _http_callbacks) {
319 callback->HandleQueue();
325 std::lock_guard<std::mutex>
lock(_new_http_requests_mutex);
326 if (!_new_http_requests.empty()) {
328 _http_requests.insert(_http_requests.end(), _new_http_requests.begin(), _new_http_requests.end());
329 _new_http_requests.clear();
333 if (_http_requests.empty())
return;
335 for (
auto it = _http_requests.begin(); it != _http_requests.end(); ) {
339 it = _http_requests.erase(it);
351 std::string user_agent = fmt::format(
"OpenTTD/{}", GetNetworkRevisionString());
352 _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);
355 WinHttpSetStatusCallback(_winhttp_session, StaticWinHttpCallback, WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS, 0);
358 WinHttpSetTimeouts(_winhttp_session, 10000, 10000, 10000, 10000);
363 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.
NetworkHTTPRequest(const std::string &uri, HTTPCallback *callback, const std::string &data)
Create a new HTTP 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.
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(const std::string &uri, HTTPCallback *callback, const std::string data="")
Connect to the given URI.
static void HTTPReceive()
Do the receiving for all HTTP connections.
#define Debug(category, level, format_string,...)
Ouptut 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.
Callback for when the HTTP handler has something to tell us.
std::string FS2OTTD(const std::wstring &name)
Convert to OpenTTD's encoding from a wide string.
std::mutex lock
synchronization for playback status fields