- Main Page
- Classes
- Files

- File List

# 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 }