pion  5.0.6
http_server.cpp
1 // ---------------------------------------------------------------------
2 // pion: a Boost C++ framework for building lightweight HTTP interfaces
3 // ---------------------------------------------------------------------
4 // Copyright (C) 2007-2014 Splunk Inc. (https://github.com/splunk/pion)
5 //
6 // Distributed under the Boost Software License, Version 1.0.
7 // See http://www.boost.org/LICENSE_1_0.txt
8 //
9 
10 #include <boost/exception/diagnostic_information.hpp>
11 #include <pion/algorithm.hpp>
12 #include <pion/http/server.hpp>
13 #include <pion/http/request.hpp>
14 #include <pion/http/request_reader.hpp>
15 #include <pion/http/response_writer.hpp>
16 
17 
18 namespace pion { // begin namespace pion
19 namespace http { // begin namespace http
20 
21 
22 // static members of server
23 
24 const unsigned int server::MAX_REDIRECTS = 10;
25 
26 
27 // server member functions
28 
29 void server::handle_connection(const tcp::connection_ptr& tcp_conn)
30 {
31  request_reader_ptr my_reader_ptr;
32  my_reader_ptr = request_reader::create(tcp_conn, boost::bind(&server::handle_request,
33  this, _1, _2, _3));
34  my_reader_ptr->set_max_content_length(m_max_content_length);
35  my_reader_ptr->receive();
36 }
37 
38 void server::handle_request(const http::request_ptr& http_request_ptr,
39  const tcp::connection_ptr& tcp_conn, const boost::system::error_code& ec)
40 {
41  if (ec || ! http_request_ptr->is_valid()) {
42  tcp_conn->set_lifecycle(tcp::connection::LIFECYCLE_CLOSE); // make sure it will get closed
43  if (tcp_conn->is_open() && (ec.category() == http::parser::get_error_category())) {
44  // HTTP parser error
45  PION_LOG_INFO(m_logger, "Invalid HTTP request (" << ec.message() << ")");
46  m_bad_request_handler(http_request_ptr, tcp_conn);
47  } else {
48  static const boost::system::error_condition
49  ERRCOND_CANCELED(boost::system::errc::operation_canceled, boost::system::system_category()),
50  ERRCOND_EOF(boost::asio::error::eof, boost::asio::error::misc_category);
51 
52  if (ec == ERRCOND_CANCELED || ec == ERRCOND_EOF) {
53  // don't spam the log with common (non-)errors that happen during normal operation
54  PION_LOG_DEBUG(m_logger, "Lost connection on port " << get_port() << " (" << ec.message() << ")");
55  } else {
56  PION_LOG_INFO(m_logger, "Lost connection on port " << get_port() << " (" << ec.message() << ")");
57  }
58 
59  tcp_conn->finish();
60  }
61  return;
62  }
63 
64  PION_LOG_DEBUG(m_logger, "Received a valid HTTP request");
65 
66  // strip off trailing slash if the request has one
67  std::string resource_requested(strip_trailing_slash(http_request_ptr->get_resource()));
68 
69  // apply any redirection
70  redirect_map_t::const_iterator it = m_redirects.find(resource_requested);
71  unsigned int num_redirects = 0;
72  while (it != m_redirects.end()) {
73  if (++num_redirects > MAX_REDIRECTS) {
74  PION_LOG_ERROR(m_logger, "Maximum number of redirects (server::MAX_REDIRECTS) exceeded for requested resource: " << http_request_ptr->get_original_resource());
75  m_server_error_handler(http_request_ptr, tcp_conn, "Maximum number of redirects (server::MAX_REDIRECTS) exceeded for requested resource");
76  return;
77  }
78  resource_requested = it->second;
79  http_request_ptr->change_resource(resource_requested);
80  it = m_redirects.find(resource_requested);
81  }
82 
83  // if authentication activated, check current request
84  if (m_auth_ptr) {
85  // try to verify authentication
86  if (! m_auth_ptr->handle_request(http_request_ptr, tcp_conn)) {
87  // the HTTP 401 message has already been sent by the authentication object
88  PION_LOG_DEBUG(m_logger, "Authentication required for HTTP resource: "
89  << resource_requested);
90  if (http_request_ptr->get_resource() != http_request_ptr->get_original_resource()) {
91  PION_LOG_DEBUG(m_logger, "Original resource requested was: " << http_request_ptr->get_original_resource());
92  }
93  return;
94  }
95  }
96 
97  // search for a handler matching the resource requested
98  request_handler_t request_handler;
99  if (find_request_handler(resource_requested, request_handler)) {
100 
101  // try to handle the request
102  try {
103  request_handler(http_request_ptr, tcp_conn);
104  PION_LOG_DEBUG(m_logger, "Found request handler for HTTP resource: "
105  << resource_requested);
106  if (http_request_ptr->get_resource() != http_request_ptr->get_original_resource()) {
107  PION_LOG_DEBUG(m_logger, "Original resource requested was: " << http_request_ptr->get_original_resource());
108  }
109  } catch (std::bad_alloc&) {
110  // propagate memory errors (FATAL)
111  throw;
112  } catch (std::exception& e) {
113  // recover gracefully from other exceptions thrown by request handlers
114  PION_LOG_ERROR(m_logger, "HTTP request handler: " << pion::diagnostic_information(e));
115  m_server_error_handler(http_request_ptr, tcp_conn, e.what());
116  } catch (boost::exception& e) {
117  // recover gracefully from boost exceptions thrown by request handlers
118  PION_LOG_ERROR(m_logger, "HTTP request handler: " << pion::diagnostic_information(e));
119  m_server_error_handler(http_request_ptr, tcp_conn, pion::diagnostic_information(e));
120  }
121 
122  } else {
123 
124  // no web services found that could handle the request
125  PION_LOG_INFO(m_logger, "No HTTP request handlers found for resource: "
126  << resource_requested);
127  if (http_request_ptr->get_resource() != http_request_ptr->get_original_resource()) {
128  PION_LOG_DEBUG(m_logger, "Original resource requested was: " << http_request_ptr->get_original_resource());
129  }
130  m_not_found_handler(http_request_ptr, tcp_conn);
131  }
132 }
133 
134 bool server::find_request_handler(const std::string& resource,
135  request_handler_t& request_handler) const
136 {
137  // first make sure that HTTP resources are registered
138  boost::mutex::scoped_lock resource_lock(m_resource_mutex);
139  if (m_resources.empty())
140  return false;
141 
142  // iterate through each resource entry that may match the resource
143  resource_map_t::const_iterator i = m_resources.upper_bound(resource);
144  while (i != m_resources.begin()) {
145  --i;
146  // check for a match if the first part of the strings match
147  if (i->first.empty() || resource.compare(0, i->first.size(), i->first) == 0) {
148  // only if the resource matches the plug-in's identifier
149  // or if resource is followed first with a '/' character
150  if (resource.size() == i->first.size() || resource[i->first.size()]=='/') {
151  request_handler = i->second;
152  return true;
153  }
154  }
155  }
156 
157  return false;
158 }
159 
160 void server::add_resource(const std::string& resource,
161  request_handler_t request_handler)
162 {
163  boost::mutex::scoped_lock resource_lock(m_resource_mutex);
164  const std::string clean_resource(strip_trailing_slash(resource));
165  m_resources.insert(std::make_pair(clean_resource, request_handler));
166  PION_LOG_INFO(m_logger, "Added request handler for HTTP resource: " << clean_resource);
167 }
168 
169 void server::remove_resource(const std::string& resource)
170 {
171  boost::mutex::scoped_lock resource_lock(m_resource_mutex);
172  const std::string clean_resource(strip_trailing_slash(resource));
173  m_resources.erase(clean_resource);
174  PION_LOG_INFO(m_logger, "Removed request handler for HTTP resource: " << clean_resource);
175 }
176 
177 void server::add_redirect(const std::string& requested_resource,
178  const std::string& new_resource)
179 {
180  boost::mutex::scoped_lock resource_lock(m_resource_mutex);
181  const std::string clean_requested_resource(strip_trailing_slash(requested_resource));
182  const std::string clean_new_resource(strip_trailing_slash(new_resource));
183  m_redirects.insert(std::make_pair(clean_requested_resource, clean_new_resource));
184  PION_LOG_INFO(m_logger, "Added redirection for HTTP resource " << clean_requested_resource << " to resource " << clean_new_resource);
185 }
186 
187 void server::handle_bad_request(const http::request_ptr& http_request_ptr,
188  const tcp::connection_ptr& tcp_conn)
189 {
190  static const std::string BAD_REQUEST_HTML =
191  "<html><head>\n"
192  "<title>400 Bad Request</title>\n"
193  "</head><body>\n"
194  "<h1>Bad Request</h1>\n"
195  "<p>Your browser sent a request that this server could not understand.</p>\n"
196  "</body></html>\n";
197  http::response_writer_ptr writer(http::response_writer::create(tcp_conn, *http_request_ptr,
198  boost::bind(&tcp::connection::finish, tcp_conn)));
199  writer->get_response().set_status_code(http::types::RESPONSE_CODE_BAD_REQUEST);
200  writer->get_response().set_status_message(http::types::RESPONSE_MESSAGE_BAD_REQUEST);
201  writer->write_no_copy(BAD_REQUEST_HTML);
202  writer->send();
203 }
204 
205 void server::handle_not_found_request(const http::request_ptr& http_request_ptr,
206  const tcp::connection_ptr& tcp_conn)
207 {
208  static const std::string NOT_FOUND_HTML_START =
209  "<html><head>\n"
210  "<title>404 Not Found</title>\n"
211  "</head><body>\n"
212  "<h1>Not Found</h1>\n"
213  "<p>The requested URL ";
214  static const std::string NOT_FOUND_HTML_FINISH =
215  " was not found on this server.</p>\n"
216  "</body></html>\n";
217  http::response_writer_ptr writer(http::response_writer::create(tcp_conn, *http_request_ptr,
218  boost::bind(&tcp::connection::finish, tcp_conn)));
219  writer->get_response().set_status_code(http::types::RESPONSE_CODE_NOT_FOUND);
220  writer->get_response().set_status_message(http::types::RESPONSE_MESSAGE_NOT_FOUND);
221  writer->write_no_copy(NOT_FOUND_HTML_START);
222  writer << algorithm::xml_encode(http_request_ptr->get_resource());
223  writer->write_no_copy(NOT_FOUND_HTML_FINISH);
224  writer->send();
225 }
226 
227 void server::handle_server_error(const http::request_ptr& http_request_ptr,
228  const tcp::connection_ptr& tcp_conn,
229  const std::string& error_msg)
230 {
231  static const std::string SERVER_ERROR_HTML_START =
232  "<html><head>\n"
233  "<title>500 Server Error</title>\n"
234  "</head><body>\n"
235  "<h1>Internal Server Error</h1>\n"
236  "<p>The server encountered an internal error: <strong>";
237  static const std::string SERVER_ERROR_HTML_FINISH =
238  "</strong></p>\n"
239  "</body></html>\n";
240  http::response_writer_ptr writer(http::response_writer::create(tcp_conn, *http_request_ptr,
241  boost::bind(&tcp::connection::finish, tcp_conn)));
242  writer->get_response().set_status_code(http::types::RESPONSE_CODE_SERVER_ERROR);
243  writer->get_response().set_status_message(http::types::RESPONSE_MESSAGE_SERVER_ERROR);
244  writer->write_no_copy(SERVER_ERROR_HTML_START);
245  writer << algorithm::xml_encode(error_msg);
246  writer->write_no_copy(SERVER_ERROR_HTML_FINISH);
247  writer->send();
248 }
249 
250 void server::handle_forbidden_request(const http::request_ptr& http_request_ptr,
251  const tcp::connection_ptr& tcp_conn,
252  const std::string& error_msg)
253 {
254  static const std::string FORBIDDEN_HTML_START =
255  "<html><head>\n"
256  "<title>403 Forbidden</title>\n"
257  "</head><body>\n"
258  "<h1>Forbidden</h1>\n"
259  "<p>User not authorized to access the requested URL ";
260  static const std::string FORBIDDEN_HTML_MIDDLE =
261  "</p><p><strong>\n";
262  static const std::string FORBIDDEN_HTML_FINISH =
263  "</strong></p>\n"
264  "</body></html>\n";
265  http::response_writer_ptr writer(http::response_writer::create(tcp_conn, *http_request_ptr,
266  boost::bind(&tcp::connection::finish, tcp_conn)));
267  writer->get_response().set_status_code(http::types::RESPONSE_CODE_FORBIDDEN);
268  writer->get_response().set_status_message(http::types::RESPONSE_MESSAGE_FORBIDDEN);
269  writer->write_no_copy(FORBIDDEN_HTML_START);
270  writer << algorithm::xml_encode(http_request_ptr->get_resource());
271  writer->write_no_copy(FORBIDDEN_HTML_MIDDLE);
272  writer << error_msg;
273  writer->write_no_copy(FORBIDDEN_HTML_FINISH);
274  writer->send();
275 }
276 
277 void server::handle_method_not_allowed(const http::request_ptr& http_request_ptr,
278  const tcp::connection_ptr& tcp_conn,
279  const std::string& allowed_methods)
280 {
281  static const std::string NOT_ALLOWED_HTML_START =
282  "<html><head>\n"
283  "<title>405 Method Not Allowed</title>\n"
284  "</head><body>\n"
285  "<h1>Not Allowed</h1>\n"
286  "<p>The requested method ";
287  static const std::string NOT_ALLOWED_HTML_FINISH =
288  " is not allowed on this server.</p>\n"
289  "</body></html>\n";
290  http::response_writer_ptr writer(http::response_writer::create(tcp_conn, *http_request_ptr,
291  boost::bind(&tcp::connection::finish, tcp_conn)));
292  writer->get_response().set_status_code(http::types::RESPONSE_CODE_METHOD_NOT_ALLOWED);
293  writer->get_response().set_status_message(http::types::RESPONSE_MESSAGE_METHOD_NOT_ALLOWED);
294  if (! allowed_methods.empty())
295  writer->get_response().add_header("Allow", allowed_methods);
296  writer->write_no_copy(NOT_ALLOWED_HTML_START);
297  writer << algorithm::xml_encode(http_request_ptr->get_method());
298  writer->write_no_copy(NOT_ALLOWED_HTML_FINISH);
299  writer->send();
300 }
301 
302 } // end namespace http
303 } // end namespace pion
static void handle_bad_request(const http::request_ptr &http_request_ptr, const tcp::connection_ptr &tcp_conn)
virtual bool find_request_handler(const std::string &resource, request_handler_t &request_handler) const
static boost::shared_ptr< response_writer > create(const tcp::connection_ptr &tcp_conn, const http::response_ptr &http_response_ptr, finished_handler_t handler=finished_handler_t())
static boost::shared_ptr< request_reader > create(const tcp::connection_ptr &tcp_conn, finished_handler_t handler)
void add_redirect(const std::string &requested_resource, const std::string &new_resource)
static void handle_not_found_request(const http::request_ptr &http_request_ptr, const tcp::connection_ptr &tcp_conn)
virtual void handle_connection(const tcp::connection_ptr &tcp_conn)
Definition: http_server.cpp:29
void add_resource(const std::string &resource, request_handler_t request_handler)
static std::string strip_trailing_slash(const std::string &str)
Definition: server.hpp:160
static void handle_forbidden_request(const http::request_ptr &http_request_ptr, const tcp::connection_ptr &tcp_conn, const std::string &error_msg)
unsigned int get_port(void) const
returns tcp port number that the server listens for connections on
Definition: server.hpp:64
static std::string xml_encode(const std::string &str)
TODO: escapes XML/HTML-encoded strings (1 < 2)
Definition: algorithm.cpp:235
static error_category_t & get_error_category(void)
returns an instance of parser::error_category_t
Definition: parser.hpp:434
boost::function2< void, const http::request_ptr &, const tcp::connection_ptr & > request_handler_t
type of function that is used to handle requests
Definition: server.hpp:43
static void handle_server_error(const http::request_ptr &http_request_ptr, const tcp::connection_ptr &tcp_conn, const std::string &error_msg)
static void handle_method_not_allowed(const http::request_ptr &http_request_ptr, const tcp::connection_ptr &tcp_conn, const std::string &allowed_methods="")
logger m_logger
primary logging interface used by this class
Definition: server.hpp:160
void remove_resource(const std::string &resource)
virtual void handle_request(const http::request_ptr &http_request_ptr, const tcp::connection_ptr &tcp_conn, const boost::system::error_code &ec)
Definition: http_server.cpp:38