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 }

Generated on Fri Apr 30 14:48:53 2010 for pion-net by  doxygen 1.4.7