pion  5.0.6
src/http_server.cpp
00001 // ---------------------------------------------------------------------
00002 // pion:  a Boost C++ framework for building lightweight HTTP interfaces
00003 // ---------------------------------------------------------------------
00004 // Copyright (C) 2007-2014 Splunk Inc.  (https://github.com/splunk/pion)
00005 //
00006 // Distributed under the Boost Software License, Version 1.0.
00007 // See http://www.boost.org/LICENSE_1_0.txt
00008 //
00009 
00010 #include <boost/exception/diagnostic_information.hpp>
00011 #include <pion/algorithm.hpp>
00012 #include <pion/http/server.hpp>
00013 #include <pion/http/request.hpp>
00014 #include <pion/http/request_reader.hpp>
00015 #include <pion/http/response_writer.hpp>
00016 
00017 
00018 namespace pion {    // begin namespace pion
00019 namespace http {    // begin namespace http
00020 
00021 
00022 // static members of server
00023 
00024 const unsigned int          server::MAX_REDIRECTS = 10;
00025 
00026 
00027 // server member functions
00028 
00029 void server::handle_connection(tcp::connection_ptr& tcp_conn)
00030 {
00031     request_reader_ptr my_reader_ptr;
00032     my_reader_ptr = request_reader::create(tcp_conn, boost::bind(&server::handle_request,
00033                                            this, _1, _2, _3));
00034     my_reader_ptr->set_max_content_length(m_max_content_length);
00035     my_reader_ptr->receive();
00036 }
00037 
00038 void server::handle_request(http::request_ptr& http_request_ptr,
00039     tcp::connection_ptr& tcp_conn, const boost::system::error_code& ec)
00040 {
00041     if (ec || ! http_request_ptr->is_valid()) {
00042         tcp_conn->set_lifecycle(tcp::connection::LIFECYCLE_CLOSE); // make sure it will get closed
00043         if (tcp_conn->is_open() && (ec.category() == http::parser::get_error_category())) {
00044             // HTTP parser error
00045             PION_LOG_INFO(m_logger, "Invalid HTTP request (" << ec.message() << ")");
00046             m_bad_request_handler(http_request_ptr, tcp_conn);
00047         } else {
00048             static const boost::system::error_condition
00049                     ERRCOND_CANCELED(boost::system::errc::operation_canceled, boost::system::system_category()),
00050                     ERRCOND_EOF(boost::asio::error::eof, boost::asio::error::misc_category);
00051 
00052             if (ec == ERRCOND_CANCELED || ec == ERRCOND_EOF) {
00053                 // don't spam the log with common (non-)errors that happen during normal operation
00054                 PION_LOG_DEBUG(m_logger, "Lost connection on port " << get_port() << " (" << ec.message() << ")");
00055             } else {
00056                 PION_LOG_INFO(m_logger, "Lost connection on port " << get_port() << " (" << ec.message() << ")");
00057             }
00058 
00059             tcp_conn->finish();
00060         }
00061         return;
00062     }
00063         
00064     PION_LOG_DEBUG(m_logger, "Received a valid HTTP request");
00065 
00066     // strip off trailing slash if the request has one
00067     std::string resource_requested(strip_trailing_slash(http_request_ptr->get_resource()));
00068 
00069     // apply any redirection
00070     redirect_map_t::const_iterator it = m_redirects.find(resource_requested);
00071     unsigned int num_redirects = 0;
00072     while (it != m_redirects.end()) {
00073         if (++num_redirects > MAX_REDIRECTS) {
00074             PION_LOG_ERROR(m_logger, "Maximum number of redirects (server::MAX_REDIRECTS) exceeded for requested resource: " << http_request_ptr->get_original_resource());
00075             m_server_error_handler(http_request_ptr, tcp_conn, "Maximum number of redirects (server::MAX_REDIRECTS) exceeded for requested resource");
00076             return;
00077         }
00078         resource_requested = it->second;
00079         http_request_ptr->change_resource(resource_requested);
00080         it = m_redirects.find(resource_requested);
00081     }
00082 
00083     // if authentication activated, check current request
00084     if (m_auth_ptr) {
00085         // try to verify authentication
00086         if (! m_auth_ptr->handle_request(http_request_ptr, tcp_conn)) {
00087             // the HTTP 401 message has already been sent by the authentication object
00088             PION_LOG_DEBUG(m_logger, "Authentication required for HTTP resource: "
00089                 << resource_requested);
00090             if (http_request_ptr->get_resource() != http_request_ptr->get_original_resource()) {
00091                 PION_LOG_DEBUG(m_logger, "Original resource requested was: " << http_request_ptr->get_original_resource());
00092             }
00093             return;
00094         }
00095     }
00096     
00097     // search for a handler matching the resource requested
00098     request_handler_t request_handler;
00099     if (find_request_handler(resource_requested, request_handler)) {
00100         
00101         // try to handle the request
00102         try {
00103             request_handler(http_request_ptr, tcp_conn);
00104             PION_LOG_DEBUG(m_logger, "Found request handler for HTTP resource: "
00105                            << resource_requested);
00106             if (http_request_ptr->get_resource() != http_request_ptr->get_original_resource()) {
00107                 PION_LOG_DEBUG(m_logger, "Original resource requested was: " << http_request_ptr->get_original_resource());
00108             }
00109         } catch (std::bad_alloc&) {
00110             // propagate memory errors (FATAL)
00111             throw;
00112         } catch (std::exception& e) {
00113             // recover gracefully from other exceptions thrown by request handlers
00114             PION_LOG_ERROR(m_logger, "HTTP request handler: " << pion::diagnostic_information(e));
00115             m_server_error_handler(http_request_ptr, tcp_conn, e.what());
00116         } catch (boost::exception& e) {
00117             // recover gracefully from boost exceptions thrown by request handlers
00118             PION_LOG_ERROR(m_logger, "HTTP request handler: " << pion::diagnostic_information(e));
00119             m_server_error_handler(http_request_ptr, tcp_conn, pion::diagnostic_information(e));
00120         }
00121         
00122     } else {
00123         
00124         // no web services found that could handle the request
00125         PION_LOG_INFO(m_logger, "No HTTP request handlers found for resource: "
00126                       << resource_requested);
00127         if (http_request_ptr->get_resource() != http_request_ptr->get_original_resource()) {
00128             PION_LOG_DEBUG(m_logger, "Original resource requested was: " << http_request_ptr->get_original_resource());
00129         }
00130         m_not_found_handler(http_request_ptr, tcp_conn);
00131     }
00132 }
00133     
00134 bool server::find_request_handler(const std::string& resource,
00135                                     request_handler_t& request_handler) const
00136 {
00137     // first make sure that HTTP resources are registered
00138     boost::mutex::scoped_lock resource_lock(m_resource_mutex);
00139     if (m_resources.empty())
00140         return false;
00141     
00142     // iterate through each resource entry that may match the resource
00143     resource_map_t::const_iterator i = m_resources.upper_bound(resource);
00144     while (i != m_resources.begin()) {
00145         --i;
00146         // check for a match if the first part of the strings match
00147         if (i->first.empty() || resource.compare(0, i->first.size(), i->first) == 0) {
00148             // only if the resource matches the plug-in's identifier
00149             // or if resource is followed first with a '/' character
00150             if (resource.size() == i->first.size() || resource[i->first.size()]=='/') {
00151                 request_handler = i->second;
00152                 return true;
00153             }
00154         }
00155     }
00156     
00157     return false;
00158 }
00159 
00160 void server::add_resource(const std::string& resource,
00161                              request_handler_t request_handler)
00162 {
00163     boost::mutex::scoped_lock resource_lock(m_resource_mutex);
00164     const std::string clean_resource(strip_trailing_slash(resource));
00165     m_resources.insert(std::make_pair(clean_resource, request_handler));
00166     PION_LOG_INFO(m_logger, "Added request handler for HTTP resource: " << clean_resource);
00167 }
00168 
00169 void server::remove_resource(const std::string& resource)
00170 {
00171     boost::mutex::scoped_lock resource_lock(m_resource_mutex);
00172     const std::string clean_resource(strip_trailing_slash(resource));
00173     m_resources.erase(clean_resource);
00174     PION_LOG_INFO(m_logger, "Removed request handler for HTTP resource: " << clean_resource);
00175 }
00176 
00177 void server::add_redirect(const std::string& requested_resource,
00178                              const std::string& new_resource)
00179 {
00180     boost::mutex::scoped_lock resource_lock(m_resource_mutex);
00181     const std::string clean_requested_resource(strip_trailing_slash(requested_resource));
00182     const std::string clean_new_resource(strip_trailing_slash(new_resource));
00183     m_redirects.insert(std::make_pair(clean_requested_resource, clean_new_resource));
00184     PION_LOG_INFO(m_logger, "Added redirection for HTTP resource " << clean_requested_resource << " to resource " << clean_new_resource);
00185 }
00186 
00187 void server::handle_bad_request(http::request_ptr& http_request_ptr,
00188                                   tcp::connection_ptr& tcp_conn)
00189 {
00190     static const std::string BAD_REQUEST_HTML =
00191         "<html><head>\n"
00192         "<title>400 Bad Request</title>\n"
00193         "</head><body>\n"
00194         "<h1>Bad Request</h1>\n"
00195         "<p>Your browser sent a request that this server could not understand.</p>\n"
00196         "</body></html>\n";
00197     http::response_writer_ptr writer(http::response_writer::create(tcp_conn, *http_request_ptr,
00198                                                             boost::bind(&tcp::connection::finish, tcp_conn)));
00199     writer->get_response().set_status_code(http::types::RESPONSE_CODE_BAD_REQUEST);
00200     writer->get_response().set_status_message(http::types::RESPONSE_MESSAGE_BAD_REQUEST);
00201     writer->write_no_copy(BAD_REQUEST_HTML);
00202     writer->send();
00203 }
00204 
00205 void server::handle_not_found_request(http::request_ptr& http_request_ptr,
00206                                        tcp::connection_ptr& tcp_conn)
00207 {
00208     static const std::string NOT_FOUND_HTML_START =
00209         "<html><head>\n"
00210         "<title>404 Not Found</title>\n"
00211         "</head><body>\n"
00212         "<h1>Not Found</h1>\n"
00213         "<p>The requested URL ";
00214     static const std::string NOT_FOUND_HTML_FINISH =
00215         " was not found on this server.</p>\n"
00216         "</body></html>\n";
00217     http::response_writer_ptr writer(http::response_writer::create(tcp_conn, *http_request_ptr,
00218                                                             boost::bind(&tcp::connection::finish, tcp_conn)));
00219     writer->get_response().set_status_code(http::types::RESPONSE_CODE_NOT_FOUND);
00220     writer->get_response().set_status_message(http::types::RESPONSE_MESSAGE_NOT_FOUND);
00221     writer->write_no_copy(NOT_FOUND_HTML_START);
00222     writer << algorithm::xml_encode(http_request_ptr->get_resource());
00223     writer->write_no_copy(NOT_FOUND_HTML_FINISH);
00224     writer->send();
00225 }
00226 
00227 void server::handle_server_error(http::request_ptr& http_request_ptr,
00228                                    tcp::connection_ptr& tcp_conn,
00229                                    const std::string& error_msg)
00230 {
00231     static const std::string SERVER_ERROR_HTML_START =
00232         "<html><head>\n"
00233         "<title>500 Server Error</title>\n"
00234         "</head><body>\n"
00235         "<h1>Internal Server Error</h1>\n"
00236         "<p>The server encountered an internal error: <strong>";
00237     static const std::string SERVER_ERROR_HTML_FINISH =
00238         "</strong></p>\n"
00239         "</body></html>\n";
00240     http::response_writer_ptr writer(http::response_writer::create(tcp_conn, *http_request_ptr,
00241                                                             boost::bind(&tcp::connection::finish, tcp_conn)));
00242     writer->get_response().set_status_code(http::types::RESPONSE_CODE_SERVER_ERROR);
00243     writer->get_response().set_status_message(http::types::RESPONSE_MESSAGE_SERVER_ERROR);
00244     writer->write_no_copy(SERVER_ERROR_HTML_START);
00245     writer << algorithm::xml_encode(error_msg);
00246     writer->write_no_copy(SERVER_ERROR_HTML_FINISH);
00247     writer->send();
00248 }
00249 
00250 void server::handle_forbidden_request(http::request_ptr& http_request_ptr,
00251                                         tcp::connection_ptr& tcp_conn,
00252                                         const std::string& error_msg)
00253 {
00254     static const std::string FORBIDDEN_HTML_START =
00255         "<html><head>\n"
00256         "<title>403 Forbidden</title>\n"
00257         "</head><body>\n"
00258         "<h1>Forbidden</h1>\n"
00259         "<p>User not authorized to access the requested URL ";
00260     static const std::string FORBIDDEN_HTML_MIDDLE =
00261         "</p><p><strong>\n";
00262     static const std::string FORBIDDEN_HTML_FINISH =
00263         "</strong></p>\n"
00264         "</body></html>\n";
00265     http::response_writer_ptr writer(http::response_writer::create(tcp_conn, *http_request_ptr,
00266                                                             boost::bind(&tcp::connection::finish, tcp_conn)));
00267     writer->get_response().set_status_code(http::types::RESPONSE_CODE_FORBIDDEN);
00268     writer->get_response().set_status_message(http::types::RESPONSE_MESSAGE_FORBIDDEN);
00269     writer->write_no_copy(FORBIDDEN_HTML_START);
00270     writer << algorithm::xml_encode(http_request_ptr->get_resource());
00271     writer->write_no_copy(FORBIDDEN_HTML_MIDDLE);
00272     writer << error_msg;
00273     writer->write_no_copy(FORBIDDEN_HTML_FINISH);
00274     writer->send();
00275 }
00276 
00277 void server::handle_method_not_allowed(http::request_ptr& http_request_ptr,
00278                                         tcp::connection_ptr& tcp_conn,
00279                                         const std::string& allowed_methods)
00280 {
00281     static const std::string NOT_ALLOWED_HTML_START =
00282         "<html><head>\n"
00283         "<title>405 Method Not Allowed</title>\n"
00284         "</head><body>\n"
00285         "<h1>Not Allowed</h1>\n"
00286         "<p>The requested method ";
00287     static const std::string NOT_ALLOWED_HTML_FINISH =
00288         " is not allowed on this server.</p>\n"
00289         "</body></html>\n";
00290     http::response_writer_ptr writer(http::response_writer::create(tcp_conn, *http_request_ptr,
00291                                                             boost::bind(&tcp::connection::finish, tcp_conn)));
00292     writer->get_response().set_status_code(http::types::RESPONSE_CODE_METHOD_NOT_ALLOWED);
00293     writer->get_response().set_status_message(http::types::RESPONSE_MESSAGE_METHOD_NOT_ALLOWED);
00294     if (! allowed_methods.empty())
00295         writer->get_response().add_header("Allow", allowed_methods);
00296     writer->write_no_copy(NOT_ALLOWED_HTML_START);
00297     writer << algorithm::xml_encode(http_request_ptr->get_method());
00298     writer->write_no_copy(NOT_ALLOWED_HTML_FINISH);
00299     writer->send();
00300 }
00301 
00302 }   // end namespace http
00303 }   // end namespace pion