2 // Copyright (c) 2017 Christopher M. Kohlhoff (chris at kohlhoff 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/boostorg/beast
10 //------------------------------------------------------------------------------
12 // Example: HTTP server, fast
14 //------------------------------------------------------------------------------
16 #include "fields_alloc.hpp"
18 #include <boost/beast/core.hpp>
19 #include <boost/beast/http.hpp>
20 #include <boost/beast/version.hpp>
21 #include <boost/asio.hpp>
30 namespace beast
= boost::beast
; // from <boost/beast.hpp>
31 namespace http
= beast::http
; // from <boost/beast/http.hpp>
32 namespace net
= boost::asio
; // from <boost/asio.hpp>
33 using tcp
= boost::asio::ip::tcp
; // from <boost/asio/ip/tcp.hpp>
35 // Return a reasonable mime type based on the extension of a file.
37 mime_type(beast::string_view path
)
40 auto const ext
= [&path
]
42 auto const pos
= path
.rfind(".");
43 if(pos
== beast::string_view::npos
)
44 return beast::string_view
{};
45 return path
.substr(pos
);
47 if(iequals(ext
, ".htm")) return "text/html";
48 if(iequals(ext
, ".html")) return "text/html";
49 if(iequals(ext
, ".php")) return "text/html";
50 if(iequals(ext
, ".css")) return "text/css";
51 if(iequals(ext
, ".txt")) return "text/plain";
52 if(iequals(ext
, ".js")) return "application/javascript";
53 if(iequals(ext
, ".json")) return "application/json";
54 if(iequals(ext
, ".xml")) return "application/xml";
55 if(iequals(ext
, ".swf")) return "application/x-shockwave-flash";
56 if(iequals(ext
, ".flv")) return "video/x-flv";
57 if(iequals(ext
, ".png")) return "image/png";
58 if(iequals(ext
, ".jpe")) return "image/jpeg";
59 if(iequals(ext
, ".jpeg")) return "image/jpeg";
60 if(iequals(ext
, ".jpg")) return "image/jpeg";
61 if(iequals(ext
, ".gif")) return "image/gif";
62 if(iequals(ext
, ".bmp")) return "image/bmp";
63 if(iequals(ext
, ".ico")) return "image/vnd.microsoft.icon";
64 if(iequals(ext
, ".tiff")) return "image/tiff";
65 if(iequals(ext
, ".tif")) return "image/tiff";
66 if(iequals(ext
, ".svg")) return "image/svg+xml";
67 if(iequals(ext
, ".svgz")) return "image/svg+xml";
68 return "application/text";
74 http_worker(http_worker
const&) = delete;
75 http_worker
& operator=(http_worker
const&) = delete;
77 http_worker(tcp::acceptor
& acceptor
, const std::string
& doc_root
) :
90 using alloc_t
= fields_alloc
<char>;
91 //using request_body_t = http::basic_dynamic_body<beast::flat_static_buffer<1024 * 1024>>;
92 using request_body_t
= http::string_body
;
94 // The acceptor used to listen for incoming connections.
95 tcp::acceptor
& acceptor_
;
97 // The path to the root of the document directory.
98 std::string doc_root_
;
100 // The socket for the currently connected client.
101 tcp::socket socket_
{acceptor_
.get_executor()};
103 // The buffer for performing reads
104 beast::flat_static_buffer
<8192> buffer_
;
106 // The allocator used for the fields in the request and reply.
107 alloc_t alloc_
{8192};
109 // The parser for reading the requests
110 boost::optional
<http::request_parser
<request_body_t
, alloc_t
>> parser_
;
112 // The timer putting a time limit on requests.
113 net::steady_timer request_deadline_
{
114 acceptor_
.get_executor(), (std::chrono::steady_clock::time_point::max
)()};
116 // The string-based response message.
117 boost::optional
<http::response
<http::string_body
, http::basic_fields
<alloc_t
>>> string_response_
;
119 // The string-based response serializer.
120 boost::optional
<http::response_serializer
<http::string_body
, http::basic_fields
<alloc_t
>>> string_serializer_
;
122 // The file-based response message.
123 boost::optional
<http::response
<http::file_body
, http::basic_fields
<alloc_t
>>> file_response_
;
125 // The file-based response serializer.
126 boost::optional
<http::response_serializer
<http::file_body
, http::basic_fields
<alloc_t
>>> file_serializer_
;
130 // Clean up any previous connection.
131 beast::error_code ec
;
133 buffer_
.consume(buffer_
.size());
135 acceptor_
.async_accept(
137 [this](beast::error_code ec
)
145 // Request must be fully processed within 60 seconds.
146 request_deadline_
.expires_after(
147 std::chrono::seconds(60));
156 // On each read the parser needs to be destroyed and
157 // recreated. We store it in a boost::optional to
160 // Arguments passed to the parser constructor are
161 // forwarded to the message object. A single argument
162 // is forwarded to the body constructor.
164 // We construct the dynamic body with a 1MB limit
165 // to prevent vulnerability to buffer attacks.
168 std::piecewise_construct
,
170 std::make_tuple(alloc_
));
176 [this](beast::error_code ec
, std::size_t)
181 process_request(parser_
->get());
185 void process_request(http::request
<request_body_t
, http::basic_fields
<alloc_t
>> const& req
)
187 switch (req
.method())
189 case http::verb::get
:
190 send_file(req
.target());
194 // We return responses indicating an error if
195 // we do not recognize the request method.
197 http::status::bad_request
,
198 "Invalid request-method '" + std::string(req
.method_string()) + "'\r\n");
203 void send_bad_response(
205 std::string
const& error
)
207 string_response_
.emplace(
208 std::piecewise_construct
,
210 std::make_tuple(alloc_
));
212 string_response_
->result(status
);
213 string_response_
->keep_alive(false);
214 string_response_
->set(http::field::server
, "Beast");
215 string_response_
->set(http::field::content_type
, "text/plain");
216 string_response_
->body() = error
;
217 string_response_
->prepare_payload();
219 string_serializer_
.emplace(*string_response_
);
224 [this](beast::error_code ec
, std::size_t)
226 socket_
.shutdown(tcp::socket::shutdown_send
, ec
);
227 string_serializer_
.reset();
228 string_response_
.reset();
233 void send_file(beast::string_view target
)
235 // Request path must be absolute and not contain "..".
236 if (target
.empty() || target
[0] != '/' || target
.find("..") != std::string::npos
)
239 http::status::not_found
,
240 "File not found\r\n");
244 std::string full_path
= doc_root_
;
249 http::file_body::value_type file
;
250 beast::error_code ec
;
253 beast::file_mode::read
,
258 http::status::not_found
,
259 "File not found\r\n");
263 file_response_
.emplace(
264 std::piecewise_construct
,
266 std::make_tuple(alloc_
));
268 file_response_
->result(http::status::ok
);
269 file_response_
->keep_alive(false);
270 file_response_
->set(http::field::server
, "Beast");
271 file_response_
->set(http::field::content_type
, mime_type(std::string(target
)));
272 file_response_
->body() = std::move(file
);
273 file_response_
->prepare_payload();
275 file_serializer_
.emplace(*file_response_
);
280 [this](beast::error_code ec
, std::size_t)
282 socket_
.shutdown(tcp::socket::shutdown_send
, ec
);
283 file_serializer_
.reset();
284 file_response_
.reset();
289 void check_deadline()
291 // The deadline may have moved, so check it has really passed.
292 if (request_deadline_
.expiry() <= std::chrono::steady_clock::now())
294 // Close socket to cancel any outstanding operation.
297 // Sleep indefinitely until we're given a new deadline.
298 request_deadline_
.expires_at(
299 (std::chrono::steady_clock::time_point::max
)());
302 request_deadline_
.async_wait(
303 [this](beast::error_code
)
310 int main(int argc
, char* argv
[])
314 // Check command line arguments.
317 std::cerr
<< "Usage: http_server_fast <address> <port> <doc_root> <num_workers> {spin|block}\n";
318 std::cerr
<< " For IPv4, try:\n";
319 std::cerr
<< " http_server_fast 0.0.0.0 80 . 100 block\n";
320 std::cerr
<< " For IPv6, try:\n";
321 std::cerr
<< " http_server_fast 0::0 80 . 100 block\n";
325 auto const address
= net::ip::make_address(argv
[1]);
326 unsigned short port
= static_cast<unsigned short>(std::atoi(argv
[2]));
327 std::string doc_root
= argv
[3];
328 int num_workers
= std::atoi(argv
[4]);
329 bool spin
= (std::strcmp(argv
[5], "spin") == 0);
331 net::io_context ioc
{1};
332 tcp::acceptor acceptor
{ioc
, {address
, port
}};
334 std::list
<http_worker
> workers
;
335 for (int i
= 0; i
< num_workers
; ++i
)
337 workers
.emplace_back(acceptor
, doc_root
);
338 workers
.back().start();
346 catch (const std::exception
& e
)
348 std::cerr
<< "Error: " << e
.what() << std::endl
;