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

source code of boost/libs/beast/example/http/server/sync-ssl/http_server_sync_ssl.cpp