|
| 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 | +namespacehttp= boost::network::http; |
| 17 | +namespaceutils= boost::network::utils; |
| 18 | + |
| 19 | +structfile_server; |
| 20 | +typedef http::async_server<file_server> server; |
| 21 | + |
| 22 | +structfile_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 | +explicitfile_cache(std::stringconst & doc_root) |
| 33 | + : doc_root_(doc_root) {} |
| 34 | + |
| 35 | +~file_cache()throw () { |
| 36 | +BOOST_FOREACH(region_map::value_typeconst & region, regions) { |
| 37 | +munmap(region.second.first, region.second.second); |
| 38 | + } |
| 39 | + } |
| 40 | + |
| 41 | +boolhas(std::stringconst & path) { |
| 42 | + boost::shared_lock<boost::shared_mutex>lock(cache_mutex); |
| 43 | +return regions.find(doc_root_ + path) != regions.end(); |
| 44 | + } |
| 45 | + |
| 46 | +booladd(std::stringconst & path) { |
| 47 | + boost::upgrade_lock<boost::shared_mutex>lock(cache_mutex); |
| 48 | +if (regions.find(doc_root_ + path) != regions.end())returntrue; |
| 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)returnfalse; |
| 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);returnfalse; } |
| 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 | +returntrue; |
| 67 | + } |
| 68 | + |
| 69 | + std::pair<void*,std::size_t>get(std::stringconst & 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 | +elsereturn std::pair<void*,std::size_t>(0,0); |
| 74 | + } |
| 75 | + |
| 76 | + boost::iterator_range<std::vector<server::response_header>::iterator>meta(std::stringconst & 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 | +returnboost::make_iterator_range(begin,end); |
| 84 | + }elsereturnboost::make_iterator_range(empty_vector); |
| 85 | + } |
| 86 | + |
| 87 | +}; |
| 88 | + |
| 89 | +structconnection_handler : boost::enable_shared_from_this<connection_handler> { |
| 90 | +explicitconnection_handler(file_cache & cache) |
| 91 | + : file_cache_(cache) {} |
| 92 | + |
| 93 | +voidoperator()(std::stringconst & 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 | +voidnot_found(std::stringconst & 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<classRange> |
| 115 | +voidsend_headers(Rangeconst & headers, server::connection_ptr connection) { |
| 116 | + connection->set_status(server::connection::ok); |
| 117 | + connection->set_headers(headers); |
| 118 | + } |
| 119 | + |
| 120 | +voidsend_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<charconst *>(mmaped_region.first) + offset, |
| 127 | +static_cast<charconst *>(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 | +voidhandle_chunk(std::pair<void *, std::size_t> mmaped_region,off_t offset, server::connection_ptr connection, boost::system::error_codeconst & ec) { |
| 141 | +if (!ec && offset < mmaped_region.second)send_file(mmaped_region, offset, connection); |
| 142 | + } |
| 143 | + |
| 144 | + file_cache & file_cache_; |
| 145 | +}; |
| 146 | + |
| 147 | +structfile_server { |
| 148 | +explicitfile_server(file_cache & cache) |
| 149 | + : cache_(cache) {} |
| 150 | + |
| 151 | +voidoperator()( |
| 152 | + server::requestconst & request, |
| 153 | + server::connection_ptr connection |
| 154 | + ) { |
| 155 | +if (request.method =="HEAD") { |
| 156 | + boost::shared_ptr<connection_handler>h(newconnection_handler(cache_)); |
| 157 | + (*h)(request.destination, connection,false); |
| 158 | + }elseif (request.method =="GET") { |
| 159 | + boost::shared_ptr<connection_handler>h(newconnection_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 | +intmain(int argc,char * argv[]) { |
| 175 | + file_cachecache("."); |
| 176 | + file_serverhandler(cache); |
| 177 | + utils::thread_poolthread_pool(4); |
| 178 | + serverinstance("127.0.0.1","8000", handler, thread_pool); |
| 179 | + instance.run(); |
| 180 | +return0; |
| 181 | +} |