in Release/src/http/client/http_client_asio.cpp [731:926]
void start_request()
{
if (m_request._cancellation_token().is_canceled())
{
request_context::report_error(make_error_code(std::errc::operation_canceled).value(),
"Request canceled by user.");
return;
}
http_proxy_type proxy_type = http_proxy_type::none;
std::string proxy_host;
int proxy_port = -1;
// There is no support for auto-detection of proxies on non-windows platforms, it must be specified explicitly
// from the client code.
if (m_http_client->client_config().proxy().is_specified())
{
proxy_type =
m_http_client->base_uri().scheme() == U("https") ? http_proxy_type::ssl_tunnel : http_proxy_type::http;
auto proxy = m_http_client->client_config().proxy();
auto proxy_uri = proxy.address();
proxy_port = proxy_uri.port() == -1 ? 8080 : proxy_uri.port();
proxy_host = utility::conversions::to_utf8string(proxy_uri.host());
}
auto start_http_request_flow = [proxy_type, proxy_host, proxy_port AND_CAPTURE_MEMBER_FUNCTION_POINTERS](
std::shared_ptr<asio_context> ctx) {
if (ctx->m_request._cancellation_token().is_canceled())
{
ctx->request_context::report_error(make_error_code(std::errc::operation_canceled).value(),
"Request canceled by user.");
return;
}
const auto& base_uri = ctx->m_http_client->base_uri();
const auto full_uri = uri_builder(base_uri).append(ctx->m_request.relative_uri()).to_uri();
// For a normal http proxy, we need to specify the full request uri, otherwise just specify the resource
auto encoded_resource =
proxy_type == http_proxy_type::http ? full_uri.to_string() : full_uri.resource().to_string();
if (encoded_resource.empty())
{
encoded_resource = U("/");
}
const auto& method = ctx->m_request.method();
// stop injection of headers via method
// resource should be ok, since it's been encoded
// and host won't resolve
if (!::web::http::details::validate_method(method))
{
ctx->report_exception(http_exception("The method string is invalid."));
return;
}
std::ostream request_stream(&ctx->m_body_buf);
request_stream.imbue(std::locale::classic());
const auto& host = utility::conversions::to_utf8string(base_uri.host());
request_stream << utility::conversions::to_utf8string(method) << " "
<< utility::conversions::to_utf8string(encoded_resource) << " "
<< "HTTP/1.1\r\n";
int port = base_uri.port();
if (base_uri.is_port_default())
{
port = (ctx->m_connection->is_ssl() ? 443 : 80);
}
// Add the Host header if user has not specified it explicitly
if (!ctx->m_request.headers().has(header_names::host))
{
request_stream << "Host: " << host;
if (!base_uri.is_port_default())
{
request_stream << ":" << port;
}
request_stream << CRLF;
}
// Extra request headers are constructed here.
std::string extra_headers;
// Add header for basic proxy authentication
if (proxy_type == http_proxy_type::http &&
ctx->m_http_client->client_config().proxy().credentials().is_set())
{
extra_headers.append(ctx->generate_basic_proxy_auth_header());
}
if (ctx->m_http_client->client_config().credentials().is_set())
{
extra_headers.append(ctx->generate_basic_auth_header());
}
extra_headers += utility::conversions::to_utf8string(ctx->get_compression_header());
// Check user specified transfer-encoding.
std::string transferencoding;
if (ctx->m_request.headers().match(header_names::transfer_encoding, transferencoding) &&
boost::icontains(transferencoding, U("chunked")))
{
ctx->m_needChunked = true;
}
else if (!ctx->m_request.headers().match(header_names::content_length, ctx->m_content_length))
{
// Stream without content length is the signal of requiring transfer encoding chunked.
if (ctx->m_request.body())
{
ctx->m_needChunked = true;
extra_headers.append("Transfer-Encoding:chunked\r\n");
}
else if (ctx->m_request.method() == methods::POST || ctx->m_request.method() == methods::PUT)
{
// Some servers do not accept POST/PUT requests with a content length of 0, such as
// lighttpd - http://serverfault.com/questions/315849/curl-post-411-length-required
// old apache versions - https://issues.apache.org/jira/browse/TS-2902
extra_headers.append("Content-Length: 0\r\n");
}
}
if (proxy_type == http_proxy_type::http)
{
extra_headers.append("Cache-Control: no-store, no-cache\r\n"
"Pragma: no-cache\r\n");
}
request_stream << utility::conversions::to_utf8string(
::web::http::details::flatten_http_headers(ctx->m_request.headers()));
request_stream << extra_headers;
// Enforce HTTP connection keep alive (even for the old HTTP/1.0 protocol).
request_stream << "Connection: Keep-Alive\r\n\r\n";
// Start connection timeout timer.
if (!ctx->m_timer.has_started())
{
ctx->m_timer.start();
}
if (ctx->m_connection->is_reused() || proxy_type == http_proxy_type::ssl_tunnel)
{
// If socket is a reused connection or we're connected via an ssl-tunneling proxy, try to write the
// request directly. In both cases we have already established a tcp connection.
ctx->write_request();
}
else
{
// If the connection is new (unresolved and unconnected socket), then start async
// call to resolve first, leading eventually to request write.
// For normal http proxies, we want to connect directly to the proxy server. It will relay our request.
auto tcp_host = proxy_type == http_proxy_type::http ? proxy_host : host;
auto tcp_port = proxy_type == http_proxy_type::http ? proxy_port : port;
tcp::resolver::query query(tcp_host, to_string(tcp_port));
ctx->m_resolver.async_resolve(query,
boost::bind(&asio_context::handle_resolve,
ctx,
boost::asio::placeholders::error,
boost::asio::placeholders::iterator));
}
// Register for notification on cancellation to abort this request.
if (ctx->m_request._cancellation_token() != pplx::cancellation_token::none())
{
// weak_ptr prevents lambda from taking shared ownership of the context.
// Otherwise context replacement in the handle_status_line() would leak the objects.
std::weak_ptr<asio_context> ctx_weak(ctx);
ctx->m_cancellationRegistration = ctx->m_request._cancellation_token().register_callback([ctx_weak]() {
if (auto ctx_lock = ctx_weak.lock())
{
// Shut down transmissions, close the socket and prevent connection from being pooled.
ctx_lock->m_connection->close();
}
});
}
};
// Note that we must not try to CONNECT using an already established connection via proxy -- this would send
// CONNECT to the end server which is definitely not what we want.
if (proxy_type == http_proxy_type::ssl_tunnel && !m_connection->is_reused())
{
// The ssl_tunnel_proxy keeps the context alive and then calls back once the ssl tunnel is established via
// 'start_http_request_flow'
std::shared_ptr<ssl_proxy_tunnel> ssl_tunnel =
std::make_shared<ssl_proxy_tunnel>(shared_from_this(), start_http_request_flow);
ssl_tunnel->start_proxy_connect();
}
else
{
start_http_request_flow(shared_from_this());
}
}