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