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 PION_LOG_DEBUG(m_logger, (cache_was_updated ? "Updated" : "Using")
00318 << " cache entry for request ("
00319 << get_resource() << "): " << relative_path);
00320 }
00321 }
00322 }
00323
00324 if (response_type == RESPONSE_UNDEFINED) {
00325
00326 if (! boost::filesystem::exists(file_path)) {
00327 PION_LOG_WARN(m_logger, "File not found ("
00328 << get_resource() << "): " << relative_path);
00329 sendNotFoundResponse(http_request_ptr, tcp_conn);
00330 return;
00331 }
00332
00333 response_file.setFilePath(file_path);
00334
00335 PION_LOG_DEBUG(m_logger, "Found file for request ("
00336 << get_resource() << "): " << relative_path);
00337
00338
00339 # if defined(BOOST_FILESYSTEM_VERSION) && BOOST_FILESYSTEM_VERSION >= 3
00340 response_file.setMimeType(findMIMEType( response_file.getFilePath().filename().string()));
00341 #else
00342 response_file.setMimeType(findMIMEType( response_file.getFilePath().leaf() ));
00343 #endif
00344
00345
00346 response_file.update();
00347
00348
00349 if (response_file.getLastModifiedString() == if_modified_since) {
00350
00351 response_type = RESPONSE_NOT_MODIFIED;
00352 } else if (http_request_ptr->get_method() == http::types::REQUEST_METHOD_HEAD) {
00353 response_type = RESPONSE_HEAD_OK;
00354 } else {
00355 response_type = RESPONSE_OK;
00356 if (m_cache_setting != 0) {
00357 if (m_max_cache_size==0 || response_file.getFileSize() <= m_max_cache_size) {
00358
00359 response_file.read();
00360 }
00361
00362 PION_LOG_DEBUG(m_logger, "Adding cache entry for request ("
00363 << get_resource() << "): " << relative_path);
00364 boost::mutex::scoped_lock cache_lock(m_cache_mutex);
00365 m_cache_map.insert( std::make_pair(relative_path, response_file) );
00366 }
00367 }
00368 }
00369
00370 if (response_type == RESPONSE_OK) {
00371
00372 DiskFileSenderPtr sender_ptr(DiskFileSender::create(response_file,
00373 http_request_ptr, tcp_conn,
00374 m_max_chunk_size));
00375 sender_ptr->send();
00376 } else if (response_type == RESPONSE_NOT_FOUND) {
00377 sendNotFoundResponse(http_request_ptr, tcp_conn);
00378 } else {
00379
00380
00381
00382 http::response_writer_ptr writer(http::response_writer::create(tcp_conn, *http_request_ptr,
00383 boost::bind(&tcp::connection::finish, tcp_conn)));
00384 writer->get_response().set_content_type(response_file.getMimeType());
00385
00386
00387 writer->get_response().add_header(http::types::HEADER_LAST_MODIFIED,
00388 response_file.getLastModifiedString());
00389
00390 switch(response_type) {
00391 case RESPONSE_UNDEFINED:
00392 case RESPONSE_NOT_FOUND:
00393 case RESPONSE_OK:
00394
00395 BOOST_ASSERT(false);
00396 break;
00397 case RESPONSE_NOT_MODIFIED:
00398
00399 writer->get_response().set_status_code(http::types::RESPONSE_CODE_NOT_MODIFIED);
00400 writer->get_response().set_status_message(http::types::RESPONSE_MESSAGE_NOT_MODIFIED);
00401 break;
00402 case RESPONSE_HEAD_OK:
00403
00404 writer->get_response().set_status_code(http::types::RESPONSE_CODE_OK);
00405 writer->get_response().set_status_message(http::types::RESPONSE_MESSAGE_OK);
00406 break;
00407 }
00408
00409
00410 writer->send();
00411 }
00412 } else if (http_request_ptr->get_method() == http::types::REQUEST_METHOD_POST
00413 || http_request_ptr->get_method() == http::types::REQUEST_METHOD_PUT
00414 || http_request_ptr->get_method() == http::types::REQUEST_METHOD_DELETE)
00415 {
00416
00417 if (!m_writable) {
00418 static const std::string NOT_ALLOWED_HTML_START =
00419 "<html><head>\n"
00420 "<title>405 Method Not Allowed</title>\n"
00421 "</head><body>\n"
00422 "<h1>Not Allowed</h1>\n"
00423 "<p>The requested method ";
00424 static const std::string NOT_ALLOWED_HTML_FINISH =
00425 " is not allowed on this server.</p>\n"
00426 "</body></html>\n";
00427 http::response_writer_ptr writer(http::response_writer::create(tcp_conn, *http_request_ptr,
00428 boost::bind(&tcp::connection::finish, tcp_conn)));
00429 writer->get_response().set_status_code(http::types::RESPONSE_CODE_METHOD_NOT_ALLOWED);
00430 writer->get_response().set_status_message(http::types::RESPONSE_MESSAGE_METHOD_NOT_ALLOWED);
00431 writer->write_no_copy(NOT_ALLOWED_HTML_START);
00432 writer << algorithm::xml_encode(http_request_ptr->get_method());
00433 writer->write_no_copy(NOT_ALLOWED_HTML_FINISH);
00434 writer->get_response().add_header("Allow", "GET, HEAD");
00435 writer->send();
00436 } else {
00437 http::response_writer_ptr writer(http::response_writer::create(tcp_conn, *http_request_ptr,
00438 boost::bind(&tcp::connection::finish, tcp_conn)));
00439 if (http_request_ptr->get_method() == http::types::REQUEST_METHOD_POST
00440 || http_request_ptr->get_method() == http::types::REQUEST_METHOD_PUT)
00441 {
00442 if (boost::filesystem::exists(file_path)) {
00443 writer->get_response().set_status_code(http::types::RESPONSE_CODE_NO_CONTENT);
00444 writer->get_response().set_status_message(http::types::RESPONSE_MESSAGE_NO_CONTENT);
00445 } else {
00446
00447
00448 if (!boost::filesystem::exists(file_path.branch_path())) {
00449 static const std::string NOT_FOUND_HTML_START =
00450 "<html><head>\n"
00451 "<title>404 Not Found</title>\n"
00452 "</head><body>\n"
00453 "<h1>Not Found</h1>\n"
00454 "<p>The directory of the requested URL ";
00455 static const std::string NOT_FOUND_HTML_FINISH =
00456 " was not found on this server.</p>\n"
00457 "</body></html>\n";
00458 writer->get_response().set_status_code(http::types::RESPONSE_CODE_NOT_FOUND);
00459 writer->get_response().set_status_message(http::types::RESPONSE_MESSAGE_NOT_FOUND);
00460 writer->write_no_copy(NOT_FOUND_HTML_START);
00461 writer << algorithm::xml_encode(http_request_ptr->get_resource());
00462 writer->write_no_copy(NOT_FOUND_HTML_FINISH);
00463 writer->send();
00464 return;
00465 }
00466 static const std::string CREATED_HTML_START =
00467 "<html><head>\n"
00468 "<title>201 Created</title>\n"
00469 "</head><body>\n"
00470 "<h1>Created</h1>\n"
00471 "<p>";
00472 static const std::string CREATED_HTML_FINISH =
00473 "</p>\n"
00474 "</body></html>\n";
00475 writer->get_response().set_status_code(http::types::RESPONSE_CODE_CREATED);
00476 writer->get_response().set_status_message(http::types::RESPONSE_MESSAGE_CREATED);
00477 writer->get_response().add_header(http::types::HEADER_LOCATION, http_request_ptr->get_resource());
00478 writer->write_no_copy(CREATED_HTML_START);
00479 writer << algorithm::xml_encode(http_request_ptr->get_resource());
00480 writer->write_no_copy(CREATED_HTML_FINISH);
00481 }
00482 std::ios_base::openmode mode = http_request_ptr->get_method() == http::types::REQUEST_METHOD_POST?
00483 std::ios::app : std::ios::out;
00484 boost::filesystem::ofstream file_stream(file_path, mode);
00485 file_stream.write(http_request_ptr->get_content(), http_request_ptr->get_content_length());
00486 file_stream.close();
00487 if (!boost::filesystem::exists(file_path)) {
00488 static const std::string PUT_FAILED_HTML_START =
00489 "<html><head>\n"
00490 "<title>500 Server Error</title>\n"
00491 "</head><body>\n"
00492 "<h1>Server Error</h1>\n"
00493 "<p>Error writing to ";
00494 static const std::string PUT_FAILED_HTML_FINISH =
00495 ".</p>\n"
00496 "</body></html>\n";
00497 writer->get_response().set_status_code(http::types::RESPONSE_CODE_SERVER_ERROR);
00498 writer->get_response().set_status_message(http::types::RESPONSE_MESSAGE_SERVER_ERROR);
00499 writer->write_no_copy(PUT_FAILED_HTML_START);
00500 writer << algorithm::xml_encode(http_request_ptr->get_resource());
00501 writer->write_no_copy(PUT_FAILED_HTML_FINISH);
00502 }
00503 writer->send();
00504 } else if (http_request_ptr->get_method() == http::types::REQUEST_METHOD_DELETE) {
00505 if (!boost::filesystem::exists(file_path)) {
00506 sendNotFoundResponse(http_request_ptr, tcp_conn);
00507 } else {
00508 try {
00509 boost::filesystem::remove(file_path);
00510 writer->get_response().set_status_code(http::types::RESPONSE_CODE_NO_CONTENT);
00511 writer->get_response().set_status_message(http::types::RESPONSE_MESSAGE_NO_CONTENT);
00512 writer->send();
00513 } catch (std::exception& e) {
00514 static const std::string DELETE_FAILED_HTML_START =
00515 "<html><head>\n"
00516 "<title>500 Server Error</title>\n"
00517 "</head><body>\n"
00518 "<h1>Server Error</h1>\n"
00519 "<p>Could not delete ";
00520 static const std::string DELETE_FAILED_HTML_FINISH =
00521 ".</p>\n"
00522 "</body></html>\n";
00523 writer->get_response().set_status_code(http::types::RESPONSE_CODE_SERVER_ERROR);
00524 writer->get_response().set_status_message(http::types::RESPONSE_MESSAGE_SERVER_ERROR);
00525 writer->write_no_copy(DELETE_FAILED_HTML_START);
00526 writer << algorithm::xml_encode(http_request_ptr->get_resource())
00527 << ".</p><p>"
00528 << boost::diagnostic_information(e);
00529 writer->write_no_copy(DELETE_FAILED_HTML_FINISH);
00530 writer->send();
00531 }
00532 }
00533 } else {
00534
00535 writer->get_response().set_status_code(http::types::RESPONSE_CODE_SERVER_ERROR);
00536 writer->get_response().set_status_message(http::types::RESPONSE_MESSAGE_SERVER_ERROR);
00537 writer->send();
00538 }
00539 }
00540 }
00541
00542 else {
00543 static const std::string NOT_IMPLEMENTED_HTML_START =
00544 "<html><head>\n"
00545 "<title>501 Not Implemented</title>\n"
00546 "</head><body>\n"
00547 "<h1>Not Implemented</h1>\n"
00548 "<p>The requested method ";
00549 static const std::string NOT_IMPLEMENTED_HTML_FINISH =
00550 " is not implemented on this server.</p>\n"
00551 "</body></html>\n";
00552 http::response_writer_ptr writer(http::response_writer::create(tcp_conn, *http_request_ptr,
00553 boost::bind(&tcp::connection::finish, tcp_conn)));
00554 writer->get_response().set_status_code(http::types::RESPONSE_CODE_NOT_IMPLEMENTED);
00555 writer->get_response().set_status_message(http::types::RESPONSE_MESSAGE_NOT_IMPLEMENTED);
00556 writer->write_no_copy(NOT_IMPLEMENTED_HTML_START);
00557 writer << algorithm::xml_encode(http_request_ptr->get_method());
00558 writer->write_no_copy(NOT_IMPLEMENTED_HTML_FINISH);
00559 writer->send();
00560 }
00561 }
00562
00563 void FileService::sendNotFoundResponse(http::request_ptr& http_request_ptr,
00564 tcp::connection_ptr& tcp_conn)
00565 {
00566 static const std::string NOT_FOUND_HTML_START =
00567 "<html><head>\n"
00568 "<title>404 Not Found</title>\n"
00569 "</head><body>\n"
00570 "<h1>Not Found</h1>\n"
00571 "<p>The requested URL ";
00572 static const std::string NOT_FOUND_HTML_FINISH =
00573 " was not found on this server.</p>\n"
00574 "</body></html>\n";
00575 http::response_writer_ptr writer(http::response_writer::create(tcp_conn, *http_request_ptr,
00576 boost::bind(&tcp::connection::finish, tcp_conn)));
00577 writer->get_response().set_status_code(http::types::RESPONSE_CODE_NOT_FOUND);
00578 writer->get_response().set_status_message(http::types::RESPONSE_MESSAGE_NOT_FOUND);
00579 if (http_request_ptr->get_method() != http::types::REQUEST_METHOD_HEAD) {
00580 writer->write_no_copy(NOT_FOUND_HTML_START);
00581 writer << algorithm::xml_encode(http_request_ptr->get_resource());
00582 writer->write_no_copy(NOT_FOUND_HTML_FINISH);
00583 }
00584 writer->send();
00585 }
00586
00587 void FileService::start(void)
00588 {
00589 PION_LOG_DEBUG(m_logger, "Starting up resource (" << get_resource() << ')');
00590
00591
00592 if (m_scan_setting != 0) {
00593
00594 if (m_cache_setting == 0 && m_scan_setting > 1)
00595 m_cache_setting = 1;
00596
00597 boost::mutex::scoped_lock cache_lock(m_cache_mutex);
00598
00599
00600 if (! m_file.empty()) {
00601
00602
00603 addCacheEntry("", m_file, m_scan_setting == 1);
00604 }
00605
00606
00607 if (! m_directory.empty())
00608 scanDirectory(m_directory);
00609 }
00610 }
00611
00612 void FileService::stop(void)
00613 {
00614 PION_LOG_DEBUG(m_logger, "Shutting down resource (" << get_resource() << ')');
00615
00616 boost::mutex::scoped_lock cache_lock(m_cache_mutex);
00617 m_cache_map.clear();
00618 }
00619
00620 void FileService::scanDirectory(const boost::filesystem::path& dir_path)
00621 {
00622 # if defined(BOOST_FILESYSTEM_VERSION) && BOOST_FILESYSTEM_VERSION >= 3
00623 PION_LOG_DEBUG(m_logger, "Scanning directory (" << get_resource() << "): "
00624 << dir_path.string());
00625 #else
00626 PION_LOG_DEBUG(m_logger, "Scanning directory (" << get_resource() << "): "
00627 << dir_path.directory_string());
00628 #endif
00629
00630
00631 boost::filesystem::directory_iterator end_itr;
00632 for ( boost::filesystem::directory_iterator itr( dir_path );
00633 itr != end_itr; ++itr )
00634 {
00635 if ( boost::filesystem::is_directory(*itr) ) {
00636
00637
00638
00639 scanDirectory(*itr);
00640
00641 } else {
00642
00643
00644
00645 # if defined(BOOST_FILESYSTEM_VERSION) && BOOST_FILESYSTEM_VERSION >= 3
00646 std::string file_path_string( itr->path().string() );
00647 std::string relative_path( file_path_string.substr(m_directory.string().size() + 1) );
00648 #else
00649 std::string file_path_string( itr->path().file_string() );
00650 std::string relative_path( file_path_string.substr(m_directory.directory_string().size() + 1) );
00651 #endif
00652
00653
00654 addCacheEntry(relative_path, *itr, m_scan_setting == 1);
00655 }
00656 }
00657 }
00658
00659 std::pair<FileService::CacheMap::iterator, bool>
00660 FileService::addCacheEntry(const std::string& relative_path,
00661 const boost::filesystem::path& file_path,
00662 const bool placeholder)
00663 {
00664 # if defined(BOOST_FILESYSTEM_VERSION) && BOOST_FILESYSTEM_VERSION >= 3
00665 DiskFile cache_entry(file_path, NULL, 0, 0, findMIMEType(file_path.filename().string()));
00666 #else
00667 DiskFile cache_entry(file_path, NULL, 0, 0, findMIMEType(file_path.leaf()));
00668 #endif
00669 if (! placeholder) {
00670 cache_entry.update();
00671
00672 if (m_max_cache_size==0 || cache_entry.getFileSize() <= m_max_cache_size) {
00673 try { cache_entry.read(); }
00674 catch (std::exception&) {
00675 # if defined(BOOST_FILESYSTEM_VERSION) && BOOST_FILESYSTEM_VERSION >= 3
00676 PION_LOG_ERROR(m_logger, "Unable to add file to cache: "
00677 << file_path.string());
00678 #else
00679 PION_LOG_ERROR(m_logger, "Unable to add file to cache: "
00680 << file_path.file_string());
00681 #endif
00682 return std::make_pair(m_cache_map.end(), false);
00683 }
00684 }
00685 }
00686
00687 std::pair<CacheMap::iterator, bool> add_entry_result
00688 = m_cache_map.insert( std::make_pair(relative_path, cache_entry) );
00689
00690 if (add_entry_result.second) {
00691 # if defined(BOOST_FILESYSTEM_VERSION) && BOOST_FILESYSTEM_VERSION >= 3
00692 PION_LOG_DEBUG(m_logger, "Added file to cache: "
00693 << file_path.string());
00694 #else
00695 PION_LOG_DEBUG(m_logger, "Added file to cache: "
00696 << file_path.file_string());
00697 #endif
00698 } else {
00699 # if defined(BOOST_FILESYSTEM_VERSION) && BOOST_FILESYSTEM_VERSION >= 3
00700 PION_LOG_ERROR(m_logger, "Unable to insert cache entry for file: "
00701 << file_path.string());
00702 #else
00703 PION_LOG_ERROR(m_logger, "Unable to insert cache entry for file: "
00704 << file_path.file_string());
00705 #endif
00706 }
00707
00708 return add_entry_result;
00709 }
00710
00711 std::string FileService::findMIMEType(const std::string& file_name) {
00712
00713 boost::call_once(FileService::createMIMETypes, m_mime_types_init_flag);
00714
00715
00716 std::string extension(file_name.substr(file_name.find_last_of('.') + 1));
00717 boost::algorithm::to_lower(extension);
00718
00719
00720 MIMETypeMap::iterator i = m_mime_types_ptr->find(extension);
00721 return (i == m_mime_types_ptr->end() ? DEFAULT_MIME_TYPE : i->second);
00722 }
00723
00724 void FileService::createMIMETypes(void) {
00725
00726 static MIMETypeMap mime_types;
00727
00728
00729 mime_types["js"] = "text/javascript";
00730 mime_types["txt"] = "text/plain";
00731 mime_types["xml"] = "text/xml";
00732 mime_types["css"] = "text/css";
00733 mime_types["htm"] = "text/html";
00734 mime_types["html"] = "text/html";
00735 mime_types["xhtml"] = "text/html";
00736 mime_types["gif"] = "image/gif";
00737 mime_types["png"] = "image/png";
00738 mime_types["jpg"] = "image/jpeg";
00739 mime_types["jpeg"] = "image/jpeg";
00740 mime_types["svg"] = "image/svg+xml";
00741 mime_types["eof"] = "application/vnd.ms-fontobject";
00742 mime_types["otf"] = "application/x-font-opentype";
00743 mime_types["ttf"] = "application/x-font-ttf";
00744 mime_types["woff"] = "application/font-woff";
00745
00746
00747
00748 m_mime_types_ptr = &mime_types;
00749 }
00750
00751
00752
00753
00754 void DiskFile::update(void)
00755 {
00756
00757 m_file_size = boost::numeric_cast<std::streamsize>(boost::filesystem::file_size( m_file_path ));
00758 m_last_modified = boost::filesystem::last_write_time( m_file_path );
00759 m_last_modified_string = http::types::get_date_string( m_last_modified );
00760 }
00761
00762 void DiskFile::read(void)
00763 {
00764
00765 m_file_content.reset(new char[m_file_size]);
00766
00767
00768 boost::filesystem::ifstream file_stream;
00769 file_stream.open(m_file_path, std::ios::in | std::ios::binary);
00770
00771
00772 if (!file_stream.is_open() || !file_stream.read(m_file_content.get(), m_file_size)) {
00773 # if defined(BOOST_FILESYSTEM_VERSION) && BOOST_FILESYSTEM_VERSION >= 3
00774 const std::string file_name = m_file_path.string();
00775 #else
00776 const std::string file_name = m_file_path.file_string();
00777 #endif
00778 BOOST_THROW_EXCEPTION( error::read_file() << error::errinfo_file_name(file_name) );
00779 }
00780 }
00781
00782 bool DiskFile::checkUpdated(void)
00783 {
00784
00785 std::streamsize cur_size = boost::numeric_cast<std::streamsize>(boost::filesystem::file_size( m_file_path ));
00786 time_t cur_modified = boost::filesystem::last_write_time( m_file_path );
00787
00788
00789 if (cur_modified == m_last_modified && cur_size == m_file_size)
00790 return false;
00791
00792
00793
00794
00795 m_file_size = cur_size;
00796 m_last_modified = cur_modified;
00797 m_last_modified_string = http::types::get_date_string( m_last_modified );
00798
00799
00800 read();
00801
00802 return true;
00803 }
00804
00805
00806
00807
00808 DiskFileSender::DiskFileSender(DiskFile& file, pion::http::request_ptr& http_request_ptr,
00809 pion::tcp::connection_ptr& tcp_conn,
00810 unsigned long max_chunk_size)
00811 : m_logger(PION_GET_LOGGER("pion.FileService.DiskFileSender")), m_disk_file(file),
00812 m_writer(pion::http::response_writer::create(tcp_conn, *http_request_ptr, boost::bind(&tcp::connection::finish, tcp_conn))),
00813 m_max_chunk_size(max_chunk_size), m_file_bytes_to_send(0), m_bytes_sent(0)
00814 {
00815 # if defined(BOOST_FILESYSTEM_VERSION) && BOOST_FILESYSTEM_VERSION >= 3
00816 PION_LOG_DEBUG(m_logger, "Preparing to send file"
00817 << (m_disk_file.hasFileContent() ? " (cached): " : ": ")
00818 << m_disk_file.getFilePath().string());
00819 #else
00820 PION_LOG_DEBUG(m_logger, "Preparing to send file"
00821 << (m_disk_file.hasFileContent() ? " (cached): " : ": ")
00822 << m_disk_file.getFilePath().file_string());
00823 #endif
00824
00825
00826 m_writer->get_response().set_content_type(m_disk_file.getMimeType());
00827
00828
00829 m_writer->get_response().add_header(http::types::HEADER_LAST_MODIFIED,
00830 m_disk_file.getLastModifiedString());
00831
00832
00833 m_writer->get_response().set_status_code(http::types::RESPONSE_CODE_OK);
00834 m_writer->get_response().set_status_message(http::types::RESPONSE_MESSAGE_OK);
00835 }
00836
00837 void DiskFileSender::send(void)
00838 {
00839
00840 if (m_disk_file.getFileSize() <= m_bytes_sent) {
00841 m_writer->send();
00842 return;
00843 }
00844
00845
00846 m_file_bytes_to_send = m_disk_file.getFileSize() - m_bytes_sent;
00847 if (m_max_chunk_size > 0 && m_file_bytes_to_send > m_max_chunk_size)
00848 m_file_bytes_to_send = m_max_chunk_size;
00849
00850
00851 char *file_content_ptr;
00852
00853 if (m_disk_file.hasFileContent()) {
00854
00855
00856 file_content_ptr = m_disk_file.getFileContent() + m_bytes_sent;
00857
00858 } else {
00859
00860
00861
00862 if (! m_file_stream.is_open()) {
00863
00864 m_file_stream.open(m_disk_file.getFilePath(), std::ios::in | std::ios::binary);
00865 if (! m_file_stream.is_open()) {
00866 # if defined(BOOST_FILESYSTEM_VERSION) && BOOST_FILESYSTEM_VERSION >= 3
00867 PION_LOG_ERROR(m_logger, "Unable to open file: "
00868 << m_disk_file.getFilePath().string());
00869 #else
00870 PION_LOG_ERROR(m_logger, "Unable to open file: "
00871 << m_disk_file.getFilePath().file_string());
00872 #endif
00873 return;
00874 }
00875 }
00876
00877
00878 if (! m_content_buf) {
00879
00880 m_content_buf.reset(new char[m_file_bytes_to_send]);
00881 }
00882 file_content_ptr = m_content_buf.get();
00883
00884
00885 if (! m_file_stream.read(m_content_buf.get(), m_file_bytes_to_send)) {
00886 if (m_file_stream.gcount() > 0) {
00887 # if defined(BOOST_FILESYSTEM_VERSION) && BOOST_FILESYSTEM_VERSION >= 3
00888 PION_LOG_ERROR(m_logger, "File size inconsistency: "
00889 << m_disk_file.getFilePath().string());
00890 #else
00891 PION_LOG_ERROR(m_logger, "File size inconsistency: "
00892 << m_disk_file.getFilePath().file_string());
00893 #endif
00894 } else {
00895 # if defined(BOOST_FILESYSTEM_VERSION) && BOOST_FILESYSTEM_VERSION >= 3
00896 PION_LOG_ERROR(m_logger, "Unable to read file: "
00897 << m_disk_file.getFilePath().string());
00898 #else
00899 PION_LOG_ERROR(m_logger, "Unable to read file: "
00900 << m_disk_file.getFilePath().file_string());
00901 #endif
00902 }
00903 return;
00904 }
00905 }
00906
00907
00908 m_writer->write_no_copy(file_content_ptr, m_file_bytes_to_send);
00909
00910 if (m_bytes_sent + m_file_bytes_to_send >= m_disk_file.getFileSize()) {
00911
00912 if (m_bytes_sent > 0) {
00913
00914 m_writer->send_final_chunk(boost::bind(&DiskFileSender::handle_write,
00915 shared_from_this(),
00916 boost::asio::placeholders::error,
00917 boost::asio::placeholders::bytes_transferred));
00918 } else {
00919
00920 m_writer->send(boost::bind(&DiskFileSender::handle_write,
00921 shared_from_this(),
00922 boost::asio::placeholders::error,
00923 boost::asio::placeholders::bytes_transferred));
00924 }
00925 } else {
00926
00927 m_writer->send_chunk(boost::bind(&DiskFileSender::handle_write,
00928 shared_from_this(),
00929 boost::asio::placeholders::error,
00930 boost::asio::placeholders::bytes_transferred));
00931 }
00932 }
00933
00934 void DiskFileSender::handle_write(const boost::system::error_code& write_error,
00935 std::size_t bytes_written)
00936 {
00937 bool finished_sending = true;
00938
00939 if (write_error) {
00940
00941 m_writer->get_connection()->set_lifecycle(tcp::connection::LIFECYCLE_CLOSE);
00942 PION_LOG_WARN(m_logger, "Error sending file (" << write_error.message() << ')');
00943 } else {
00944
00945
00946
00947
00948 m_bytes_sent += m_file_bytes_to_send;
00949
00950 if (m_bytes_sent >= m_disk_file.getFileSize()) {
00951
00952 PION_LOG_DEBUG(m_logger, "Sent "
00953 << (m_file_bytes_to_send < m_disk_file.getFileSize() ? "file chunk" : "complete file")
00954 << " of " << m_file_bytes_to_send << " bytes (finished"
00955 << (m_writer->get_connection()->get_keep_alive() ? ", keeping alive)" : ", closing)") );
00956 } else {
00957
00958 PION_LOG_DEBUG(m_logger, "Sent file chunk of " << m_file_bytes_to_send << " bytes");
00959 finished_sending = false;
00960 m_writer->clear();
00961 }
00962 }
00963
00964 if (finished_sending) {
00965
00966
00967
00968 m_writer->get_connection()->finish();
00969 } else {
00970 send();
00971 }
00972 }
00973
00974
00975 }
00976 }
00977
00978
00980 extern "C" PION_PLUGIN pion::plugins::FileService *pion_create_FileService(void)
00981 {
00982 return new pion::plugins::FileService();
00983 }
00984
00986 extern "C" PION_PLUGIN void pion_destroy_FileService(pion::plugins::FileService *service_ptr)
00987 {
00988 delete service_ptr;
00989 }