Start wxWebRequestWinHTTP implementation

Can already run the GET request in the sample app
This commit is contained in:
Tobias Taschner
2018-10-20 19:21:35 +02:00
parent e07c1bf40c
commit a4279752f8
9 changed files with 676 additions and 26 deletions

View File

@@ -18,16 +18,433 @@
#if wxUSE_WEBREQUEST
#include "wx/mstream.h"
#include "wx/uri.h"
#include "wx/msw/webrequest_winhttp.h"
#ifndef WX_PRECOMP
#include "wx/log.h"
#include "wx/utils.h"
#include "wx/translation.h"
#endif
// For MSVC we can link in the required library explicitly, for the other
// compilers (e.g. MinGW) this needs to be done at makefiles level.
#ifdef __VISUALC__
#pragma comment(lib, "Winhttp")
#endif
// Define constants potentially missing in old SDKs
#ifndef WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY
#define WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY 4
#endif
#ifndef WINHTTP_PROTOCOL_FLAG_HTTP2
#define WINHTTP_PROTOCOL_FLAG_HTTP2 0x1
#endif
#ifndef WINHTTP_OPTION_ENABLE_HTTP_PROTOCOL
#define WINHTTP_OPTION_ENABLE_HTTP_PROTOCOL 133
#endif
#ifndef WINHTTP_DECOMPRESSION_FLAG_ALL
#define WINHTTP_DECOMPRESSION_FLAG_GZIP 0x00000001
#define WINHTTP_DECOMPRESSION_FLAG_DEFLATE 0x00000002
#define WINHTTP_DECOMPRESSION_FLAG_ALL ( \
WINHTTP_DECOMPRESSION_FLAG_GZIP | \
WINHTTP_DECOMPRESSION_FLAG_DEFLATE)
#endif
#ifndef WINHTTP_OPTION_DECOMPRESSION
#define WINHTTP_OPTION_DECOMPRESSION 118
#endif
#ifndef WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1
#define WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1 0x00000200
#endif
#ifndef WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2
#define WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2 0x00000800
#endif
// Helper functions
static wxString wxWinHTTPErrorToString(DWORD errorCode)
{
wxString errorString;
LPVOID msgBuf;
if ( FormatMessageW(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS |
FORMAT_MESSAGE_FROM_HMODULE,
GetModuleHandle(TEXT("WINHTTP")),
errorCode,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPWSTR)&msgBuf,
0, NULL) )
{
errorString.assign((LPWSTR)msgBuf);
LocalFree(msgBuf);
// Truncate trailing \n\r
if ( errorString.size() > 2 )
errorString.resize(errorString.size());
}
return errorString;
}
static wxString wxWinHTTPQueryHeaderString(HINTERNET hRequest, DWORD dwInfoLevel,
LPCWSTR pwszName = WINHTTP_HEADER_NAME_BY_INDEX)
{
wxString result;
DWORD bufferLen = 0;
::WinHttpQueryHeaders(hRequest, dwInfoLevel, pwszName, NULL, &bufferLen,
WINHTTP_NO_HEADER_INDEX);
if ( ::GetLastError() == ERROR_INSUFFICIENT_BUFFER )
{
wxWCharBuffer resBuf(bufferLen);
if ( ::WinHttpQueryHeaders(hRequest, dwInfoLevel, pwszName,
resBuf.data(), &bufferLen, WINHTTP_NO_HEADER_INDEX) )
result.assign(resBuf);
}
return result;
}
static wxString wxWinHTTPQueryOptionString(HINTERNET hInternet, DWORD dwOption)
{
wxString result;
DWORD bufferLen = 0;
::WinHttpQueryOption(hInternet, dwOption, NULL, &bufferLen);
if ( ::GetLastError() == ERROR_INSUFFICIENT_BUFFER )
{
wxWCharBuffer resBuf(bufferLen);
if ( ::WinHttpQueryOption(hInternet, dwOption, resBuf.data(), &bufferLen) )
result.assign(resBuf);
}
return result;
}
static void CALLBACK wxRequestStatusCallback(
HINTERNET WXUNUSED(hInternet),
DWORD_PTR dwContext,
DWORD dwInternetStatus,
LPVOID lpvStatusInformation,
DWORD dwStatusInformationLength
)
{
if ( dwContext )
{
wxWebRequestWinHTTP* request =
reinterpret_cast<wxWebRequestWinHTTP*>(dwContext);
request->HandleCallback(dwInternetStatus, lpvStatusInformation,
dwStatusInformationLength);
}
}
//
// wxWebRequestWinHTTP
//
wxWebRequestWinHTTP::wxWebRequestWinHTTP(int id, wxWebSessionWinHTTP& session, const wxString& url):
wxWebRequest(id),
m_session(session),
m_url(url),
m_connect(NULL),
m_request(NULL)
{
m_headers = session.GetHeaders();
}
wxWebRequestWinHTTP::~wxWebRequestWinHTTP()
{
if ( m_request )
::WinHttpCloseHandle(m_request);
if ( m_connect )
::WinHttpCloseHandle(m_connect);
}
void wxWebRequestWinHTTP::HandleCallback(DWORD dwInternetStatus,
LPVOID lpvStatusInformation, DWORD dwStatusInformationLength)
{
bool handleLastError = false;
static const int readSize = 8 * 1024;
switch ( dwInternetStatus )
{
case WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE:
if ( ::WinHttpReceiveResponse(m_request, NULL) )
{
m_response.reset(new wxWebResponseWinHTTP(*this));
if ( CheckServerStatus() )
{
// Start reading the response
if ( !m_response->ReadData() )
handleLastError = true;
}
}
else
handleLastError = true;
break;
case WINHTTP_CALLBACK_STATUS_READ_COMPLETE:
if ( dwStatusInformationLength > 0 )
{
if ( !m_response->ReportAvailableData(dwStatusInformationLength) )
handleLastError = true;
}
else
{
m_response->ReportDataComplete();
SetState(State_Ready);
}
break;
case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR:
{
LPWINHTTP_ASYNC_RESULT asyncResult = reinterpret_cast<LPWINHTTP_ASYNC_RESULT>(lpvStatusInformation);
SetState(State_Failed, wxWinHTTPErrorToString(asyncResult->dwError));
break;
}
}
if (handleLastError)
SetFailedWithLastError();
}
void wxWebRequestWinHTTP::SetFailedWithLastError()
{
wxString failMessage = wxWinHTTPErrorToString(::GetLastError());
SetState(State_Failed, failMessage);
}
void wxWebRequestWinHTTP::SetMethod(const wxString& method)
{
// TODO: implement
}
void wxWebRequestWinHTTP::SetData(const wxString& text, const wxString& contentType)
{
// TODO: implement
}
void wxWebRequestWinHTTP::SetData(const wxInputStream& dataStream, const wxString& contentType)
{
// TODO: implement
}
void wxWebRequestWinHTTP::Start()
{
if ( GetState() != State_Idle ) // Completed requests can not be restarted
return;
// Parse the URL
wxURI uri(m_url);
bool isSecure = uri.GetScheme().IsSameAs("HTTPS", false);
int port;
if ( !uri.HasPort() )
port = (isSecure) ? 443 : 80;
else
port = wxAtoi(uri.GetPort());
// Open a connction
m_connect = ::WinHttpConnect(m_session.GetHandle(), uri.GetServer().wc_str(),
port, 0);
if ( m_connect == NULL )
{
SetFailedWithLastError();
return;
}
// Open a request
m_request = ::WinHttpOpenRequest(m_connect,
L"GET", uri.GetPath().wc_str(),
NULL, WINHTTP_NO_REFERER,
WINHTTP_DEFAULT_ACCEPT_TYPES,
(isSecure) ? WINHTTP_FLAG_SECURE : 0);
if ( m_request == NULL )
{
SetFailedWithLastError();
return;
}
// Register callback
if ( ::WinHttpSetStatusCallback(m_request,
(WINHTTP_STATUS_CALLBACK)wxRequestStatusCallback,
WINHTTP_CALLBACK_FLAG_READ_COMPLETE |
WINHTTP_CALLBACK_FLAG_SENDREQUEST_COMPLETE |
WINHTTP_CALLBACK_FLAG_REQUEST_ERROR,
0) == WINHTTP_INVALID_STATUS_CALLBACK )
{
SetFailedWithLastError();
return;
}
// Combine all headers to a string
wxString allHeaders;
for (wxWebRequestHeaderMap::const_iterator header = m_headers.begin(); header != m_headers.end(); ++header)
allHeaders.append(wxString::Format("%s: %s\n", header->first, header->second));
// Send request
if ( WinHttpSendRequest(m_request,
allHeaders.wc_str(), allHeaders.length(),
NULL, 0, 0,
(DWORD_PTR)this) )
{
SetState(State_Active);
}
else
SetFailedWithLastError();
}
void wxWebRequestWinHTTP::Cancel()
{
// TODO: implement
}
wxWebResponse* wxWebRequestWinHTTP::GetResponse()
{
return m_response.get();
}
//
// wxWebResponseWinHTTP
//
wxWebResponseWinHTTP::wxWebResponseWinHTTP(wxWebRequestWinHTTP& request):
m_request(request),
m_readSize(8 * 1024)
{
wxString contentLengthStr = wxWinHTTPQueryHeaderString(m_request.GetHandle(),
WINHTTP_QUERY_CONTENT_LENGTH);
if ( contentLengthStr.empty() ||
!contentLengthStr.ToLongLong(&m_contentLength) )
m_contentLength = -1;
}
wxString wxWebResponseWinHTTP::GetURL() const
{
return wxWinHTTPQueryOptionString(m_request.GetHandle(), WINHTTP_OPTION_URL);
}
wxString wxWebResponseWinHTTP::GetHeader(const wxString& name) const
{
return wxWinHTTPQueryHeaderString(m_request.GetHandle(),
WINHTTP_QUERY_CUSTOM, name.wc_str());
}
int wxWebResponseWinHTTP::GetStatus() const
{
DWORD status = 0;
DWORD statusSize = sizeof(status);
if ( !::WinHttpQueryHeaders(m_request.GetHandle(),
WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
WINHTTP_HEADER_NAME_BY_INDEX, &status, &statusSize, 0) )
{
status = 0;
wxLogLastError("WinHttpQueryHeaders/status");
}
return status;
}
wxString wxWebResponseWinHTTP::GetStatusText() const
{
return wxWinHTTPQueryHeaderString(m_request.GetHandle(), WINHTTP_QUERY_STATUS_TEXT);
}
wxInputStream* wxWebResponseWinHTTP::GetStream() const
{
return m_stream.get();
}
wxString wxWebResponseWinHTTP::AsString(wxMBConv* conv) const
{
// TODO: try to determine encoding type from content-type header
if ( !conv )
conv = &wxConvUTF8;
size_t outLen = 0;
return conv->cMB2WC((const char*) m_readBuffer.GetData(), m_readBuffer.GetDataLen(), &outLen);
}
bool wxWebResponseWinHTTP::ReadData()
{
if ( ::WinHttpReadData(m_request.GetHandle(),
m_readBuffer.GetAppendBuf(m_readSize), m_readSize, NULL) )
return true;
else
return false;
}
bool wxWebResponseWinHTTP::ReportAvailableData(DWORD dataLen)
{
m_readBuffer.UngetAppendBuf(dataLen);
return ReadData();
}
void wxWebResponseWinHTTP::ReportDataComplete()
{
m_stream.reset(new wxMemoryInputStream(m_readBuffer.GetData(), m_readBuffer.GetDataLen()));
}
//
// wxWebSessionWinHTTP
//
wxWebSessionWinHTTP::wxWebSessionWinHTTP():
m_initialized(false),
m_handle(NULL)
{
}
wxWebSessionWinHTTP::~wxWebSessionWinHTTP()
{
if ( m_handle != INVALID_HANDLE_VALUE )
::WinHttpCloseHandle(m_handle);
}
void wxWebSessionWinHTTP::Init()
{
DWORD accessType;
if ( wxCheckOsVersion(6, 3) )
accessType = WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY;
else
accessType = WINHTTP_ACCESS_TYPE_DEFAULT_PROXY;
m_handle = ::WinHttpOpen(GetHeaders().find("User-Agent")->second.wc_str(), accessType,
WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS,
WINHTTP_FLAG_ASYNC);
if ( m_handle != NULL )
{
// Try to enable HTTP/2 (available since Win 10 1607)
DWORD protFlags = WINHTTP_PROTOCOL_FLAG_HTTP2;
::WinHttpSetOption(m_handle, WINHTTP_OPTION_ENABLE_HTTP_PROTOCOL,
&protFlags, sizeof(protFlags));
// Try to enable GZIP and DEFLATE (available since Win 8.1)
DWORD decompressFlags = WINHTTP_DECOMPRESSION_FLAG_ALL;
::WinHttpSetOption(m_handle, WINHTTP_OPTION_DECOMPRESSION,
&decompressFlags, sizeof(decompressFlags));
// Try to enable modern TLS for older Windows versions
if ( !wxCheckOsVersion(6, 3) )
{
DWORD securityFlags = WINHTTP_FLAG_SECURE_PROTOCOL_SSL3 |
WINHTTP_FLAG_SECURE_PROTOCOL_TLS1 |
WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1 |
WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2;
::WinHttpSetOption(m_handle, WINHTTP_OPTION_SECURE_PROTOCOLS,
&securityFlags, sizeof(securityFlags));
}
}
else
wxLogLastError("WinHttpOpen");
m_initialized = true;
}
wxWebRequest* wxWebSessionWinHTTP::CreateRequest(const wxString& url, int id)
{
return NULL;
}
void wxWebSessionWinHTTP::SetHeader(const wxString& name, const wxString& value)
{
if ( !m_initialized )
Init();
return new wxWebRequestWinHTTP(id, *this, url);
}
#endif // wxUSE_WEBREQUEST