1 /***
2 * Copyright (C) Microsoft. All rights reserved.
3 * Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
4 *
5 * =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
6 *
7 * HTTP Library: Client-side APIs.
8 *
9 * This file contains shared code across all http_client implementations.
10 *
11 * For the latest on this and related APIs, please see: https://github.com/Microsoft/cpprestsdk
12 *
13 * =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
14 ****/
15
16 #include "stdafx.h"
17
18 #include "http_client_impl.h"
19
20 namespace web
21 {
22 namespace http
23 {
24 namespace client
25 {
26 // Helper function to check to make sure the uri is valid.
verify_uri(const uri & uri)27 static void verify_uri(const uri& uri)
28 {
29 // Some things like proper URI schema are verified by the URI class.
30 // We only need to check certain things specific to HTTP.
31 if (uri.scheme() != _XPLATSTR("http") && uri.scheme() != _XPLATSTR("https"))
32 {
33 throw std::invalid_argument("URI scheme must be 'http' or 'https'");
34 }
35
36 if (uri.host().empty())
37 {
38 throw std::invalid_argument("URI must contain a hostname.");
39 }
40 }
41
42 namespace details
43 {
44 #if defined(_WIN32) || defined(CPPREST_FORCE_HTTP_CLIENT_WINHTTPPAL)
45 const utility::char_t* get_with_body_err_msg =
46 _XPLATSTR("A GET or HEAD request should not have an entity body.");
47 #endif
48
complete_headers()49 void request_context::complete_headers()
50 {
51 // We have already read (and transmitted) the request body. Should we explicitly close the stream?
52 // Well, there are test cases that assumes that the istream is valid when t receives the response!
53 // For now, we will drop our reference which will close the stream if the user doesn't have one.
54 m_request.set_body(Concurrency::streams::istream());
55 m_request_completion.set(m_response);
56 }
57
complete_request(utility::size64_t body_size)58 void request_context::complete_request(utility::size64_t body_size)
59 {
60 m_response._get_impl()->_complete(body_size);
61
62 finish();
63 }
64
report_error(unsigned long error_code,const std::string & errorMessage)65 void request_context::report_error(unsigned long error_code, const std::string& errorMessage)
66 {
67 report_exception(http_exception(static_cast<int>(error_code), errorMessage));
68 }
69
70 #if defined(_WIN32)
report_error(unsigned long error_code,const std::wstring & errorMessage)71 void request_context::report_error(unsigned long error_code, const std::wstring& errorMessage)
72 {
73 report_exception(http_exception(static_cast<int>(error_code), errorMessage));
74 }
75 #endif
76
report_exception(std::exception_ptr exceptionPtr)77 void request_context::report_exception(std::exception_ptr exceptionPtr)
78 {
79 auto response_impl = m_response._get_impl();
80
81 // If cancellation has been triggered then ignore any errors.
82 if (m_request._cancellation_token().is_canceled())
83 {
84 exceptionPtr =
85 std::make_exception_ptr(http_exception((int)std::errc::operation_canceled, std::generic_category()));
86 }
87
88 // First try to complete the headers with an exception.
89 if (m_request_completion.set_exception(exceptionPtr))
90 {
91 // Complete the request with no msg body. The exception
92 // should only be propagated to one of the tce.
93 response_impl->_complete(0);
94 }
95 else
96 {
97 // Complete the request with an exception
98 response_impl->_complete(0, exceptionPtr);
99 }
100
101 finish();
102 }
103
handle_compression()104 bool request_context::handle_compression()
105 {
106 // If the response body is compressed we will read the encoding header and create a decompressor object which will
107 // later decompress the body
108 try
109 {
110 utility::string_t encoding;
111 http_headers& headers = m_response.headers();
112
113 // Note that some headers, for example "Transfer-Encoding: chunked", may legitimately not produce a decompressor
114 if (m_http_client->client_config().request_compressed_response() &&
115 headers.match(web::http::header_names::content_encoding, encoding))
116 {
117 // Note that, while Transfer-Encoding (chunked only) is valid with Content-Encoding,
118 // we don't need to look for it here because winhttp de-chunks for us in that case
119 m_decompressor = compression::details::get_decompressor_from_header(
120 encoding, compression::details::header_types::content_encoding, m_request.decompress_factories());
121 }
122 else if (!m_request.decompress_factories().empty() &&
123 headers.match(web::http::header_names::transfer_encoding, encoding))
124 {
125 m_decompressor = compression::details::get_decompressor_from_header(
126 encoding, compression::details::header_types::transfer_encoding, m_request.decompress_factories());
127 }
128 }
129 catch (...)
130 {
131 report_exception(std::current_exception());
132 return false;
133 }
134
135 return true;
136 }
137
get_compression_header() const138 utility::string_t request_context::get_compression_header() const
139 {
140 utility::string_t headers;
141
142 // Add the correct header needed to request a compressed response if supported
143 // on this platform and it has been specified in the config and/or request
144 if (m_http_client->client_config().request_compressed_response())
145 {
146 if (!m_request.decompress_factories().empty() || web::http::compression::builtin::supported())
147 {
148 // Accept-Encoding -- request Content-Encoding from the server
149 headers.append(header_names::accept_encoding + U(": "));
150 headers.append(compression::details::build_supported_header(
151 compression::details::header_types::accept_encoding, m_request.decompress_factories()));
152 headers.append(U("\r\n"));
153 }
154 }
155 else if (!m_request.decompress_factories().empty())
156 {
157 // TE -- request Transfer-Encoding from the server
158 headers.append(header_names::connection + U(": TE\r\n") + // Required by Section 4.3 of RFC-7230
159 header_names::te + U(": "));
160 headers.append(compression::details::build_supported_header(compression::details::header_types::te,
161 m_request.decompress_factories()));
162 headers.append(U("\r\n"));
163 }
164
165 return headers;
166 }
167
_get_readbuffer()168 concurrency::streams::streambuf<uint8_t> request_context::_get_readbuffer()
169 {
170 auto instream = m_request.body();
171
172 _ASSERTE((bool)instream);
173 return instream.streambuf();
174 }
175
_get_writebuffer()176 concurrency::streams::streambuf<uint8_t> request_context::_get_writebuffer()
177 {
178 auto outstream = m_response._get_impl()->outstream();
179
180 _ASSERTE((bool)outstream);
181 return outstream.streambuf();
182 }
183
request_context(const std::shared_ptr<_http_client_communicator> & client,const http_request & request)184 request_context::request_context(const std::shared_ptr<_http_client_communicator>& client, const http_request& request)
185 : m_http_client(client), m_request(request), m_uploaded(0), m_downloaded(0)
186 {
187 auto responseImpl = m_response._get_impl();
188
189 // Copy the user specified output stream over to the response
190 responseImpl->set_outstream(request._get_impl()->_response_stream(), false);
191
192 // Prepare for receiving data from the network. Ideally, this should be done after
193 // we receive the headers and determine that there is a response body. We will do it here
194 // since it is not immediately apparent where that would be in the callback handler
195 responseImpl->_prepare_to_receive_data();
196 }
197
async_send_request_impl(const std::shared_ptr<request_context> & request)198 void _http_client_communicator::async_send_request_impl(const std::shared_ptr<request_context>& request)
199 {
200 auto self = std::static_pointer_cast<_http_client_communicator>(this->shared_from_this());
201 // Schedule a task to start sending.
202 pplx::create_task([self, request] {
203 try
204 {
205 self->send_request(request);
206 }
207 catch (...)
208 {
209 request->report_exception(std::current_exception());
210 }
211 });
212 }
213
async_send_request(const std::shared_ptr<request_context> & request)214 void _http_client_communicator::async_send_request(const std::shared_ptr<request_context>& request)
215 {
216 if (m_client_config.guarantee_order())
217 {
218 pplx::extensibility::scoped_critical_section_t l(m_client_lock);
219
220 if (m_outstanding)
221 {
222 m_requests_queue.push(request);
223 }
224 else
225 {
226 async_send_request_impl(request);
227 m_outstanding = true;
228 }
229 }
230 else
231 {
232 async_send_request_impl(request);
233 }
234 }
235
finish_request()236 void _http_client_communicator::finish_request()
237 {
238 // If guarantee order is specified we don't need to do anything.
239 if (m_client_config.guarantee_order())
240 {
241 pplx::extensibility::scoped_critical_section_t l(m_client_lock);
242
243 if (m_requests_queue.empty())
244 {
245 m_outstanding = false;
246 }
247 else
248 {
249 auto request = m_requests_queue.front();
250 m_requests_queue.pop();
251
252 async_send_request_impl(request);
253 }
254 }
255 }
256
client_config() const257 const http_client_config& _http_client_communicator::client_config() const { return m_client_config; }
258
base_uri() const259 const uri& _http_client_communicator::base_uri() const { return m_uri; }
260
_http_client_communicator(http::uri && address,http_client_config && client_config)261 _http_client_communicator::_http_client_communicator(http::uri&& address, http_client_config&& client_config)
262 : m_uri(std::move(address)), m_client_config(std::move(client_config)), m_outstanding(false)
263 {
264 }
265
finish()266 inline void request_context::finish()
267 {
268 // If cancellation is enabled and registration was performed, unregister.
269 if (m_cancellationRegistration != pplx::cancellation_token_registration())
270 {
271 _ASSERTE(m_request._cancellation_token() != pplx::cancellation_token::none());
272 m_request._cancellation_token().deregister_callback(m_cancellationRegistration);
273 }
274
275 m_http_client->finish_request();
276 }
277
278 } // namespace details
279
280 /// <summary>
281 /// Private implementation of http_client. Manages the http request processing pipeline.
282 /// </summary>
283 class http_pipeline
284 {
285 public:
http_pipeline(std::shared_ptr<details::_http_client_communicator> last)286 http_pipeline(std::shared_ptr<details::_http_client_communicator> last) : m_last_stage(std::move(last)) {}
287
288 // pplx::extensibility::recursive_lock_t does not support move/copy, but does not delete the functions either.
289 http_pipeline(const http_pipeline&) = delete;
290 http_pipeline(http_pipeline&&) = delete;
291 http_pipeline& operator=(const http_pipeline&) = delete;
292 http_pipeline& operator=(http_pipeline&&) = delete;
293
294 /// <summary>
295 /// Initiate an http request into the pipeline
296 /// </summary>
297 /// <param name="request">Http request</param>
propagate(http_request request)298 pplx::task<http_response> propagate(http_request request)
299 {
300 std::shared_ptr<http_pipeline_stage> first;
301 {
302 pplx::extensibility::scoped_recursive_lock_t l(m_lock);
303 first = (m_stages.size() > 0) ? m_stages[0] : m_last_stage;
304 }
305 return first->propagate(request);
306 }
307
308 /// <summary>
309 /// Adds an HTTP pipeline stage to the pipeline.
310 /// </summary>
311 /// <param name="stage">A pipeline stage.</param>
append(const std::shared_ptr<http_pipeline_stage> & stage)312 void append(const std::shared_ptr<http_pipeline_stage>& stage)
313 {
314 pplx::extensibility::scoped_recursive_lock_t l(m_lock);
315
316 if (m_stages.size() > 0)
317 {
318 std::shared_ptr<http_pipeline_stage> penultimate = m_stages[m_stages.size() - 1];
319 penultimate->set_next_stage(stage);
320 }
321 stage->set_next_stage(m_last_stage);
322
323 m_stages.push_back(stage);
324 }
325
326 // The last stage is always set up by the client or listener and cannot
327 // be changed. All application-defined stages are executed before the
328 // last stage, which is typically a send or dispatch.
329 const std::shared_ptr<details::_http_client_communicator> m_last_stage;
330
331 private:
332 // The vector of pipeline stages.
333 std::vector<std::shared_ptr<http_pipeline_stage>> m_stages;
334
335 pplx::extensibility::recursive_lock_t m_lock;
336 };
337
add_handler(const std::function<pplx::task<http_response> __cdecl (http_request,std::shared_ptr<http::http_pipeline_stage>)> & handler)338 void http_client::add_handler(
339 const std::function<pplx::task<http_response> __cdecl(http_request, std::shared_ptr<http::http_pipeline_stage>)>&
340 handler)
341 {
342 class function_pipeline_wrapper : public http::http_pipeline_stage
343 {
344 public:
345 function_pipeline_wrapper(const std::function<pplx::task<http_response> __cdecl(
346 http_request, std::shared_ptr<http::http_pipeline_stage>)>& handler)
347 : m_handler(handler)
348 {
349 }
350
351 virtual pplx::task<http_response> propagate(http_request request) override
352 {
353 return m_handler(std::move(request), next_stage());
354 }
355
356 private:
357 std::function<pplx::task<http_response>(http_request, std::shared_ptr<http::http_pipeline_stage>)> m_handler;
358 };
359
360 m_pipeline->append(std::make_shared<function_pipeline_wrapper>(handler));
361 }
362
add_handler(const std::shared_ptr<http::http_pipeline_stage> & stage)363 void http_client::add_handler(const std::shared_ptr<http::http_pipeline_stage>& stage) { m_pipeline->append(stage); }
364
http_client(const uri & base_uri)365 http_client::http_client(const uri& base_uri) : http_client(base_uri, http_client_config()) {}
366
http_client(const uri & base_uri,const http_client_config & client_config)367 http_client::http_client(const uri& base_uri, const http_client_config& client_config)
368 {
369 std::shared_ptr<details::_http_client_communicator> final_pipeline_stage;
370
371 if (base_uri.scheme().empty())
372 {
373 auto uribuilder = uri_builder(base_uri);
374 uribuilder.set_scheme(_XPLATSTR("http"));
375 uri uriWithScheme = uribuilder.to_uri();
376 verify_uri(uriWithScheme);
377 final_pipeline_stage =
378 details::create_platform_final_pipeline_stage(std::move(uriWithScheme), http_client_config(client_config));
379 }
380 else
381 {
382 verify_uri(base_uri);
383 final_pipeline_stage =
384 details::create_platform_final_pipeline_stage(uri(base_uri), http_client_config(client_config));
385 }
386
387 m_pipeline = std::make_shared<http_pipeline>(std::move(final_pipeline_stage));
388
389 #if _WIN32_WINNT >= _WIN32_WINNT_VISTA
390 add_handler(std::static_pointer_cast<http::http_pipeline_stage>(
391 std::make_shared<oauth1::details::oauth1_handler>(client_config.oauth1())));
392 #endif
393
394 add_handler(std::static_pointer_cast<http::http_pipeline_stage>(
395 std::make_shared<oauth2::details::oauth2_handler>(client_config.oauth2())));
396 }
397
~http_client()398 http_client::~http_client() CPPREST_NOEXCEPT {}
399
client_config() const400 const http_client_config& http_client::client_config() const { return m_pipeline->m_last_stage->client_config(); }
401
base_uri() const402 const uri& http_client::base_uri() const { return m_pipeline->m_last_stage->base_uri(); }
403
404 // Macros to help build string at compile time and avoid overhead.
405 #define STRINGIFY(x) _XPLATSTR(#x)
406 #define TOSTRING(x) STRINGIFY(x)
407 #define USERAGENT \
408 _XPLATSTR("cpprestsdk/") \
409 TOSTRING(CPPREST_VERSION_MAJOR) \
410 _XPLATSTR(".") TOSTRING(CPPREST_VERSION_MINOR) _XPLATSTR(".") TOSTRING(CPPREST_VERSION_REVISION)
411
request(http_request request,const pplx::cancellation_token & token)412 pplx::task<http_response> http_client::request(http_request request, const pplx::cancellation_token& token)
413 {
414 if (!request.headers().has(header_names::user_agent))
415 {
416 request.headers().add(header_names::user_agent, USERAGENT);
417 }
418
419 request._set_base_uri(base_uri());
420 request._set_cancellation_token(token);
421 return m_pipeline->propagate(request);
422 }
423
424 } // namespace client
425 } // namespace http
426 } // namespace web
427