00001
00002
00003
00004
00005
00006
00007
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 {
00028 namespace plugins {
00029
00030
00031
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;
00037 const unsigned long FileService::DEFAULT_MAX_CHUNK_SIZE = 0;
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
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
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
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
00120 const std::string relative_path(get_relative_resource(http_request_ptr->get_resource()));
00121
00122
00123 boost::filesystem::path file_path;
00124 if (relative_path.empty()) {
00125
00126
00127 if (m_file.empty()) {
00128
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
00138
00139 if (m_directory.empty()) {
00140
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
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
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
00213 enum ResponseType {
00214 RESPONSE_UNDEFINED,
00215 RESPONSE_OK,
00216 RESPONSE_HEAD_OK,
00217 RESPONSE_NOT_FOUND,
00218 RESPONSE_NOT_MODIFIED
00219 } response_type = RESPONSE_UNDEFINED;
00220
00221
00222 DiskFile response_file;
00223
00224
00225 const std::string if_modified_since(http_request_ptr->get_header(http::types::HEADER_IF_MODIFIED_SINCE));
00226
00227
00228
00229 if (m_cache_setting > 0 || m_scan_setting > 0) {
00230
00231
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
00237
00238 if (m_scan_setting == 1 || m_scan_setting == 3) {
00239
00240
00241
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
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
00258
00259
00260 response_file.setFilePath(cache_itr->second.getFilePath());
00261 response_file.setMimeType(cache_itr->second.getMimeType());
00262
00263
00264 response_file.update();
00265
00266
00267 if (response_file.getLastModifiedString() == if_modified_since) {
00268
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
00282
00283
00284 bool cache_was_updated = false;
00285
00286 if (cache_itr->second.getLastModified() == 0) {
00287
00288
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
00293 cache_itr->second.read();
00294 } else {
00295 cache_itr->second.resetFileContent();
00296 }
00297
00298 } else if (m_cache_setting == 1) {
00299
00300
00301 cache_was_updated = cache_itr->second.checkUpdated();
00302
00303 }
00304
00305
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
00315 response_file = cache_itr->second;
00316
00317
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
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
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
00351 response_file.update();
00352
00353
00354 if (response_file.getLastModifiedString() == if_modified_since) {
00355
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
00364 response_file.read();
00365 }
00366
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
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
00385
00386
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
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
00400 BOOST_ASSERT(false);
00401 break;
00402 case RESPONSE_NOT_MODIFIED:
00403
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
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
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
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
00452
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
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
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
00597 if (m_scan_setting != 0) {
00598
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
00605 if (! m_file.empty()) {
00606
00607
00608 addCacheEntry("", m_file, m_scan_setting == 1);
00609 }
00610
00611
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
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
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
00642
00643
00644 scanDirectory(*itr);
00645
00646 } else {
00647
00648
00649
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
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
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
00718 boost::call_once(FileService::createMIMETypes, m_mime_types_init_flag);
00719
00720
00721 std::string extension(file_name.substr(file_name.find_last_of('.') + 1));
00722 boost::algorithm::to_lower(extension);
00723
00724
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
00731 static MIMETypeMap mime_types;
00732
00733
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
00753 m_mime_types_ptr = &mime_types;
00754 }
00755
00756
00757
00758
00759 void DiskFile::update(void)
00760 {
00761
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
00770 m_file_content.reset(new char[m_file_size]);
00771
00772
00773 boost::filesystem::ifstream file_stream;
00774 file_stream.open(m_file_path, std::ios::in | std::ios::binary);
00775
00776
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
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
00794 if (cur_modified == m_last_modified && cur_size == m_file_size)
00795 return false;
00796
00797
00798
00799
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
00805 read();
00806
00807 return true;
00808 }
00809
00810
00811
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
00831 m_writer->get_response().set_content_type(m_disk_file.getMimeType());
00832
00833
00834 m_writer->get_response().add_header(http::types::HEADER_LAST_MODIFIED,
00835 m_disk_file.getLastModifiedString());
00836
00837
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
00845 if (m_disk_file.getFileSize() <= m_bytes_sent) {
00846 m_writer->send();
00847 return;
00848 }
00849
00850
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
00856 char *file_content_ptr;
00857
00858 if (m_disk_file.hasFileContent()) {
00859
00860
00861 file_content_ptr = m_disk_file.getFileContent() + m_bytes_sent;
00862
00863 } else {
00864
00865
00866
00867 if (! m_file_stream.is_open()) {
00868
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
00883 if (! m_content_buf) {
00884
00885 m_content_buf.reset(new char[m_file_bytes_to_send]);
00886 }
00887 file_content_ptr = m_content_buf.get();
00888
00889
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
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
00917 if (m_bytes_sent > 0) {
00918
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
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
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
00946 m_writer->get_connection()->set_lifecycle(tcp::connection::LIFECYCLE_CLOSE);
00947 PION_LOG_WARN(m_logger, "Error sending file (" << write_error.message() << ')');
00948 } else {
00949
00950
00951
00952
00953 m_bytes_sent += m_file_bytes_to_send;
00954
00955 if (m_bytes_sent >= m_disk_file.getFileSize()) {
00956
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
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
00971
00972
00973 m_writer->get_connection()->finish();
00974 } else {
00975 send();
00976 }
00977 }
00978
00979
00980 }
00981 }
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 }