C++ Rest SDK
The C++ REST SDK is a Microsoft project for cloud-based client-server communication in native code using a modern asynchronous C++ API design. This project aims to help C++ developers connect to and interact with services.
http_client_impl.h
1 /***
2 * ==++==
3 *
4 * Copyright (c) Microsoft Corporation. All rights reserved.
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *
16 * ==--==
17 * =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
18 *
19 * HTTP Library: Client-side APIs, non-public declarations used in the implementation.
20 *
21 * For the latest on this and related APIs, please see http://casablanca.codeplex.com.
22 *
23 * =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
24 ****/
25 #pragma once
26 
27 #include "cpprest/details/basic_types.h"
28 #include "cpprest/details/http_helpers.h"
29 
30 #ifdef _WIN32
31 # define CRLF _XPLATSTR("\r\n")
32 #else
33 # define CRLF std::string("\r\n")
34 #endif
35 
36 namespace web { namespace http { namespace client { namespace details
37 {
38 
39 #ifdef _WIN32
40 static const utility::char_t * get_with_body = _XPLATSTR("A GET or HEAD request should not have an entity body.");
41 
42 // Helper function to trim leading and trailing null characters from a string.
43 static void trim_nulls(utility::string_t &str)
44 {
45  size_t index;
46  for(index = 0; index < str.size() && str[index] == 0; ++index);
47  str.erase(0, index);
48  for(index = str.size(); index > 0 && str[index - 1] == 0; --index);
49  str.erase(index);
50 }
51 
52 #endif
53 
54 // Flatten the http_headers into a name:value pairs separated by a carriage return and line feed.
55 static utility::string_t flatten_http_headers(const http_headers &headers)
56 {
57  utility::string_t flattened_headers;
58  for(auto iter = headers.begin(); iter != headers.end(); ++iter)
59  {
60  flattened_headers.append(iter->first);
61  flattened_headers.push_back(':');
62  flattened_headers.append(iter->second);
63  flattened_headers.append(CRLF);
64  }
65  return flattened_headers;
66 }
67 
68 #ifdef _WIN32
69 static void parse_headers_string(_Inout_z_ utf16char *headersStr, http_headers &headers)
73 {
74  utf16char *context = nullptr;
75  utf16char *line = wcstok_s(headersStr, CRLF, &context);
76  while(line != nullptr)
77  {
78  const utility::string_t header_line(line);
79  const size_t colonIndex = header_line.find_first_of(_XPLATSTR(":"));
80  if(colonIndex != utility::string_t::npos)
81  {
82  utility::string_t key = header_line.substr(0, colonIndex);
83  utility::string_t value = header_line.substr(colonIndex + 1, header_line.length() - colonIndex - 1);
84  http::details::trim_whitespace(key);
85  http::details::trim_whitespace(value);
86  headers.add(key, value);
87  }
88  line = wcstok_s(nullptr, CRLF, &context);
89  }
90 }
91 #endif
92 
93 class _http_client_communicator;
94 
95 // Request context encapsulating everything necessary for creating and responding to a request.
97 {
98 public:
99 
100  // Destructor to clean up any held resources.
101  virtual ~request_context()
102  {
103  }
104 
105  void complete_headers()
106  {
107  // We have already read (and transmitted) the request body. Should we explicitly close the stream?
108  // Well, there are test cases that assumes that the istream is valid when t receives the response!
109  // For now, we will drop our reference which will close the stream if the user doesn't have one.
111  m_request_completion.set(m_response);
112  }
113 
117  void complete_request(utility::size64_t body_size)
118  {
119  m_response._get_impl()->_complete(body_size);
120 
121  finish();
122  }
123 
124  void report_error(unsigned long error_code, const std::string &errorMessage)
125  {
126  report_exception(http_exception(static_cast<int>(error_code), errorMessage));
127  }
128 
129 #ifdef _WIN32
130  void report_error(unsigned long error_code, const std::wstring &errorMessage)
131  {
132  report_exception(http_exception(static_cast<int>(error_code), errorMessage));
133  }
134 #endif
135 
136  template<typename _ExceptionType>
137  void report_exception(const _ExceptionType &e)
138  {
139  report_exception(std::make_exception_ptr(e));
140  }
141 
142  virtual void report_exception(std::exception_ptr exceptionPtr)
143  {
144  auto response_impl = m_response._get_impl();
145 
146  // If cancellation has been triggered then ignore any errors.
147  if(m_request._cancellation_token().is_canceled())
148  {
149  exceptionPtr = std::make_exception_ptr(http_exception((int)std::errc::operation_canceled, std::generic_category()));
150  }
151 
152  // First try to complete the headers with an exception.
153  if(m_request_completion.set_exception(exceptionPtr))
154  {
155  // Complete the request with no msg body. The exception
156  // should only be propagated to one of the tce.
157  response_impl->_complete(0);
158  }
159  else
160  {
161  // Complete the request with an exception
162  response_impl->_complete(0, exceptionPtr);
163  }
164 
165  finish();
166  }
167 
168  virtual concurrency::streams::streambuf<uint8_t> _get_readbuffer()
169  {
170  auto instream = m_request.body();
171 
172  _ASSERTE((bool)instream);
173  return instream.streambuf();
174  }
175 
176  concurrency::streams::streambuf<uint8_t> _get_writebuffer()
177  {
178  auto outstream = m_response._get_impl()->outstream();
179 
180  _ASSERTE((bool)outstream);
181  return outstream.streambuf();
182  }
183 
184  // Reference to the http_client implementation.
185  std::shared_ptr<_http_client_communicator> m_http_client;
186 
187  // request/response pair.
188  http_request m_request;
189  http_response m_response;
190 
191  utility::size64_t m_uploaded;
192  utility::size64_t m_downloaded;
193 
194  // task completion event to signal request is completed.
195  pplx::task_completion_event<http_response> m_request_completion;
196 
197  // Registration for cancellation notification if enabled.
198  pplx::cancellation_token_registration m_cancellationRegistration;
199 
200 protected:
201 
202  request_context(const std::shared_ptr<_http_client_communicator> &client, const http_request &request)
203  : m_http_client(client),
204  m_request(request),
205  m_uploaded(0),
206  m_downloaded(0)
207  {
208  auto responseImpl = m_response._get_impl();
209 
210  // Copy the user specified output stream over to the response
211  responseImpl->set_outstream(request._get_impl()->_response_stream(), false);
212 
213  // Prepare for receiving data from the network. Ideally, this should be done after
214  // we receive the headers and determine that there is a response body. We will do it here
215  // since it is not immediately apparent where that would be in the callback handler
216  responseImpl->_prepare_to_receive_data();
217  }
218 
219  virtual void finish();
220 };
221 
222 //
223 // Interface used by client implementations. Concrete implementations are responsible for
224 // sending HTTP requests and receiving the responses.
225 //
227 {
228 public:
229 
230  // Destructor to clean up any held resources.
231  virtual ~_http_client_communicator() {}
232 
233  // Asynchronously send a HTTP request and process the response.
234  void async_send_request(const std::shared_ptr<request_context> &request)
235  {
236  if(m_client_config.guarantee_order())
237  {
238  // Send to call block to be processed.
239  push_request(request);
240  }
241  else
242  {
243  // Schedule a task to start sending.
244  pplx::create_task([this, request]
245  {
246  open_and_send_request(request);
247  });
248  }
249  }
250 
251  void finish_request()
252  {
253  // If guarantee order is specified we don't need to do anything.
254  if(m_client_config.guarantee_order())
255  {
257 
258  --m_scheduled;
259 
260  if( !m_requests_queue.empty())
261  {
262  auto request = m_requests_queue.front();
263  m_requests_queue.pop();
264 
265  // Schedule a task to start sending.
266  pplx::create_task([this, request]
267  {
268  open_and_send_request(request);
269  });
270  }
271  }
272  }
273 
274  const http_client_config& client_config() const
275  {
276  return m_client_config;
277  }
278 
279  const uri & base_uri() const
280  {
281  return m_uri;
282  }
283 
284 protected:
286  : m_uri(std::move(address)), m_client_config(std::move(client_config)), m_opened(false), m_scheduled(0)
287  {
288  }
289 
290  // Method to open client.
291  virtual unsigned long open() = 0;
292 
293  // HTTP client implementations must implement send_request.
294  virtual void send_request(_In_ const std::shared_ptr<request_context> &request) = 0;
295 
296  // URI to connect to.
297  const http::uri m_uri;
298 
299 private:
300 
301  http_client_config m_client_config;
302 
303  bool m_opened;
304 
305  pplx::extensibility::critical_section_t m_open_lock;
306 
307  // Wraps opening the client around sending a request.
308  void open_and_send_request(const std::shared_ptr<request_context> &request)
309  {
310  // First see if client needs to be opened.
311  auto error = open_if_required();
312 
313  if (error != 0)
314  {
315  // Failed to open
316  request->report_error(error, _XPLATSTR("Open failed"));
317 
318  // DO NOT TOUCH the this pointer after completing the request
319  // This object could be freed along with the request as it could
320  // be the last reference to this object
321  return;
322  }
323 
324  send_request(request);
325  }
326 
327  unsigned long open_if_required()
328  {
329  unsigned long error = 0;
330 
331  if(!m_opened)
332  {
334 
335  // Check again with the lock held
336  if (!m_opened)
337  {
338  error = open();
339 
340  if (error == 0)
341  {
342  m_opened = true;
343  }
344  }
345  }
346 
347  return error;
348  }
349 
350  void push_request(const std::shared_ptr<request_context> &request)
351  {
353 
354  if(++m_scheduled == 1)
355  {
356  // Schedule a task to start sending.
357  pplx::create_task([this, request]()
358  {
359  open_and_send_request(request);
360  });
361  }
362  else
363  {
364  m_requests_queue.push(request);
365  }
366  }
367 
368  // Queue used to guarantee ordering of requests, when applicable.
369  std::queue<std::shared_ptr<request_context>> m_requests_queue;
370  int m_scheduled;
371 };
372 
373 inline void request_context::finish()
374 {
375  // If cancellation is enabled and registration was performed, unregister.
376  if(m_cancellationRegistration != pplx::cancellation_token_registration())
377  {
378  _ASSERTE(m_request._cancellation_token() != pplx::cancellation_token::none());
379  m_request._cancellation_token().deregister_callback(m_cancellationRegistration);
380  }
381 
382  m_http_client->finish_request();
383 }
384 
386 {
387 public:
388  http_network_handler(const uri &base_uri, const http_client_config &client_config);
389 
391 
392  const std::shared_ptr<details::_http_client_communicator>& http_client_impl() const
393  {
394  return m_http_client_impl;
395  }
396 
397 private:
398  std::shared_ptr<_http_client_communicator> m_http_client_impl;
399 };
400 
401 // Helper function to check to make sure the uri is valid.
402 void verify_uri(const uri &uri)
403 {
404  // Some things like proper URI schema are verified by the URI class.
405  // We only need to check certain things specific to HTTP.
406  if (uri.scheme() != _XPLATSTR("http") && uri.scheme() != _XPLATSTR("https"))
407  {
408  throw std::invalid_argument("URI scheme must be 'http' or 'https'");
409  }
410 
411  if(uri.host().empty())
412  {
413  throw std::invalid_argument("URI must contain a hostname.");
414  }
415 }
416 
417 } // namespace details
418 
419 http_client::http_client(const uri &base_uri)
420 {
421  build_pipeline(base_uri, http_client_config());
422 }
423 
424 http_client::http_client(const uri &base_uri, const http_client_config &client_config)
425 {
426  build_pipeline(base_uri, client_config);
427 }
428 
429 void http_client::build_pipeline(const uri &base_uri, const http_client_config &client_config)
430 {
431  if (base_uri.scheme().empty())
432  {
433  auto uribuilder = uri_builder(base_uri);
434  uribuilder.set_scheme(_XPLATSTR("http"));
435  uri uriWithScheme = uribuilder.to_uri();
436  details::verify_uri(uriWithScheme);
437  m_pipeline = ::web::http::http_pipeline::create_pipeline(std::make_shared<details::http_network_handler>(uriWithScheme, client_config));
438  }
439  else
440  {
441  details::verify_uri(base_uri);
442  m_pipeline = ::web::http::http_pipeline::create_pipeline(std::make_shared<details::http_network_handler>(base_uri, client_config));
443  }
444 
445 #if !defined(CPPREST_TARGET_XP)
446  add_handler(std::static_pointer_cast<http::http_pipeline_stage>(
447  std::make_shared<oauth1::details::oauth1_handler>(client_config.oauth1())));
448 #endif
449 
450  add_handler(std::static_pointer_cast<http::http_pipeline_stage>(
451  std::make_shared<oauth2::details::oauth2_handler>(client_config.oauth2())));
452 }
453 
455 {
456  auto ph = std::static_pointer_cast<details::http_network_handler>(m_pipeline->last_stage());
457  return ph->http_client_impl()->client_config();
458 }
459 
460 const uri & http_client::base_uri() const
461 {
462  auto ph = std::static_pointer_cast<details::http_network_handler>(m_pipeline->last_stage());
463  return ph->http_client_impl()->base_uri();
464 }
465 
466 }}} // namespaces
iterator end()
Returns an iterator referring to the past-the-end header field.
Definition: http_headers.h:254
A generic RAII wrapper for locks that implements the critical_section interface cpprest_synchronizati...
Definition: pplxlinux.h:264
Represents an HTTP error. This class holds an error message and an optional error code...
Definition: http_msg.h:130
Represents HTTP headers, acts like a map.
Definition: http_headers.h:75
concurrency::streams::istream body() const
Produces a stream which the caller may use to retrieve data from an incoming request.
Definition: http_msg.h:1049
const std::shared_ptr< oauth2::experimental::oauth2_config > oauth2() const
Get OAuth 2.0 configuration.
Definition: http_client.h:134
_ASYNCRTIMP http_client(const uri &base_uri)
Creates a new http_client connected to specified uri.
Definition: http_client_impl.h:419
HTTP client handler class, used to represent an HTTP pipeline stage.
Definition: http_msg.h:1302
A flexible, protocol independent URI implementation.
Definition: base_uri.h:151
The web namespace contains functionality common to multiple protocols like HTTP and WebSockets...
Definition: base_uri.h:37
static std::shared_ptr< http_pipeline > create_pipeline(const std::shared_ptr< http_pipeline_stage > &last)
Create an http pipeline that consists of a linear chain of stages
Definition: http_msg.h:1387
iterator begin()
Returns an iterator referring to the first header field.
Definition: http_headers.h:247
HTTP client configuration class, used to set the possible configuration options used to create an htt...
Definition: http_client.h:90
void add(const key_type &name, const _t1 &value)
Adds a header field using the '<<' operator.
Definition: http_headers.h:158
void set_body(utf8string &&body_text, const utf8string &content_type=utf8string("text/plain; charset=utf-8"))
Sets the body of the message to a textual string and set the "Content-Type" header. Assumes the character encoding of the string is UTF-8.
Definition: http_msg.h:921
bool is_canceled() const
Returns true if the token has been canceled.
Definition: pplxcancellation_token.h:791
_ASYNCRTIMP const http_client_config & client_config() const
Get client configuration object
Definition: http_client_impl.h:454
Definition: http_client_impl.h:385
virtual pplx::task< http_response > propagate(http_request request)
Runs this stage against the given request and passes onto the next stage.
The Parallel Patterns Library (PPL) task class. A task object represents work that can be executed as...
Definition: pplxtasks.h:176
const utility::string_t & host() const
Get the host component of the URI as an encoded string.
Definition: base_uri.h:298
The cancellation_token_registration class represents a callback notification from a cancellation_toke...
Definition: pplxcancellation_token.h:616
Definition: http_client_impl.h:96
_ASYNCRTIMP const uri & base_uri() const
Gets the base URI.
Definition: http_client_impl.h:460
Base interface for all asynchronous input streams.
Definition: astreambuf.h:791
static cancellation_token none()
Returns a cancellation token which can never be subject to cancellation.
Definition: pplxcancellation_token.h:724
Represents an HTTP request.
Definition: http_msg.h:771
bool guarantee_order() const
Get the 'guarantee order' property
Definition: http_client.h:188
void complete_request(utility::size64_t body_size)
Completes this request, setting the underlying task completion event, and cleaning up the handles ...
Definition: http_client_impl.h:117
const std::shared_ptr< oauth1::experimental::oauth1_config > oauth1() const
Get OAuth 1.0 configuration.
Definition: http_client.h:115
Builder for constructing URIs incrementally.
Definition: uri_builder.h:40
void add_handler(const std::function< pplx::task< http_response >(http_request, std::shared_ptr< http::http_pipeline_stage >)> &handler)
Adds an HTTP pipeline stage to the client.
Definition: http_client.h:423
const utility::string_t & scheme() const
Get the scheme component of the URI as an encoded string.
Definition: base_uri.h:286
The task_completion_event class allows you to delay the execution of a task until a condition is sati...
Definition: pplxtasks.h:2674