• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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