Skip to content

Commit 7f21db4

Browse files
committed
Example File Server with async_server and mmap.
This commit implements a file server using the asynchronous file_server and mmap on Linux. This is a functional file server which serves all data as 'x-application/octet-stream'. The implementation caches the region of the mmap for every file accessed through the server.
1 parent a155444 commit 7f21db4

File tree

3 files changed

+187
-0
lines changed

3 files changed

+187
-0
lines changed

libs/network/example/CMakeLists.txt

+3
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,15 @@ if (Boost_FOUND)
1515
add_executable(http_client http_client.cpp)
1616
add_executable(simple_wget simple_wget.cpp)
1717
add_executable(hello_world_server http/hello_world_server.cpp)
18+
add_executable(fileserver http/fileserver.cpp)
1819
add_executable(uri uri.cpp)
1920
target_link_libraries(http_client ${Boost_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ${OPENSSL_LIBRARIES} )
2021
target_link_libraries(simple_wget ${Boost_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ${OPENSSL_LIBRARIES} )
2122
target_link_libraries(hello_world_server ${Boost_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ${OPENSSL_LIBRARIES} )
23+
target_link_libraries(fileserver ${Boost_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
2224
set_target_properties(http_client PROPERTIES RUNTIME_OUTPUT_DIRECTORY ../../../build/example)
2325
set_target_properties(simple_wget PROPERTIES RUNTIME_OUTPUT_DIRECTORY ../../../build/example)
2426
set_target_properties(hello_world_server PROPERTIES RUNTIME_OUTPUT_DIRECTORY ../../../build/example)
27+
set_target_properties(fileserver PROPERTIES RUNTIME_OUTPUT_DIRECTORY ../../../build/example)
2528
set_target_properties(uri PROPERTIES RUNTIME_OUTPUT_DIRECTORY ../../../build/example)
2629
endif()

libs/network/example/Jamfile.v2

+3
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,6 @@ exe hello_world_server : http/hello_world_server.cpp ;
3333
exe hello_world_client : http/hello_world_client.cpp ;
3434

3535
exe one_liner : http/one_liner.cpp ;
36+
37+
exe fileserver : http/fileserver.cpp ;
38+
+181
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
// Copyright 2010 Dean Michael Berris.
2+
// Distributed under the Boost Software License, Version 1.0.
3+
// (See accompanying file LICENSE_1_0.txt or copy at
4+
// http://www.boost.org/LICENSE_1_0.txt)
5+
6+
#include <boost/network/include/http/server.hpp>
7+
#include <boost/thread.hpp>
8+
#include <boost/lexical_cast.hpp>
9+
#include <sys/mman.h>
10+
#include <sys/types.h>
11+
#include <sys/stat.h>
12+
#include <sys/fcntl.h>
13+
#include <unistd.h>
14+
#include <iostream>
15+
16+
namespace http = boost::network::http;
17+
namespace utils = boost::network::utils;
18+
19+
struct file_server;
20+
typedef http::async_server<file_server> server;
21+
22+
struct file_cache {
23+
24+
typedef std::map<std::string, std::pair<void*,std::size_t> > region_map;
25+
typedef std::map<std::string, std::vector<server::response_header> > meta_map;
26+
27+
std::string doc_root_;
28+
region_map regions;
29+
meta_map file_headers;
30+
boost::shared_mutex cache_mutex;
31+
32+
explicit file_cache(std::string const & doc_root)
33+
: doc_root_(doc_root) {}
34+
35+
~file_cache() throw () {
36+
BOOST_FOREACH(region_map::value_type const & region, regions) {
37+
munmap(region.second.first, region.second.second);
38+
}
39+
}
40+
41+
bool has(std::string const & path) {
42+
boost::shared_lock<boost::shared_mutex> lock(cache_mutex);
43+
return regions.find(doc_root_ + path) != regions.end();
44+
}
45+
46+
bool add(std::string const & path) {
47+
boost::upgrade_lock<boost::shared_mutex> lock(cache_mutex);
48+
if (regions.find(doc_root_ + path) != regions.end()) return true;
49+
std::string real_filename = doc_root_+path;
50+
int fd = open(real_filename.c_str(), O_RDONLY|O_NOATIME|O_NONBLOCK);
51+
if (fd == -1) return false;
52+
std::size_t size = lseek(fd, 0, SEEK_END);
53+
void * region = mmap(0, size, PROT_READ, MAP_PRIVATE, fd, 0);
54+
if (region == MAP_FAILED) { close(fd); return false; }
55+
56+
boost::upgrade_to_unique_lock<boost::shared_mutex> unique_lock(lock);
57+
regions.insert(std::make_pair(real_filename, std::make_pair(region, size)));
58+
static server::response_header common_headers[] = {
59+
{"Connection","close"}
60+
,{"Content-Type", "x-application/octet-stream"}
61+
,{"Content-Length", "0"}
62+
};
63+
std::vector<server::response_header> headers(common_headers, common_headers+3);
64+
headers[2].value = boost::lexical_cast<std::string>(size);
65+
file_headers.insert(std::make_pair(real_filename, headers));
66+
return true;
67+
}
68+
69+
std::pair<void*,std::size_t> get(std::string const & path) {
70+
boost::shared_lock<boost::shared_mutex> lock(cache_mutex);
71+
region_map::const_iterator region = regions.find(doc_root_+path);
72+
if (region != regions.end()) return region->second;
73+
else return std::pair<void*,std::size_t>(0,0);
74+
}
75+
76+
boost::iterator_range<std::vector<server::response_header>::iterator> meta(std::string const & path) {
77+
boost::shared_lock<boost::shared_mutex> lock(cache_mutex);
78+
static std::vector<server::response_header> empty_vector;
79+
meta_map::iterator headers = file_headers.find(doc_root_+path);
80+
if (headers != file_headers.end()) {
81+
std::vector<server::response_header>::iterator begin = headers->second.begin()
82+
, end = headers->second.end();
83+
return boost::make_iterator_range(begin,end);
84+
} else return boost::make_iterator_range(empty_vector);
85+
}
86+
87+
};
88+
89+
struct connection_handler : boost::enable_shared_from_this<connection_handler> {
90+
explicit connection_handler(file_cache & cache)
91+
: file_cache_(cache) {}
92+
93+
void operator()(std::string const & path, server::connection_ptr connection, bool serve_body) {
94+
bool ok = false;
95+
if (!file_cache_.has(path)) ok = file_cache_.add(path);
96+
if (ok) {
97+
send_headers(file_cache_.meta(path), connection);
98+
if (serve_body) send_file(file_cache_.get(path), 0, connection);
99+
} else {
100+
not_found(path, connection);
101+
}
102+
}
103+
104+
void not_found(std::string const & path, server::connection_ptr connection) {
105+
static server::response_header headers[] = {
106+
{"Connection","close"}
107+
,{"Content-Type", "text/plain"}
108+
};
109+
connection->set_status(server::connection::not_found);
110+
connection->set_headers(boost::make_iterator_range(headers, headers+2));
111+
connection->write("File Not Found!");
112+
}
113+
114+
template <class Range>
115+
void send_headers(Range const & headers, server::connection_ptr connection) {
116+
connection->set_status(server::connection::ok);
117+
connection->set_headers(headers);
118+
}
119+
120+
void send_file(std::pair<void *,std::size_t> mmaped_region, off_t offset, server::connection_ptr connection) {
121+
// chunk it up page by page
122+
std::size_t adjusted_offset = offset+4096;
123+
off_t rightmost_bound = std::min(mmaped_region.second, adjusted_offset);
124+
connection->write(
125+
boost::make_iterator_range(
126+
static_cast<char const *>(mmaped_region.first) + offset,
127+
static_cast<char const *>(mmaped_region.first) + rightmost_bound
128+
)
129+
, boost::bind(
130+
&connection_handler::handle_chunk,
131+
connection_handler::shared_from_this(),
132+
mmaped_region,
133+
rightmost_bound,
134+
connection,
135+
_1
136+
)
137+
);
138+
}
139+
140+
void handle_chunk(std::pair<void *, std::size_t> mmaped_region, off_t offset, server::connection_ptr connection, boost::system::error_code const & ec) {
141+
if (!ec && offset < mmaped_region.second) send_file(mmaped_region, offset, connection);
142+
}
143+
144+
file_cache & file_cache_;
145+
};
146+
147+
struct file_server {
148+
explicit file_server(file_cache & cache)
149+
: cache_(cache) {}
150+
151+
void operator()(
152+
server::request const & request,
153+
server::connection_ptr connection
154+
) {
155+
if (request.method == "HEAD") {
156+
boost::shared_ptr<connection_handler> h(new connection_handler(cache_));
157+
(*h)(request.destination, connection, false);
158+
} else if (request.method == "GET") {
159+
boost::shared_ptr<connection_handler> h(new connection_handler(cache_));
160+
(*h)(request.destination, connection, true);
161+
} else {
162+
static server::response_header error_headers[] = {
163+
{ "Connection", "close" }
164+
};
165+
connection->set_status(server::connection::not_supported);
166+
connection->set_headers(boost::make_iterator_range(error_headers, error_headers+1));
167+
connection->write("Method not supported.");
168+
}
169+
}
170+
171+
file_cache & cache_;
172+
};
173+
174+
int main(int argc, char * argv[]) {
175+
file_cache cache(".");
176+
file_server handler(cache);
177+
utils::thread_pool thread_pool(4);
178+
server instance("127.0.0.1", "8000", handler, thread_pool);
179+
instance.run();
180+
return 0;
181+
}

0 commit comments

Comments
 (0)