Skip to content

Use memory mapped file for static file server #1632

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 31, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 118 additions & 18 deletions httplib.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,6 @@
#define CPPHTTPLIB_RECV_BUFSIZ size_t(4096u)
#endif

#ifndef CPPHTTPLIB_SEND_BUFSIZ
#define CPPHTTPLIB_SEND_BUFSIZ size_t(4096u)
#endif

#ifndef CPPHTTPLIB_COMPRESSION_BUFSIZ
#define CPPHTTPLIB_COMPRESSION_BUFSIZ size_t(16384u)
#endif
Expand Down Expand Up @@ -197,6 +193,7 @@ using socket_t = SOCKET;
#endif
#include <csignal>
#include <pthread.h>
#include <sys/mman.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/un.h>
Expand Down Expand Up @@ -2148,6 +2145,29 @@ class stream_line_reader {
std::string glowable_buffer_;
};

class mmap {
public:
mmap(const char *path);
~mmap();

bool open(const char *path);
void close();

bool is_open() const;
size_t size() const;
const char *data() const;

private:
#if defined(_WIN32)
HANDLE hFile_;
HANDLE hMapping_;
#else
int fd_;
#endif
size_t size_;
void *addr_;
};

} // namespace detail

// ----------------------------------------------------------------------------
Expand Down Expand Up @@ -2528,6 +2548,95 @@ inline void stream_line_reader::append(char c) {
}
}

inline mmap::mmap(const char *path)
#if defined(_WIN32)
: hFile_(NULL), hMapping_(NULL)
#else
: fd_(-1)
#endif
,
size_(0), addr_(nullptr) {
if (!open(path)) { std::runtime_error(""); }
}

inline mmap::~mmap() { close(); }

inline bool mmap::open(const char *path) {
close();

#if defined(_WIN32)
hFile_ = ::CreateFileA(path, GENERIC_READ, FILE_SHARE_READ, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

if (hFile_ == INVALID_HANDLE_VALUE) { return false; }

size_ = ::GetFileSize(hFile_, NULL);

hMapping_ = ::CreateFileMapping(hFile_, NULL, PAGE_READONLY, 0, 0, NULL);

if (hMapping_ == NULL) {
close();
return false;
}

addr_ = ::MapViewOfFile(hMapping_, FILE_MAP_READ, 0, 0, 0);
#else
fd_ = ::open(path, O_RDONLY);
if (fd_ == -1) { return false; }

struct stat sb;
if (fstat(fd_, &sb) == -1) {
close();
return false;
}
size_ = static_cast<size_t>(sb.st_size);

addr_ = ::mmap(NULL, size_, PROT_READ, MAP_PRIVATE, fd_, 0);
#endif

if (addr_ == nullptr) {
close();
return false;
}

return true;
}

inline bool mmap::is_open() const { return addr_ != nullptr; }

inline size_t mmap::size() const { return size_; }

inline const char *mmap::data() const { return (const char *)addr_; }

inline void mmap::close() {
#if defined(_WIN32)
if (addr_) {
::UnmapViewOfFile(addr_);
addr_ = nullptr;
}

if (hMapping_) {
::CloseHandle(hMapping_);
hMapping_ = NULL;
}

if (hFile_ != INVALID_HANDLE_VALUE) {
::CloseHandle(hFile_);
hFile_ = INVALID_HANDLE_VALUE;
}
#else
if (addr_ != nullptr) {
munmap(addr_, size_);
addr_ = nullptr;
}

if (fd_ != -1) {
::close(fd_);
fd_ = -1;
}
#endif
size_ = 0;
}
inline int close_socket(socket_t sock) {
#ifdef _WIN32
return closesocket(sock);
Expand Down Expand Up @@ -5963,24 +6072,15 @@ inline bool Server::handle_file_request(const Request &req, Response &res,
res.set_header(kv.first.c_str(), kv.second);
}

auto fs =
std::make_shared<std::ifstream>(path, std::ios_base::binary);

fs->seekg(0, std::ios_base::end);
auto size = static_cast<size_t>(fs->tellg());
fs->seekg(0);
auto mm = std::make_shared<detail::mmap>(path.c_str());
if (!mm->is_open()) { return false; }

res.set_content_provider(
size,
mm->size(),
detail::find_content_type(path, file_extension_and_mimetype_map_,
default_file_mimetype_),
[fs](size_t offset, size_t length, DataSink &sink) -> bool {
std::array<char, CPPHTTPLIB_SEND_BUFSIZ> buf{};
length = std::min(length, CPPHTTPLIB_SEND_BUFSIZ);

fs->seekg(static_cast<std::streamsize>(offset), std::ios_base::beg);
fs->read(buf.data(), static_cast<std::streamsize>(length));
sink.write(buf.data(), length);
[mm](size_t offset, size_t length, DataSink &sink) -> bool {
sink.write(mm->data() + offset, length);
return true;
});

Expand Down