pion  5.0.6
services/FileService.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/asio.hpp>
00011 #include <boost/bind.hpp>
00012 #include <boost/assert.hpp>
00013 #include <boost/lexical_cast.hpp>
00014 #include <boost/filesystem/operations.hpp>
00015 #include <boost/filesystem/fstream.hpp>
00016 #include <boost/algorithm/string/case_conv.hpp>
00017 #include <boost/exception/diagnostic_information.hpp>
00018 
00019 #include "FileService.hpp"
00020 #include <pion/error.hpp>
00021 #include <pion/plugin.hpp>
00022 #include <pion/algorithm.hpp>
00023 #include <pion/http/response_writer.hpp>
00024 
00025 using namespace pion;
00026 
00027 namespace pion {        // begin namespace pion
00028 namespace plugins {     // begin namespace plugins
00029 
00030 
00031 // static members of FileService
00032 
00033 const std::string           FileService::DEFAULT_MIME_TYPE("application/octet-stream");
00034 const unsigned int          FileService::DEFAULT_CACHE_SETTING = 1;
00035 const unsigned int          FileService::DEFAULT_SCAN_SETTING = 0;
00036 const unsigned long         FileService::DEFAULT_MAX_CACHE_SIZE = 0;    /* 0=disabled */
00037 const unsigned long         FileService::DEFAULT_MAX_CHUNK_SIZE = 0;    /* 0=disabled */
00038 boost::once_flag            FileService::m_mime_types_init_flag = BOOST_ONCE_INIT;
00039 FileService::MIMETypeMap    *FileService::m_mime_types_ptr = NULL;
00040 
00041 
00042 // FileService member functions
00043 
00044 FileService::FileService(void)
00045     : m_logger(PION_GET_LOGGER("pion.FileService")),
00046     m_cache_setting(DEFAULT_CACHE_SETTING),
00047     m_scan_setting(DEFAULT_SCAN_SETTING),
00048     m_max_cache_size(DEFAULT_MAX_CACHE_SIZE),
00049     m_max_chunk_size(DEFAULT_MAX_CHUNK_SIZE),
00050     m_writable(false)
00051 {}
00052 
00053 void FileService::set_option(const std::string& name, const std::string& value)
00054 {
00055     if (name == "directory") {
00056         m_directory = value;
00057         m_directory.normalize();
00058         plugin::check_cygwin_path(m_directory, value);
00059         // make sure that the directory exists
00060         if (! boost::filesystem::exists(m_directory) || ! boost::filesystem::is_directory(m_directory)) {
00061 # if defined(BOOST_FILESYSTEM_VERSION) && BOOST_FILESYSTEM_VERSION >= 3
00062             const std::string dir_name = m_directory.string();
00063 #else
00064             const std::string dir_name = m_directory.directory_string();
00065 #endif
00066             BOOST_THROW_EXCEPTION( error::directory_not_found() << error::errinfo_dir_name(dir_name) );
00067         }
00068     } else if (name == "file") {
00069         m_file = value;
00070         plugin::check_cygwin_path(m_file, value);
00071         // make sure that the directory exists
00072         if (! boost::filesystem::exists(m_file) || boost::filesystem::is_directory(m_file)) {
00073 # if defined(BOOST_FILESYSTEM_VERSION) && BOOST_FILESYSTEM_VERSION >= 3
00074             const std::string file_name = m_file.string();
00075 #else
00076             const std::string file_name = m_file.file_string();
00077 #endif
00078             BOOST_THROW_EXCEPTION( error::file_not_found() << error::errinfo_file_name(file_name) );
00079         }
00080     } else if (name == "cache") {
00081         if (value == "0") {
00082             m_cache_setting = 0;
00083         } else if (value == "1") {
00084             m_cache_setting = 1;
00085         } else if (value == "2") {
00086             m_cache_setting = 2;
00087         } else {
00088             BOOST_THROW_EXCEPTION( error::bad_arg() << error::errinfo_arg_name(name) );
00089         }
00090     } else if (name == "scan") {
00091         if (value == "0") {
00092             m_scan_setting = 0;
00093         } else if (value == "1") {
00094             m_scan_setting = 1;
00095         } else if (value == "2") {
00096             m_scan_setting = 2;
00097         } else if (value == "3") {
00098             m_scan_setting = 3;
00099         } else {
00100             BOOST_THROW_EXCEPTION( error::bad_arg() << error::errinfo_arg_name(name) );
00101         }
00102     } else if (name == "max_chunk_size") {
00103         m_max_chunk_size = boost::lexical_cast<unsigned long>(value);
00104     } else if (name == "writable") {
00105         if (value == "true") {
00106             m_writable = true;
00107         } else if (value == "false") {
00108             m_writable = false;
00109         } else {
00110             BOOST_THROW_EXCEPTION( error::bad_arg() << error::errinfo_arg_name(name) );
00111         }
00112     } else {
00113         BOOST_THROW_EXCEPTION( error::bad_arg() << error::errinfo_arg_name(name) );
00114     }
00115 }
00116 
00117 void FileService::operator()(http::request_ptr& http_request_ptr, tcp::connection_ptr& tcp_conn)
00118 {
00119     // get the relative resource path for the request
00120     const std::string relative_path(get_relative_resource(http_request_ptr->get_resource()));
00121 
00122     // determine the path of the file being requested
00123     boost::filesystem::path file_path;
00124     if (relative_path.empty()) {
00125         // request matches resource exactly
00126 
00127         if (m_file.empty()) {
00128             // no file is specified, either in the request or in the options
00129             PION_LOG_WARN(m_logger, "No file option defined ("
00130                           << get_resource() << ")");
00131             sendNotFoundResponse(http_request_ptr, tcp_conn);
00132             return;
00133         } else {
00134             file_path = m_file;
00135         }
00136     } else {
00137         // request does not match resource
00138 
00139         if (m_directory.empty()) {
00140             // no directory is specified for the relative file
00141             PION_LOG_WARN(m_logger, "No directory option defined ("
00142                           << get_resource() << "): " << relative_path);
00143             sendNotFoundResponse(http_request_ptr, tcp_conn);
00144             return;
00145         } else {
00146             file_path = m_directory / relative_path;
00147         }
00148     }
00149 
00150     // make sure that the requested file is within the configured directory
00151     file_path.normalize();
00152 # if defined(BOOST_FILESYSTEM_VERSION) && BOOST_FILESYSTEM_VERSION >= 3
00153     std::string file_string = file_path.string();
00154     if (file_string.find(m_directory.string()) != 0) {
00155 #else
00156     std::string file_string = file_path.file_string();
00157     if (file_string.find(m_directory.directory_string()) != 0) {
00158 #endif
00159         PION_LOG_WARN(m_logger, "Request for file outside of directory ("
00160                       << get_resource() << "): " << relative_path);
00161         static const std::string FORBIDDEN_HTML_START =
00162             "<html><head>\n"
00163             "<title>403 Forbidden</title>\n"
00164             "</head><body>\n"
00165             "<h1>Forbidden</h1>\n"
00166             "<p>The requested URL ";
00167         static const std::string FORBIDDEN_HTML_FINISH =
00168             " is not in the configured directory.</p>\n"
00169             "</body></html>\n";
00170         http::response_writer_ptr writer(http::response_writer::create(tcp_conn, *http_request_ptr,
00171                                      boost::bind(&tcp::connection::finish, tcp_conn)));
00172         writer->get_response().set_status_code(http::types::RESPONSE_CODE_FORBIDDEN);
00173         writer->get_response().set_status_message(http::types::RESPONSE_MESSAGE_FORBIDDEN);
00174         if (http_request_ptr->get_method() != http::types::REQUEST_METHOD_HEAD) {
00175             writer->write_no_copy(FORBIDDEN_HTML_START);
00176             writer << algorithm::xml_encode(http_request_ptr->get_resource());
00177             writer->write_no_copy(FORBIDDEN_HTML_FINISH);
00178         }
00179         writer->send();
00180         return;
00181     }
00182 
00183     // requests specifying directories are not allowed
00184     if (boost::filesystem::is_directory(file_path)) {
00185         PION_LOG_WARN(m_logger, "Request for directory ("
00186                       << get_resource() << "): " << relative_path);
00187         static const std::string FORBIDDEN_HTML_START =
00188             "<html><head>\n"
00189             "<title>403 Forbidden</title>\n"
00190             "</head><body>\n"
00191             "<h1>Forbidden</h1>\n"
00192             "<p>The requested URL ";
00193         static const std::string FORBIDDEN_HTML_FINISH =
00194             " is a directory.</p>\n"
00195             "</body></html>\n";
00196         http::response_writer_ptr writer(http::response_writer::create(tcp_conn, *http_request_ptr,
00197                                      boost::bind(&tcp::connection::finish, tcp_conn)));
00198         writer->get_response().set_status_code(http::types::RESPONSE_CODE_FORBIDDEN);
00199         writer->get_response().set_status_message(http::types::RESPONSE_MESSAGE_FORBIDDEN);
00200         if (http_request_ptr->get_method() != http::types::REQUEST_METHOD_HEAD) {
00201             writer->write_no_copy(FORBIDDEN_HTML_START);
00202             writer << algorithm::xml_encode(http_request_ptr->get_resource());
00203             writer->write_no_copy(FORBIDDEN_HTML_FINISH);
00204         }
00205         writer->send();
00206         return;
00207     }
00208 
00209     if (http_request_ptr->get_method() == http::types::REQUEST_METHOD_GET 
00210         || http_request_ptr->get_method() == http::types::REQUEST_METHOD_HEAD)
00211     {
00212         // the type of response we will send
00213         enum ResponseType {
00214             RESPONSE_UNDEFINED,     // initial state until we know how to respond
00215             RESPONSE_OK,            // normal response that includes the file's content
00216             RESPONSE_HEAD_OK,       // response to HEAD request (would send file's content)
00217             RESPONSE_NOT_FOUND,     // Not Found (404)
00218             RESPONSE_NOT_MODIFIED   // Not Modified (304) response to If-Modified-Since
00219         } response_type = RESPONSE_UNDEFINED;
00220 
00221         // used to hold our response information
00222         DiskFile response_file;
00223 
00224         // get the If-Modified-Since request header
00225         const std::string if_modified_since(http_request_ptr->get_header(http::types::HEADER_IF_MODIFIED_SINCE));
00226 
00227         // check the cache for a corresponding entry (if enabled)
00228         // note that m_cache_setting may equal 0 if m_scan_setting == 1
00229         if (m_cache_setting > 0 || m_scan_setting > 0) {
00230 
00231             // search for a matching cache entry
00232             boost::mutex::scoped_lock cache_lock(m_cache_mutex);
00233             CacheMap::iterator cache_itr = m_cache_map.find(relative_path);
00234 
00235             if (cache_itr == m_cache_map.end()) {
00236                 // no existing cache entries found
00237 
00238                 if (m_scan_setting == 1 || m_scan_setting == 3) {
00239                     // do not allow files to be added;
00240                     // all requests must correspond with existing cache entries
00241                     // since no match was found, just return file not found
00242                     PION_LOG_WARN(m_logger, "Request for unknown file ("
00243                                   << get_resource() << "): " << relative_path);
00244                     response_type = RESPONSE_NOT_FOUND;
00245                 } else {
00246                     PION_LOG_DEBUG(m_logger, "No cache entry for request ("
00247                                    << get_resource() << "): " << relative_path);
00248                 }
00249 
00250             } else {
00251                 // found an existing cache entry
00252 
00253                 PION_LOG_DEBUG(m_logger, "Found cache entry for request ("
00254                                << get_resource() << "): " << relative_path);
00255 
00256                 if (m_cache_setting == 0) {
00257                     // cache is disabled
00258 
00259                     // copy & re-use file_path and mime_type
00260                     response_file.setFilePath(cache_itr->second.getFilePath());
00261                     response_file.setMimeType(cache_itr->second.getMimeType());
00262 
00263                     // get the file_size and last_modified timestamp
00264                     response_file.update();
00265 
00266                     // just compare strings for simplicity (parsing this date format sucks!)
00267                     if (response_file.getLastModifiedString() == if_modified_since) {
00268                         // no need to read the file; the modified times match!
00269                         response_type = RESPONSE_NOT_MODIFIED;
00270                     } else {
00271                         if (http_request_ptr->get_method() == http::types::REQUEST_METHOD_HEAD) {
00272                             response_type = RESPONSE_HEAD_OK;
00273                         } else {
00274                             response_type = RESPONSE_OK;
00275                             PION_LOG_DEBUG(m_logger, "Cache disabled, reading file ("
00276                                            << get_resource() << "): " << relative_path);
00277                         }
00278                     }
00279 
00280                 } else {
00281                     // cache is enabled
00282 
00283                     // true if the entry was updated (used for log message)
00284                     bool cache_was_updated = false;
00285 
00286                     if (cache_itr->second.getLastModified() == 0) {
00287 
00288                         // cache file for the first time
00289                         cache_was_updated = true;
00290                         cache_itr->second.update();
00291                         if (m_max_cache_size==0 || cache_itr->second.getFileSize() <= m_max_cache_size) {
00292                             // read the file (may throw exception)
00293                             cache_itr->second.read();
00294                         } else {
00295                             cache_itr->second.resetFileContent();
00296                         }
00297 
00298                     } else if (m_cache_setting == 1) {
00299 
00300                         // check if file has been updated (may throw exception)
00301                         cache_was_updated = cache_itr->second.checkUpdated();
00302 
00303                     } // else cache_setting == 2 (use existing values)
00304 
00305                     // get the response type
00306                     if (cache_itr->second.getLastModifiedString() == if_modified_since) {
00307                         response_type = RESPONSE_NOT_MODIFIED;
00308                     } else if (http_request_ptr->get_method() == http::types::REQUEST_METHOD_HEAD) {
00309                         response_type = RESPONSE_HEAD_OK;
00310                     } else {
00311                         response_type = RESPONSE_OK;
00312                     }
00313 
00314                     // copy cache contents so that we can release the mutex
00315                     response_file = cache_itr->second;
00316 
00317                     // check if max size has been exceeded
00318                     if (cache_was_updated && m_max_cache_size > 0 && cache_itr->second.getFileSize() > m_max_cache_size) {
00319                         cache_itr->second.resetFileContent();
00320                     }
00321 
00322                     PION_LOG_DEBUG(m_logger, (cache_was_updated ? "Updated" : "Using")
00323                                    << " cache entry for request ("
00324                                    << get_resource() << "): " << relative_path);
00325                 }
00326             }
00327         }
00328 
00329         if (response_type == RESPONSE_UNDEFINED) {
00330             // make sure that the file exists
00331             if (! boost::filesystem::exists(file_path)) {
00332                 PION_LOG_WARN(m_logger, "File not found ("
00333                               << get_resource() << "): " << relative_path);
00334                 sendNotFoundResponse(http_request_ptr, tcp_conn);
00335                 return;
00336             }
00337 
00338             response_file.setFilePath(file_path);
00339 
00340             PION_LOG_DEBUG(m_logger, "Found file for request ("
00341                            << get_resource() << "): " << relative_path);
00342 
00343             // determine the MIME type
00344 # if defined(BOOST_FILESYSTEM_VERSION) && BOOST_FILESYSTEM_VERSION >= 3
00345             response_file.setMimeType(findMIMEType( response_file.getFilePath().filename().string()));
00346 #else
00347             response_file.setMimeType(findMIMEType( response_file.getFilePath().leaf() ));
00348 #endif
00349 
00350             // get the file_size and last_modified timestamp
00351             response_file.update();
00352 
00353             // just compare strings for simplicity (parsing this date format sucks!)
00354             if (response_file.getLastModifiedString() == if_modified_since) {
00355                 // no need to read the file; the modified times match!
00356                 response_type = RESPONSE_NOT_MODIFIED;
00357             } else if (http_request_ptr->get_method() == http::types::REQUEST_METHOD_HEAD) {
00358                 response_type = RESPONSE_HEAD_OK;
00359             } else {
00360                 response_type = RESPONSE_OK;
00361                 if (m_cache_setting != 0) {
00362                     if (m_max_cache_size==0 || response_file.getFileSize() <= m_max_cache_size) {
00363                         // read the file (may throw exception)
00364                         response_file.read();
00365                     }
00366                     // add new entry to the cache
00367                     PION_LOG_DEBUG(m_logger, "Adding cache entry for request ("
00368                                    << get_resource() << "): " << relative_path);
00369                     boost::mutex::scoped_lock cache_lock(m_cache_mutex);
00370                     m_cache_map.insert( std::make_pair(relative_path, response_file) );
00371                 }
00372             }
00373         }
00374 
00375         if (response_type == RESPONSE_OK) {
00376             // use DiskFileSender to send a file
00377             DiskFileSenderPtr sender_ptr(DiskFileSender::create(response_file,
00378                                                                 http_request_ptr, tcp_conn,
00379                                                                 m_max_chunk_size));
00380             sender_ptr->send();
00381         } else if (response_type == RESPONSE_NOT_FOUND) {
00382             sendNotFoundResponse(http_request_ptr, tcp_conn);
00383         } else {
00384             // sending headers only -> use our own response object
00385 
00386             // prepare a response and set the Content-Type
00387             http::response_writer_ptr writer(http::response_writer::create(tcp_conn, *http_request_ptr,
00388                                          boost::bind(&tcp::connection::finish, tcp_conn)));
00389             writer->get_response().set_content_type(response_file.getMimeType());
00390 
00391             // set Last-Modified header to enable client-side caching
00392             writer->get_response().add_header(http::types::HEADER_LAST_MODIFIED,
00393                                             response_file.getLastModifiedString());
00394 
00395             switch(response_type) {
00396                 case RESPONSE_UNDEFINED:
00397                 case RESPONSE_NOT_FOUND:
00398                 case RESPONSE_OK:
00399                     // this should never happen
00400                     BOOST_ASSERT(false);
00401                     break;
00402                 case RESPONSE_NOT_MODIFIED:
00403                     // set "Not Modified" response
00404                     writer->get_response().set_status_code(http::types::RESPONSE_CODE_NOT_MODIFIED);
00405                     writer->get_response().set_status_message(http::types::RESPONSE_MESSAGE_NOT_MODIFIED);
00406                     break;
00407                 case RESPONSE_HEAD_OK:
00408                     // set "OK" response (not really necessary since this is the default)
00409                     writer->get_response().set_status_code(http::types::RESPONSE_CODE_OK);
00410                     writer->get_response().set_status_message(http::types::RESPONSE_MESSAGE_OK);
00411                     break;
00412             }
00413 
00414             // send the response
00415             writer->send();
00416         }
00417     } else if (http_request_ptr->get_method() == http::types::REQUEST_METHOD_POST
00418                || http_request_ptr->get_method() == http::types::REQUEST_METHOD_PUT
00419                || http_request_ptr->get_method() == http::types::REQUEST_METHOD_DELETE)
00420     {
00421         // If not writable, then send 405 (Method Not Allowed) response for POST, PUT or DELETE requests.
00422         if (!m_writable) {
00423             static const std::string NOT_ALLOWED_HTML_START =
00424                 "<html><head>\n"
00425                 "<title>405 Method Not Allowed</title>\n"
00426                 "</head><body>\n"
00427                 "<h1>Not Allowed</h1>\n"
00428                 "<p>The requested method ";
00429             static const std::string NOT_ALLOWED_HTML_FINISH =
00430                 " is not allowed on this server.</p>\n"
00431                 "</body></html>\n";
00432             http::response_writer_ptr writer(http::response_writer::create(tcp_conn, *http_request_ptr,
00433                                          boost::bind(&tcp::connection::finish, tcp_conn)));
00434             writer->get_response().set_status_code(http::types::RESPONSE_CODE_METHOD_NOT_ALLOWED);
00435             writer->get_response().set_status_message(http::types::RESPONSE_MESSAGE_METHOD_NOT_ALLOWED);
00436             writer->write_no_copy(NOT_ALLOWED_HTML_START);
00437             writer << algorithm::xml_encode(http_request_ptr->get_method());
00438             writer->write_no_copy(NOT_ALLOWED_HTML_FINISH);
00439             writer->get_response().add_header("Allow", "GET, HEAD");
00440             writer->send();
00441         } else {
00442             http::response_writer_ptr writer(http::response_writer::create(tcp_conn, *http_request_ptr,
00443                                          boost::bind(&tcp::connection::finish, tcp_conn)));
00444             if (http_request_ptr->get_method() == http::types::REQUEST_METHOD_POST
00445                 || http_request_ptr->get_method() == http::types::REQUEST_METHOD_PUT)
00446             {
00447                 if (boost::filesystem::exists(file_path)) {
00448                     writer->get_response().set_status_code(http::types::RESPONSE_CODE_NO_CONTENT);
00449                     writer->get_response().set_status_message(http::types::RESPONSE_MESSAGE_NO_CONTENT);
00450                 } else {
00451                     // The file doesn't exist yet, so it will be created below, unless the
00452                     // directory of the requested file also doesn't exist.
00453                     if (!boost::filesystem::exists(file_path.branch_path())) {
00454                         static const std::string NOT_FOUND_HTML_START =
00455                             "<html><head>\n"
00456                             "<title>404 Not Found</title>\n"
00457                             "</head><body>\n"
00458                             "<h1>Not Found</h1>\n"
00459                             "<p>The directory of the requested URL ";
00460                         static const std::string NOT_FOUND_HTML_FINISH =
00461                             " was not found on this server.</p>\n"
00462                             "</body></html>\n";
00463                         writer->get_response().set_status_code(http::types::RESPONSE_CODE_NOT_FOUND);
00464                         writer->get_response().set_status_message(http::types::RESPONSE_MESSAGE_NOT_FOUND);
00465                         writer->write_no_copy(NOT_FOUND_HTML_START);
00466                         writer << algorithm::xml_encode(http_request_ptr->get_resource());
00467                         writer->write_no_copy(NOT_FOUND_HTML_FINISH);
00468                         writer->send();
00469                         return;
00470                     }
00471                     static const std::string CREATED_HTML_START =
00472                         "<html><head>\n"
00473                         "<title>201 Created</title>\n"
00474                         "</head><body>\n"
00475                         "<h1>Created</h1>\n"
00476                         "<p>";
00477                     static const std::string CREATED_HTML_FINISH =
00478                         "</p>\n"
00479                         "</body></html>\n";
00480                     writer->get_response().set_status_code(http::types::RESPONSE_CODE_CREATED);
00481                     writer->get_response().set_status_message(http::types::RESPONSE_MESSAGE_CREATED);
00482                     writer->get_response().add_header(http::types::HEADER_LOCATION, http_request_ptr->get_resource());
00483                     writer->write_no_copy(CREATED_HTML_START);
00484                     writer << algorithm::xml_encode(http_request_ptr->get_resource());
00485                     writer->write_no_copy(CREATED_HTML_FINISH);
00486                 }
00487                 std::ios_base::openmode mode = http_request_ptr->get_method() == http::types::REQUEST_METHOD_POST?
00488                                                std::ios::app : std::ios::out;
00489                 boost::filesystem::ofstream file_stream(file_path, mode);
00490                 file_stream.write(http_request_ptr->get_content(), http_request_ptr->get_content_length());
00491                 file_stream.close();
00492                 if (!boost::filesystem::exists(file_path)) {
00493                     static const std::string PUT_FAILED_HTML_START =
00494                         "<html><head>\n"
00495                         "<title>500 Server Error</title>\n"
00496                         "</head><body>\n"
00497                         "<h1>Server Error</h1>\n"
00498                         "<p>Error writing to ";
00499                     static const std::string PUT_FAILED_HTML_FINISH =
00500                         ".</p>\n"
00501                         "</body></html>\n";
00502                     writer->get_response().set_status_code(http::types::RESPONSE_CODE_SERVER_ERROR);
00503                     writer->get_response().set_status_message(http::types::RESPONSE_MESSAGE_SERVER_ERROR);
00504                     writer->write_no_copy(PUT_FAILED_HTML_START);
00505                     writer << algorithm::xml_encode(http_request_ptr->get_resource());
00506                     writer->write_no_copy(PUT_FAILED_HTML_FINISH);
00507                 }
00508                 writer->send();
00509             } else if (http_request_ptr->get_method() == http::types::REQUEST_METHOD_DELETE) {
00510                 if (!boost::filesystem::exists(file_path)) {
00511                     sendNotFoundResponse(http_request_ptr, tcp_conn);
00512                 } else {
00513                     try {
00514                         boost::filesystem::remove(file_path);
00515                         writer->get_response().set_status_code(http::types::RESPONSE_CODE_NO_CONTENT);
00516                         writer->get_response().set_status_message(http::types::RESPONSE_MESSAGE_NO_CONTENT);
00517                         writer->send();
00518                     } catch (std::exception& e) {
00519                         static const std::string DELETE_FAILED_HTML_START =
00520                             "<html><head>\n"
00521                             "<title>500 Server Error</title>\n"
00522                             "</head><body>\n"
00523                             "<h1>Server Error</h1>\n"
00524                             "<p>Could not delete ";
00525                         static const std::string DELETE_FAILED_HTML_FINISH =
00526                             ".</p>\n"
00527                             "</body></html>\n";
00528                         writer->get_response().set_status_code(http::types::RESPONSE_CODE_SERVER_ERROR);
00529                         writer->get_response().set_status_message(http::types::RESPONSE_MESSAGE_SERVER_ERROR);
00530                         writer->write_no_copy(DELETE_FAILED_HTML_START);
00531                         writer << algorithm::xml_encode(http_request_ptr->get_resource())
00532                             << ".</p><p>"
00533                             << boost::diagnostic_information(e);
00534                         writer->write_no_copy(DELETE_FAILED_HTML_FINISH);
00535                         writer->send();
00536                     }
00537                 }
00538             } else {
00539                 // This should never be reached.
00540                 writer->get_response().set_status_code(http::types::RESPONSE_CODE_SERVER_ERROR);
00541                 writer->get_response().set_status_message(http::types::RESPONSE_MESSAGE_SERVER_ERROR);
00542                 writer->send();
00543             }
00544         }
00545     }
00546     // Any method not handled above is unimplemented.
00547     else {
00548         static const std::string NOT_IMPLEMENTED_HTML_START =
00549             "<html><head>\n"
00550             "<title>501 Not Implemented</title>\n"
00551             "</head><body>\n"
00552             "<h1>Not Implemented</h1>\n"
00553             "<p>The requested method ";
00554         static const std::string NOT_IMPLEMENTED_HTML_FINISH =
00555             " is not implemented on this server.</p>\n"
00556             "</body></html>\n";
00557         http::response_writer_ptr writer(http::response_writer::create(tcp_conn, *http_request_ptr,
00558                                      boost::bind(&tcp::connection::finish, tcp_conn)));
00559         writer->get_response().set_status_code(http::types::RESPONSE_CODE_NOT_IMPLEMENTED);
00560         writer->get_response().set_status_message(http::types::RESPONSE_MESSAGE_NOT_IMPLEMENTED);
00561         writer->write_no_copy(NOT_IMPLEMENTED_HTML_START);
00562         writer << algorithm::xml_encode(http_request_ptr->get_method());
00563         writer->write_no_copy(NOT_IMPLEMENTED_HTML_FINISH);
00564         writer->send();
00565     }
00566 }
00567 
00568 void FileService::sendNotFoundResponse(http::request_ptr& http_request_ptr,
00569                                        tcp::connection_ptr& tcp_conn)
00570 {
00571     static const std::string NOT_FOUND_HTML_START =
00572         "<html><head>\n"
00573         "<title>404 Not Found</title>\n"
00574         "</head><body>\n"
00575         "<h1>Not Found</h1>\n"
00576         "<p>The requested URL ";
00577     static const std::string NOT_FOUND_HTML_FINISH =
00578         " was not found on this server.</p>\n"
00579         "</body></html>\n";
00580     http::response_writer_ptr writer(http::response_writer::create(tcp_conn, *http_request_ptr,
00581                                  boost::bind(&tcp::connection::finish, tcp_conn)));
00582     writer->get_response().set_status_code(http::types::RESPONSE_CODE_NOT_FOUND);
00583     writer->get_response().set_status_message(http::types::RESPONSE_MESSAGE_NOT_FOUND);
00584     if (http_request_ptr->get_method() != http::types::REQUEST_METHOD_HEAD) {
00585         writer->write_no_copy(NOT_FOUND_HTML_START);
00586         writer << algorithm::xml_encode(http_request_ptr->get_resource());
00587         writer->write_no_copy(NOT_FOUND_HTML_FINISH);
00588     }
00589     writer->send();
00590 }
00591 
00592 void FileService::start(void)
00593 {
00594     PION_LOG_DEBUG(m_logger, "Starting up resource (" << get_resource() << ')');
00595 
00596     // scan directory/file if scan setting != 0
00597     if (m_scan_setting != 0) {
00598         // force caching if scan == (2 | 3)
00599         if (m_cache_setting == 0 && m_scan_setting > 1)
00600             m_cache_setting = 1;
00601 
00602         boost::mutex::scoped_lock cache_lock(m_cache_mutex);
00603 
00604         // add entry for file if one is defined
00605         if (! m_file.empty()) {
00606             // use empty relative_path for file option
00607             // use placeholder entry (do not pre-populate) if scan == 1
00608             addCacheEntry("", m_file, m_scan_setting == 1);
00609         }
00610 
00611         // scan directory if one is defined
00612         if (! m_directory.empty())
00613             scanDirectory(m_directory);
00614     }
00615 }
00616 
00617 void FileService::stop(void)
00618 {
00619     PION_LOG_DEBUG(m_logger, "Shutting down resource (" << get_resource() << ')');
00620     // clear cached files (if started again, it will re-scan)
00621     boost::mutex::scoped_lock cache_lock(m_cache_mutex);
00622     m_cache_map.clear();
00623 }
00624 
00625 void FileService::scanDirectory(const boost::filesystem::path& dir_path)
00626 {
00627 # if defined(BOOST_FILESYSTEM_VERSION) && BOOST_FILESYSTEM_VERSION >= 3
00628     PION_LOG_DEBUG(m_logger, "Scanning directory (" << get_resource() << "): "
00629                    << dir_path.string());
00630 #else
00631     PION_LOG_DEBUG(m_logger, "Scanning directory (" << get_resource() << "): "
00632                    << dir_path.directory_string());
00633 #endif
00634 
00635     // iterate through items in the directory
00636     boost::filesystem::directory_iterator end_itr;
00637     for ( boost::filesystem::directory_iterator itr( dir_path );
00638           itr != end_itr; ++itr )
00639     {
00640         if ( boost::filesystem::is_directory(*itr) ) {
00641             // item is a sub-directory
00642 
00643             // recursively call scanDirectory()
00644             scanDirectory(*itr);
00645 
00646         } else {
00647             // item is a regular file
00648 
00649             // figure out relative path to the file
00650 # if defined(BOOST_FILESYSTEM_VERSION) && BOOST_FILESYSTEM_VERSION >= 3
00651             std::string file_path_string( itr->path().string() );
00652             std::string relative_path( file_path_string.substr(m_directory.string().size() + 1) );
00653 #else
00654             std::string file_path_string( itr->path().file_string() );
00655             std::string relative_path( file_path_string.substr(m_directory.directory_string().size() + 1) );
00656 #endif
00657 
00658             // add item to cache (use placeholder if scan == 1)
00659             addCacheEntry(relative_path, *itr, m_scan_setting == 1);
00660         }
00661     }
00662 }
00663 
00664 std::pair<FileService::CacheMap::iterator, bool>
00665 FileService::addCacheEntry(const std::string& relative_path,
00666                            const boost::filesystem::path& file_path,
00667                            const bool placeholder)
00668 {
00669 # if defined(BOOST_FILESYSTEM_VERSION) && BOOST_FILESYSTEM_VERSION >= 3
00670     DiskFile cache_entry(file_path, NULL, 0, 0, findMIMEType(file_path.filename().string()));
00671 #else
00672     DiskFile cache_entry(file_path, NULL, 0, 0, findMIMEType(file_path.leaf()));
00673 #endif
00674     if (! placeholder) {
00675         cache_entry.update();
00676         // only read the file if its size is <= max_cache_size
00677         if (m_max_cache_size==0 || cache_entry.getFileSize() <= m_max_cache_size) {
00678             try { cache_entry.read(); }
00679             catch (std::exception&) {
00680 # if defined(BOOST_FILESYSTEM_VERSION) && BOOST_FILESYSTEM_VERSION >= 3
00681                 PION_LOG_ERROR(m_logger, "Unable to add file to cache: "
00682                                << file_path.string());
00683 #else
00684                 PION_LOG_ERROR(m_logger, "Unable to add file to cache: "
00685                                << file_path.file_string());
00686 #endif
00687                 return std::make_pair(m_cache_map.end(), false);
00688             }
00689         }
00690     }
00691 
00692     std::pair<CacheMap::iterator, bool> add_entry_result
00693         = m_cache_map.insert( std::make_pair(relative_path, cache_entry) );
00694 
00695     if (add_entry_result.second) {
00696 # if defined(BOOST_FILESYSTEM_VERSION) && BOOST_FILESYSTEM_VERSION >= 3
00697         PION_LOG_DEBUG(m_logger, "Added file to cache: "
00698                        << file_path.string());
00699 #else
00700         PION_LOG_DEBUG(m_logger, "Added file to cache: "
00701                        << file_path.file_string());
00702 #endif
00703     } else {
00704 # if defined(BOOST_FILESYSTEM_VERSION) && BOOST_FILESYSTEM_VERSION >= 3
00705         PION_LOG_ERROR(m_logger, "Unable to insert cache entry for file: "
00706                        << file_path.string());
00707 #else
00708         PION_LOG_ERROR(m_logger, "Unable to insert cache entry for file: "
00709                        << file_path.file_string());
00710 #endif
00711     }
00712 
00713     return add_entry_result;
00714 }
00715 
00716 std::string FileService::findMIMEType(const std::string& file_name) {
00717     // initialize m_mime_types if it hasn't been done already
00718     boost::call_once(FileService::createMIMETypes, m_mime_types_init_flag);
00719 
00720     // determine the file's extension
00721     std::string extension(file_name.substr(file_name.find_last_of('.') + 1));
00722     boost::algorithm::to_lower(extension);
00723 
00724     // search for the matching mime type and return the result
00725     MIMETypeMap::iterator i = m_mime_types_ptr->find(extension);
00726     return (i == m_mime_types_ptr->end() ? DEFAULT_MIME_TYPE : i->second);
00727 }
00728 
00729 void FileService::createMIMETypes(void) {
00730     // create the map
00731     static MIMETypeMap mime_types;
00732 
00733     // populate mime types
00734     mime_types["js"] = "text/javascript";
00735     mime_types["txt"] = "text/plain";
00736     mime_types["xml"] = "text/xml";
00737     mime_types["css"] = "text/css";
00738     mime_types["htm"] = "text/html";
00739     mime_types["html"] = "text/html";
00740     mime_types["xhtml"] = "text/html";
00741     mime_types["gif"] = "image/gif";
00742     mime_types["png"] = "image/png";
00743     mime_types["jpg"] = "image/jpeg";
00744     mime_types["jpeg"] = "image/jpeg";
00745     mime_types["svg"] = "image/svg+xml";
00746     mime_types["eof"] = "application/vnd.ms-fontobject";
00747     mime_types["otf"] = "application/x-font-opentype";
00748     mime_types["ttf"] = "application/x-font-ttf";
00749     mime_types["woff"] = "application/font-woff";
00750     // ...
00751 
00752     // set the static pointer
00753     m_mime_types_ptr = &mime_types;
00754 }
00755 
00756 
00757 // DiskFile member functions
00758 
00759 void DiskFile::update(void)
00760 {
00761     // set file_size and last_modified
00762     m_file_size = boost::numeric_cast<std::streamsize>(boost::filesystem::file_size( m_file_path ));
00763     m_last_modified = boost::filesystem::last_write_time( m_file_path );
00764     m_last_modified_string = http::types::get_date_string( m_last_modified );
00765 }
00766 
00767 void DiskFile::read(void)
00768 {
00769     // re-allocate storage buffer for the file's content
00770     m_file_content.reset(new char[m_file_size]);
00771 
00772     // open the file for reading
00773     boost::filesystem::ifstream file_stream;
00774     file_stream.open(m_file_path, std::ios::in | std::ios::binary);
00775 
00776     // read the file into memory
00777     if (!file_stream.is_open() || !file_stream.read(m_file_content.get(), m_file_size)) {
00778 # if defined(BOOST_FILESYSTEM_VERSION) && BOOST_FILESYSTEM_VERSION >= 3
00779         const std::string file_name = m_file_path.string();
00780 #else
00781         const std::string file_name = m_file_path.file_string();
00782 #endif
00783         BOOST_THROW_EXCEPTION( error::read_file() << error::errinfo_file_name(file_name) );
00784     }
00785 }
00786 
00787 bool DiskFile::checkUpdated(void)
00788 {
00789     // get current values
00790     std::streamsize cur_size = boost::numeric_cast<std::streamsize>(boost::filesystem::file_size( m_file_path ));
00791     time_t cur_modified = boost::filesystem::last_write_time( m_file_path );
00792 
00793     // check if file has not been updated
00794     if (cur_modified == m_last_modified && cur_size == m_file_size)
00795         return false;
00796 
00797     // file has been updated
00798 
00799     // update file_size and last_modified timestamp
00800     m_file_size = cur_size;
00801     m_last_modified = cur_modified;
00802     m_last_modified_string = http::types::get_date_string( m_last_modified );
00803 
00804     // read new contents
00805     read();
00806 
00807     return true;
00808 }
00809 
00810 
00811 // DiskFileSender member functions
00812 
00813 DiskFileSender::DiskFileSender(DiskFile& file, pion::http::request_ptr& http_request_ptr,
00814                                pion::tcp::connection_ptr& tcp_conn,
00815                                unsigned long max_chunk_size)
00816     : m_logger(PION_GET_LOGGER("pion.FileService.DiskFileSender")), m_disk_file(file),
00817     m_writer(pion::http::response_writer::create(tcp_conn, *http_request_ptr, boost::bind(&tcp::connection::finish, tcp_conn))),
00818     m_max_chunk_size(max_chunk_size), m_file_bytes_to_send(0), m_bytes_sent(0)
00819 {
00820 # if defined(BOOST_FILESYSTEM_VERSION) && BOOST_FILESYSTEM_VERSION >= 3
00821     PION_LOG_DEBUG(m_logger, "Preparing to send file"
00822                    << (m_disk_file.hasFileContent() ? " (cached): " : ": ")
00823                    << m_disk_file.getFilePath().string());
00824 #else
00825     PION_LOG_DEBUG(m_logger, "Preparing to send file"
00826                    << (m_disk_file.hasFileContent() ? " (cached): " : ": ")
00827                    << m_disk_file.getFilePath().file_string());
00828 #endif
00829 
00830         // set the Content-Type HTTP header using the file's MIME type
00831     m_writer->get_response().set_content_type(m_disk_file.getMimeType());
00832 
00833     // set Last-Modified header to enable client-side caching
00834     m_writer->get_response().add_header(http::types::HEADER_LAST_MODIFIED,
00835                                       m_disk_file.getLastModifiedString());
00836 
00837     // use "200 OK" HTTP response
00838     m_writer->get_response().set_status_code(http::types::RESPONSE_CODE_OK);
00839     m_writer->get_response().set_status_message(http::types::RESPONSE_MESSAGE_OK);
00840 }
00841 
00842 void DiskFileSender::send(void)
00843 {
00844     // check if we have nothing to send (send 0 byte response content)
00845     if (m_disk_file.getFileSize() <= m_bytes_sent) {
00846         m_writer->send();
00847         return;
00848     }
00849 
00850     // calculate the number of bytes to send (m_file_bytes_to_send)
00851     m_file_bytes_to_send = m_disk_file.getFileSize() - m_bytes_sent;
00852     if (m_max_chunk_size > 0 && m_file_bytes_to_send > m_max_chunk_size)
00853         m_file_bytes_to_send = m_max_chunk_size;
00854 
00855     // get the content to send (file_content_ptr)
00856     char *file_content_ptr;
00857 
00858     if (m_disk_file.hasFileContent()) {
00859 
00860         // the entire file IS cached in memory (m_disk_file.file_content)
00861         file_content_ptr = m_disk_file.getFileContent() + m_bytes_sent;
00862 
00863     } else {
00864         // the file is not cached in memory
00865 
00866         // check if the file has been opened yet
00867         if (! m_file_stream.is_open()) {
00868             // open the file for reading
00869             m_file_stream.open(m_disk_file.getFilePath(), std::ios::in | std::ios::binary);
00870             if (! m_file_stream.is_open()) {
00871 # if defined(BOOST_FILESYSTEM_VERSION) && BOOST_FILESYSTEM_VERSION >= 3
00872                 PION_LOG_ERROR(m_logger, "Unable to open file: "
00873                                << m_disk_file.getFilePath().string());
00874 #else
00875                 PION_LOG_ERROR(m_logger, "Unable to open file: "
00876                                << m_disk_file.getFilePath().file_string());
00877 #endif
00878                 return;
00879             }
00880         }
00881 
00882         // check if the content buffer was initialized yet
00883         if (! m_content_buf) {
00884             // allocate memory for the new content buffer
00885             m_content_buf.reset(new char[m_file_bytes_to_send]);
00886         }
00887         file_content_ptr = m_content_buf.get();
00888 
00889         // read a block of data from the file into the content buffer
00890         if (! m_file_stream.read(m_content_buf.get(), m_file_bytes_to_send)) {
00891             if (m_file_stream.gcount() > 0) {
00892 # if defined(BOOST_FILESYSTEM_VERSION) && BOOST_FILESYSTEM_VERSION >= 3
00893                 PION_LOG_ERROR(m_logger, "File size inconsistency: "
00894                                << m_disk_file.getFilePath().string());
00895 #else
00896                 PION_LOG_ERROR(m_logger, "File size inconsistency: "
00897                                << m_disk_file.getFilePath().file_string());
00898 #endif
00899             } else {
00900 # if defined(BOOST_FILESYSTEM_VERSION) && BOOST_FILESYSTEM_VERSION >= 3
00901                 PION_LOG_ERROR(m_logger, "Unable to read file: "
00902                                << m_disk_file.getFilePath().string());
00903 #else
00904                 PION_LOG_ERROR(m_logger, "Unable to read file: "
00905                                << m_disk_file.getFilePath().file_string());
00906 #endif
00907             }
00908             return;
00909         }
00910     }
00911 
00912     // send the content
00913     m_writer->write_no_copy(file_content_ptr, m_file_bytes_to_send);
00914 
00915     if (m_bytes_sent + m_file_bytes_to_send >= m_disk_file.getFileSize()) {
00916         // this is the last piece of data to send
00917         if (m_bytes_sent > 0) {
00918             // send last chunk in a series
00919             m_writer->send_final_chunk(boost::bind(&DiskFileSender::handle_write,
00920                                                  shared_from_this(),
00921                                                  boost::asio::placeholders::error,
00922                                                  boost::asio::placeholders::bytes_transferred));
00923         } else {
00924             // sending entire file at once
00925             m_writer->send(boost::bind(&DiskFileSender::handle_write,
00926                                        shared_from_this(),
00927                                        boost::asio::placeholders::error,
00928                                        boost::asio::placeholders::bytes_transferred));
00929         }
00930     } else {
00931         // there will be more data -> send a chunk
00932         m_writer->send_chunk(boost::bind(&DiskFileSender::handle_write,
00933                                         shared_from_this(),
00934                                         boost::asio::placeholders::error,
00935                                         boost::asio::placeholders::bytes_transferred));
00936     }
00937 }
00938 
00939 void DiskFileSender::handle_write(const boost::system::error_code& write_error,
00940                                  std::size_t bytes_written)
00941 {
00942     bool finished_sending = true;
00943 
00944     if (write_error) {
00945         // encountered error sending response data
00946         m_writer->get_connection()->set_lifecycle(tcp::connection::LIFECYCLE_CLOSE); // make sure it will get closed
00947         PION_LOG_WARN(m_logger, "Error sending file (" << write_error.message() << ')');
00948     } else {
00949         // response data sent OK
00950 
00951         // use m_file_bytes_to_send instead of bytes_written; bytes_written
00952         // includes bytes for HTTP headers and chunking headers
00953         m_bytes_sent += m_file_bytes_to_send;
00954 
00955         if (m_bytes_sent >= m_disk_file.getFileSize()) {
00956             // finished sending
00957             PION_LOG_DEBUG(m_logger, "Sent "
00958                            << (m_file_bytes_to_send < m_disk_file.getFileSize() ? "file chunk" : "complete file")
00959                            << " of " << m_file_bytes_to_send << " bytes (finished"
00960                            << (m_writer->get_connection()->get_keep_alive() ? ", keeping alive)" : ", closing)") );
00961         } else {
00962             // NOT finished sending
00963             PION_LOG_DEBUG(m_logger, "Sent file chunk of " << m_file_bytes_to_send << " bytes");
00964             finished_sending = false;
00965             m_writer->clear();
00966         }
00967     }
00968 
00969     if (finished_sending) {
00970         // connection::finish() calls tcp::server::finish_connection, which will either:
00971         // a) call http::server::handle_connection again if keep-alive is true; or,
00972         // b) close the socket and remove it from the server's connection pool
00973         m_writer->get_connection()->finish();
00974     } else {
00975         send();
00976     }
00977 }
00978 
00979 
00980 }   // end namespace plugins
00981 }   // end namespace pion
00982 
00983 
00985 extern "C" PION_PLUGIN pion::plugins::FileService *pion_create_FileService(void)
00986 {
00987     return new pion::plugins::FileService();
00988 }
00989 
00991 extern "C" PION_PLUGIN void pion_destroy_FileService(pion::plugins::FileService *service_ptr)
00992 {
00993     delete service_ptr;
00994 }