|
| 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