Skip to content

Commit e09e7ab

Browse files
mtrenkmanndeanberris
authored andcommitted
Add echo_async_server example (cpp-netlib#862)
1 parent b284b14 commit e09e7ab

File tree

1 file changed

+242
-0
lines changed

1 file changed

+242
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
// Copyright 2018 Martin Trenkmann
2+
// Based on cpp-netlib/libs/network/example/http/fileserver.cpp
3+
// Distributed under the Boost Software License, Version 1.0.
4+
// (See accompanying file LICENSE_1_0.txt or copy at
5+
// http://www.boost.org/LICENSE_1_0.txt)
6+
7+
// This example implements a minimal echo server that receives plain text via
8+
// POST requests and sends the same text back to the client (with an additional
9+
// newline appended for nicer output). Since the server uses asynchronous I/O,
10+
// the body data is not available through request.body, but must be read using
11+
// connection->read(callback).
12+
//
13+
// This code is minimal, it does not...
14+
// - handle any kinds of errors,
15+
// - look at the URI path (request.destination),
16+
// - handle request methods other than POST (request.method),
17+
// - check for too large bodies (which make the server vulnerable to attacks),
18+
// - model state, e.g. database connections (this type of context should be
19+
// injected into the post_request_handler constructor).
20+
//
21+
// About handling large body data:
22+
// According to https://tools.ietf.org/html/rfc2616#section-8.2.3, HTTP clients
23+
// may send a "Expect: 100-continue" header before sending large body data in
24+
// order to negotiate whether the server is willing to accept the data. If the
25+
// server agrees, it should send an interim response with a "100 Continue"
26+
// status code. The client then should start sending the data and upon
27+
// completion, the server should send a final response including a status code
28+
// and possibly headers and body data.
29+
//
30+
// About cpp-netlib 0.13 and the "Expect: 100-continue" header:
31+
// When a client sends a POST request with a "Expect: 100-continue" header, the
32+
// callback passed into connection->read() gets triggered with an empty chunk of
33+
// data. As described before, the server should then send an "100 Continue"
34+
// interim response, but this seems to be not supported by the library at the
35+
// moment, because trying to set and send a status code on the connection object
36+
// more than once throws an exception. However, since clients are allowed to
37+
// send data even though they never received a "100 Continue", the server should
38+
// continue reading data from the connection, by calling connection->read()
39+
// again, until the expected number of bytes, based on the "Content-Length"
40+
// header, has been received.
41+
//
42+
// About curl and the "Expect: 100-continue" header:
43+
// According to https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Expect
44+
// no common browsers make use of the "Expect" header, but some other clients
45+
// such as curl do so by default. In fact, curl sends a "Expect: 100-continue"
46+
// header with POST requests that contain at least 1KiB of data (see example
47+
// session below). Fortunately, curl also starts sending the data if it does
48+
// not receive a "100 Continue" status code after a timeout of 1 second, which
49+
// makes it "compatible" with servers that cannot send interim responses such as
50+
// cpp-netlib 0.13.
51+
//
52+
// The timeout in curl is configurable via "--expect100-timeout <seconds>"
53+
// command line argument. The minimum value seems to be 1 second.
54+
//
55+
// Example session:
56+
// Two POST requests are sent to the echo server using curl. The first one with
57+
// data less than 1KiB, the second one with data more than 1KiB which shows the
58+
// use of the "Expect: 100-continue" header.
59+
//
60+
// The "lorem" command generates lorem ipsum text. It can be installed via
61+
// "sudo apt-get install libtext-lorem-perl" on Debian-based systems.
62+
//
63+
// 1. Sending less than 1KiB
64+
// -------------------------
65+
//
66+
// $ curl -v -H 'Content-Type: text/plain' -d "$(lorem -s 3 | fold)" http://localhost:8000
67+
// * Rebuilt URL to: http://localhost:8000/
68+
// * Trying 127.0.0.1...
69+
// * Connected to localhost (127.0.0.1) port 8000 (#0)
70+
// > POST / HTTP/1.1
71+
// > Host: localhost:8000
72+
// > User-Agent: curl/7.47.0
73+
// > Accept: */*
74+
// > Content-Type: text/plain
75+
// > Content-Length: 155
76+
// >
77+
// * upload completely sent off: 155 out of 155 bytes
78+
// < HTTP/1.1 200 OK
79+
// < Content-Type: text/plain
80+
// < Content-Length: 156
81+
// <
82+
// Iusto autem omnis necessitatibus quia omnis nemo sequi. Occaecati est explicabo
83+
// qui placeat ipsa debitis fugit sit. Quasi sequi in est eius qui molestiae.
84+
// * Connection #0 to host localhost left intact
85+
//
86+
// SERVER OUTPUT
87+
//
88+
// Host: localhost:8000
89+
// User-Agent: curl/7.47.0
90+
// Accept: */*
91+
// Content-Type: text/plain
92+
// Content-Length: 155
93+
// Chunk size: 155
94+
//
95+
// 2. Sending more than 1KiB
96+
// -------------------------
97+
//
98+
// $ curl -v -H 'Content-Type: text/plain' -d "$(lorem -s 30 | fold)" http://localhost:8000
99+
// * Rebuilt URL to: http://localhost:8000/
100+
// * Trying 127.0.0.1...
101+
// * Connected to localhost (127.0.0.1) port 8000 (#0)
102+
// > POST / HTTP/1.1
103+
// > Host: localhost:8000
104+
// > User-Agent: curl/7.47.0
105+
// > Accept: */*
106+
// > Content-Type: text/plain
107+
// > Content-Length: 1324
108+
// > Expect: 100-continue
109+
// >
110+
// * Done waiting for 100-continue
111+
// * We are completely uploaded and fine
112+
// < HTTP/1.1 200 OK
113+
// < Content-Type: text/plain
114+
// < Content-Length: 1325
115+
// <
116+
// Fugit quo aliquid in et consectetur sed id. Aliquam dolor optio labore sit autem
117+
// . Culpa at omnis et consectetur minima nostrum sed. Veniam similique dolorum des
118+
// erunt aut et et aut quo. Laudantium nesciunt est repellat. Dolores adipisci alia
119+
// s dicta dicta. Impedit porro pariatur quisquam sit ex ducimus a. Consequatur ius
120+
// to possimus in sint nesciunt molestiae fugiat et. Est praesentium quos quam libe
121+
// ro vel nostrum placeat consequuntur. Tempora nihil aut aliquam. Atque ab sunt ut
122+
// sed quo ut. Quia qui omnis non. In laboriosam possimus laboriosam consequatur v
123+
// el dolores. Reprehenderit totam quis dolore debitis ullam. Aut iure omnis invent
124+
// ore quaerat aut veniam vel magni. Temporibus voluptatibus accusamus qui facilis
125+
// at aut. Voluptatem provident incidunt officia. Quos quo autem quae illo. Modi am
126+
// et quis eveniet. Nemo tenetur quia unde. Velit molestiae laborum eum. Repellat m
127+
// olestias eos eos accusantium dolorem molestias pariatur ex. Nihil dolorum possim
128+
// us ut ut beatae. Quia nam sit aut voluptatum maiores quibusdam id aliquid. Nulla
129+
// numquam rem quo doloremque ut ut. Aspernatur accusamus illo illo dolores repudi
130+
// andae dicta reiciendis. Quis laborum magni et incidunt nihil. Ea quia consequunt
131+
// ur quos minima aut veniam ratione sed. Ea deleniti accusamus est quo nisi. Quia
132+
// quibusdam et aut reiciendis.
133+
// * Connection #0 to host localhost left intact
134+
//
135+
// SERVER OUTPUT
136+
//
137+
// Host: localhost:8000
138+
// User-Agent: curl/7.47.0
139+
// Accept: */*
140+
// Content-Type: text/plain
141+
// Content-Length: 1324
142+
// Expect: 100-continue
143+
// Chunk size: 0
144+
// Chunk size: 1024
145+
// Chunk size: 300
146+
147+
#include <boost/asio/signal_set.hpp>
148+
#include <boost/network/protocol/http/server.hpp>
149+
#include <cassert>
150+
#include <memory>
151+
#include <string>
152+
#include <vector>
153+
154+
namespace http = boost::network::http;
155+
156+
struct request_handler;
157+
using echo_server = http::server<request_handler>;
158+
159+
struct post_request_handler
160+
: public std::enable_shared_from_this<post_request_handler> {
161+
explicit post_request_handler(const echo_server::request& request)
162+
: content_length_(0) {
163+
for (const auto& header : request.headers) {
164+
std::cout << header.name << ": " << header.value << '\n';
165+
if (boost::iequals(header.name, "content-length")) {
166+
content_length_ = std::stoul(header.value);
167+
}
168+
}
169+
}
170+
171+
void operator()(echo_server::connection::input_range chunk,
172+
boost::system::error_code ec, size_t chunk_size,
173+
echo_server::connection_ptr connection) {
174+
assert(chunk.size() == chunk_size);
175+
std::cout << "Chunk size: " << chunk_size << '\n';
176+
177+
if (ec) {
178+
std::cerr << "Error code: " << ec << '\n';
179+
return;
180+
}
181+
182+
body_.append(chunk.begin(), chunk.end());
183+
if (body_.size() < content_length_) {
184+
auto self = this->shared_from_this();
185+
connection->read([self](echo_server::connection::input_range chunk,
186+
boost::system::error_code ec, size_t chunk_size,
187+
echo_server::connection_ptr connection) {
188+
(*self)(chunk, ec, chunk_size, connection);
189+
});
190+
return;
191+
}
192+
193+
body_.push_back('\n');
194+
std::vector<echo_server::response_header> headers;
195+
headers.push_back({"Content-Type", "text/plain"});
196+
headers.push_back({"Content-Length", std::to_string(body_.size())});
197+
connection->set_status(echo_server::connection::ok);
198+
connection->set_headers(headers);
199+
connection->write(body_);
200+
}
201+
202+
private:
203+
size_t content_length_;
204+
std::string body_;
205+
};
206+
207+
struct request_handler {
208+
void operator()(const echo_server::request& request,
209+
echo_server::connection_ptr connection) {
210+
if (request.method == "POST") {
211+
auto h = std::make_shared<post_request_handler>(request);
212+
connection->read([h](echo_server::connection::input_range chunk,
213+
boost::system::error_code ec, size_t chunk_size,
214+
echo_server::connection_ptr connection) {
215+
(*h)(chunk, ec, chunk_size, connection);
216+
});
217+
}
218+
}
219+
};
220+
221+
int main() {
222+
try {
223+
request_handler handler;
224+
auto io_service = std::make_shared<boost::asio::io_service>();
225+
echo_server server(
226+
echo_server::options(handler).io_service(io_service).port("8000"));
227+
server.run();
228+
229+
// Clean shutdown when pressing Ctrl+C.
230+
boost::asio::signal_set signals(*io_service, SIGINT, SIGTERM);
231+
signals.async_wait([&server](const boost::system::error_code& ec,
232+
int /* signal_number */) {
233+
if (!ec) {
234+
server.stop();
235+
}
236+
});
237+
return EXIT_SUCCESS;
238+
} catch (const std::exception& error) {
239+
std::cerr << error.what() << std::endl;
240+
return EXIT_FAILURE;
241+
}
242+
}

0 commit comments

Comments
 (0)