pion-net  4.0.9
HTTPMessage.hpp
1 // ------------------------------------------------------------------
2 // pion-net: a C++ framework for building lightweight HTTP interfaces
3 // ------------------------------------------------------------------
4 // Copyright (C) 2007-2008 Atomic Labs, Inc. (http://www.atomiclabs.com)
5 //
6 // Distributed under the Boost Software License, Version 1.0.
7 // See http://www.boost.org/LICENSE_1_0.txt
8 //
9 
10 #ifndef __PION_HTTPMESSAGE_HEADER__
11 #define __PION_HTTPMESSAGE_HEADER__
12 
13 #include <iosfwd>
14 #include <vector>
15 #include <cstring>
16 #include <boost/cstdint.hpp>
17 #include <boost/asio.hpp>
18 #include <boost/scoped_array.hpp>
19 #include <boost/lexical_cast.hpp>
20 #include <boost/algorithm/string/trim.hpp>
21 #include <boost/regex.hpp>
22 #include <pion/PionConfig.hpp>
23 #include <pion/net/HTTPTypes.hpp>
24 
25 
26 namespace pion { // begin namespace pion
27 namespace net { // begin namespace net (Pion Network Library)
28 
29 
30 // forward declaration for class used by send() and receive()
31 class TCPConnection;
32 
33 
37 class PION_NET_API HTTPMessage
38  : public HTTPTypes
39 {
40 public:
41 
43  typedef std::vector<boost::asio::const_buffer> WriteBuffers;
44 
46  typedef std::vector<char> ChunkCache;
47 
49  struct ReceiveError
50  : public boost::system::error_category
51  {
52  virtual ~ReceiveError() {}
53  virtual inline const char *name() const { return "ReceiveError"; }
54  virtual inline std::string message(int ev) const {
55  std::string result;
56  switch(ev) {
57  case 1:
58  result = "HTTP message parsing error";
59  break;
60  default:
61  result = "Unknown receive error";
62  break;
63  }
64  return result;
65  }
66  };
67 
70  {
71  STATUS_NONE, // no data received (i.e. all lost)
72  STATUS_TRUNCATED, // one or more missing packets at the end
73  STATUS_PARTIAL, // one or more missing packets but NOT at the end
74  STATUS_OK // no missing packets
75  };
76 
79  : m_is_valid(false), m_is_chunked(false), m_chunks_supported(false),
80  m_do_not_send_content_length(false),
81  m_version_major(1), m_version_minor(1), m_content_length(0),
82  m_status(STATUS_NONE), m_has_missing_packets(false), m_has_data_after_missing(false)
83  {}
84 
86  HTTPMessage(const HTTPMessage& http_msg)
87  : m_first_line(http_msg.m_first_line),
88  m_is_valid(http_msg.m_is_valid),
89  m_is_chunked(http_msg.m_is_chunked),
90  m_chunks_supported(http_msg.m_chunks_supported),
91  m_do_not_send_content_length(http_msg.m_do_not_send_content_length),
92  m_remote_ip(http_msg.m_remote_ip),
93  m_version_major(http_msg.m_version_major),
94  m_version_minor(http_msg.m_version_minor),
95  m_content_length(http_msg.m_content_length),
96  m_chunk_cache(http_msg.m_chunk_cache),
97  m_headers(http_msg.m_headers),
98  m_status(http_msg.m_status),
99  m_has_missing_packets(http_msg.m_has_missing_packets),
100  m_has_data_after_missing(http_msg.m_has_data_after_missing)
101  {
102  if (http_msg.m_content_buf) {
103  char *ptr = createContentBuffer();
104  memcpy(ptr, http_msg.m_content_buf.get(), m_content_length);
105  }
106  }
107 
109  inline HTTPMessage& operator=(const HTTPMessage& http_msg) {
110  m_first_line = http_msg.m_first_line;
111  m_is_valid = http_msg.m_is_valid;
112  m_is_chunked = http_msg.m_is_chunked;
113  m_chunks_supported = http_msg.m_chunks_supported;
114  m_do_not_send_content_length = http_msg.m_do_not_send_content_length;
115  m_remote_ip = http_msg.m_remote_ip;
116  m_version_major = http_msg.m_version_major;
117  m_version_minor = http_msg.m_version_minor;
118  m_content_length = http_msg.m_content_length;
119  m_chunk_cache = http_msg.m_chunk_cache;
120  m_headers = http_msg.m_headers;
121  m_status = http_msg.m_status;
122  m_has_missing_packets = http_msg.m_has_missing_packets;
123  m_has_data_after_missing = http_msg.m_has_data_after_missing;
124  if (http_msg.m_content_buf) {
125  char *ptr = createContentBuffer();
126  memcpy(ptr, http_msg.m_content_buf.get(), m_content_length);
127  }
128  return *this;
129  }
130 
132  virtual ~HTTPMessage() {}
133 
135  virtual void clear(void) {
136  clearFirstLine();
137  m_is_valid = m_is_chunked = m_chunks_supported
138  = m_do_not_send_content_length = false;
139  m_remote_ip = boost::asio::ip::address_v4(0);
140  m_version_major = m_version_minor = 1;
141  m_content_length = 0;
142  m_content_buf.reset();
143  m_chunk_cache.clear();
144  m_headers.clear();
145  m_cookie_params.clear();
146  m_status = STATUS_NONE;
147  m_has_missing_packets = false;
148  m_has_data_after_missing = false;
149  }
150 
152  virtual bool isContentLengthImplied(void) const = 0;
153 
155  inline bool isValid(void) const { return m_is_valid; }
156 
158  inline bool getChunksSupported(void) const { return m_chunks_supported; }
159 
161  inline boost::asio::ip::address& getRemoteIp(void) {
162  return m_remote_ip;
163  }
164 
166  inline boost::uint16_t getVersionMajor(void) const { return m_version_major; }
167 
169  inline boost::uint16_t getVersionMinor(void) const { return m_version_minor; }
170 
172  inline std::string getVersionString(void) const {
173  std::string http_version(STRING_HTTP_VERSION);
174  http_version += boost::lexical_cast<std::string>(getVersionMajor());
175  http_version += '.';
176  http_version += boost::lexical_cast<std::string>(getVersionMinor());
177  return http_version;
178  }
179 
181  inline std::size_t getContentLength(void) const { return m_content_length; }
182 
184  inline bool isChunked(void) const { return m_is_chunked; }
185 
187  inline char *getContent(void) { return m_content_buf.get(); }
188 
190  inline const char *getContent(void) const { return m_content_buf.get(); }
191 
193  inline ChunkCache& getChunkCache(void) { return m_chunk_cache; }
194 
196  inline const std::string& getHeader(const std::string& key) const {
197  return getValue(m_headers, key);
198  }
199 
201  inline Headers& getHeaders(void) {
202  return m_headers;
203  }
204 
206  inline bool hasHeader(const std::string& key) const {
207  return(m_headers.find(key) != m_headers.end());
208  }
209 
212  inline const std::string& getCookie(const std::string& key) const {
213  return getValue(m_cookie_params, key);
214  }
215 
218  return m_cookie_params;
219  }
220 
223  inline bool hasCookie(const std::string& key) const {
224  return(m_cookie_params.find(key) != m_cookie_params.end());
225  }
226 
229  inline void addCookie(const std::string& key, const std::string& value) {
230  m_cookie_params.insert(std::make_pair(key, value));
231  }
232 
235  inline void changeCookie(const std::string& key, const std::string& value) {
236  changeValue(m_cookie_params, key, value);
237  }
238 
241  inline void deleteCookie(const std::string& key) {
242  deleteValue(m_cookie_params, key);
243  }
244 
246  inline const std::string& getFirstLine(void) const {
247  if (m_first_line.empty())
248  updateFirstLine();
249  return m_first_line;
250  }
251 
253  inline bool hasMissingPackets() const { return m_has_missing_packets; }
254 
256  inline void setMissingPackets(bool newVal) { m_has_missing_packets = newVal; }
257 
259  inline bool hasDataAfterMissingPackets() const { return m_has_data_after_missing; }
260 
261  inline void setDataAfterMissingPacket(bool newVal) { m_has_data_after_missing = newVal; }
262 
264  inline void setIsValid(bool b = true) { m_is_valid = b; }
265 
267  inline void setChunksSupported(bool b) { m_chunks_supported = b; }
268 
270  inline void setRemoteIp(const boost::asio::ip::address& ip) { m_remote_ip = ip; }
271 
273  inline void setVersionMajor(const boost::uint16_t n) {
274  m_version_major = n;
275  clearFirstLine();
276  }
277 
279  inline void setVersionMinor(const boost::uint16_t n) {
280  m_version_minor = n;
281  clearFirstLine();
282  }
283 
285  inline void setContentLength(const std::size_t n) { m_content_length = n; }
286 
288  inline void setDoNotSendContentLength(void) { m_do_not_send_content_length = true; }
289 
291  inline DataStatus getStatus() const { return m_status; }
292 
294  inline void setStatus(DataStatus newVal) { m_status = newVal; }
295 
297  inline void updateContentLengthUsingHeader(void) {
298  Headers::const_iterator i = m_headers.find(HEADER_CONTENT_LENGTH);
299  if (i == m_headers.end()) {
300  m_content_length = 0;
301  } else {
302  std::string trimmed_length(i->second);
303  boost::algorithm::trim(trimmed_length);
304  m_content_length = boost::lexical_cast<std::size_t>(trimmed_length);
305  }
306  }
307 
310  m_is_chunked = false;
311  Headers::const_iterator i = m_headers.find(HEADER_TRANSFER_ENCODING);
312  if (i != m_headers.end()) {
313  // From RFC 2616, sec 3.6: All transfer-coding values are case-insensitive.
314  m_is_chunked = boost::regex_match(i->second, REGEX_ICASE_CHUNKED);
315  // ignoring other possible values for now
316  }
317  }
318 
321  inline char *createContentBuffer(void) {
322  m_content_buf.reset(new char[m_content_length + 1]);
323  m_content_buf[m_content_length] = '\0';
324  return m_content_buf.get();
325  }
326 
328  inline void setContent(const std::string& content) {
329  setContentLength(content.size());
330  createContentBuffer();
331  memcpy(m_content_buf.get(), content.c_str(), content.size());
332  }
333 
335  inline void clearContent(void) {
336  setContentLength(0);
337  createContentBuffer();
338  deleteValue(m_headers, HEADER_CONTENT_TYPE);
339  }
340 
342  inline void setContentType(const std::string& type) {
343  changeValue(m_headers, HEADER_CONTENT_TYPE, type);
344  }
345 
347  inline void addHeader(const std::string& key, const std::string& value) {
348  m_headers.insert(std::make_pair(key, value));
349  }
350 
352  inline void changeHeader(const std::string& key, const std::string& value) {
353  changeValue(m_headers, key, value);
354  }
355 
357  inline void deleteHeader(const std::string& key) {
358  deleteValue(m_headers, key);
359  }
360 
362  inline bool checkKeepAlive(void) const {
363  return (getHeader(HEADER_CONNECTION) != "close"
364  && (getVersionMajor() > 1
365  || (getVersionMajor() >= 1 && getVersionMinor() >= 1)) );
366  }
367 
375  inline void prepareBuffersForSend(WriteBuffers& write_buffers,
376  const bool keep_alive,
377  const bool using_chunks)
378  {
379  // update message headers
380  prepareHeadersForSend(keep_alive, using_chunks);
381  // add first message line
382  write_buffers.push_back(boost::asio::buffer(getFirstLine()));
383  write_buffers.push_back(boost::asio::buffer(STRING_CRLF));
384  // append HTTP headers
385  appendHeaders(write_buffers);
386  }
387 
388 
398  std::size_t send(TCPConnection& tcp_conn, boost::system::error_code& ec,
399  bool headers_only = false);
400 
410  std::size_t receive(TCPConnection& tcp_conn, boost::system::error_code& ec,
411  bool headers_only = false);
412 
422  std::size_t write(std::ostream& out, boost::system::error_code& ec,
423  bool headers_only = false);
424 
434  std::size_t read(std::istream& in, boost::system::error_code& ec,
435  bool headers_only = false);
436 
440  void concatenateChunks(void);
441 
442 
443 protected:
444 
451  inline void prepareHeadersForSend(const bool keep_alive,
452  const bool using_chunks)
453  {
454  changeHeader(HEADER_CONNECTION, (keep_alive ? "Keep-Alive" : "close") );
455  if (using_chunks) {
456  if (getChunksSupported())
457  changeHeader(HEADER_TRANSFER_ENCODING, "chunked");
458  } else if (! m_do_not_send_content_length) {
459  changeHeader(HEADER_CONTENT_LENGTH, boost::lexical_cast<std::string>(getContentLength()));
460  }
461  }
462 
468  inline void appendHeaders(WriteBuffers& write_buffers) {
469  // add HTTP headers
470  for (Headers::const_iterator i = m_headers.begin(); i != m_headers.end(); ++i) {
471  write_buffers.push_back(boost::asio::buffer(i->first));
472  write_buffers.push_back(boost::asio::buffer(HEADER_NAME_VALUE_DELIMITER));
473  write_buffers.push_back(boost::asio::buffer(i->second));
474  write_buffers.push_back(boost::asio::buffer(STRING_CRLF));
475  }
476  // add an extra CRLF to end HTTP headers
477  write_buffers.push_back(boost::asio::buffer(STRING_CRLF));
478  }
479 
488  template <typename DictionaryType>
489  inline static const std::string& getValue(const DictionaryType& dict,
490  const std::string& key)
491  {
492  typename DictionaryType::const_iterator i = dict.find(key);
493  return ( (i==dict.end()) ? STRING_EMPTY : i->second );
494  }
495 
505  template <typename DictionaryType>
506  inline static void changeValue(DictionaryType& dict,
507  const std::string& key, const std::string& value)
508 
509  {
510  // retrieve all current values for key
511  std::pair<typename DictionaryType::iterator, typename DictionaryType::iterator>
512  result_pair = dict.equal_range(key);
513  if (result_pair.first == dict.end()) {
514  // no values exist -> add a new key
515  dict.insert(std::make_pair(key, value));
516  } else {
517  // set the first value found for the key to the new one
518  result_pair.first->second = value;
519  // remove any remaining values
520  typename DictionaryType::iterator i;
521  ++(result_pair.first);
522  while (result_pair.first != result_pair.second) {
523  i = result_pair.first;
524  ++(result_pair.first);
525  dict.erase(i);
526  }
527  }
528  }
529 
536  template <typename DictionaryType>
537  inline static void deleteValue(DictionaryType& dict,
538  const std::string& key)
539  {
540  std::pair<typename DictionaryType::iterator, typename DictionaryType::iterator>
541  result_pair = dict.equal_range(key);
542  if (result_pair.first != dict.end())
543  dict.erase(result_pair.first, result_pair.second);
544  }
545 
548  inline void clearFirstLine(void) const {
549  if (! m_first_line.empty())
550  m_first_line.clear();
551  }
552 
554  virtual void updateFirstLine(void) const = 0;
555 
556 
559  mutable std::string m_first_line;
560 
561 
562 private:
563 
565  static const boost::regex REGEX_ICASE_CHUNKED;
566 
568  bool m_is_valid;
569 
571  bool m_is_chunked;
572 
574  bool m_chunks_supported;
575 
577  bool m_do_not_send_content_length;
578 
580  boost::asio::ip::address m_remote_ip;
581 
583  boost::uint16_t m_version_major;
584 
586  boost::uint16_t m_version_minor;
587 
589  std::size_t m_content_length;
590 
592  boost::scoped_array<char> m_content_buf;
593 
595  ChunkCache m_chunk_cache;
596 
598  Headers m_headers;
599 
601  CookieParams m_cookie_params;
602 
604  DataStatus m_status;
605 
607  bool m_has_missing_packets;
608 
610  bool m_has_data_after_missing;
611 };
612 
613 
614 } // end namespace net
615 } // end namespace pion
616 
617 #endif
void setMissingPackets(bool newVal)
set to true when missing packets detected
void addHeader(const std::string &key, const std::string &value)
adds a value for the HTTP header named key
bool checkKeepAlive(void) const
returns true if the HTTP connection may be kept alive
const std::string & getCookie(const std::string &key) const
HTTPMessage(void)
constructs a new HTTP message object
Definition: HTTPMessage.hpp:78
data type for library errors returned during receive() operations
Definition: HTTPMessage.hpp:49
void addCookie(const std::string &key, const std::string &value)
boost::asio::ip::address & getRemoteIp(void)
returns IP address of the remote endpoint
virtual void clear(void)
clears all message data
bool hasCookie(const std::string &key) const
HTTPMessage & operator=(const HTTPMessage &http_msg)
assignment operator
const std::string & getHeader(const std::string &key) const
returns a value for the header if any are defined; otherwise, an empty string
CookieParams & getCookieParams(void)
returns the cookie parameters
void setRemoteIp(const boost::asio::ip::address &ip)
sets IP address of the remote endpoint
void setContent(const std::string &content)
resets payload content to match the value of a string
static void changeValue(DictionaryType &dict, const std::string &key, const std::string &value)
Headers & getHeaders(void)
returns a reference to the HTTP headers
void clearContent(void)
clears payload content buffer
static void deleteValue(DictionaryType &dict, const std::string &key)
virtual ~HTTPMessage()
virtual destructor
DataStatus getStatus() const
return the data receival status
bool hasDataAfterMissingPackets() const
true if more data seen after the missing packets
void prepareHeadersForSend(const bool keep_alive, const bool using_chunks)
std::vector< boost::asio::const_buffer > WriteBuffers
data type for I/O write buffers (these wrap existing data to be sent)
Definition: HTTPMessage.hpp:43
void setVersionMajor(const boost::uint16_t n)
sets the major HTTP version number
std::size_t getContentLength(void) const
returns the length of the payload content (in bytes)
char * createContentBuffer(void)
void changeHeader(const std::string &key, const std::string &value)
changes the value for the HTTP header named key
StringDictionary CookieParams
data type for HTTP cookie parameters
Definition: HTTPTypes.hpp:103
boost::uint16_t getVersionMajor(void) const
returns the major HTTP version number
void setDoNotSendContentLength(void)
if called, the content-length will not be sent in the HTTP headers
void updateContentLengthUsingHeader(void)
sets the length of the payload content using the Content-Length header
DataStatus
defines message data integrity status codes
Definition: HTTPMessage.hpp:69
ChunkCache & getChunkCache(void)
returns a reference to the chunk cache
bool hasMissingPackets() const
true if there were missing packets
bool hasHeader(const std::string &key) const
returns true if at least one value for the header is defined
HTTPMessage(const HTTPMessage &http_msg)
copy constructor
Definition: HTTPMessage.hpp:86
void setContentLength(const std::size_t n)
sets the length of the payload content (in bytes)
void deleteCookie(const std::string &key)
void setIsValid(bool b=true)
sets whether or not the message is valid
boost::uint16_t getVersionMinor(void) const
returns the minor HTTP version number
void changeCookie(const std::string &key, const std::string &value)
static const std::string & getValue(const DictionaryType &dict, const std::string &key)
std::vector< char > ChunkCache
used to cache chunked data
Definition: HTTPMessage.hpp:46
void setContentType(const std::string &type)
sets the content type for the message payload
std::string getVersionString(void) const
returns a string representation of the HTTP version (i.e. "HTTP/1.1")
void updateTransferCodingUsingHeader(void)
sets the transfer coding using the Transfer-Encoding header
void appendHeaders(WriteBuffers &write_buffers)
void setVersionMinor(const boost::uint16_t n)
sets the minor HTTP version number
const std::string & getFirstLine(void) const
returns a string containing the first line for the HTTP message
void setChunksSupported(bool b)
set to true if chunked transfer encodings are supported
void clearFirstLine(void) const
bool getChunksSupported(void) const
returns true if chunked transfer encodings are supported
void deleteHeader(const std::string &key)
removes all values for the HTTP header named key
bool isChunked(void) const
returns true if the message content is chunked
bool isValid(void) const
returns true if the message is valid
void prepareBuffersForSend(WriteBuffers &write_buffers, const bool keep_alive, const bool using_chunks)
const char * getContent(void) const
returns a const pointer to the payload content, or NULL if there is none
char * getContent(void)
returns a pointer to the payload content, or NULL if there is none