]>
Commit | Line | Data |
---|---|---|
b32b8144 FG |
1 | // |
2 | // Copyright (c) 2017 Christopher M. Kohlhoff (chris at kohlhoff 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, small | |
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.hpp> | |
20 | #include <chrono> | |
21 | #include <cstdlib> | |
22 | #include <ctime> | |
23 | #include <iostream> | |
24 | #include <memory> | |
25 | #include <string> | |
26 | ||
92f5a8d4 TL |
27 | namespace beast = boost::beast; // from <boost/beast.hpp> |
28 | namespace http = beast::http; // from <boost/beast/http.hpp> | |
29 | namespace net = boost::asio; // from <boost/asio.hpp> | |
30 | using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp> | |
b32b8144 FG |
31 | |
32 | namespace my_program_state | |
33 | { | |
34 | std::size_t | |
35 | request_count() | |
36 | { | |
37 | static std::size_t count = 0; | |
38 | return ++count; | |
39 | } | |
40 | ||
41 | std::time_t | |
42 | now() | |
43 | { | |
44 | return std::time(0); | |
45 | } | |
46 | } | |
47 | ||
48 | class http_connection : public std::enable_shared_from_this<http_connection> | |
49 | { | |
50 | public: | |
51 | http_connection(tcp::socket socket) | |
52 | : socket_(std::move(socket)) | |
53 | { | |
54 | } | |
55 | ||
56 | // Initiate the asynchronous operations associated with the connection. | |
57 | void | |
58 | start() | |
59 | { | |
60 | read_request(); | |
61 | check_deadline(); | |
62 | } | |
63 | ||
64 | private: | |
65 | // The socket for the currently connected client. | |
66 | tcp::socket socket_; | |
67 | ||
68 | // The buffer for performing reads. | |
92f5a8d4 | 69 | beast::flat_buffer buffer_{8192}; |
b32b8144 FG |
70 | |
71 | // The request message. | |
72 | http::request<http::dynamic_body> request_; | |
73 | ||
74 | // The response message. | |
75 | http::response<http::dynamic_body> response_; | |
76 | ||
77 | // The timer for putting a deadline on connection processing. | |
92f5a8d4 TL |
78 | net::steady_timer deadline_{ |
79 | socket_.get_executor(), std::chrono::seconds(60)}; | |
b32b8144 FG |
80 | |
81 | // Asynchronously receive a complete request message. | |
82 | void | |
83 | read_request() | |
84 | { | |
85 | auto self = shared_from_this(); | |
86 | ||
87 | http::async_read( | |
88 | socket_, | |
89 | buffer_, | |
90 | request_, | |
92f5a8d4 | 91 | [self](beast::error_code ec, |
b32b8144 FG |
92 | std::size_t bytes_transferred) |
93 | { | |
94 | boost::ignore_unused(bytes_transferred); | |
95 | if(!ec) | |
96 | self->process_request(); | |
97 | }); | |
98 | } | |
99 | ||
100 | // Determine what needs to be done with the request message. | |
101 | void | |
102 | process_request() | |
103 | { | |
104 | response_.version(request_.version()); | |
105 | response_.keep_alive(false); | |
106 | ||
107 | switch(request_.method()) | |
108 | { | |
109 | case http::verb::get: | |
110 | response_.result(http::status::ok); | |
111 | response_.set(http::field::server, "Beast"); | |
112 | create_response(); | |
113 | break; | |
114 | ||
115 | default: | |
116 | // We return responses indicating an error if | |
117 | // we do not recognize the request method. | |
118 | response_.result(http::status::bad_request); | |
119 | response_.set(http::field::content_type, "text/plain"); | |
92f5a8d4 | 120 | beast::ostream(response_.body()) |
b32b8144 | 121 | << "Invalid request-method '" |
92f5a8d4 | 122 | << std::string(request_.method_string()) |
b32b8144 FG |
123 | << "'"; |
124 | break; | |
125 | } | |
126 | ||
127 | write_response(); | |
128 | } | |
129 | ||
130 | // Construct a response message based on the program state. | |
131 | void | |
132 | create_response() | |
133 | { | |
134 | if(request_.target() == "/count") | |
135 | { | |
136 | response_.set(http::field::content_type, "text/html"); | |
92f5a8d4 | 137 | beast::ostream(response_.body()) |
b32b8144 FG |
138 | << "<html>\n" |
139 | << "<head><title>Request count</title></head>\n" | |
140 | << "<body>\n" | |
141 | << "<h1>Request count</h1>\n" | |
142 | << "<p>There have been " | |
143 | << my_program_state::request_count() | |
144 | << " requests so far.</p>\n" | |
145 | << "</body>\n" | |
146 | << "</html>\n"; | |
147 | } | |
148 | else if(request_.target() == "/time") | |
149 | { | |
150 | response_.set(http::field::content_type, "text/html"); | |
92f5a8d4 | 151 | beast::ostream(response_.body()) |
b32b8144 FG |
152 | << "<html>\n" |
153 | << "<head><title>Current time</title></head>\n" | |
154 | << "<body>\n" | |
155 | << "<h1>Current time</h1>\n" | |
156 | << "<p>The current time is " | |
157 | << my_program_state::now() | |
158 | << " seconds since the epoch.</p>\n" | |
159 | << "</body>\n" | |
160 | << "</html>\n"; | |
161 | } | |
162 | else | |
163 | { | |
164 | response_.result(http::status::not_found); | |
165 | response_.set(http::field::content_type, "text/plain"); | |
92f5a8d4 | 166 | beast::ostream(response_.body()) << "File not found\r\n"; |
b32b8144 FG |
167 | } |
168 | } | |
169 | ||
170 | // Asynchronously transmit the response message. | |
171 | void | |
172 | write_response() | |
173 | { | |
174 | auto self = shared_from_this(); | |
175 | ||
176 | response_.set(http::field::content_length, response_.body().size()); | |
177 | ||
178 | http::async_write( | |
179 | socket_, | |
180 | response_, | |
92f5a8d4 | 181 | [self](beast::error_code ec, std::size_t) |
b32b8144 FG |
182 | { |
183 | self->socket_.shutdown(tcp::socket::shutdown_send, ec); | |
184 | self->deadline_.cancel(); | |
185 | }); | |
186 | } | |
187 | ||
188 | // Check whether we have spent enough time on this connection. | |
189 | void | |
190 | check_deadline() | |
191 | { | |
192 | auto self = shared_from_this(); | |
193 | ||
194 | deadline_.async_wait( | |
92f5a8d4 | 195 | [self](beast::error_code ec) |
b32b8144 FG |
196 | { |
197 | if(!ec) | |
198 | { | |
199 | // Close socket to cancel any outstanding operation. | |
200 | self->socket_.close(ec); | |
201 | } | |
202 | }); | |
203 | } | |
204 | }; | |
205 | ||
206 | // "Loop" forever accepting new connections. | |
207 | void | |
208 | http_server(tcp::acceptor& acceptor, tcp::socket& socket) | |
209 | { | |
210 | acceptor.async_accept(socket, | |
92f5a8d4 | 211 | [&](beast::error_code ec) |
b32b8144 FG |
212 | { |
213 | if(!ec) | |
214 | std::make_shared<http_connection>(std::move(socket))->start(); | |
215 | http_server(acceptor, socket); | |
216 | }); | |
217 | } | |
218 | ||
219 | int | |
220 | main(int argc, char* argv[]) | |
221 | { | |
222 | try | |
223 | { | |
224 | // Check command line arguments. | |
225 | if(argc != 3) | |
226 | { | |
227 | std::cerr << "Usage: " << argv[0] << " <address> <port>\n"; | |
228 | std::cerr << " For IPv4, try:\n"; | |
229 | std::cerr << " receiver 0.0.0.0 80\n"; | |
230 | std::cerr << " For IPv6, try:\n"; | |
231 | std::cerr << " receiver 0::0 80\n"; | |
232 | return EXIT_FAILURE; | |
233 | } | |
234 | ||
92f5a8d4 | 235 | auto const address = net::ip::make_address(argv[1]); |
b32b8144 FG |
236 | unsigned short port = static_cast<unsigned short>(std::atoi(argv[2])); |
237 | ||
92f5a8d4 | 238 | net::io_context ioc{1}; |
b32b8144 FG |
239 | |
240 | tcp::acceptor acceptor{ioc, {address, port}}; | |
241 | tcp::socket socket{ioc}; | |
242 | http_server(acceptor, socket); | |
243 | ||
244 | ioc.run(); | |
245 | } | |
246 | catch(std::exception const& e) | |
247 | { | |
248 | std::cerr << "Error: " << e.what() << std::endl; | |
249 | return EXIT_FAILURE; | |
250 | } | |
251 | } |