pion-net
4.0.9
|
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.string(); 00141 if (file_string.find(m_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().filename().native() )); 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.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().string() ); 00618 std::string relative_path( file_path_string.substr(m_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.filename().native())); 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.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.string()); 00651 } else { 00652 PION_LOG_ERROR(m_logger, "Unable to insert cache entry for file: " 00653 << file_path.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.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().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().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().string()); 00815 } else { 00816 PION_LOG_ERROR(m_logger, "Unable to read file: " 00817 << m_disk_file.getFilePath().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 }