# net/services/FileService.cpp

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