|
| 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 <memory> |
| 7 | +#include <thread> |
| 8 | +#include <boost/network/include/http/server.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 | +#include <cassert> |
| 16 | + |
| 17 | +namespace http = boost::network::http; |
| 18 | +namespace utils = boost::network::utils; |
| 19 | + |
| 20 | +struct file_server; |
| 21 | +typedef http::server<file_server> server; |
| 22 | + |
| 23 | +struct file_cache |
| 24 | +{ |
| 25 | + // <real_filename, <region, file-size> > |
| 26 | + typedef std::map<std::string, std::pair<void *, std::size_t> > region_map; |
| 27 | + // <real_filename, <headers> > |
| 28 | + typedef std::map<std::string, std::vector<server::response_header> > meta_map; |
| 29 | + |
| 30 | + std::string doc_root_; |
| 31 | + region_map regions_; |
| 32 | + meta_map file_headers_; |
| 33 | + std::mutex cache_mutex_; |
| 34 | + |
| 35 | + explicit file_cache(std::string doc_root) : doc_root_(std::move(doc_root)) {} |
| 36 | + |
| 37 | + ~file_cache() throw() |
| 38 | + { |
| 39 | + for (auto ®ion : regions_) |
| 40 | + { |
| 41 | + munmap(region.second.first, region.second.second); |
| 42 | + } |
| 43 | + } |
| 44 | + |
| 45 | + bool has(std::string const &path) |
| 46 | + { |
| 47 | + std::unique_lock<std::mutex> lock(cache_mutex_); |
| 48 | + return regions_.find(doc_root_ + path) != regions_.end(); |
| 49 | + } |
| 50 | + |
| 51 | + bool add(std::string const &path) |
| 52 | + { |
| 53 | + std::unique_lock<std::mutex> lock(cache_mutex_); |
| 54 | + std::string real_filename = doc_root_ + path; |
| 55 | + if (regions_.find(real_filename) != regions_.end()) return true; |
| 56 | +#ifdef O_NOATIME |
| 57 | + int fd = open(real_filename.c_str(), O_RDONLY | O_NOATIME | O_NONBLOCK); |
| 58 | +#else |
| 59 | + int fd = open(real_filename.c_str(), O_RDONLY | O_NONBLOCK); |
| 60 | +#endif |
| 61 | + if (fd == -1) return false; |
| 62 | + off_t size = lseek(fd, 0, SEEK_END); |
| 63 | + if (size == -1) { |
| 64 | + close(fd); |
| 65 | + return false; |
| 66 | + } |
| 67 | + void *region = mmap(0, size, PROT_READ, MAP_PRIVATE, fd, 0); |
| 68 | + if (region == MAP_FAILED) |
| 69 | + { |
| 70 | + close(fd); |
| 71 | + return false; |
| 72 | + } |
| 73 | + |
| 74 | + regions_.insert(std::make_pair(real_filename, std::make_pair(region, size))); |
| 75 | + static server::response_header common_headers[] = { |
| 76 | + {"Connection", "close"}, |
| 77 | + {"Content-Type", "x-application/octet-stream"}, |
| 78 | + {"Content-Length", "0"}}; |
| 79 | + std::vector<server::response_header> headers(common_headers, |
| 80 | + common_headers + 3); |
| 81 | + headers[2].value = std::to_string(size); |
| 82 | + file_headers_.insert(std::make_pair(real_filename, headers)); |
| 83 | + close(fd); |
| 84 | + return true; |
| 85 | + } |
| 86 | + |
| 87 | + std::pair<void *, std::size_t> get(std::string const &path) |
| 88 | + { |
| 89 | + std::unique_lock<std::mutex> lock(cache_mutex_); |
| 90 | + region_map::const_iterator region = regions_.find(doc_root_ + path); |
| 91 | + if (region != regions_.end()) |
| 92 | + return region->second; |
| 93 | + else |
| 94 | + return std::pair<void *, std::size_t>(0, 0); |
| 95 | + } |
| 96 | + |
| 97 | + boost::iterator_range<std::vector<server::response_header>::iterator> meta(std::string const &path) |
| 98 | + { |
| 99 | + std::unique_lock<std::mutex> lock(cache_mutex_); |
| 100 | + static std::vector<server::response_header> empty_vector; |
| 101 | + auto headers = file_headers_.find(doc_root_ + path); |
| 102 | + if (headers != file_headers_.end()) { |
| 103 | + // 返回一个headers的范围,信息是缓存了的headers |
| 104 | + auto begin = headers->second.begin(); |
| 105 | + auto end = headers->second.end(); |
| 106 | + return boost::make_iterator_range(begin, end); |
| 107 | + } else { |
| 108 | + // 没有命中headers,返回一个空的header |
| 109 | + return boost::make_iterator_range(empty_vector); |
| 110 | + } |
| 111 | + } |
| 112 | +}; |
| 113 | + |
| 114 | + |
| 115 | + |
| 116 | +struct connection_handler : std::enable_shared_from_this<connection_handler> |
| 117 | +{ |
| 118 | + explicit connection_handler(file_cache &cache) : file_cache_(cache) {} |
| 119 | + |
| 120 | + file_cache &file_cache_; |
| 121 | + |
| 122 | + void operator()(std::string const &path, |
| 123 | + server::connection_ptr connection, |
| 124 | + bool serve_body) |
| 125 | + { |
| 126 | + bool ok = file_cache_.has(path); |
| 127 | + |
| 128 | + if (!ok) |
| 129 | + ok = file_cache_.add(path); |
| 130 | + |
| 131 | + if (ok) { |
| 132 | + send_headers(file_cache_.meta(path), connection); |
| 133 | + if (serve_body) { |
| 134 | + send_file(file_cache_.get(path), 0, connection); |
| 135 | + } |
| 136 | + } else { |
| 137 | + not_found(path, connection); |
| 138 | + } |
| 139 | + } |
| 140 | + |
| 141 | + void not_found(std::string const &, server::connection_ptr connection) |
| 142 | + { |
| 143 | + static server::response_header headers[] = {{"Connection", "close"}, |
| 144 | + {"Content-Type", "text/plain"}}; |
| 145 | + connection->set_status(server::connection::not_found); |
| 146 | + connection->set_headers(boost::make_iterator_range(headers, headers + 2)); |
| 147 | + connection->write("File Not Found!"); |
| 148 | + } |
| 149 | + |
| 150 | + template <class Range> |
| 151 | + void send_headers(Range const &headers, server::connection_ptr connection) { |
| 152 | + connection->set_status(server::connection::ok); |
| 153 | + connection->set_headers(headers); |
| 154 | + } |
| 155 | + |
| 156 | + void send_file(std::pair<void *, std::size_t> mmaped_region, |
| 157 | + off_t offset, |
| 158 | + server::connection_ptr connection) |
| 159 | + { |
| 160 | + // chunk it up page by page |
| 161 | + std::size_t adjusted_offset = offset + 4096; |
| 162 | + off_t rightmost_bound = std::min(mmaped_region.second, adjusted_offset); |
| 163 | + auto self = this->shared_from_this(); |
| 164 | + connection->write(asio::const_buffers_1(static_cast<char const *>(mmaped_region.first) + offset, rightmost_bound - offset), |
| 165 | + [=] (std::error_code const &ec) { |
| 166 | + self->handle_chunk(mmaped_region, rightmost_bound, connection, ec); |
| 167 | + }); |
| 168 | + } |
| 169 | + |
| 170 | + void handle_chunk(std::pair<void *, std::size_t> mmaped_region, |
| 171 | + off_t offset, |
| 172 | + server::connection_ptr connection, |
| 173 | + std::error_code const &ec) |
| 174 | + { |
| 175 | + assert(offset >= 0); |
| 176 | + if (!ec && static_cast<std::size_t>(offset) < mmaped_region.second) { |
| 177 | + send_file(mmaped_region, offset, connection); |
| 178 | + } |
| 179 | + } |
| 180 | +}; |
| 181 | + |
| 182 | + |
| 183 | +struct file_server |
| 184 | +{ |
| 185 | + explicit file_server(file_cache &cache) : file_cache_(cache) {} |
| 186 | + file_cache &file_cache_; |
| 187 | + |
| 188 | + void operator()(server::request const &request, |
| 189 | + server::connection_ptr connection) |
| 190 | + { |
| 191 | + if (request.method == "HEAD") |
| 192 | + { |
| 193 | + std::shared_ptr<connection_handler> handler(new connection_handler(file_cache_)); |
| 194 | + (*handler)(request.destination, connection, false); |
| 195 | + } |
| 196 | + else if (request.method == "GET") |
| 197 | + { |
| 198 | + std::shared_ptr<connection_handler> handler(new connection_handler(file_cache_)); |
| 199 | + (*handler)(request.destination, connection, true); |
| 200 | + } |
| 201 | + else |
| 202 | + { |
| 203 | + static server::response_header error_headers[] = {{"Connection", "close"}}; |
| 204 | + connection->set_status(server::connection::not_supported); |
| 205 | + connection->set_headers(boost::make_iterator_range(error_headers, error_headers + 1)); |
| 206 | + connection->write("Method not supported."); |
| 207 | + } |
| 208 | + } |
| 209 | +}; |
| 210 | + |
| 211 | +// 可用浏览器测试: http://127.0.0.1:8000/file_server 下载file_server文件 |
| 212 | +int main(int, char *[]) { |
| 213 | + try |
| 214 | + { |
| 215 | + file_cache cache("."); |
| 216 | + file_server handler(cache); |
| 217 | + server::options options(handler); |
| 218 | + options.reuse_address(true); |
| 219 | + server instance(options.thread_pool(std::make_shared<utils::thread_pool>(4)).address("0.0.0.0").port("8000")); |
| 220 | + instance.run(); |
| 221 | + } |
| 222 | + catch (std::exception &e) { |
| 223 | + std::cerr << e.what() << std::endl; |
| 224 | + } |
| 225 | +} |
0 commit comments