staticlib_pion  2.0.2
 All Classes Namespaces Files Functions Variables Enumerations Enumerator Friends Macros
http_response_writer.hpp
Go to the documentation of this file.
1 /*
2  * Copyright 2015, alex at staticlibs.net
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
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 // pion: a Boost C++ framework for building lightweight HTTP interfaces
19 // ---------------------------------------------------------------------
20 // Copyright (C) 2007-2014 Splunk Inc. (https://github.com/splunk/pion)
21 //
22 // Distributed under the Boost Software License, Version 1.0.
23 // See http://www.boost.org/LICENSE_1_0.txt
24 //
25 
26 #ifndef STATICLIB_PION_HTTP_RESPONSE_WRITER_HPP
27 #define STATICLIB_PION_HTTP_RESPONSE_WRITER_HPP
28 
29 #include <functional>
30 #include <list>
31 #include <memory>
32 #include <string>
33 #include <vector>
34 
35 #include "asio.hpp"
36 
37 #include "staticlib/config.hpp"
38 #include "staticlib/io/span.hpp"
39 
44 
45 namespace staticlib {
46 namespace pion {
47 
55  tcp_connection_ptr tcp_conn;
56 
60  std::vector<asio::const_buffer> content_buffers;
61 
65  std::vector<std::unique_ptr<char[]>> payload_cache;
66 
70  size_t content_length;
71 
75  bool client_supports_chunks;
76 
80  bool sending_chunks;
81 
85  bool sent_headers;
86 
90  std::unique_ptr<http_response> response;
91 
95  std::string response_line;
96 
97 public:
98 
105  http_response_writer(tcp_connection_ptr& tcp_conn, const http_request& http_request) :
106  tcp_conn(tcp_conn),
107  content_length(0),
108  client_supports_chunks(true),
109  sending_chunks(false),
110  sent_headers(false),
111  response(new http_response(http_request)) {
112  // set whether or not the client supports chunks
113  supports_chunked_messages(response->get_chunks_supported());
114  }
115 
120 
125 
132  return *response;
133  }
134 
138  void clear() {
139  content_buffers.clear();
140  payload_cache.clear();
141  content_length = 0;
142  }
143 
149  void write(sl::io::span<const char> data) {
150  if (response->is_body_allowed() && data.size() > 0) {
151  content_buffers.push_back(add_to_cache(data));
152  content_length += data.size();
153  }
154  }
155 
163  void write_nocopy(sl::io::span<const char> data) {
164  if (response->is_body_allowed() && data.size() > 0) {
165  content_buffers.push_back(asio::buffer(data.data(), data.size()));
166  content_length += data.size();
167  }
168  }
169 
177  static void send(std::unique_ptr<http_response_writer> self) {
178  auto self_ptr = self.get();
179  auto self_shared = sl::support::make_shared_with_release_deleter(self.release());
180  self_ptr->send_more_data(false,
181  [self_shared](const std::error_code& ec, std::size_t bt) {
182  auto self = sl::support::make_unique_from_shared_with_release_deleter(self_shared);
183  if (nullptr != self.get()) {
184  handle_write(std::move(self), ec, bt);
185  } else {
186  STATICLIB_PION_LOG_WARN("staticlib.pion.http_response_writer",
187  "Lost context detected in 'async_write'");
188  }
189  });
190  }
191 
202  template <typename SendHandler> void send_chunk(SendHandler send_handler) {
203  sending_chunks = true;
204  if (!supports_chunked_messages()) {
205  // sending data in chunks, but the client does not support chunking;
206  // make sure that the connection will be closed when we are all done
207  tcp_conn->set_lifecycle(tcp_connection::lifecycle::close);
208  }
209  // make sure that we did not lose the TCP connection
210  if (tcp_conn->is_open()) {
211  // send more data
212  send_more_data(false, send_handler);
213  } else {
214  tcp_conn->finish();
215  }
216  }
217 
227  static void send_final_chunk(std::unique_ptr<http_response_writer> self) {
228  self->sending_chunks = true;
229  auto self_ptr = self.get();
230  auto self_shared = sl::support::make_shared_with_release_deleter(self.release());
231  self_ptr->send_more_data(true,
232  [self_shared](const std::error_code& ec, std::size_t bt) {
233  auto self = sl::support::make_unique_from_shared_with_release_deleter(self_shared);
234  if (nullptr != self.get()) {
235  handle_write(std::move(self), ec, bt);
236  } else {
237  STATICLIB_PION_LOG_WARN("staticlib.pion.http_response_writer",
238  "Lost context detected in 'async_write' final");
239  }
240  });
241  }
242 
248  tcp_connection_ptr& get_connection() {
249  return tcp_conn;
250  }
251 
257  size_t get_content_length() const {
258  return content_length;
259  }
260 
267  client_supports_chunks = b;
268  }
269 
276  return client_supports_chunks;
277  }
278 
284  bool sending_chunked_message() const {
285  return sending_chunks;
286  }
287 
288 private:
289 
295  void prepare_buffers_for_send(std::vector<asio::const_buffer>& write_buffers) {
296  if (get_content_length() > 0) {
297  response->set_content_length(get_content_length());
298  }
299  response->prepare_buffers_for_send(write_buffers, get_connection()->get_keep_alive(),
301  }
302 
310  static void handle_write(std::unique_ptr<http_response_writer> self,
311  const std::error_code& ec, std::size_t bytes_written) {
312  if (!ec) {
313  // response sent OK
314  if (self->sending_chunked_message()) {
315  STATICLIB_PION_LOG_DEBUG("staticlib.pion.http_response_writer",
316  "Sent HTTP response chunk of " << bytes_written << " bytes");
317  } else {
318  STATICLIB_PION_LOG_DEBUG("staticlib.pion.http_response_writer",
319  "Sent HTTP response of " << bytes_written << " bytes ("
320  << (self->get_connection()->get_keep_alive() ? "keeping alive)" : "closing)"));
321  }
322  }
323  self->tcp_conn->finish();
324  }
325 
332  template <typename SendHandler>
333  void send_more_data(const bool send_final_chunk, SendHandler send_handler) {
334  // make sure that we did not lose the TCP connection
335  if (tcp_conn->is_open()) {
336  // make sure that the content-length is up-to-date
337  // prepare the write buffers to be sent
338  auto write_buffers = std::vector<asio::const_buffer>();
339  prepare_write_buffers(write_buffers, send_final_chunk);
340  // send data in the write buffers
341  auto send_handler_stranded = tcp_conn->get_strand().wrap(send_handler);
342  tcp_conn->async_write(write_buffers, send_handler_stranded);
343  } else {
344  tcp_conn->finish();
345  }
346  }
347 
354  void prepare_write_buffers(std::vector<asio::const_buffer>& write_buffers,
355  const bool send_final_chunk) {
356  // check if the HTTP headers have been sent yet
357  if (! sent_headers) {
358  // initialize write buffers for send operation
359  prepare_buffers_for_send(write_buffers);
360 
361  // only send the headers once
362  sent_headers = true;
363  }
364 
365  // combine I/O write buffers (headers and content) so that everything
366  // can be sent together; otherwise, we would have to send headers
367  // and content separately, which would not be as efficient
368 
369  // don't send anything if there is no data in content buffers
370  if (content_length > 0) {
372  // prepare the next chunk of data to send
373  // write chunk length in hex
374  auto cast_buf = std::array<char, 35>();
375  #ifdef _WIN32
376  auto written = sprintf_s(cast_buf.data(), cast_buf.size(), "%lx", static_cast<long>(content_length));
377  #else // !WIN32
378  auto written = sprintf(cast_buf.data(), "%lx", static_cast<long>(content_length));
379  #endif // WIN32
380 
381  // add chunk length as a string at the back of the text cache
382  // append length of chunk to write_buffers
383  write_buffers.push_back(add_to_cache({cast_buf.data(), written}));
384  // append an extra CRLF for chunk formatting
385  write_buffers.push_back(asio::buffer(http_message::STRING_CRLF));
386 
387  // append response content buffers
388  write_buffers.insert(write_buffers.end(), content_buffers.begin(),
389  content_buffers.end());
390  // append an extra CRLF for chunk formatting
391  write_buffers.push_back(asio::buffer(http_message::STRING_CRLF));
392  } else {
393  // append response content buffers
394  write_buffers.insert(write_buffers.end(), content_buffers.begin(),
395  content_buffers.end());
396  }
397  }
398 
399  // prepare a zero-byte (final) chunk
400  if (send_final_chunk && supports_chunked_messages() && sending_chunked_message()) {
401  // add chunk length as a string at the back of the text cache
402  char zero = '0';
403  // append length of chunk to write_buffers
404  write_buffers.push_back(add_to_cache({std::addressof(zero), 1}));
405  // append an extra CRLF for chunk formatting
406  write_buffers.push_back(asio::buffer(http_message::STRING_CRLF));
407  write_buffers.push_back(asio::buffer(http_message::STRING_CRLF));
408  }
409  }
410 
417  asio::const_buffer add_to_cache(sl::io::span<const char> data) {
418  payload_cache.emplace_back(new char[data.size()]);
419  char* dest = payload_cache.back().get();
420  std::memcpy(dest, data.data(), data.size());
421  return asio::buffer(dest, data.size());
422  }
423 };
424 
428 using response_writer_ptr = std::unique_ptr<http_response_writer>;
429 
430 } // namespace
431 }
432 
433 #endif // STATICLIB_PION_HTTP_RESPONSE_WRITER_HPP