• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
mime_type(beast::string_view path)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
path_cat(beast::string_view base,beast::string_view path)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
handle_request(beast::string_view doc_root,http::request<Body,http::basic_fields<Allocator>> && req,Send && send)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
send_lambdahttp_session::send_lambda196     send_lambda(http_session& self)
197         : self_(self)
198     {
199     }
200 
201     template<bool isRequest, class Body, class Fields>
202     void
operator ()http_session::send_lambda203     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::
http_session(tcp::socket && socket,boost::shared_ptr<shared_state> const & state)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::
run()236 run()
237 {
238     do_read();
239 }
240 
241 // Report a failure
242 void
243 http_session::
fail(beast::error_code ec,char const * what)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::
do_read()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::
on_read(beast::error_code ec,std::size_t)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::
on_write(beast::error_code ec,std::size_t,bool close)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 }
372