| 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 |
| 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(timer: 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. |
| 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 |
| 83 | read_request() |
| 84 | { |
| 85 | auto self = shared_from_this(); |
| 86 | |
| 87 | http::async_read( |
| 88 | stream&: socket_, |
| 89 | buffer&: buffer_, |
| 90 | msg&: request_, |
| 91 | handler: [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 |
| 102 | process_request() |
| 103 | { |
| 104 | response_.version(value: request_.version()); |
| 105 | response_.keep_alive(value: false); |
| 106 | |
| 107 | switch(request_.method()) |
| 108 | { |
| 109 | case http::verb::get: |
| 110 | response_.result(v: http::status::ok); |
| 111 | response_.set(name: http::field::server, value: "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(v: http::status::bad_request); |
| 119 | response_.set(name: http::field::content_type, value: "text/plain" ); |
| 120 | beast::ostream(buffer&: 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 |
| 132 | create_response() |
| 133 | { |
| 134 | if(request_.target() == "/count" ) |
| 135 | { |
| 136 | response_.set(name: http::field::content_type, value: "text/html" ); |
| 137 | beast::ostream(buffer&: 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(name: http::field::content_type, value: "text/html" ); |
| 151 | beast::ostream(buffer&: 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(v: http::status::not_found); |
| 165 | response_.set(name: http::field::content_type, value: "text/plain" ); |
| 166 | beast::ostream(buffer&: response_.body()) << "File not found\r\n" ; |
| 167 | } |
| 168 | } |
| 169 | |
| 170 | // Asynchronously transmit the response message. |
| 171 | void |
| 172 | write_response() |
| 173 | { |
| 174 | auto self = shared_from_this(); |
| 175 | |
| 176 | response_.content_length(value: response_.body().size()); |
| 177 | |
| 178 | http::async_write( |
| 179 | stream&: socket_, |
| 180 | msg: response_, |
| 181 | handler: [self](beast::error_code ec, std::size_t) |
| 182 | { |
| 183 | self->socket_.shutdown(what: 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( |
| 195 | token: [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 |
| 208 | http_server(tcp::acceptor& acceptor, tcp::socket& socket) |
| 209 | { |
| 210 | acceptor.async_accept(peer&: socket, |
| 211 | token: [&](beast::error_code ec) |
| 212 | { |
| 213 | if(!ec) |
| 214 | std::make_shared<http_connection>(args: 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 | |
| 235 | auto const address = net::ip::make_address(str: argv[1]); |
| 236 | unsigned short port = static_cast<unsigned short>(std::atoi(nptr: 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 | |