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
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>
31
32 namespace my_program_state
33 {
34 std::size_t
request_count()35 request_count()
36 {
37 static std::size_t count = 0;
38 return ++count;
39 }
40
41 std::time_t
now()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:
http_connection(tcp::socket socket)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
start()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.
69 beast::flat_buffer buffer_{8192};
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.
78 net::steady_timer deadline_{
79 socket_.get_executor(), std::chrono::seconds(60)};
80
81 // Asynchronously receive a complete request message.
82 void
read_request()83 read_request()
84 {
85 auto self = shared_from_this();
86
87 http::async_read(
88 socket_,
89 buffer_,
90 request_,
91 [self](beast::error_code ec,
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
process_request()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");
120 beast::ostream(response_.body())
121 << "Invalid request-method '"
122 << std::string(request_.method_string())
123 << "'";
124 break;
125 }
126
127 write_response();
128 }
129
130 // Construct a response message based on the program state.
131 void
create_response()132 create_response()
133 {
134 if(request_.target() == "/count")
135 {
136 response_.set(http::field::content_type, "text/html");
137 beast::ostream(response_.body())
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");
151 beast::ostream(response_.body())
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");
166 beast::ostream(response_.body()) << "File not found\r\n";
167 }
168 }
169
170 // Asynchronously transmit the response message.
171 void
write_response()172 write_response()
173 {
174 auto self = shared_from_this();
175
176 response_.content_length(response_.body().size());
177
178 http::async_write(
179 socket_,
180 response_,
181 [self](beast::error_code ec, std::size_t)
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
check_deadline()190 check_deadline()
191 {
192 auto self = shared_from_this();
193
194 deadline_.async_wait(
195 [self](beast::error_code ec)
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
http_server(tcp::acceptor & acceptor,tcp::socket & socket)208 http_server(tcp::acceptor& acceptor, tcp::socket& socket)
209 {
210 acceptor.async_accept(socket,
211 [&](beast::error_code ec)
212 {
213 if(!ec)
214 std::make_shared<http_connection>(std::move(socket))->start();
215 http_server(acceptor, socket);
216 });
217 }
218
219 int
main(int argc,char * argv[])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
235 auto const address = net::ip::make_address(argv[1]);
236 unsigned short port = static_cast<unsigned short>(std::atoi(argv[2]));
237
238 net::io_context ioc{1};
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 }
252