FileResourceHandler: Open file once on init, more error handling

Keeping the file open allows better prefetching/caching on both
the application layer (ifstream) and OS level (vfs layer) as
the access pattern is sequential and predictable. We also save
three syscalls (open, seek, close) for every chunk we send to
the client (256kb).
This commit is contained in:
Simon Rettberg
2025-06-02 12:15:28 +02:00
parent 2557402367
commit 5b0a136542
2 changed files with 36 additions and 43 deletions
+33 -42
View File
@@ -19,8 +19,6 @@
#include "FileResourceHandler.hpp" #include "FileResourceHandler.hpp"
#include <fstream>
#include "core/ILogger.hpp" #include "core/ILogger.hpp"
#include "core/MimeTypes.hpp" #include "core/MimeTypes.hpp"
@@ -32,38 +30,37 @@ namespace lms::core
} }
FileResourceHandler::FileResourceHandler(const std::filesystem::path& path, std::string_view mimeType) FileResourceHandler::FileResourceHandler(const std::filesystem::path& path, std::string_view mimeType)
: _path{ path } : _mimeType{ mimeType }
, _mimeType{ mimeType } , _ifs{ path, std::ios::in | std::ios::binary }
{ {
if (!_ifs)
LMS_LOG(UTILS, ERROR, "Cannot open file stream for " << path);
else
{
_ifs.seekg(0, std::ios::end);
if (!_ifs.fail())
_fileSize = static_cast<::uint64_t>(_ifs.tellg());
LMS_LOG(UTILS, DEBUG, "File " << path << ", fileSize = " << _fileSize);
}
} }
Wt::Http::ResponseContinuation* FileResourceHandler::processRequest(const Wt::Http::Request& request, Wt::Http::Response& response) Wt::Http::ResponseContinuation* FileResourceHandler::processRequest(const Wt::Http::Request& request, Wt::Http::Response& response)
{ {
::uint64_t startByte{ _offset }; if (_offset == 0)
std::ifstream ifs{ _path, std::ios::in | std::ios::binary };
if (startByte == 0)
{ {
if (!ifs) if (!_ifs)
{ {
LMS_LOG(UTILS, ERROR, "Cannot open file stream for " << _path);
response.setStatus(404); response.setStatus(404);
return {}; return {};
} }
ifs.seekg(0, std::ios::end);
const ::uint64_t fileSize{ static_cast<::uint64_t>(ifs.tellg()) };
ifs.seekg(0, std::ios::beg);
LMS_LOG(UTILS, DEBUG, "File " << _path << ", fileSize = " << fileSize);
response.addHeader("Accept-Ranges", "bytes"); response.addHeader("Accept-Ranges", "bytes");
const Wt::Http::Request::ByteRangeSpecifier ranges{ request.getRanges(fileSize) }; const Wt::Http::Request::ByteRangeSpecifier ranges{ request.getRanges(_fileSize) };
if (!ranges.isSatisfiable()) if (!ranges.isSatisfiable())
{ {
std::ostringstream contentRange; std::ostringstream contentRange;
contentRange << "bytes */" << fileSize; contentRange << "bytes */" << _fileSize;
response.setStatus(416); // Requested range not satisfiable response.setStatus(416); // Requested range not satisfiable
response.addHeader("Content-Range", contentRange.str()); response.addHeader("Content-Range", contentRange.str());
@@ -76,57 +73,51 @@ namespace lms::core
LMS_LOG(UTILS, DEBUG, "Range requested = " << ranges[0].firstByte() << "-" << ranges[0].lastByte()); LMS_LOG(UTILS, DEBUG, "Range requested = " << ranges[0].firstByte() << "-" << ranges[0].lastByte());
response.setStatus(206); response.setStatus(206);
startByte = ranges[0].firstByte(); _offset = ranges[0].firstByte();
_beyondLastByte = ranges[0].lastByte() + 1; _beyondLastByte = ranges[0].lastByte() + 1;
std::ostringstream contentRange; std::ostringstream contentRange;
contentRange << "bytes " << startByte << "-" contentRange << "bytes " << _offset << "-"
<< _beyondLastByte - 1 << "/" << fileSize; << _beyondLastByte - 1 << "/" << _fileSize;
response.addHeader("Content-Range", contentRange.str()); response.addHeader("Content-Range", contentRange.str());
response.setContentLength(_beyondLastByte - startByte); response.setContentLength(_beyondLastByte - _offset);
} }
else else
{ {
LMS_LOG(UTILS, DEBUG, "No range requested"); LMS_LOG(UTILS, DEBUG, "No range requested");
response.setStatus(200); response.setStatus(200);
_beyondLastByte = fileSize; _beyondLastByte = _fileSize;
response.setContentLength(_beyondLastByte); response.setContentLength(_beyondLastByte);
} }
LMS_LOG(UTILS, DEBUG, "Mimetype set to '" << _mimeType << "'"); LMS_LOG(UTILS, DEBUG, "Mimetype set to '" << _mimeType << "'");
response.setMimeType(_mimeType); response.setMimeType(_mimeType);
}
else if (!ifs)
{
LMS_LOG(UTILS, ERROR, "Cannot reopen file stream for " << _path);
return {};
}
ifs.seekg(static_cast<std::istream::pos_type>(startByte)); _ifs.seekg(static_cast<std::istream::pos_type>(_offset));
} // end initial response setup
std::vector<char> buf; ::uint64_t restSize = _beyondLastByte - _offset;
buf.resize(_chunkSize); ::uint64_t pieceSize = std::min(restSize, _chunkSize);
::uint64_t restSize = _beyondLastByte - startByte; std::vector<char> buf(pieceSize);
::uint64_t pieceSize = buf.size() > restSize ? restSize : buf.size();
ifs.read(&buf[0], pieceSize); _ifs.read(buf.data(), buf.size());
const ::uint64_t actualPieceSize{ static_cast<::uint64_t>(ifs.gcount()) }; const ::uint64_t actualPieceSize{ static_cast<::uint64_t>(_ifs.gcount()) };
if (actualPieceSize > 0) if (actualPieceSize > 0)
{ {
response.out().write(&buf[0], actualPieceSize); response.out().write(buf.data(), actualPieceSize);
LMS_LOG(UTILS, DEBUG, "Written " << actualPieceSize << " bytes, range = " << startByte << "-" << startByte + actualPieceSize - 1 << ""); LMS_LOG(UTILS, DEBUG, "Written " << actualPieceSize << " bytes, range = " << _offset << "-" << _offset + actualPieceSize - 1 << "");
} }
else else
{
LMS_LOG(UTILS, DEBUG, "Written 0 byte"); LMS_LOG(UTILS, DEBUG, "Written 0 byte");
}
if (ifs.good() && actualPieceSize < restSize) if (!_ifs.good())
LMS_LOG(UTILS, WARNING, "Error reading from file!");
else if (actualPieceSize < restSize)
{ {
_offset = startByte + actualPieceSize; _offset += actualPieceSize;
LMS_LOG(UTILS, DEBUG, "Job not complete! Remaining range: " << _offset << "-" << _beyondLastByte - 1); LMS_LOG(UTILS, DEBUG, "Job not complete! Remaining range: " << _offset << "-" << _beyondLastByte - 1);
return response.createContinuation(); return response.createContinuation();
+3 -1
View File
@@ -22,6 +22,7 @@
#include <filesystem> #include <filesystem>
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <fstream>
#include "core/IResourceHandler.hpp" #include "core/IResourceHandler.hpp"
@@ -38,9 +39,10 @@ namespace lms::core
static constexpr std::size_t _chunkSize{ 262'144 }; static constexpr std::size_t _chunkSize{ 262'144 };
std::filesystem::path _path;
std::string _mimeType; std::string _mimeType;
::uint64_t _beyondLastByte{}; ::uint64_t _beyondLastByte{};
::uint64_t _offset{}; ::uint64_t _offset{};
::uint64_t _fileSize{};
std::ifstream _ifs;
}; };
} // namespace lms::core } // namespace lms::core