1
+ // clang-format off
1
2
// Copyright (c) Microsoft. All rights reserved.
2
3
#include " mat/config.h"
3
4
4
5
#ifdef HAVE_MAT_DEFAULT_HTTP_CLIENT
5
-
6
+ #pragma warning(push)
7
+ #pragma warning(disable:4189) /* Turn off Level 4: local variable is initialized but not referenced. dwError unused in Release without printing it. */
6
8
#include " HttpClient_WinInet.hpp"
7
9
#include " utils/Utils.hpp"
8
10
@@ -22,12 +24,13 @@ class WinInetRequestWrapper
22
24
protected:
23
25
HttpClient_WinInet& m_parent;
24
26
std::string m_id;
25
- IHttpResponseCallback* m_appCallback {};
26
- HINTERNET m_hWinInetSession {};
27
- HINTERNET m_hWinInetRequest {};
27
+ IHttpResponseCallback* m_appCallback {nullptr };
28
+ HINTERNET m_hWinInetSession {nullptr };
29
+ HINTERNET m_hWinInetRequest {nullptr };
28
30
SimpleHttpRequest* m_request;
29
- BYTE m_buffer[1024 ] {};
30
-
31
+ BYTE m_buffer[1024 ] {0 };
32
+ bool isCallbackCalled {false };
33
+ bool isAborted {false };
31
34
public:
32
35
WinInetRequestWrapper (HttpClient_WinInet& parent, SimpleHttpRequest* request)
33
36
: m_parent(parent),
@@ -50,15 +53,32 @@ class WinInetRequestWrapper
50
53
}
51
54
}
52
55
53
- /* *
54
- * Async cancel pending request
55
- */
56
+ // / <summary>
57
+ // / Asynchronously cancel pending request. This method is not directly calling
58
+ // / the object destructor, but rather hints the implementation to speed-up the
59
+ // / destruction.
60
+ // /
61
+ // / Two possible outcomes:.
62
+ // //
63
+ // / - set isAborted to true: cancel request without sending to WinInet stack,
64
+ // / in case if request has not been sent to WinInet stack yet.
65
+ // //
66
+ // / - close m_hWinInetRequest handle: WinInet fails all subsequent attempts to
67
+ // / use invalidated handle and aborts all pending WinInet worker threads on it.
68
+ // / In that case we complete with ERROR_INTERNET_OPERATION_CANCELLED.
69
+ // /
70
+ // / It may happen that we get some feedback from WinInet, i.e. we are canceling
71
+ // / at that same moment when the request is complete. In that case we process
72
+ // / completion in the callback (INTERNET_STATUS_REQUEST_COMPLETE).
73
+ // / </summary>
56
74
void cancel ()
57
75
{
76
+ LOCKGUARD (m_parent.m_requestsMutex );
77
+ isAborted = true ;
58
78
if (m_hWinInetRequest != nullptr )
59
79
{
60
80
::InternetCloseHandle (m_hWinInetRequest);
61
- // don't wait for request callback
81
+ // async request callback destroys the object
62
82
}
63
83
}
64
84
@@ -110,28 +130,61 @@ class WinInetRequestWrapper
110
130
return true ;
111
131
}
112
132
133
+ // Asynchronously send HTTP request and invoke response callback.
134
+ // Ownership semantics: send(...) method self-destroys *this* upon
135
+ // receiving WinInet callback. There must be absolutely no methods
136
+ // that attempt to use the object after triggering send on it.
137
+ // Send operation on request may be issued no more than once.
138
+ //
139
+ // Implementation details:
140
+ //
141
+ // lockguard around m_requestsMutex covers the following stages:
142
+ // - request added to map
143
+ // - URL parsed
144
+ // - DNS lookup performed, socket opened, SSL handshake
145
+ // - MS-Root SSL cert validation (if requested)
146
+ // - populating HTTP request headers
147
+ // - scheduling async(!) upload of HTTP post body
148
+ //
149
+ // Note that if any of the stages above fails, we invoke onRequestComplete(...).
150
+ // That method destroys "this" request object and in order to avoid
151
+ // any corruption we immediately return after invoking onRequestComplete(...).
152
+ //
113
153
void send (IHttpResponseCallback* callback)
114
154
{
155
+ LOCKGUARD (m_parent.m_requestsMutex );
156
+ // Register app callback and request in HttpClient map
115
157
m_appCallback = callback;
158
+ m_parent.m_requests [m_id] = this ;
116
159
160
+ // If outside code asked us to abort that request before we could proceed with
161
+ // creating a WinInet handle, then clean it right away before proceeding with
162
+ // any async WinInet API calls.
163
+ if (isAborted)
117
164
{
118
- std::lock_guard<std::mutex> lock (m_parent.m_requestsMutex );
119
- m_parent.m_requests [m_id] = this ;
165
+ // Request force-aborted before creating a WinInet handle.
166
+ DispatchEvent (OnConnectFailed);
167
+ onRequestComplete (ERROR_INTERNET_OPERATION_CANCELLED);
168
+ return ;
120
169
}
121
170
171
+ DispatchEvent (OnConnecting);
122
172
URL_COMPONENTSA urlc;
123
173
memset (&urlc, 0 , sizeof (urlc));
124
174
urlc.dwStructSize = sizeof (urlc);
125
- char hostname[256 ];
175
+ char hostname[256 ] = { 0 } ;
126
176
urlc.lpszHostName = hostname;
127
177
urlc.dwHostNameLength = sizeof (hostname);
128
- char path[1024 ];
178
+ char path[1024 ] = { 0 } ;
129
179
urlc.lpszUrlPath = path;
130
180
urlc.dwUrlPathLength = sizeof (path);
131
- if (!::InternetCrackUrlA (m_request->m_url .data (), (DWORD)m_request->m_url .size (), 0 , &urlc)) {
181
+ if (!::InternetCrackUrlA (m_request->m_url .data (), (DWORD)m_request->m_url .size (), 0 , &urlc))
182
+ {
132
183
DWORD dwError = ::GetLastError ();
133
184
LOG_WARN (" InternetCrackUrl() failed: dwError=%d url=%s" , dwError, m_request->m_url .data ());
134
- onRequestComplete (dwError);
185
+ // Invalid URL passed to WinInet API
186
+ DispatchEvent (OnConnectFailed);
187
+ onRequestComplete (ERROR_INTERNET_OPERATION_CANCELLED);
135
188
return ;
136
189
}
137
190
@@ -140,7 +193,9 @@ class WinInetRequestWrapper
140
193
if (m_hWinInetSession == NULL ) {
141
194
DWORD dwError = ::GetLastError ();
142
195
LOG_WARN (" InternetConnect() failed: %d" , dwError);
143
- onRequestComplete (dwError);
196
+ // Cannot connect to host
197
+ DispatchEvent (OnConnectFailed);
198
+ onRequestComplete (ERROR_INTERNET_OPERATION_CANCELLED);
144
199
return ;
145
200
}
146
201
// TODO: Session handle for the same target should be cached across requests to enable keep-alive.
@@ -155,7 +210,9 @@ class WinInetRequestWrapper
155
210
if (m_hWinInetRequest == NULL ) {
156
211
DWORD dwError = ::GetLastError ();
157
212
LOG_WARN (" HttpOpenRequest() failed: %d" , dwError);
158
- onRequestComplete (dwError);
213
+ // Request cannot be opened to given URL because of some connectivity issue
214
+ DispatchEvent (OnConnectFailed);
215
+ onRequestComplete (ERROR_INTERNET_OPERATION_CANCELLED);
159
216
return ;
160
217
}
161
218
@@ -164,6 +221,8 @@ class WinInetRequestWrapper
164
221
{
165
222
if (!isMsRootCert ())
166
223
{
224
+ // Request cannot be completed: end-point certificate is not MS-Rooted
225
+ DispatchEvent (OnConnectFailed);
167
226
onRequestComplete (ERROR_INTERNET_SEC_INVALID_CERT);
168
227
return ;
169
228
}
@@ -175,8 +234,20 @@ class WinInetRequestWrapper
175
234
for (auto const & header : m_request->m_headers ) {
176
235
os << header.first << " : " << header.second << " \r\n " ;
177
236
}
178
- ::HttpAddRequestHeadersA (m_hWinInetRequest, os.str().data(), static_cast<DWORD>(os.tellp()), HTTP_ADDREQ_FLAG_ADD | HTTP_ADDREQ_FLAG_REPLACE);
179
237
238
+ if (!::HttpAddRequestHeadersA (m_hWinInetRequest, os.str ().data (), static_cast <DWORD>(os.tellp ()), HTTP_ADDREQ_FLAG_ADD | HTTP_ADDREQ_FLAG_REPLACE))
239
+ {
240
+ DWORD dwError = ::GetLastError ();
241
+ LOG_WARN (" HttpAddRequestHeadersA() failed: %d" , dwError);
242
+ // Unable to add request headers. There's no point in proceeding with upload because
243
+ // our server is expecting those custom request headers to always be there.
244
+ DispatchEvent (OnConnectFailed);
245
+ onRequestComplete (ERROR_INTERNET_OPERATION_CANCELLED);
246
+ return ;
247
+ }
248
+
249
+ // Try to send headers and request body to server
250
+ DispatchEvent (OnSending);
180
251
void *data = static_cast <void *>(m_request->m_body .data ());
181
252
DWORD size = static_cast <DWORD>(m_request->m_body .size ());
182
253
BOOL bResult = ::HttpSendRequest (m_hWinInetRequest, NULL , 0 , data, (DWORD)size);
@@ -185,8 +256,12 @@ class WinInetRequestWrapper
185
256
if (bResult == TRUE && dwError != ERROR_IO_PENDING) {
186
257
dwError = ::GetLastError ();
187
258
LOG_WARN (" HttpSendRequest() failed: %d" , dwError);
188
- onRequestComplete (dwError);
259
+ // Unable to send requerst
260
+ DispatchEvent (OnSendFailed);
261
+ onRequestComplete (ERROR_INTERNET_OPERATION_CANCELLED);
262
+ return ;
189
263
}
264
+ // Async request has been queued in WinInet thread pool
190
265
}
191
266
192
267
static void CALLBACK winInetCallback (HINTERNET hInternet, DWORD_PTR dwContext, DWORD dwInternetStatus, LPVOID lpvStatusInformation, DWORD dwStatusInformationLength)
@@ -207,6 +282,12 @@ class WinInetRequestWrapper
207
282
return ;
208
283
}
209
284
285
+ case INTERNET_STATUS_HANDLE_CLOSING:
286
+ // HANDLE_CLOSING should always come after REQUEST_COMPLETE. When (and if)
287
+ // it (ever) happens, WinInetRequestWrapper* self pointer may point to object
288
+ // that has been already destroyed. We do not perform any actions on it.
289
+ return ;
290
+
210
291
case INTERNET_STATUS_REQUEST_COMPLETE: {
211
292
assert (dwStatusInformationLength >= sizeof (INTERNET_ASYNC_RESULT));
212
293
INTERNET_ASYNC_RESULT& result = *static_cast <INTERNET_ASYNC_RESULT*>(lpvStatusInformation);
@@ -222,6 +303,14 @@ class WinInetRequestWrapper
222
303
}
223
304
}
224
305
306
+ void DispatchEvent (HttpStateEvent type)
307
+ {
308
+ if (m_appCallback != nullptr )
309
+ {
310
+ m_appCallback->OnHttpStateEvent (type, static_cast <void *>(m_hWinInetRequest), 0 );
311
+ }
312
+ }
313
+
225
314
void onRequestComplete (DWORD dwError)
226
315
{
227
316
std::unique_ptr<SimpleHttpResponse> response (new SimpleHttpResponse (m_id));
@@ -257,6 +346,7 @@ class WinInetRequestWrapper
257
346
} while (m_bufferUsed == sizeof (m_buffer));
258
347
}
259
348
349
+ // SUCCESS with no IO_PENDING means we're done with the response body: try to parse the response headers.
260
350
if (dwError == ERROR_SUCCESS) {
261
351
response->m_result = HttpResult_OK;
262
352
@@ -308,6 +398,10 @@ class WinInetRequestWrapper
308
398
response->m_headers .add (name, value1);
309
399
ptr = eol + 2 ;
310
400
}
401
+ // This event handler covers the only positive case when we actually got some server response.
402
+ // We may still invoke OnHttpResponse(...) below for this positive as well as other negative
403
+ // cases where there was a short-read, connection failuire or timeout on reading the response.
404
+ DispatchEvent (OnResponse);
311
405
312
406
} else {
313
407
switch (dwError) {
@@ -348,9 +442,18 @@ class WinInetRequestWrapper
348
442
}
349
443
}
350
444
351
- // 'response' gets released in EventsUploadContext.clear()
352
- m_appCallback->OnHttpResponse (response.release ());
353
- m_parent.erase (m_id);
445
+ assert (isCallbackCalled == false );
446
+ if (!isCallbackCalled)
447
+ {
448
+ // Only one WinInet worker thread may invoke async callback for a given request at any given moment of time.
449
+ // That ensures that isCallbackCalled does not require a lock around it. We unregister the callback here
450
+ // to ensure that no more callbacks are coming for that m_hWinInetRequest.
451
+ ::InternetSetStatusCallback (m_hWinInetRequest, NULL );
452
+ isCallbackCalled = true ;
453
+ m_appCallback->OnHttpResponse (response.release ());
454
+ // HttpClient parent is destroying this HttpRequest object by id
455
+ m_parent.erase (m_id);
456
+ }
354
457
}
355
458
};
356
459
@@ -370,9 +473,13 @@ HttpClient_WinInet::~HttpClient_WinInet()
370
473
::InternetCloseHandle (m_hInternet);
371
474
}
372
475
476
+ /* *
477
+ * This method is called exclusively from onRequestComplete .
478
+ * No other code paths that lead to request destruction.
479
+ */
373
480
void HttpClient_WinInet::erase (std::string const & id)
374
481
{
375
- std::lock_guard<std::mutex> lock (m_requestsMutex);
482
+ LOCKGUARD (m_requestsMutex);
376
483
auto it = m_requests.find (id);
377
484
if (it != m_requests.end ()) {
378
485
auto req = it->second ;
@@ -397,11 +504,10 @@ void HttpClient_WinInet::SendRequestAsync(IHttpRequest* request, IHttpResponseCa
397
504
398
505
void HttpClient_WinInet::CancelRequestAsync (std::string const & id)
399
506
{
400
- WinInetRequestWrapper* request = nullptr ;
401
507
LOCKGUARD (m_requestsMutex);
402
508
auto it = m_requests.find (id);
403
509
if (it != m_requests.end ()) {
404
- request = it->second ;
510
+ auto request = it->second ;
405
511
if (request) {
406
512
request->cancel ();
407
513
}
@@ -414,7 +520,7 @@ void HttpClient_WinInet::CancelAllRequests()
414
520
// vector of all request IDs
415
521
std::vector<std::string> ids;
416
522
{
417
- std::lock_guard<std::mutex> lock (m_requestsMutex);
523
+ LOCKGUARD (m_requestsMutex);
418
524
for (auto const & item : m_requests) {
419
525
ids.push_back (item.first );
420
526
}
@@ -452,5 +558,6 @@ bool HttpClient_WinInet::IsMsRootCheckRequired()
452
558
}
453
559
454
560
} ARIASDK_NS_END
455
-
561
+ # pragma warning(pop)
456
562
#endif // HAVE_MAT_DEFAULT_HTTP_CLIENT
563
+ // clang-format on
0 commit comments