spectrum2/3rdparty/cpprestsdk/tests/functional/http/utilities/test_http_client.cpp
2015-11-19 15:19:14 +01:00

588 lines
19 KiB
C++

/***
* ==++==
*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* ==--==
* =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
*
* Defines a test client to handle requests and sending responses.
*
* =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
****/
#include "stdafx.h"
#include "cpprest/uri.h"
#include "cpprest/details/http_helpers.h"
#include "test_http_client.h"
#ifdef _WIN32
#include <winhttp.h>
#pragma warning ( push )
#pragma warning ( disable : 4457 )
#include <agents.h>
#pragma warning ( pop )
#endif
using namespace web; using namespace utility;
namespace tests { namespace functional { namespace http { namespace utilities {
// Flatten the http_headers into a name:value pairs separated by a carriage return and line feed.
utility::string_t flatten_http_headers(const std::map<utility::string_t, utility::string_t> &headers)
{
utility::string_t flattened_headers;
for(auto iter = headers.begin(); iter != headers.end(); ++iter)
{
utility::string_t temp((*iter).first + U(":") + (*iter).second + U("\r\n"));
flattened_headers.append(utility::string_t(temp.begin(), temp.end()));
}
return flattened_headers;
}
#ifdef _WIN32
// Helper function to query for the size of header values.
static void query_header_length(HINTERNET request_handle, DWORD header, DWORD &length)
{
WinHttpQueryHeaders(
request_handle,
header,
WINHTTP_HEADER_NAME_BY_INDEX,
WINHTTP_NO_OUTPUT_BUFFER,
&length,
WINHTTP_NO_HEADER_INDEX);
}
// Helper function to get the status code from a WinHTTP response.
static void parse_status_code(HINTERNET request_handle, unsigned short &code)
{
DWORD length = 0;
query_header_length(request_handle, WINHTTP_QUERY_STATUS_CODE, length);
utility::string_t buffer;
buffer.resize(length);
WinHttpQueryHeaders(
request_handle,
WINHTTP_QUERY_STATUS_CODE,
WINHTTP_HEADER_NAME_BY_INDEX,
&buffer[0],
&length,
WINHTTP_NO_HEADER_INDEX);
code = (unsigned short)_wtoi(buffer.c_str());
}
// Helper function to trim leading and trailing null characters from a string.
static void trim_nulls(utility::string_t &str)
{
size_t index;
for(index = 0; index < str.size() && str[index] == 0; ++index);
str.erase(0, index);
index;
for(index = str.size(); index > 0 && str[index - 1] == 0; --index);
str.erase(index);
}
// Helper function to get the reason phrase from a WinHTTP response.
static void parse_reason_phrase(HINTERNET request_handle, utility::string_t &phrase)
{
DWORD length = 0;
query_header_length(request_handle, WINHTTP_QUERY_STATUS_TEXT, length);
phrase.resize(length);
WinHttpQueryHeaders(
request_handle,
WINHTTP_QUERY_STATUS_TEXT,
WINHTTP_HEADER_NAME_BY_INDEX,
&phrase[0],
&length,
WINHTTP_NO_HEADER_INDEX);
// WinHTTP reports back the wrong length, trim any null characters.
trim_nulls(phrase);
}
/// <summary>
/// Parses a string containing Http headers.
/// </summary>
static void parse_winhttp_headers(HINTERNET request_handle, utf16char *headersStr, test_response *p_response)
{
// Status code and reason phrase.
parse_status_code(request_handle, p_response->m_status_code);
parse_reason_phrase(request_handle, p_response->m_reason_phrase);
utf16char *context = nullptr;
utf16char *line = wcstok_s(headersStr, U("\r\n"), &context);
while(line != nullptr)
{
const utility::string_t header_line(line);
const size_t colonIndex = header_line.find_first_of(U(":"));
if(colonIndex != utility::string_t::npos)
{
utility::string_t key = header_line.substr(0, colonIndex);
utility::string_t value = header_line.substr(colonIndex + 1, header_line.length() - colonIndex - 1);
web::http::details::trim_whitespace(key);
web::http::details::trim_whitespace(value);
p_response->m_headers[key] = value;
}
line = wcstok_s(nullptr, U("\r\n"), &context);
}
}
class _test_http_client
{
public:
_test_http_client(const utility::string_t &uri)
: m_uri(uri), m_hSession(nullptr), m_hConnection(nullptr)
{}
unsigned long open()
{
// Open session.
m_hSession = WinHttpOpen(
U("test_http_client"),
WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
WINHTTP_NO_PROXY_NAME,
WINHTTP_NO_PROXY_BYPASS,
WINHTTP_FLAG_ASYNC);
if(!m_hSession)
{
return GetLastError();
}
// Set timeouts.
int multiplier=10;
if(!WinHttpSetTimeouts(m_hSession, 60000*multiplier, 60000*multiplier, 30000*multiplier, 30000*multiplier))
{
return GetLastError();
}
// Set max connection to use per server to 1.
DWORD maxConnections = 1;
if(!WinHttpSetOption(m_hSession, WINHTTP_OPTION_MAX_CONNS_PER_SERVER, &maxConnections, sizeof(maxConnections)))
{
return GetLastError();
}
// Register asynchronous callback.
if(WINHTTP_INVALID_STATUS_CALLBACK == WinHttpSetStatusCallback(
m_hSession,
&_test_http_client::completion_callback,
WINHTTP_CALLBACK_FLAG_ALL_COMPLETIONS,
0))
{
return GetLastError();
}
// Open connection.
::http::uri u(m_uri);
unsigned int port = u.is_port_default() ? INTERNET_DEFAULT_PORT : u.port();
m_hConnection = WinHttpConnect(
m_hSession,
u.host().c_str(),
(INTERNET_PORT)port,
0);
if(m_hConnection == nullptr)
{
return GetLastError();
}
return 0;
}
unsigned long close()
{
// Release memory for each request.
std::for_each(m_responses_memory.begin(), m_responses_memory.end(), [](test_response *p_response)
{
delete p_response;
});
if(m_hConnection != nullptr)
{
if(WinHttpCloseHandle(m_hConnection) == NULL)
{
return GetLastError();
}
}
if(m_hSession != nullptr)
{
// Unregister the callback.
if(!WinHttpSetStatusCallback(
m_hSession,
NULL,
WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS,
NULL))
{
return GetLastError();
}
if(WinHttpCloseHandle(m_hSession) == NULL)
{
return GetLastError();
}
}
return 0;
}
unsigned long request(
const utility::string_t &method,
const utility::string_t &path,
const std::map<utility::string_t, utility::string_t> headers,
void * data,
size_t data_length)
{
HINTERNET request_handle = WinHttpOpenRequest(
m_hConnection,
method.c_str(),
path.c_str(),
NULL,
WINHTTP_NO_REFERER,
WINHTTP_DEFAULT_ACCEPT_TYPES,
0);
if(request_handle == nullptr)
{
return GetLastError();
}
// Add headers.
if(!headers.empty())
{
utility::string_t flattened_headers = flatten_http_headers(headers);
if(!WinHttpAddRequestHeaders(
request_handle,
flattened_headers.c_str(),
(DWORD)flattened_headers.length(),
WINHTTP_ADDREQ_FLAG_ADD))
{
return GetLastError();
}
}
if(!WinHttpSendRequest(
request_handle,
WINHTTP_NO_ADDITIONAL_HEADERS,
0,
data,
(DWORD)data_length,
(DWORD)data_length,
(DWORD_PTR)new test_response(this)))
{
return GetLastError();
}
return 0;
}
test_response * wait_for_response()
{
return wait_for_responses(1)[0];
}
pplx::task<test_response *> next_response()
{
return pplx::create_task([this]() -> test_response *
{
return wait_for_response();
});
}
std::vector<test_response *> wait_for_responses(const size_t count)
{
std::vector<test_response *> m_test_responses;
for(size_t i = 0; i < count; ++i)
{
m_test_responses.push_back(Concurrency::receive(m_responses));
}
return m_test_responses;
}
std::vector<pplx::task<test_response *>> next_responses(const size_t count)
{
std::vector<pplx::task_completion_event<test_response *>> events;
std::vector<pplx::task<test_response *>> responses;
for(size_t i = 0; i < count; ++i)
{
events.push_back(pplx::task_completion_event<test_response *>());
responses.push_back(pplx::create_task(events[i]));
}
pplx::create_task([this, count, events]()
{
for(size_t i = 0; i < count; ++i)
{
events[i].set(wait_for_response());
}
});
return responses;
}
private:
// WinHTTP callback.
static void CALLBACK completion_callback(
HINTERNET hRequestHandle,
DWORD_PTR context,
DWORD statusCode,
void* statusInfo,
DWORD)
{
test_response * p_response = reinterpret_cast<test_response *>(context);
if(p_response != nullptr)
{
if(statusCode == WINHTTP_CALLBACK_STATUS_REQUEST_ERROR)
{
WINHTTP_ASYNC_RESULT *pStatusInfo = static_cast<WINHTTP_ASYNC_RESULT *>(statusInfo);
pStatusInfo;
throw std::exception("Error in WinHTTP callback");
}
else if(statusCode == WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE)
{
if(!WinHttpReceiveResponse(hRequestHandle, NULL))
{
throw std::exception("Error receiving response");
}
}
else if(statusCode == WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE)
{
DWORD headers_length;
WinHttpQueryHeaders(
hRequestHandle,
WINHTTP_QUERY_RAW_HEADERS_CRLF,
WINHTTP_HEADER_NAME_BY_INDEX,
WINHTTP_NO_OUTPUT_BUFFER,
&headers_length,
WINHTTP_NO_HEADER_INDEX);
// Now allocate buffer for headers and query for them.
std::vector<unsigned char> header_raw_buffer;
header_raw_buffer.resize(headers_length);
utf16char * header_buffer = reinterpret_cast<utf16char *>(&header_raw_buffer[0]);
if(!WinHttpQueryHeaders(
hRequestHandle,
WINHTTP_QUERY_RAW_HEADERS_CRLF,
WINHTTP_HEADER_NAME_BY_INDEX,
header_buffer,
&headers_length,
WINHTTP_NO_HEADER_INDEX))
{
throw std::exception("Error querying for headers");
}
parse_winhttp_headers(hRequestHandle, header_buffer, p_response);
// Check to see if the response has a body or not.
if(!WinHttpQueryDataAvailable(hRequestHandle, nullptr))
{
throw std::exception("Error reading response body");
}
}
else if(statusCode == WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE)
{
DWORD num_bytes = *(PDWORD)statusInfo;
if ( num_bytes > 0 )
{
size_t current_size = p_response->m_data.size();
p_response->m_data.resize(current_size+(size_t)num_bytes);
// Actual WinHTTP call to read in body.
if(!WinHttpReadData(
hRequestHandle,
&p_response->m_data[current_size],
(DWORD)num_bytes,
NULL))
{
throw std::exception("Error reading response body");
}
}
else
{
WinHttpCloseHandle(hRequestHandle);
p_response->m_client->m_responses_memory.push_back(p_response);
Concurrency::asend(p_response->m_client->m_responses, p_response);
}
}
else if(statusCode == WINHTTP_CALLBACK_STATUS_READ_COMPLETE)
{
if(!WinHttpQueryDataAvailable(hRequestHandle, nullptr))
{
throw std::exception("Error reading response body");
}
}
}
}
Concurrency::unbounded_buffer<test_response *> m_responses;
// Used to store all requests to simplify memory management.
std::vector<test_response *> m_responses_memory;
const utility::string_t m_uri;
HINTERNET m_hSession;
HINTERNET m_hConnection;
};
#else
class _test_http_client
{
private:
const web::http::uri m_uri;
typename web::http::client::http_client m_client;
std::vector<pplx::task<web::http::http_response>> m_responses;
std::vector<test_response*> m_test_responses;
public:
_test_http_client(utility::string_t uri) : m_uri(web::http::uri::encode_uri(uri)), m_client(m_uri.authority()) {}
unsigned long open() { return 0; }
unsigned long close() { return 0; }
unsigned long request(
const utility::string_t &method,
const utility::string_t &path,
const std::map<utility::string_t, utility::string_t>& headers,
void * data,
size_t data_length)
{
auto localHeaders = headers;
localHeaders["User-Agent"] = "test_http_client";
web::http::http_request request;
request.set_method(method);
request.set_request_uri(web::http::uri_builder(m_uri).append_path(path).to_uri());
auto& hDest = request.headers();
for (auto it = localHeaders.begin(); it != localHeaders.end(); ++it)
{
auto& currentValue = hDest[it->first];
if (currentValue.empty())
currentValue = it->second;
else
currentValue = currentValue + U(", ") + it->second;
}
request.set_body(utility::string_t(reinterpret_cast<const char*>(data), data_length));
m_responses.push_back(m_client.request(request));
return 0;
}
test_response * wait_for_response()
{
return wait_for_responses(1)[0];
}
pplx::task<test_response *> next_response()
{
return pplx::create_task([this]() -> test_response *
{
return wait_for_response();
});
}
std::vector<test_response *> wait_for_responses(const size_t count)
{
if (count > m_responses.size())
throw std::logic_error("count too big");
std::vector<test_response *> m_test_responses;
for(size_t i = 0; i < count; ++i)
{
auto response = m_responses[0].get();
auto tr = new test_response(this);
tr->m_status_code = response.status_code();
tr->m_reason_phrase = response.reason_phrase();
for (auto it = response.headers().begin(); it != response.headers().end(); ++it)
{
tr->m_headers[it->first] = it->second;
}
tr->m_data = response.extract_vector().get();
m_test_responses.push_back(tr);
m_responses.erase(m_responses.begin());
}
return m_test_responses;
}
std::vector<pplx::task<test_response *>> next_responses(const size_t count)
{
std::vector<pplx::task<test_response *>> result;
for (size_t i = 0; i < count; ++i)
{
result.push_back(next_response());
}
return result;
}
};
#endif
test_http_client::test_http_client(const web::http::uri &uri)
{
m_impl = std::unique_ptr<_test_http_client>(new _test_http_client(uri.to_string()));
}
test_http_client::~test_http_client()
{
}
test_http_client::test_http_client(test_http_client &&other) : m_impl(std::move(other.m_impl)) {}
test_http_client & test_http_client::operator=(test_http_client &&other)
{
if(this != &other)
{
this->m_impl = std::move(other.m_impl);
}
return *this;
}
unsigned long test_http_client::open() { return m_impl->open(); }
unsigned long test_http_client::close() { return m_impl->close(); }
unsigned long test_http_client::request(const utility::string_t &method, const utility::string_t &path)
{
return request(method, path, std::map<utility::string_t, utility::string_t>());
}
unsigned long test_http_client::request(
const utility::string_t &method,
const utility::string_t &path,
const std::map<utility::string_t, utility::string_t> &headers)
{
return request(method, path, headers, std::string());
}
unsigned long test_http_client::request(
const utility::string_t &method,
const utility::string_t &path,
const std::string &data)
{
return request(method, path, std::map<utility::string_t, utility::string_t>(), data);
}
unsigned long test_http_client::request(
const utility::string_t &method,
const utility::string_t &path,
const utility::string_t &content_type,
const std::string &data)
{
std::map<utility::string_t, utility::string_t> headers;
headers[U("Content-Type")] = content_type;
return request(method, path, headers, data);
}
unsigned long test_http_client::request(
const utility::string_t &method,
const utility::string_t &path,
const std::map<utility::string_t, utility::string_t> &headers,
const std::string &data)
{
return m_impl->request(method, path, headers, (void *)&data[0], data.size());
}
test_response * test_http_client::wait_for_response() { return m_impl->wait_for_response(); }
pplx::task<test_response *> test_http_client::next_response() { return m_impl->next_response(); }
std::vector<test_response *> test_http_client::wait_for_responses(const size_t count) { return m_impl->wait_for_responses(count); }
std::vector<pplx::task<test_response *>> test_http_client::next_responses(const size_t count) { return m_impl->next_responses(count); }
}}}}