]> git.proxmox.com Git - ceph.git/blob - ceph/src/boost/libs/beast/example/websocket/server/chat-multi/http_session.cpp
import new upstream nautilus stable release 14.2.8
[ceph.git] / ceph / src / boost / libs / beast / example / websocket / server / chat-multi / http_session.cpp
1 //
2 // Copyright (c) 2016-2019 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 // Official repository: https://github.com/vinniefalco/CppCon2018
8 //
9
10 #include "http_session.hpp"
11 #include "websocket_session.hpp"
12 #include <boost/config.hpp>
13 #include <iostream>
14
15 #define BOOST_NO_CXX14_GENERIC_LAMBDAS
16
17 //------------------------------------------------------------------------------
18
19 // Return a reasonable mime type based on the extension of a file.
20 beast::string_view
21 mime_type(beast::string_view path)
22 {
23 using beast::iequals;
24 auto const ext = [&path]
25 {
26 auto const pos = path.rfind(".");
27 if(pos == beast::string_view::npos)
28 return beast::string_view{};
29 return path.substr(pos);
30 }();
31 if(iequals(ext, ".htm")) return "text/html";
32 if(iequals(ext, ".html")) return "text/html";
33 if(iequals(ext, ".php")) return "text/html";
34 if(iequals(ext, ".css")) return "text/css";
35 if(iequals(ext, ".txt")) return "text/plain";
36 if(iequals(ext, ".js")) return "application/javascript";
37 if(iequals(ext, ".json")) return "application/json";
38 if(iequals(ext, ".xml")) return "application/xml";
39 if(iequals(ext, ".swf")) return "application/x-shockwave-flash";
40 if(iequals(ext, ".flv")) return "video/x-flv";
41 if(iequals(ext, ".png")) return "image/png";
42 if(iequals(ext, ".jpe")) return "image/jpeg";
43 if(iequals(ext, ".jpeg")) return "image/jpeg";
44 if(iequals(ext, ".jpg")) return "image/jpeg";
45 if(iequals(ext, ".gif")) return "image/gif";
46 if(iequals(ext, ".bmp")) return "image/bmp";
47 if(iequals(ext, ".ico")) return "image/vnd.microsoft.icon";
48 if(iequals(ext, ".tiff")) return "image/tiff";
49 if(iequals(ext, ".tif")) return "image/tiff";
50 if(iequals(ext, ".svg")) return "image/svg+xml";
51 if(iequals(ext, ".svgz")) return "image/svg+xml";
52 return "application/text";
53 }
54
55 // Append an HTTP rel-path to a local filesystem path.
56 // The returned path is normalized for the platform.
57 std::string
58 path_cat(
59 beast::string_view base,
60 beast::string_view path)
61 {
62 if(base.empty())
63 return std::string(path);
64 std::string result(base);
65 #ifdef BOOST_MSVC
66 char constexpr path_separator = '\\';
67 if(result.back() == path_separator)
68 result.resize(result.size() - 1);
69 result.append(path.data(), path.size());
70 for(auto& c : result)
71 if(c == '/')
72 c = path_separator;
73 #else
74 char constexpr path_separator = '/';
75 if(result.back() == path_separator)
76 result.resize(result.size() - 1);
77 result.append(path.data(), path.size());
78 #endif
79 return result;
80 }
81
82 // This function produces an HTTP response for the given
83 // request. The type of the response object depends on the
84 // contents of the request, so the interface requires the
85 // caller to pass a generic lambda for receiving the response.
86 template<
87 class Body, class Allocator,
88 class Send>
89 void
90 handle_request(
91 beast::string_view doc_root,
92 http::request<Body, http::basic_fields<Allocator>>&& req,
93 Send&& send)
94 {
95 // Returns a bad request response
96 auto const bad_request =
97 [&req](beast::string_view why)
98 {
99 http::response<http::string_body> res{http::status::bad_request, req.version()};
100 res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
101 res.set(http::field::content_type, "text/html");
102 res.keep_alive(req.keep_alive());
103 res.body() = std::string(why);
104 res.prepare_payload();
105 return res;
106 };
107
108 // Returns a not found response
109 auto const not_found =
110 [&req](beast::string_view target)
111 {
112 http::response<http::string_body> res{http::status::not_found, req.version()};
113 res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
114 res.set(http::field::content_type, "text/html");
115 res.keep_alive(req.keep_alive());
116 res.body() = "The resource '" + std::string(target) + "' was not found.";
117 res.prepare_payload();
118 return res;
119 };
120
121 // Returns a server error response
122 auto const server_error =
123 [&req](beast::string_view what)
124 {
125 http::response<http::string_body> res{http::status::internal_server_error, req.version()};
126 res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
127 res.set(http::field::content_type, "text/html");
128 res.keep_alive(req.keep_alive());
129 res.body() = "An error occurred: '" + std::string(what) + "'";
130 res.prepare_payload();
131 return res;
132 };
133
134 // Make sure we can handle the method
135 if( req.method() != http::verb::get &&
136 req.method() != http::verb::head)
137 return send(bad_request("Unknown HTTP-method"));
138
139 // Request path must be absolute and not contain "..".
140 if( req.target().empty() ||
141 req.target()[0] != '/' ||
142 req.target().find("..") != beast::string_view::npos)
143 return send(bad_request("Illegal request-target"));
144
145 // Build the path to the requested file
146 std::string path = path_cat(doc_root, req.target());
147 if(req.target().back() == '/')
148 path.append("index.html");
149
150 // Attempt to open the file
151 beast::error_code ec;
152 http::file_body::value_type body;
153 body.open(path.c_str(), beast::file_mode::scan, ec);
154
155 // Handle the case where the file doesn't exist
156 if(ec == boost::system::errc::no_such_file_or_directory)
157 return send(not_found(req.target()));
158
159 // Handle an unknown error
160 if(ec)
161 return send(server_error(ec.message()));
162
163 // Cache the size since we need it after the move
164 auto const size = body.size();
165
166 // Respond to HEAD request
167 if(req.method() == http::verb::head)
168 {
169 http::response<http::empty_body> res{http::status::ok, req.version()};
170 res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
171 res.set(http::field::content_type, mime_type(path));
172 res.content_length(size);
173 res.keep_alive(req.keep_alive());
174 return send(std::move(res));
175 }
176
177 // Respond to GET request
178 http::response<http::file_body> res{
179 std::piecewise_construct,
180 std::make_tuple(std::move(body)),
181 std::make_tuple(http::status::ok, req.version())};
182 res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
183 res.set(http::field::content_type, mime_type(path));
184 res.content_length(size);
185 res.keep_alive(req.keep_alive());
186 return send(std::move(res));
187 }
188
189 //------------------------------------------------------------------------------
190
191 struct http_session::send_lambda
192 {
193 http_session& self_;
194
195 explicit
196 send_lambda(http_session& self)
197 : self_(self)
198 {
199 }
200
201 template<bool isRequest, class Body, class Fields>
202 void
203 operator()(http::message<isRequest, Body, Fields>&& msg) const
204 {
205 // The lifetime of the message has to extend
206 // for the duration of the async operation so
207 // we use a shared_ptr to manage it.
208 auto sp = boost::make_shared<
209 http::message<isRequest, Body, Fields>>(std::move(msg));
210
211 // Write the response
212 auto self = self_.shared_from_this();
213 http::async_write(
214 self_.stream_,
215 *sp,
216 [self, sp](beast::error_code ec, std::size_t bytes)
217 {
218 self->on_write(ec, bytes, sp->need_eof());
219 });
220 }
221 };
222
223 //------------------------------------------------------------------------------
224
225 http_session::
226 http_session(
227 tcp::socket&& socket,
228 boost::shared_ptr<shared_state> const& state)
229 : stream_(std::move(socket))
230 , state_(state)
231 {
232 }
233
234 void
235 http_session::
236 run()
237 {
238 do_read();
239 }
240
241 // Report a failure
242 void
243 http_session::
244 fail(beast::error_code ec, char const* what)
245 {
246 // Don't report on canceled operations
247 if(ec == net::error::operation_aborted)
248 return;
249
250 std::cerr << what << ": " << ec.message() << "\n";
251 }
252
253 void
254 http_session::
255 do_read()
256 {
257 // Construct a new parser for each message
258 parser_.emplace();
259
260 // Apply a reasonable limit to the allowed size
261 // of the body in bytes to prevent abuse.
262 parser_->body_limit(10000);
263
264 // Set the timeout.
265 stream_.expires_after(std::chrono::seconds(30));
266
267 // Read a request
268 http::async_read(
269 stream_,
270 buffer_,
271 parser_->get(),
272 beast::bind_front_handler(
273 &http_session::on_read,
274 shared_from_this()));
275 }
276
277 void
278 http_session::
279 on_read(beast::error_code ec, std::size_t)
280 {
281 // This means they closed the connection
282 if(ec == http::error::end_of_stream)
283 {
284 stream_.socket().shutdown(tcp::socket::shutdown_send, ec);
285 return;
286 }
287
288 // Handle the error, if any
289 if(ec)
290 return fail(ec, "read");
291
292 // See if it is a WebSocket Upgrade
293 if(websocket::is_upgrade(parser_->get()))
294 {
295 // Create a websocket session, transferring ownership
296 // of both the socket and the HTTP request.
297 boost::make_shared<websocket_session>(
298 stream_.release_socket(),
299 state_)->run(parser_->release());
300 return;
301 }
302
303 // Send the response
304 #ifndef BOOST_NO_CXX14_GENERIC_LAMBDAS
305 //
306 // The following code requires generic
307 // lambdas, available in C++14 and later.
308 //
309 handle_request(
310 state_->doc_root(),
311 std::move(req_),
312 [this](auto&& response)
313 {
314 // The lifetime of the message has to extend
315 // for the duration of the async operation so
316 // we use a shared_ptr to manage it.
317 using response_type = typename std::decay<decltype(response)>::type;
318 auto sp = boost::make_shared<response_type>(std::forward<decltype(response)>(response));
319
320 #if 0
321 // NOTE This causes an ICE in gcc 7.3
322 // Write the response
323 http::async_write(this->stream_, *sp,
324 [self = shared_from_this(), sp](
325 beast::error_code ec, std::size_t bytes)
326 {
327 self->on_write(ec, bytes, sp->need_eof());
328 });
329 #else
330 // Write the response
331 auto self = shared_from_this();
332 http::async_write(stream_, *sp,
333 [self, sp](
334 beast::error_code ec, std::size_t bytes)
335 {
336 self->on_write(ec, bytes, sp->need_eof());
337 });
338 #endif
339 });
340 #else
341 //
342 // This code uses the function object type send_lambda in
343 // place of a generic lambda which is not available in C++11
344 //
345 handle_request(
346 state_->doc_root(),
347 parser_->release(),
348 send_lambda(*this));
349
350 #endif
351 }
352
353 void
354 http_session::
355 on_write(beast::error_code ec, std::size_t, bool close)
356 {
357 // Handle the error, if any
358 if(ec)
359 return fail(ec, "write");
360
361 if(close)
362 {
363 // This means we should close the connection, usually because
364 // the response indicated the "Connection: close" semantic.
365 stream_.socket().shutdown(tcp::socket::shutdown_send, ec);
366 return;
367 }
368
369 // Read another request
370 do_read();
371 }