spectrum2/3rdparty/cpprestsdk/include/cpprest/details/http_client_impl.h
2015-11-19 15:19:14 +01:00

466 lines
14 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.
*
* ==--==
* =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
*
* HTTP Library: Client-side APIs, non-public declarations used in the implementation.
*
* For the latest on this and related APIs, please see http://casablanca.codeplex.com.
*
* =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
****/
#pragma once
#include "cpprest/details/basic_types.h"
#include "cpprest/details/http_helpers.h"
#ifdef _WIN32
# define CRLF _XPLATSTR("\r\n")
#else
# define CRLF std::string("\r\n")
#endif
namespace web { namespace http { namespace client { namespace details
{
#ifdef _WIN32
static const utility::char_t * get_with_body = _XPLATSTR("A GET or HEAD request should not have an entity body.");
// 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);
for(index = str.size(); index > 0 && str[index - 1] == 0; --index);
str.erase(index);
}
#endif
// Flatten the http_headers into a name:value pairs separated by a carriage return and line feed.
static utility::string_t flatten_http_headers(const http_headers &headers)
{
utility::string_t flattened_headers;
for(auto iter = headers.begin(); iter != headers.end(); ++iter)
{
flattened_headers.append(iter->first);
flattened_headers.push_back(':');
flattened_headers.append(iter->second);
flattened_headers.append(CRLF);
}
return flattened_headers;
}
#ifdef _WIN32
/// <summary>
/// Parses a string containing Http headers.
/// </summary>
static void parse_headers_string(_Inout_z_ utf16char *headersStr, http_headers &headers)
{
utf16char *context = nullptr;
utf16char *line = wcstok_s(headersStr, CRLF, &context);
while(line != nullptr)
{
const utility::string_t header_line(line);
const size_t colonIndex = header_line.find_first_of(_XPLATSTR(":"));
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);
http::details::trim_whitespace(key);
http::details::trim_whitespace(value);
headers.add(key, value);
}
line = wcstok_s(nullptr, CRLF, &context);
}
}
#endif
class _http_client_communicator;
// Request context encapsulating everything necessary for creating and responding to a request.
class request_context
{
public:
// Destructor to clean up any held resources.
virtual ~request_context()
{
}
void complete_headers()
{
// We have already read (and transmitted) the request body. Should we explicitly close the stream?
// Well, there are test cases that assumes that the istream is valid when t receives the response!
// For now, we will drop our reference which will close the stream if the user doesn't have one.
m_request.set_body(Concurrency::streams::istream());
m_request_completion.set(m_response);
}
/// <summary>
/// Completes this request, setting the underlying task completion event, and cleaning up the handles
/// </summary>
void complete_request(utility::size64_t body_size)
{
m_response._get_impl()->_complete(body_size);
finish();
}
void report_error(unsigned long error_code, const std::string &errorMessage)
{
report_exception(http_exception(static_cast<int>(error_code), errorMessage));
}
#ifdef _WIN32
void report_error(unsigned long error_code, const std::wstring &errorMessage)
{
report_exception(http_exception(static_cast<int>(error_code), errorMessage));
}
#endif
template<typename _ExceptionType>
void report_exception(const _ExceptionType &e)
{
report_exception(std::make_exception_ptr(e));
}
virtual void report_exception(std::exception_ptr exceptionPtr)
{
auto response_impl = m_response._get_impl();
// If cancellation has been triggered then ignore any errors.
if(m_request._cancellation_token().is_canceled())
{
exceptionPtr = std::make_exception_ptr(http_exception((int)std::errc::operation_canceled, std::generic_category()));
}
// First try to complete the headers with an exception.
if(m_request_completion.set_exception(exceptionPtr))
{
// Complete the request with no msg body. The exception
// should only be propagated to one of the tce.
response_impl->_complete(0);
}
else
{
// Complete the request with an exception
response_impl->_complete(0, exceptionPtr);
}
finish();
}
virtual concurrency::streams::streambuf<uint8_t> _get_readbuffer()
{
auto instream = m_request.body();
_ASSERTE((bool)instream);
return instream.streambuf();
}
concurrency::streams::streambuf<uint8_t> _get_writebuffer()
{
auto outstream = m_response._get_impl()->outstream();
_ASSERTE((bool)outstream);
return outstream.streambuf();
}
// Reference to the http_client implementation.
std::shared_ptr<_http_client_communicator> m_http_client;
// request/response pair.
http_request m_request;
http_response m_response;
utility::size64_t m_uploaded;
utility::size64_t m_downloaded;
// task completion event to signal request is completed.
pplx::task_completion_event<http_response> m_request_completion;
// Registration for cancellation notification if enabled.
pplx::cancellation_token_registration m_cancellationRegistration;
protected:
request_context(const std::shared_ptr<_http_client_communicator> &client, const http_request &request)
: m_http_client(client),
m_request(request),
m_uploaded(0),
m_downloaded(0)
{
auto responseImpl = m_response._get_impl();
// Copy the user specified output stream over to the response
responseImpl->set_outstream(request._get_impl()->_response_stream(), false);
// Prepare for receiving data from the network. Ideally, this should be done after
// we receive the headers and determine that there is a response body. We will do it here
// since it is not immediately apparent where that would be in the callback handler
responseImpl->_prepare_to_receive_data();
}
virtual void finish();
};
//
// Interface used by client implementations. Concrete implementations are responsible for
// sending HTTP requests and receiving the responses.
//
class _http_client_communicator
{
public:
// Destructor to clean up any held resources.
virtual ~_http_client_communicator() {}
// Asynchronously send a HTTP request and process the response.
void async_send_request(const std::shared_ptr<request_context> &request)
{
if(m_client_config.guarantee_order())
{
// Send to call block to be processed.
push_request(request);
}
else
{
// Schedule a task to start sending.
pplx::create_task([this, request]
{
open_and_send_request(request);
});
}
}
void finish_request()
{
// If guarantee order is specified we don't need to do anything.
if(m_client_config.guarantee_order())
{
pplx::extensibility::scoped_critical_section_t l(m_open_lock);
--m_scheduled;
if( !m_requests_queue.empty())
{
auto request = m_requests_queue.front();
m_requests_queue.pop();
// Schedule a task to start sending.
pplx::create_task([this, request]
{
open_and_send_request(request);
});
}
}
}
const http_client_config& client_config() const
{
return m_client_config;
}
const uri & base_uri() const
{
return m_uri;
}
protected:
_http_client_communicator(http::uri address, http_client_config client_config)
: m_uri(std::move(address)), m_client_config(std::move(client_config)), m_opened(false), m_scheduled(0)
{
}
// Method to open client.
virtual unsigned long open() = 0;
// HTTP client implementations must implement send_request.
virtual void send_request(_In_ const std::shared_ptr<request_context> &request) = 0;
// URI to connect to.
const http::uri m_uri;
private:
http_client_config m_client_config;
bool m_opened;
pplx::extensibility::critical_section_t m_open_lock;
// Wraps opening the client around sending a request.
void open_and_send_request(const std::shared_ptr<request_context> &request)
{
// First see if client needs to be opened.
auto error = open_if_required();
if (error != 0)
{
// Failed to open
request->report_error(error, _XPLATSTR("Open failed"));
// DO NOT TOUCH the this pointer after completing the request
// This object could be freed along with the request as it could
// be the last reference to this object
return;
}
send_request(request);
}
unsigned long open_if_required()
{
unsigned long error = 0;
if(!m_opened)
{
pplx::extensibility::scoped_critical_section_t l(m_open_lock);
// Check again with the lock held
if (!m_opened)
{
error = open();
if (error == 0)
{
m_opened = true;
}
}
}
return error;
}
void push_request(const std::shared_ptr<request_context> &request)
{
pplx::extensibility::scoped_critical_section_t l(m_open_lock);
if(++m_scheduled == 1)
{
// Schedule a task to start sending.
pplx::create_task([this, request]()
{
open_and_send_request(request);
});
}
else
{
m_requests_queue.push(request);
}
}
// Queue used to guarantee ordering of requests, when applicable.
std::queue<std::shared_ptr<request_context>> m_requests_queue;
int m_scheduled;
};
inline void request_context::finish()
{
// If cancellation is enabled and registration was performed, unregister.
if(m_cancellationRegistration != pplx::cancellation_token_registration())
{
_ASSERTE(m_request._cancellation_token() != pplx::cancellation_token::none());
m_request._cancellation_token().deregister_callback(m_cancellationRegistration);
}
m_http_client->finish_request();
}
class http_network_handler : public http_pipeline_stage
{
public:
http_network_handler(const uri &base_uri, const http_client_config &client_config);
virtual pplx::task<http_response> propagate(http_request request);
const std::shared_ptr<details::_http_client_communicator>& http_client_impl() const
{
return m_http_client_impl;
}
private:
std::shared_ptr<_http_client_communicator> m_http_client_impl;
};
// Helper function to check to make sure the uri is valid.
void verify_uri(const uri &uri)
{
// Some things like proper URI schema are verified by the URI class.
// We only need to check certain things specific to HTTP.
if (uri.scheme() != _XPLATSTR("http") && uri.scheme() != _XPLATSTR("https"))
{
throw std::invalid_argument("URI scheme must be 'http' or 'https'");
}
if(uri.host().empty())
{
throw std::invalid_argument("URI must contain a hostname.");
}
}
} // namespace details
http_client::http_client(const uri &base_uri)
{
build_pipeline(base_uri, http_client_config());
}
http_client::http_client(const uri &base_uri, const http_client_config &client_config)
{
build_pipeline(base_uri, client_config);
}
void http_client::build_pipeline(const uri &base_uri, const http_client_config &client_config)
{
if (base_uri.scheme().empty())
{
auto uribuilder = uri_builder(base_uri);
uribuilder.set_scheme(_XPLATSTR("http"));
uri uriWithScheme = uribuilder.to_uri();
details::verify_uri(uriWithScheme);
m_pipeline = ::web::http::http_pipeline::create_pipeline(std::make_shared<details::http_network_handler>(uriWithScheme, client_config));
}
else
{
details::verify_uri(base_uri);
m_pipeline = ::web::http::http_pipeline::create_pipeline(std::make_shared<details::http_network_handler>(base_uri, client_config));
}
#if !defined(CPPREST_TARGET_XP) && (!defined(WINAPI_FAMILY) || WINAPI_FAMILY != WINAPI_FAMILY_PHONE_APP || _MSC_VER > 1700)
add_handler(std::static_pointer_cast<http::http_pipeline_stage>(
std::make_shared<oauth1::details::oauth1_handler>(client_config.oauth1())));
#endif
add_handler(std::static_pointer_cast<http::http_pipeline_stage>(
std::make_shared<oauth2::details::oauth2_handler>(client_config.oauth2())));
}
const http_client_config & http_client::client_config() const
{
auto ph = std::static_pointer_cast<details::http_network_handler>(m_pipeline->last_stage());
return ph->http_client_impl()->client_config();
}
const uri & http_client::base_uri() const
{
auto ph = std::static_pointer_cast<details::http_network_handler>(m_pipeline->last_stage());
return ph->http_client_impl()->base_uri();
}
}}} // namespaces