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