]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
1 | // |
2 | // Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) | |
3 | // | |
4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying | |
5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | |
6 | // | |
7 | ||
8 | #ifndef BEAST_EXAMPLE_HTTP_ASYNC_SERVER_H_INCLUDED | |
9 | #define BEAST_EXAMPLE_HTTP_ASYNC_SERVER_H_INCLUDED | |
10 | ||
11 | #include "file_body.hpp" | |
12 | #include "mime_type.hpp" | |
13 | ||
14 | #include <beast/http.hpp> | |
15 | #include <beast/core/handler_helpers.hpp> | |
16 | #include <beast/core/handler_ptr.hpp> | |
17 | #include <beast/core/placeholders.hpp> | |
18 | #include <beast/core/streambuf.hpp> | |
19 | #include <boost/asio.hpp> | |
20 | #include <cstddef> | |
21 | #include <cstdio> | |
22 | #include <iostream> | |
23 | #include <memory> | |
24 | #include <mutex> | |
25 | #include <thread> | |
26 | #include <utility> | |
27 | ||
28 | namespace beast { | |
29 | namespace http { | |
30 | ||
31 | class http_async_server | |
32 | { | |
33 | using endpoint_type = boost::asio::ip::tcp::endpoint; | |
34 | using address_type = boost::asio::ip::address; | |
35 | using socket_type = boost::asio::ip::tcp::socket; | |
36 | ||
37 | using req_type = request<string_body>; | |
38 | using resp_type = response<file_body>; | |
39 | ||
40 | std::mutex m_; | |
41 | bool log_ = true; | |
42 | boost::asio::io_service ios_; | |
43 | boost::asio::ip::tcp::acceptor acceptor_; | |
44 | socket_type sock_; | |
45 | std::string root_; | |
46 | std::vector<std::thread> thread_; | |
47 | ||
48 | public: | |
49 | http_async_server(endpoint_type const& ep, | |
50 | std::size_t threads, std::string const& root) | |
51 | : acceptor_(ios_) | |
52 | , sock_(ios_) | |
53 | , root_(root) | |
54 | { | |
55 | acceptor_.open(ep.protocol()); | |
56 | acceptor_.bind(ep); | |
57 | acceptor_.listen( | |
58 | boost::asio::socket_base::max_connections); | |
59 | acceptor_.async_accept(sock_, | |
60 | std::bind(&http_async_server::on_accept, this, | |
61 | beast::asio::placeholders::error)); | |
62 | thread_.reserve(threads); | |
63 | for(std::size_t i = 0; i < threads; ++i) | |
64 | thread_.emplace_back( | |
65 | [&] { ios_.run(); }); | |
66 | } | |
67 | ||
68 | ~http_async_server() | |
69 | { | |
70 | error_code ec; | |
71 | ios_.dispatch( | |
72 | [&]{ acceptor_.close(ec); }); | |
73 | for(auto& t : thread_) | |
74 | t.join(); | |
75 | } | |
76 | ||
77 | template<class... Args> | |
78 | void | |
79 | log(Args const&... args) | |
80 | { | |
81 | if(log_) | |
82 | { | |
83 | std::lock_guard<std::mutex> lock(m_); | |
84 | log_args(args...); | |
85 | } | |
86 | } | |
87 | ||
88 | private: | |
89 | template<class Stream, class Handler, | |
90 | bool isRequest, class Body, class Fields> | |
91 | class write_op | |
92 | { | |
93 | struct data | |
94 | { | |
95 | bool cont; | |
96 | Stream& s; | |
97 | message<isRequest, Body, Fields> m; | |
98 | ||
99 | data(Handler& handler, Stream& s_, | |
100 | message<isRequest, Body, Fields>&& m_) | |
101 | : cont(beast_asio_helpers:: | |
102 | is_continuation(handler)) | |
103 | , s(s_) | |
104 | , m(std::move(m_)) | |
105 | { | |
106 | } | |
107 | }; | |
108 | ||
109 | handler_ptr<data, Handler> d_; | |
110 | ||
111 | public: | |
112 | write_op(write_op&&) = default; | |
113 | write_op(write_op const&) = default; | |
114 | ||
115 | template<class DeducedHandler, class... Args> | |
116 | write_op(DeducedHandler&& h, Stream& s, Args&&... args) | |
117 | : d_(std::forward<DeducedHandler>(h), | |
118 | s, std::forward<Args>(args)...) | |
119 | { | |
120 | (*this)(error_code{}, false); | |
121 | } | |
122 | ||
123 | void | |
124 | operator()(error_code ec, bool again = true) | |
125 | { | |
126 | auto& d = *d_; | |
127 | d.cont = d.cont || again; | |
128 | if(! again) | |
129 | { | |
130 | beast::http::async_write(d.s, d.m, std::move(*this)); | |
131 | return; | |
132 | } | |
133 | d_.invoke(ec); | |
134 | } | |
135 | ||
136 | friend | |
137 | void* asio_handler_allocate( | |
138 | std::size_t size, write_op* op) | |
139 | { | |
140 | return beast_asio_helpers:: | |
141 | allocate(size, op->d_.handler()); | |
142 | } | |
143 | ||
144 | friend | |
145 | void asio_handler_deallocate( | |
146 | void* p, std::size_t size, write_op* op) | |
147 | { | |
148 | return beast_asio_helpers:: | |
149 | deallocate(p, size, op->d_.handler()); | |
150 | } | |
151 | ||
152 | friend | |
153 | bool asio_handler_is_continuation(write_op* op) | |
154 | { | |
155 | return op->d_->cont; | |
156 | } | |
157 | ||
158 | template<class Function> | |
159 | friend | |
160 | void asio_handler_invoke(Function&& f, write_op* op) | |
161 | { | |
162 | return beast_asio_helpers:: | |
163 | invoke(f, op->d_.handler()); | |
164 | } | |
165 | }; | |
166 | ||
167 | template<class Stream, | |
168 | bool isRequest, class Body, class Fields, | |
169 | class DeducedHandler> | |
170 | static | |
171 | void | |
172 | async_write(Stream& stream, message< | |
173 | isRequest, Body, Fields>&& msg, | |
174 | DeducedHandler&& handler) | |
175 | { | |
176 | write_op<Stream, typename std::decay<DeducedHandler>::type, | |
177 | isRequest, Body, Fields>{std::forward<DeducedHandler>( | |
178 | handler), stream, std::move(msg)}; | |
179 | } | |
180 | ||
181 | class peer : public std::enable_shared_from_this<peer> | |
182 | { | |
183 | int id_; | |
184 | streambuf sb_; | |
185 | socket_type sock_; | |
186 | http_async_server& server_; | |
187 | boost::asio::io_service::strand strand_; | |
188 | req_type req_; | |
189 | ||
190 | public: | |
191 | peer(peer&&) = default; | |
192 | peer(peer const&) = default; | |
193 | peer& operator=(peer&&) = delete; | |
194 | peer& operator=(peer const&) = delete; | |
195 | ||
196 | peer(socket_type&& sock, http_async_server& server) | |
197 | : sock_(std::move(sock)) | |
198 | , server_(server) | |
199 | , strand_(sock_.get_io_service()) | |
200 | { | |
201 | static int n = 0; | |
202 | id_ = ++n; | |
203 | } | |
204 | ||
205 | void | |
206 | fail(error_code ec, std::string what) | |
207 | { | |
208 | if(ec != boost::asio::error::operation_aborted) | |
209 | server_.log("#", id_, " ", what, ": ", ec.message(), "\n"); | |
210 | } | |
211 | ||
212 | void run() | |
213 | { | |
214 | do_read(); | |
215 | } | |
216 | ||
217 | void do_read() | |
218 | { | |
219 | async_read(sock_, sb_, req_, strand_.wrap( | |
220 | std::bind(&peer::on_read, shared_from_this(), | |
221 | asio::placeholders::error))); | |
222 | } | |
223 | ||
224 | void on_read(error_code const& ec) | |
225 | { | |
226 | if(ec) | |
227 | return fail(ec, "read"); | |
228 | auto path = req_.url; | |
229 | if(path == "/") | |
230 | path = "/index.html"; | |
231 | path = server_.root_ + path; | |
232 | if(! boost::filesystem::exists(path)) | |
233 | { | |
234 | response<string_body> res; | |
235 | res.status = 404; | |
236 | res.reason = "Not Found"; | |
237 | res.version = req_.version; | |
238 | res.fields.insert("Server", "http_async_server"); | |
239 | res.fields.insert("Content-Type", "text/html"); | |
240 | res.body = "The file '" + path + "' was not found"; | |
241 | prepare(res); | |
242 | async_write(sock_, std::move(res), | |
243 | std::bind(&peer::on_write, shared_from_this(), | |
244 | asio::placeholders::error)); | |
245 | return; | |
246 | } | |
247 | try | |
248 | { | |
249 | resp_type res; | |
250 | res.status = 200; | |
251 | res.reason = "OK"; | |
252 | res.version = req_.version; | |
253 | res.fields.insert("Server", "http_async_server"); | |
254 | res.fields.insert("Content-Type", mime_type(path)); | |
255 | res.body = path; | |
256 | prepare(res); | |
257 | async_write(sock_, std::move(res), | |
258 | std::bind(&peer::on_write, shared_from_this(), | |
259 | asio::placeholders::error)); | |
260 | } | |
261 | catch(std::exception const& e) | |
262 | { | |
263 | response<string_body> res; | |
264 | res.status = 500; | |
265 | res.reason = "Internal Error"; | |
266 | res.version = req_.version; | |
267 | res.fields.insert("Server", "http_async_server"); | |
268 | res.fields.insert("Content-Type", "text/html"); | |
269 | res.body = | |
270 | std::string{"An internal error occurred"} + e.what(); | |
271 | prepare(res); | |
272 | async_write(sock_, std::move(res), | |
273 | std::bind(&peer::on_write, shared_from_this(), | |
274 | asio::placeholders::error)); | |
275 | } | |
276 | } | |
277 | ||
278 | void on_write(error_code ec) | |
279 | { | |
280 | if(ec) | |
281 | fail(ec, "write"); | |
282 | do_read(); | |
283 | } | |
284 | }; | |
285 | ||
286 | void | |
287 | log_args() | |
288 | { | |
289 | } | |
290 | ||
291 | template<class Arg, class... Args> | |
292 | void | |
293 | log_args(Arg const& arg, Args const&... args) | |
294 | { | |
295 | std::cerr << arg; | |
296 | log_args(args...); | |
297 | } | |
298 | ||
299 | void | |
300 | fail(error_code ec, std::string what) | |
301 | { | |
302 | log(what, ": ", ec.message(), "\n"); | |
303 | } | |
304 | ||
305 | void | |
306 | on_accept(error_code ec) | |
307 | { | |
308 | if(! acceptor_.is_open()) | |
309 | return; | |
310 | if(ec) | |
311 | return fail(ec, "accept"); | |
312 | socket_type sock(std::move(sock_)); | |
313 | acceptor_.async_accept(sock_, | |
314 | std::bind(&http_async_server::on_accept, this, | |
315 | asio::placeholders::error)); | |
316 | std::make_shared<peer>(std::move(sock), *this)->run(); | |
317 | } | |
318 | }; | |
319 | ||
320 | } // http | |
321 | } // beast | |
322 | ||
323 | #endif |