Skip to content

Http server

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
find_package(Asio 1.21.0 QUIET)
if (NOT Asio_FOUND)
    FetchContent_Declare(asio GIT_REPOSITORY https://github.com/chriskohlhoff/asio.git GIT_TAG asio-1-21-0)
    FetchContent_GetProperties(asio)
    if (NOT asio_POPULATED)
        FetchContent_Populate(asio)
        add_library(asio INTERFACE)
        target_include_directories(asio INTERFACE ${asio_SOURCE_DIR}/asio/include)
        target_compile_definitions(asio INTERFACE ASIO_STANDALONE ASIO_NO_DEPRECATED)
        target_link_libraries(asio INTERFACE Threads::Threads)
    endif ()
endif()

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
add_executable(http_server
        connection.cpp
        connection.hpp
        connection_manager.cpp
        connection_manager.hpp
        header.hpp
        main.cpp
        mime_types.cpp
        mime_types.hpp
        reply.cpp
        reply.hpp
        request.hpp
        request_handler.cpp
        request_handler.hpp
        request_parser.cpp
        request_parser.hpp
        server.cpp
        server.hpp)
target_link_libraries(http_server asio)

1
2
3
4
5
6
7
8
9
int main(int argc, char *argv[]) {
    try {
        if (argc != 4) {
            std::cout << "Usage: http_server <address> <port> <doc_root>\n";
            std::cout << "  For IPv4, try: ";
            std::cout << "    receiver 0.0.0.0 80 .\n";
            std::cout << "  For IPv6, try: ";
            std::cout << "    receiver 0::0 80 .\n";
        }

1
2
3
4
5
6
std::string address = argc > 1 ? argv[1] : "0.0.0.0";
std::string port = argc > 2 ? argv[2] : "80";
std::string doc_root = argc > 3 ? argv[3] : ".";
std::cout << "Server address: http://" << address << ":" << port
          << '\n';
http::server::server s(address, port, doc_root);

1
s.run();

1
2
3
} catch (std::exception &e) {
    std::cerr << "exception: " << e.what() << "\n";
}

Share Snippets


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
class server {
  public:
    server(const server &) = delete;
    server &operator=(const server &) = delete;

    // Construct the server to listen on the specified TCP address and
    // port, and serve up files from the given directory.
    explicit server(const std::string &address, const std::string &port,
                    const std::string &doc_root);

    // Run the server's io_context loop.
    void run();

  private:
    // Perform an asynchronous accept operation.
    void schedule_accept();

    // Wait for a request to stop the server.
    void schedule_await_stop();

    // The io_context used to perform asynchronous operations.
    asio::io_context io_context_;

    // Executor for the io context
    asio::thread_pool pool_;

    // The signal_set is used to register for process termination
    // notifications.
    asio::signal_set signals_;

    // Acceptor used to listen for incoming connections.
    asio::ip::tcp::acceptor acceptor_;

    // The connection manager which owns all live connections.
    connection_manager connection_manager_;

    // The handler for all incoming requests.
    request_handler request_handler_;

    // Helper class to setup signals
    void setup_signals();

    // Helper class to setup acceptor
    void setup_acceptor(const std::string &address,
                        const std::string &port);
};

Share Snippets


1
2
3
4
5
6
7
server::server(const std::string &address, const std::string &port,
               const std::string &doc_root)
    : io_context_(1), signals_(io_context_), acceptor_(io_context_),
      connection_manager_(), request_handler_(doc_root) {
    setup_signals();
    setup_acceptor(address, port);
}

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
void server::setup_acceptor(
    const std::string &address,
    const std::string &port) {

    // Open the acceptor with the option to
    // reuse the address (i.e. SO_REUSEADDR).
    asio::ip::tcp::resolver resolver(io_context_);
    asio::ip::tcp::endpoint endpoint =
        *resolver.resolve(address, port).begin();
    acceptor_.open(endpoint.protocol());
    acceptor_.set_option(asio::socket_base::reuse_address(true));
    acceptor_.bind(endpoint);
    acceptor_.listen();

    schedule_accept();
}

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
    void server::setup_signals() {
        // Register to handle the signals that indicate when the server
        // should exit. It is safe to register for the same signal multiple
        // times in a program, provided all registration for the specified
        // signal is made through Asio.
        signals_.add(SIGINT);
        signals_.add(SIGTERM);
#if defined(SIGQUIT)
        signals_.add(SIGQUIT);
#endif // defined(SIGQUIT)
        schedule_await_stop();
    }

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
void server::run() {
    // The io_context::run() call will block until all asynchronous
    // operations have finished. While the server is running, there is
    // always at least one asynchronous operation outstanding: the
    // asynchronous accept call waiting for new incoming connections.
    for (size_t i = 0; i < std::thread::hardware_concurrency(); ++i) {
        asio::post(pool_, [this] { io_context_.run(); });
    }
    pool_.join();
}

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
void server::schedule_accept() {
    acceptor_.async_accept([this](std::error_code ec,
                                  asio::ip::tcp::socket socket) {
        // Check whether the server was stopped by a signal before
        // this completion handler had a chance to run.
        if (!acceptor_.is_open()) {
            return;
        }

        if (!ec) {
            connection_manager_.start(std::make_shared<connection>(
                std::move(socket), connection_manager_, request_handler_));
        }

        schedule_accept();
    });
}

1
2
3
4
5
6
7
8
9
void server::schedule_await_stop() {
    signals_.async_wait([this](std::error_code /*ec*/, int /*signo*/) {
        // The server is stopped by cancelling all outstanding
        // asynchronous operations. Once all operations have finished
        // the io_context::run() call will exit.
        acceptor_.close();
        connection_manager_.stop_all();
    });
}

Share Snippets


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Manages open connections so that they may be cleanly stopped when
// the server needs to shut down.
class connection_manager {
  public:
    connection_manager(const connection_manager &) = delete;
    connection_manager &operator=(const connection_manager &) = delete;

    // Construct a connection manager.
    connection_manager();

    // Add the specified connection to the manager and start it.
    void start(connection_ptr c);

    // Stop the specified connection.
    void stop(connection_ptr c);

    // Stop all connections.
    void stop_all();

  private:
    // The managed connections.
    std::set<connection_ptr> connections_;
};

Share Snippets


1
connection_manager::connection_manager() = default;

1
2
3
4
void connection_manager::start(connection_ptr c) {
    connections_.insert(c);
    c->start();
}

1
2
3
4
void connection_manager::stop(connection_ptr c) {
    connections_.erase(c);
    c->stop();
}

1
2
3
4
5
6
void connection_manager::stop_all() {
    for (auto c : connections_) {
        c->stop();
    }
    connections_.clear();
}

Share Snippets


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class connection : public std::enable_shared_from_this<connection> {
  public:
    connection(const connection &) = delete;
    connection &operator=(const connection &) = delete;

    // Construct a connection with the given socket.
    explicit connection(asio::ip::tcp::socket socket,
                        connection_manager &manager,
                        request_handler &handler);

    // Start the first asynchronous operation for the connection.
    void start();

    // Stop all asynchronous operations associated with the connection.
    void stop();

  private:
    // Perform an asynchronous read operation.
    void schedule_read();

    // Perform an asynchronous write operation.
    void schedule_write();

    // Socket for the connection.
    asio::ip::tcp::socket socket_;

    // The manager for this connection.
    connection_manager &connection_manager_;

    // The handler used to process the incoming request.
    request_handler &request_handler_;

    // Buffer for incoming data.
    std::array<char, 8192> buffer_;

    // The incoming request.
    request request_;

    // The parser for the incoming request.
    request_parser request_parser_;

    // The reply to be sent back to the client.
    reply reply_;
};

Share Snippets


1
2
3
4
5
connection::connection(asio::ip::tcp::socket socket,
                       connection_manager &manager,
                       request_handler &handler)
    : socket_(std::move(socket)), connection_manager_(manager),
      request_handler_(handler) {}

1
void connection::start() { schedule_read(); }

1
void connection::stop() { socket_.close(); }

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
void connection::schedule_read() {
    auto self(shared_from_this());
    socket_.async_read_some(
        asio::buffer(buffer_),
        [this, self](std::error_code ec, std::size_t bytes_transferred) {
            if (!ec) {
                // Parse as an HTTP request
                request_parser::result_type result;
                std::tie(result, std::ignore) = request_parser_.parse(
                    request_, buffer_.data(),
                    buffer_.data() + bytes_transferred);

                // Generate reply with the request handler and write it
                if (result == request_parser::good) {
                    request_handler_.handle_request(request_, reply_);
                    schedule_write();
                } else if (result == request_parser::bad) {
                    reply_ = reply::stock_reply(reply::bad_request);
                    schedule_write();
                } else {
                    schedule_read();
                }
            } else if (ec != asio::error::operation_aborted) {
                connection_manager_.stop(shared_from_this());
            }
        });
}

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
void connection::schedule_write() {
    auto self(shared_from_this());
    asio::async_write(socket_, reply_.to_buffers(),
                      [this, self](std::error_code ec, std::size_t) {
                          if (!ec) {
                              // Initiate graceful connection closure.
                              asio::error_code ignored_ec;
                              socket_.shutdown(
                                  asio::ip::tcp::socket::shutdown_both,
                                  ignored_ec);
                          }

                          if (ec != asio::error::operation_aborted) {
                              connection_manager_.stop(shared_from_this());
                          }
                      });
}

Share Snippets


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
struct reply {
    // The status of the reply.
    enum status_type {
        ok = 200,
        created = 201,
        accepted = 202,
        no_content = 204,
        multiple_choices = 300,
        moved_permanently = 301,
        moved_temporarily = 302,
        not_modified = 304,
        bad_request = 400,
        unauthorized = 401,
        forbidden = 403,
        not_found = 404,
        internal_server_error = 500,
        not_implemented = 501,
        bad_gateway = 502,
        service_unavailable = 503
    } status;

    // The headers to be included in the reply.
    std::vector<header> headers;

    // The content to be sent in the reply.
    std::string content;

    // Convert the reply into a vector of buffers. The buffers do not
    // own the underlying memory blocks, therefore the reply object
    // must remain valid and not be changed until the write operation
    // has completed.
    std::vector<asio::const_buffer> to_buffers();

    // Get a stock reply.
    static reply stock_reply(status_type status);
};

Share Snippets


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
namespace status_strings {

    const std::string ok = "HTTP/1.0 200 OK\r\n";
    const std::string created = "HTTP/1.0 201 Created\r\n";
    const std::string accepted = "HTTP/1.0 202 Accepted\r\n";
    const std::string no_content = "HTTP/1.0 204 No Content\r\n";
    const std::string multiple_choices =
        "HTTP/1.0 300 Multiple Choices\r\n";
    const std::string moved_permanently =
        "HTTP/1.0 301 Moved Permanently\r\n";
    const std::string moved_temporarily =
        "HTTP/1.0 302 Moved Temporarily\r\n";
    const std::string not_modified = "HTTP/1.0 304 Not Modified\r\n";
    const std::string bad_request = "HTTP/1.0 400 Bad Request\r\n";
    const std::string unauthorized = "HTTP/1.0 401 Unauthorized\r\n";
    const std::string forbidden = "HTTP/1.0 403 Forbidden\r\n";
    const std::string not_found = "HTTP/1.0 404 Not Found\r\n";
    const std::string internal_server_error =
        "HTTP/1.0 500 Internal Server Error\r\n";
    const std::string not_implemented = "HTTP/1.0 501 Not Implemented\r\n";
    const std::string bad_gateway = "HTTP/1.0 502 Bad Gateway\r\n";
    const std::string service_unavailable =
        "HTTP/1.0 503 Service Unavailable\r\n";

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
    asio::const_buffer to_buffer(reply::status_type status) {
        switch (status) {
        case reply::ok:
            return asio::buffer(ok);
        case reply::created:
            return asio::buffer(created);
        case reply::accepted:
            return asio::buffer(accepted);
        case reply::no_content:
            return asio::buffer(no_content);
        case reply::multiple_choices:
            return asio::buffer(multiple_choices);
        case reply::moved_permanently:
            return asio::buffer(moved_permanently);
        case reply::moved_temporarily:
            return asio::buffer(moved_temporarily);
        case reply::not_modified:
            return asio::buffer(not_modified);
        case reply::bad_request:
            return asio::buffer(bad_request);
        case reply::unauthorized:
            return asio::buffer(unauthorized);
        case reply::forbidden:
            return asio::buffer(forbidden);
        case reply::not_found:
            return asio::buffer(not_found);
        case reply::internal_server_error:
            return asio::buffer(internal_server_error);
        case reply::not_implemented:
            return asio::buffer(not_implemented);
        case reply::bad_gateway:
            return asio::buffer(bad_gateway);
        case reply::service_unavailable:
            return asio::buffer(service_unavailable);
        default:
            return asio::buffer(internal_server_error);
        }
    }
} // namespace status_strings

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
namespace misc_strings {
    const char name_value_separator[] = {':', ' '};
    const char crlf[] = {'\r', '\n'};
} // namespace misc_strings

std::vector<asio::const_buffer> reply::to_buffers() {
    std::vector<asio::const_buffer> buffers;
    buffers.push_back(status_strings::to_buffer(status));
    for (std::size_t i = 0; i < headers.size(); ++i) {
        header &h = headers[i];
        buffers.push_back(asio::buffer(h.name));
        buffers.push_back(asio::buffer(misc_strings::name_value_separator));
        buffers.push_back(asio::buffer(h.value));
        buffers.push_back(asio::buffer(misc_strings::crlf));
    }
    buffers.push_back(asio::buffer(misc_strings::crlf));
    buffers.push_back(asio::buffer(content));
    return buffers;
}

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
namespace stock_replies {

    namespace as_string {
        constexpr char ok[] = "";
        constexpr char created[] = "<html>"
                                   "<head><title>Created</title></head>"
                                   "<body><h1>201 Created</h1></body>"
                                   "</html>";
        constexpr char accepted[] = "<html>"
                                    "<head><title>Accepted</title></head>"
                                    "<body><h1>202 Accepted</h1></body>"
                                    "</html>";
        constexpr char no_content[] =
            "<html>"
            "<head><title>No Content</title></head>"
            "<body><h1>204 Content</h1></body>"
            "</html>";
        constexpr char multiple_choices[] =
            "<html>"
            "<head><title>Multiple Choices</title></head>"
            "<body><h1>300 Multiple Choices</h1></body>"
            "</html>";
        constexpr char moved_permanently[] =
            "<html>"
            "<head><title>Moved Permanently</title></head>"
            "<body><h1>301 Moved Permanently</h1></body>"
            "</html>";
        constexpr char moved_temporarily[] =
            "<html>"
            "<head><title>Moved Temporarily</title></head>"
            "<body><h1>302 Moved Temporarily</h1></body>"
            "</html>";
        constexpr char not_modified[] =
            "<html>"
            "<head><title>Not Modified</title></head>"
            "<body><h1>304 Not Modified</h1></body>"
            "</html>";
        constexpr char bad_request[] =
            "<html>"
            "<head><title>Bad Request</title></head>"
            "<body><h1>400 Bad Request</h1></body>"
            "</html>";
        constexpr char unauthorized[] =
            "<html>"
            "<head><title>Unauthorized</title></head>"
            "<body><h1>401 Unauthorized</h1></body>"
            "</html>";
        constexpr char forbidden[] = "<html>"
                                     "<head><title>Forbidden</title></head>"
                                     "<body><h1>403 Forbidden</h1></body>"
                                     "</html>";
        constexpr char not_found[] = "<html>"
                                     "<head><title>Not Found</title></head>"
                                     "<body><h1>404 Not Found</h1></body>"
                                     "</html>";
        constexpr char internal_server_error[] =
            "<html>"
            "<head><title>Internal Server Error</title></head>"
            "<body><h1>500 Internal Server Error</h1></body>"
            "</html>";
        constexpr char not_implemented[] =
            "<html>"
            "<head><title>Not Implemented</title></head>"
            "<body><h1>501 Not Implemented</h1></body>"
            "</html>";
        constexpr char bad_gateway[] =
            "<html>"
            "<head><title>Bad Gateway</title></head>"
            "<body><h1>502 Bad Gateway</h1></body>"
            "</html>";
        constexpr char service_unavailable[] =
            "<html>"
            "<head><title>Service Unavailable</title></head>"
            "<body><h1>503 Service Unavailable</h1></body>"
            "</html>";
    } // namespace as_string

    std::string to_string(reply::status_type status) {
        switch (status) {
        case reply::ok: // NOLINT(bugprone-branch-clone)
            return as_string::ok;
        case reply::created:
            return as_string::created;
        case reply::accepted:
            return as_string::accepted;
        case reply::no_content:
            return as_string::no_content;
        case reply::multiple_choices:
            return as_string::multiple_choices;
        case reply::moved_permanently:
            return as_string::moved_permanently;
        case reply::moved_temporarily:
            return as_string::moved_temporarily;
        case reply::not_modified:
            return as_string::not_modified;
        case reply::bad_request:
            return as_string::bad_request;
        case reply::unauthorized:
            return as_string::unauthorized;
        case reply::forbidden:
            return as_string::forbidden;
        case reply::not_found:
            return as_string::not_found;
        case reply::internal_server_error:
            return as_string::internal_server_error;
        case reply::not_implemented:
            return as_string::not_implemented;
        case reply::bad_gateway:
            return as_string::bad_gateway;
        case reply::service_unavailable:
            return as_string::service_unavailable;
        default:
            return as_string::internal_server_error;
        }
    }
} // namespace stock_replies

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
reply reply::stock_reply(reply::status_type status) {
    reply rep;
    rep.status = status;
    rep.headers.resize(2);
    rep.headers[0].name = "Content-Length";
    rep.content = stock_replies::to_string(status);
    rep.headers[0].value = std::to_string(rep.content.size());
    rep.headers[1].name = "Content-Type";
    rep.headers[1].value = "text/html";
    return rep;
}

Share Snippets


1
2
3
4
struct header {
    std::string name;
    std::string value;
};

Share Snippets


1
2
3
4
5
6
7
struct request {
    std::string method;
    std::string uri;
    int http_version_major;
    int http_version_minor;
    std::vector<header> headers;
};

Share Snippets


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
class request_parser {
  public:
    // Construct ready to parse the request method.
    request_parser();

    // Reset to initial parser state.
    void reset();

    // Result of parse.
    enum result_type { good, bad, indeterminate };

    // Parse some data. The enum return value is good when a complete
    // request has been parsed, bad if the data is invalid,
    // indeterminate when more data is required. The InputIterator
    // return value indicates how much of the input has been consumed.
    template <typename InputIterator>
    std::tuple<result_type, InputIterator>
    parse(request &req, InputIterator begin, InputIterator end) {
        while (begin != end) {
            result_type result = consume(req, *begin++);
            if (result == good || result == bad)
                return std::make_tuple(result, begin);
        }
        return std::make_tuple(indeterminate, begin);
    }

  private:
    // Handle the next character of input.
    result_type consume(request &req, char input);

    // Check if a byte is an HTTP character.
    static bool is_char(int c);

    // Check if a byte is an HTTP control character.
    static bool is_ctl(int c);

    // Check if a byte is defined as an HTTP tspecial character.
    static bool is_tspecial(int c);

    // Check if a byte is a digit.
    static bool is_digit(int c);

    // The current state of the parser.
    enum state {
        method_start,
        method,
        uri,
        http_version_h,
        http_version_t_1,
        http_version_t_2,
        http_version_p,
        http_version_slash,
        http_version_major_start,
        http_version_major,
        http_version_minor_start,
        http_version_minor,
        expecting_newline_1,
        header_line_start,
        header_lws,
        header_name,
        space_before_header_value,
        header_value,
        expecting_newline_2,
        expecting_newline_3
    } state_;
};

Share Snippets


1
request_parser::request_parser() : state_(method_start) {}

1
void request_parser::reset() { state_ = method_start; }

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
request_parser::result_type request_parser::consume(request &req,
                                                    char input) {
    switch (state_) {
    case method_start:
        if (!is_char(input) || is_ctl(input) || is_tspecial(input)) {
            return bad;
        } else {
            state_ = method;
            req.method.push_back(input);
            return indeterminate;
        }
    case method:
        if (input == ' ') {
            state_ = uri;
            return indeterminate;
        } else if (!is_char(input) || is_ctl(input) || is_tspecial(input)) {
            return bad;
        } else {
            req.method.push_back(input);
            return indeterminate;
        }
    case uri:
        if (input == ' ') {
            state_ = http_version_h;
            return indeterminate;
        } else if (is_ctl(input)) {
            return bad;
        } else {
            req.uri.push_back(input);
            return indeterminate;
        }
    case http_version_h:
        if (input == 'H') {
            state_ = http_version_t_1;
            return indeterminate;
        } else {
            return bad;
        }
    case http_version_t_1:
        if (input == 'T') {
            state_ = http_version_t_2;
            return indeterminate;
        } else {
            return bad;
        }
    case http_version_t_2:
        if (input == 'T') {
            state_ = http_version_p;
            return indeterminate;
        } else {
            return bad;
        }
    case http_version_p:
        if (input == 'P') {
            state_ = http_version_slash;
            return indeterminate;
        } else {
            return bad;
        }
    case http_version_slash:
        if (input == '/') {
            req.http_version_major = 0;
            req.http_version_minor = 0;
            state_ = http_version_major_start;
            return indeterminate;
        } else {
            return bad;
        }
    case http_version_major_start:
        if (is_digit(input)) {
            req.http_version_major =
                req.http_version_major * 10 + input - '0';
            state_ = http_version_major;
            return indeterminate;
        } else {
            return bad;
        }
    case http_version_major:
        if (input == '.') {
            state_ = http_version_minor_start;
            return indeterminate;
        } else if (is_digit(input)) {
            req.http_version_major =
                req.http_version_major * 10 + input - '0';
            return indeterminate;
        } else {
            return bad;
        }
    case http_version_minor_start:
        if (is_digit(input)) {
            req.http_version_minor =
                req.http_version_minor * 10 + input - '0';
            state_ = http_version_minor;
            return indeterminate;
        } else {
            return bad;
        }
    case http_version_minor:
        if (input == '\r') {
            state_ = expecting_newline_1;
            return indeterminate;
        } else if (is_digit(input)) {
            req.http_version_minor =
                req.http_version_minor * 10 + input - '0';
            return indeterminate;
        } else {
            return bad;
        }
    case expecting_newline_1:
        if (input == '\n') {
            state_ = header_line_start;
            return indeterminate;
        } else {
            return bad;
        }
    case header_line_start:
        if (input == '\r') {
            state_ = expecting_newline_3;
            return indeterminate;
        } else if (!req.headers.empty() &&
                   (input == ' ' || input == '\t')) {
            state_ = header_lws;
            return indeterminate;
        } else if (!is_char(input) || is_ctl(input) || is_tspecial(input)) {
            return bad;
        } else {
            req.headers.push_back(header());
            req.headers.back().name.push_back(input);
            state_ = header_name;
            return indeterminate;
        }
    case header_lws:
        if (input == '\r') {
            state_ = expecting_newline_2;
            return indeterminate;
        } else if (input == ' ' || input == '\t') {
            return indeterminate;
        } else if (is_ctl(input)) {
            return bad;
        } else {
            state_ = header_value;
            req.headers.back().value.push_back(input);
            return indeterminate;
        }
    case header_name:
        if (input == ':') {
            state_ = space_before_header_value;
            return indeterminate;
        } else if (!is_char(input) || is_ctl(input) || is_tspecial(input)) {
            return bad;
        } else {
            req.headers.back().name.push_back(input);
            return indeterminate;
        }
    case space_before_header_value:
        if (input == ' ') {
            state_ = header_value;
            return indeterminate;
        } else {
            return bad;
        }
    case header_value:
        if (input == '\r') {
            state_ = expecting_newline_2;
            return indeterminate;
        } else if (is_ctl(input)) {
            return bad;
        } else {
            req.headers.back().value.push_back(input);
            return indeterminate;
        }
    case expecting_newline_2:
        if (input == '\n') {
            state_ = header_line_start;
            return indeterminate;
        } else {
            return bad;
        }
    case expecting_newline_3:
        return (input == '\n') ? good : bad;
    default:
        return bad;
    }
}

1
bool request_parser::is_char(int c) { return c >= 0 && c <= 127; }

1
2
3
bool request_parser::is_ctl(int c) {
    return (c >= 0 && c <= 31) || (c == 127);
}

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
bool request_parser::is_tspecial(int c) {
    switch (c) {
    case '(':
    case ')':
    case '<':
    case '>':
    case '@':
    case ',':
    case ';':
    case ':':
    case '\\':
    case '"':
    case '/':
    case '[':
    case ']':
    case '?':
    case '=':
    case '{':
    case '}':
    case ' ':
    case '\t':
        return true;
    default:
        return false;
    }
}

1
bool request_parser::is_digit(int c) { return c >= '0' && c <= '9'; }

Share Snippets


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
class request_handler {
  public:
    request_handler(const request_handler &) = delete;
    request_handler &operator=(const request_handler &) = delete;

    // Construct with a directory containing files to be served.
    explicit request_handler(const std::string &doc_root);

    // Handle a request and produce a reply.
    void handle_request(const request &req, reply &rep);

  private:
    // The directory containing the files to be served.
    std::string doc_root_;

    // Perform URL-decoding on a string. Returns false if the encoding
    // was invalid.
    static bool url_decode(const std::string &in, std::string &out);
};

Share Snippets


1
2
request_handler::request_handler(const std::string &doc_root)
    : doc_root_(doc_root) {}

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
void request_handler::handle_request(const request &req, reply &rep) {
    // Decode url to path.
    std::string request_path;
    if (!url_decode(req.uri, request_path)) {
        rep = reply::stock_reply(reply::bad_request);
        return;
    }

    // Request path must be absolute and not contain "..".
    if (request_path.empty() || request_path[0] != '/' ||
        request_path.find("..") != std::string::npos) {
        rep = reply::stock_reply(reply::bad_request);
        return;
    }

    // If path ends in slash (i.e. is a directory) then add
    // "index.html".
    if (request_path[request_path.size() - 1] == '/') {
        request_path += "index.html";
    }

    // Determine the file extension.
    std::size_t last_slash_pos = request_path.find_last_of("/");
    std::size_t last_dot_pos = request_path.find_last_of(".");
    std::string extension;
    if (last_dot_pos != std::string::npos &&
        last_dot_pos > last_slash_pos) {
        extension = request_path.substr(last_dot_pos + 1);
    }

    // Open the file to send back.
    std::string full_path = doc_root_ + request_path;
    std::ifstream is(full_path.c_str(), std::ios::in | std::ios::binary);
    if (!is) {
        rep = reply::stock_reply(reply::not_found);
        return;
    }

    // Fill out the reply to be sent to the client.
    rep.status = reply::ok;
    char buf[512];
    while (is.read(buf, sizeof(buf)).gcount() > 0)
        rep.content.append(buf, is.gcount());
    rep.headers.resize(2);
    rep.headers[0].name = "Content-Length";
    rep.headers[0].value = std::to_string(rep.content.size());
    rep.headers[1].name = "Content-Type";
    rep.headers[1].value = mime_types::extension_to_type(extension);
}

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
bool request_handler::url_decode(const std::string &in, std::string &out) {
    out.clear();
    out.reserve(in.size());
    for (std::size_t i = 0; i < in.size(); ++i) {
        if (in[i] == '%') {
            if (i + 3 <= in.size()) {
                int value = 0;
                std::istringstream is(in.substr(i + 1, 2));
                if (is >> std::hex >> value) {
                    out += static_cast<char>(value);
                    i += 2;
                } else {
                    return false;
                }
            } else {
                return false;
            }
        } else if (in[i] == '+') {
            out += ' ';
        } else {
            out += in[i];
        }
    }
    return true;
}

Share Snippets


1
std::string extension_to_type(const std::string &extension);

Share Snippets


1
2
3
4
5
6
7
8
constexpr struct mapping {
    const char *extension;
    const char *mime_type;
} mappings[] = {{"gif", "image/gif"},
                {"htm", "text/html"},
                {"html", "text/html"},
                {"jpg", "image/jpeg"},
                {"png", "image/png"}};

1
2
3
4
5
6
7
8
9
std::string extension_to_type(const std::string &extension) {
    for (mapping m : mappings) {
        if (m.extension == extension) {
            return m.mime_type;
        }
    }

    return "text/plain";
}

Share Snippets