2 // Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com)
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)
7 // Official repository: https://github.com/vinniefalco/CppCon2018
10 #include "http_session.hpp"
11 #include "websocket_session.hpp"
12 #include <boost/config.hpp>
15 #define BOOST_NO_CXX14_GENERIC_LAMBDAS
17 //------------------------------------------------------------------------------
19 // Return a reasonable mime type based on the extension of a file.
21 mime_type(beast::string_view path
)
24 auto const ext
= [&path
]
26 auto const pos
= path
.rfind(".");
27 if(pos
== beast::string_view::npos
)
28 return beast::string_view
{};
29 return path
.substr(pos
);
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";
55 // Append an HTTP rel-path to a local filesystem path.
56 // The returned path is normalized for the platform.
59 beast::string_view base
,
60 beast::string_view path
)
63 return std::string(path
);
64 std::string
result(base
);
66 char constexpr path_separator
= '\\';
67 if(result
.back() == path_separator
)
68 result
.resize(result
.size() - 1);
69 result
.append(path
.data(), path
.size());
74 char constexpr path_separator
= '/';
75 if(result
.back() == path_separator
)
76 result
.resize(result
.size() - 1);
77 result
.append(path
.data(), path
.size());
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.
87 class Body
, class Allocator
,
91 beast::string_view doc_root
,
92 http::request
<Body
, http::basic_fields
<Allocator
>>&& req
,
95 // Returns a bad request response
96 auto const bad_request
=
97 [&req
](beast::string_view why
)
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();
108 // Returns a not found response
109 auto const not_found
=
110 [&req
](beast::string_view target
)
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();
121 // Returns a server error response
122 auto const server_error
=
123 [&req
](beast::string_view what
)
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();
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"));
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"));
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");
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
);
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()));
159 // Handle an unknown error
161 return send(server_error(ec
.message()));
163 // Cache the size since we need it after the move
164 auto const size
= body
.size();
166 // Respond to HEAD request
167 if(req
.method() == http::verb::head
)
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
));
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
));
189 //------------------------------------------------------------------------------
191 struct http_session::send_lambda
196 send_lambda(http_session
& self
)
201 template<bool isRequest
, class Body
, class Fields
>
203 operator()(http::message
<isRequest
, Body
, Fields
>&& msg
) const
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
));
211 // Write the response
212 auto self
= self_
.shared_from_this();
216 [self
, sp
](beast::error_code ec
, std::size_t bytes
)
218 self
->on_write(ec
, bytes
, sp
->need_eof());
223 //------------------------------------------------------------------------------
227 tcp::socket
&& socket
,
228 boost::shared_ptr
<shared_state
> const& state
)
229 : stream_(std::move(socket
))
244 fail(beast::error_code ec
, char const* what
)
246 // Don't report on canceled operations
247 if(ec
== net::error::operation_aborted
)
250 std::cerr
<< what
<< ": " << ec
.message() << "\n";
257 // Construct a new parser for each message
260 // Apply a reasonable limit to the allowed size
261 // of the body in bytes to prevent abuse.
262 parser_
->body_limit(10000);
265 stream_
.expires_after(std::chrono::seconds(30));
272 beast::bind_front_handler(
273 &http_session::on_read
,
274 shared_from_this()));
279 on_read(beast::error_code ec
, std::size_t)
281 // This means they closed the connection
282 if(ec
== http::error::end_of_stream
)
284 stream_
.socket().shutdown(tcp::socket::shutdown_send
, ec
);
288 // Handle the error, if any
290 return fail(ec
, "read");
292 // See if it is a WebSocket Upgrade
293 if(websocket::is_upgrade(parser_
->get()))
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());
304 #ifndef BOOST_NO_CXX14_GENERIC_LAMBDAS
306 // The following code requires generic
307 // lambdas, available in C++14 and later.
312 [this](auto&& response
)
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
));
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
)
327 self
->on_write(ec
, bytes
, sp
->need_eof());
330 // Write the response
331 auto self
= shared_from_this();
332 http::async_write(stream_
, *sp
,
334 beast::error_code ec
, std::size_t bytes
)
336 self
->on_write(ec
, bytes
, sp
->need_eof());
342 // This code uses the function object type send_lambda in
343 // place of a generic lambda which is not available in C++11
355 on_write(beast::error_code ec
, std::size_t, bool close
)
357 // Handle the error, if any
359 return fail(ec
, "write");
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
);
369 // Read another request