]> git.proxmox.com Git - ceph.git/blob - ceph/src/boost/libs/beast/example/http/server/sync/http_server_sync.cpp
update sources to v12.2.3
[ceph.git] / ceph / src / boost / libs / beast / example / http / server / sync / http_server_sync.cpp
1 //
2 // Copyright (c) 2016-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 // Official repository: https://github.com/boostorg/beast
8 //
9
10 //------------------------------------------------------------------------------
11 //
12 // Example: HTTP server, synchronous
13 //
14 //------------------------------------------------------------------------------
15
16 #include <boost/beast/core.hpp>
17 #include <boost/beast/http.hpp>
18 #include <boost/beast/version.hpp>
19 #include <boost/asio/ip/tcp.hpp>
20 #include <boost/config.hpp>
21 #include <cstdlib>
22 #include <iostream>
23 #include <memory>
24 #include <string>
25 #include <thread>
26
27 using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
28 namespace http = boost::beast::http; // from <boost/beast/http.hpp>
29
30 //------------------------------------------------------------------------------
31
32 // Return a reasonable mime type based on the extension of a file.
33 boost::beast::string_view
34 mime_type(boost::beast::string_view path)
35 {
36 using boost::beast::iequals;
37 auto const ext = [&path]
38 {
39 auto const pos = path.rfind(".");
40 if(pos == boost::beast::string_view::npos)
41 return boost::beast::string_view{};
42 return path.substr(pos);
43 }();
44 if(iequals(ext, ".htm")) return "text/html";
45 if(iequals(ext, ".html")) return "text/html";
46 if(iequals(ext, ".php")) return "text/html";
47 if(iequals(ext, ".css")) return "text/css";
48 if(iequals(ext, ".txt")) return "text/plain";
49 if(iequals(ext, ".js")) return "application/javascript";
50 if(iequals(ext, ".json")) return "application/json";
51 if(iequals(ext, ".xml")) return "application/xml";
52 if(iequals(ext, ".swf")) return "application/x-shockwave-flash";
53 if(iequals(ext, ".flv")) return "video/x-flv";
54 if(iequals(ext, ".png")) return "image/png";
55 if(iequals(ext, ".jpe")) return "image/jpeg";
56 if(iequals(ext, ".jpeg")) return "image/jpeg";
57 if(iequals(ext, ".jpg")) return "image/jpeg";
58 if(iequals(ext, ".gif")) return "image/gif";
59 if(iequals(ext, ".bmp")) return "image/bmp";
60 if(iequals(ext, ".ico")) return "image/vnd.microsoft.icon";
61 if(iequals(ext, ".tiff")) return "image/tiff";
62 if(iequals(ext, ".tif")) return "image/tiff";
63 if(iequals(ext, ".svg")) return "image/svg+xml";
64 if(iequals(ext, ".svgz")) return "image/svg+xml";
65 return "application/text";
66 }
67
68 // Append an HTTP rel-path to a local filesystem path.
69 // The returned path is normalized for the platform.
70 std::string
71 path_cat(
72 boost::beast::string_view base,
73 boost::beast::string_view path)
74 {
75 if(base.empty())
76 return path.to_string();
77 std::string result = base.to_string();
78 #if BOOST_MSVC
79 char constexpr path_separator = '\\';
80 if(result.back() == path_separator)
81 result.resize(result.size() - 1);
82 result.append(path.data(), path.size());
83 for(auto& c : result)
84 if(c == '/')
85 c = path_separator;
86 #else
87 char constexpr path_separator = '/';
88 if(result.back() == path_separator)
89 result.resize(result.size() - 1);
90 result.append(path.data(), path.size());
91 #endif
92 return result;
93 }
94
95 // This function produces an HTTP response for the given
96 // request. The type of the response object depends on the
97 // contents of the request, so the interface requires the
98 // caller to pass a generic lambda for receiving the response.
99 template<
100 class Body, class Allocator,
101 class Send>
102 void
103 handle_request(
104 boost::beast::string_view doc_root,
105 http::request<Body, http::basic_fields<Allocator>>&& req,
106 Send&& send)
107 {
108 // Returns a bad request response
109 auto const bad_request =
110 [&req](boost::beast::string_view why)
111 {
112 http::response<http::string_body> res{http::status::bad_request, 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() = why.to_string();
117 res.prepare_payload();
118 return res;
119 };
120
121 // Returns a not found response
122 auto const not_found =
123 [&req](boost::beast::string_view target)
124 {
125 http::response<http::string_body> res{http::status::not_found, 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() = "The resource '" + target.to_string() + "' was not found.";
130 res.prepare_payload();
131 return res;
132 };
133
134 // Returns a server error response
135 auto const server_error =
136 [&req](boost::beast::string_view what)
137 {
138 http::response<http::string_body> res{http::status::internal_server_error, req.version()};
139 res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
140 res.set(http::field::content_type, "text/html");
141 res.keep_alive(req.keep_alive());
142 res.body() = "An error occurred: '" + what.to_string() + "'";
143 res.prepare_payload();
144 return res;
145 };
146
147 // Make sure we can handle the method
148 if( req.method() != http::verb::get &&
149 req.method() != http::verb::head)
150 return send(bad_request("Unknown HTTP-method"));
151
152 // Request path must be absolute and not contain "..".
153 if( req.target().empty() ||
154 req.target()[0] != '/' ||
155 req.target().find("..") != boost::beast::string_view::npos)
156 return send(bad_request("Illegal request-target"));
157
158 // Build the path to the requested file
159 std::string path = path_cat(doc_root, req.target());
160 if(req.target().back() == '/')
161 path.append("index.html");
162
163 // Attempt to open the file
164 boost::beast::error_code ec;
165 http::file_body::value_type body;
166 body.open(path.c_str(), boost::beast::file_mode::scan, ec);
167
168 // Handle the case where the file doesn't exist
169 if(ec == boost::system::errc::no_such_file_or_directory)
170 return send(not_found(req.target()));
171
172 // Handle an unknown error
173 if(ec)
174 return send(server_error(ec.message()));
175
176 // Respond to HEAD request
177 if(req.method() == http::verb::head)
178 {
179 http::response<http::empty_body> res{http::status::ok, req.version()};
180 res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
181 res.set(http::field::content_type, mime_type(path));
182 res.content_length(body.size());
183 res.keep_alive(req.keep_alive());
184 return send(std::move(res));
185 }
186
187 // Respond to GET request
188 http::response<http::file_body> res{
189 std::piecewise_construct,
190 std::make_tuple(std::move(body)),
191 std::make_tuple(http::status::ok, req.version())};
192 res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
193 res.set(http::field::content_type, mime_type(path));
194 res.content_length(body.size());
195 res.keep_alive(req.keep_alive());
196 return send(std::move(res));
197 }
198
199 //------------------------------------------------------------------------------
200
201 // Report a failure
202 void
203 fail(boost::system::error_code ec, char const* what)
204 {
205 std::cerr << what << ": " << ec.message() << "\n";
206 }
207
208 // This is the C++11 equivalent of a generic lambda.
209 // The function object is used to send an HTTP message.
210 template<class Stream>
211 struct send_lambda
212 {
213 Stream& stream_;
214 bool& close_;
215 boost::system::error_code& ec_;
216
217 explicit
218 send_lambda(
219 Stream& stream,
220 bool& close,
221 boost::system::error_code& ec)
222 : stream_(stream)
223 , close_(close)
224 , ec_(ec)
225 {
226 }
227
228 template<bool isRequest, class Body, class Fields>
229 void
230 operator()(http::message<isRequest, Body, Fields>&& msg) const
231 {
232 // Determine if we should close the connection after
233 close_ = msg.need_eof();
234
235 // We need the serializer here because the serializer requires
236 // a non-const file_body, and the message oriented version of
237 // http::write only works with const messages.
238 http::serializer<isRequest, Body, Fields> sr{msg};
239 http::write(stream_, sr, ec_);
240 }
241 };
242
243 // Handles an HTTP server connection
244 void
245 do_session(
246 tcp::socket& socket,
247 std::string const& doc_root)
248 {
249 bool close = false;
250 boost::system::error_code ec;
251
252 // This buffer is required to persist across reads
253 boost::beast::flat_buffer buffer;
254
255 // This lambda is used to send messages
256 send_lambda<tcp::socket> lambda{socket, close, ec};
257
258 for(;;)
259 {
260 // Read a request
261 http::request<http::string_body> req;
262 http::read(socket, buffer, req, ec);
263 if(ec == http::error::end_of_stream)
264 break;
265 if(ec)
266 return fail(ec, "read");
267
268 // Send the response
269 handle_request(doc_root, std::move(req), lambda);
270 if(ec)
271 return fail(ec, "write");
272 if(close)
273 {
274 // This means we should close the connection, usually because
275 // the response indicated the "Connection: close" semantic.
276 break;
277 }
278 }
279
280 // Send a TCP shutdown
281 socket.shutdown(tcp::socket::shutdown_send, ec);
282
283 // At this point the connection is closed gracefully
284 }
285
286 //------------------------------------------------------------------------------
287
288 int main(int argc, char* argv[])
289 {
290 try
291 {
292 // Check command line arguments.
293 if (argc != 4)
294 {
295 std::cerr <<
296 "Usage: http-server-sync <address> <port> <doc_root>\n" <<
297 "Example:\n" <<
298 " http-server-sync 0.0.0.0 8080 .\n";
299 return EXIT_FAILURE;
300 }
301 auto const address = boost::asio::ip::make_address(argv[1]);
302 auto const port = static_cast<unsigned short>(std::atoi(argv[2]));
303 std::string const doc_root = argv[3];
304
305 // The io_context is required for all I/O
306 boost::asio::io_context ioc{1};
307
308 // The acceptor receives incoming connections
309 tcp::acceptor acceptor{ioc, {address, port}};
310 for(;;)
311 {
312 // This will receive the new connection
313 tcp::socket socket{ioc};
314
315 // Block until we get a connection
316 acceptor.accept(socket);
317
318 // Launch the session, transferring ownership of the socket
319 std::thread{std::bind(
320 &do_session,
321 std::move(socket),
322 doc_root)}.detach();
323 }
324 }
325 catch (const std::exception& e)
326 {
327 std::cerr << "Error: " << e.what() << std::endl;
328 return EXIT_FAILURE;
329 }
330 }