Skip to content

Commit cf080ef

Browse files
committed
Fixes cpp-netlib#635 - bug in async server's connection read
This changes the example file server to use the read handler for POST/PUT requests. This currently assumes that there's a content-length header (not much error handling is happening here, but it's meant as a proof of concept anyway). Using this code path in an example should be good enough for the moment until we have better tests and a better API for this functionality.
1 parent d23bfb2 commit cf080ef

File tree

2 files changed

+72
-12
lines changed

2 files changed

+72
-12
lines changed

boost/network/protocol/http/server/async_connection.hpp

+3-2
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@ struct async_connection
378378
boost::throw_exception(std::system_error(*error_encountered));
379379
if (new_start != read_buffer_.begin()) {
380380
input_range input =
381-
boost::make_iterator_range(new_start, read_buffer_.end());
381+
boost::make_iterator_range(new_start, data_end);
382382
buffer_type::iterator start_tmp = new_start;
383383
new_start = read_buffer_.begin();
384384
auto self = this->shared_from_this();
@@ -392,7 +392,8 @@ struct async_connection
392392
socket().async_read_some(::asio::buffer(read_buffer_),
393393
strand.wrap([this, self, callback](
394394
std::error_code ec, size_t bytes_transferred) {
395-
callback(ec, bytes_transferred);
395+
this->wrap_read_handler(callback, ec,
396+
bytes_transferred);
396397
}));
397398
}
398399

libs/network/example/http/fileserver.cpp

+69-10
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ struct file_server;
2121
typedef http::server<file_server> server;
2222

2323
struct file_cache {
24-
2524
typedef std::map<std::string, std::pair<void *, std::size_t> > region_map;
2625
typedef std::map<std::string, std::vector<server::response_header> > meta_map;
2726

@@ -66,7 +65,8 @@ struct file_cache {
6665

6766
regions.insert(std::make_pair(real_filename, std::make_pair(region, size)));
6867
static server::response_header common_headers[] = {
69-
{"Connection", "close"}, {"Content-Type", "x-application/octet-stream"},
68+
{"Connection", "close"},
69+
{"Content-Type", "x-application/octet-stream"},
7070
{"Content-Length", "0"}};
7171
std::vector<server::response_header> headers(common_headers,
7272
common_headers + 3);
@@ -91,8 +91,7 @@ struct file_cache {
9191
static std::vector<server::response_header> empty_vector;
9292
auto headers = file_headers.find(doc_root_ + path);
9393
if (headers != file_headers.end()) {
94-
auto begin = headers->second.begin(),
95-
end = headers->second.end();
94+
auto begin = headers->second.begin(), end = headers->second.end();
9695
return boost::make_iterator_range(begin, end);
9796
} else
9897
return boost::make_iterator_range(empty_vector);
@@ -138,22 +137,75 @@ struct connection_handler : std::enable_shared_from_this<connection_handler> {
138137
asio::const_buffers_1(
139138
static_cast<char const *>(mmaped_region.first) + offset,
140139
rightmost_bound - offset),
141-
[=] (std::error_code const &ec) {
140+
[=](std::error_code const &ec) {
142141
self->handle_chunk(mmaped_region, rightmost_bound, connection, ec);
143142
});
144143
}
145144

146145
void handle_chunk(std::pair<void *, std::size_t> mmaped_region, off_t offset,
147146
server::connection_ptr connection,
148147
std::error_code const &ec) {
149-
assert(offset>=0);
148+
assert(offset >= 0);
150149
if (!ec && static_cast<std::size_t>(offset) < mmaped_region.second)
151150
send_file(mmaped_region, offset, connection);
152151
}
153152

154153
file_cache &file_cache_;
155154
};
156155

156+
struct input_consumer : public std::enable_shared_from_this<input_consumer> {
157+
// Maximum size for incoming request bodies.
158+
static constexpr std::size_t MAX_INPUT_BODY_SIZE = 2 << 16;
159+
160+
explicit input_consumer(std::shared_ptr<connection_handler> h,
161+
server::request r)
162+
: request_(std::move(r)), handler_(std::move(h)), content_length_{0} {
163+
for (const auto &header : request_.headers) {
164+
if (boost::iequals(header.name, "content-length")) {
165+
content_length_ = std::stoul(header.value);
166+
std::cerr << "Content length: " << content_length_ << '\n';
167+
break;
168+
}
169+
}
170+
}
171+
172+
void operator()(server::connection::input_range input, std::error_code ec,
173+
std::size_t bytes_transferred,
174+
server::connection_ptr connection) {
175+
std::cerr << "Callback: " << bytes_transferred << "; ec = " << ec << '\n';
176+
if (ec == asio::error::eof) return;
177+
if (!ec) {
178+
if (empty(input))
179+
return (*handler_)(request_.destination, connection, true);
180+
request_.body.insert(request_.body.end(), boost::begin(input),
181+
boost::end(input));
182+
if (request_.body.size() > MAX_INPUT_BODY_SIZE) {
183+
connection->set_status(server::connection::bad_request);
184+
static server::response_header error_headers[] = {
185+
{"Connection", "close"}};
186+
connection->set_headers(
187+
boost::make_iterator_range(error_headers, error_headers + 1));
188+
connection->write("Body too large.");
189+
return;
190+
}
191+
std::cerr << "Body: " << request_.body << '\n';
192+
if (request_.body.size() == content_length_)
193+
return (*handler_)(request_.destination, connection, true);
194+
std::cerr << "Scheduling another read...\n";
195+
auto self = this->shared_from_this();
196+
connection->read([self](server::connection::input_range input,
197+
std::error_code ec, std::size_t bytes_transferred,
198+
server::connection_ptr connection) {
199+
(*self)(input, ec, bytes_transferred, connection);
200+
});
201+
}
202+
}
203+
204+
server::request request_;
205+
std::shared_ptr<connection_handler> handler_;
206+
size_t content_length_;
207+
};
208+
157209
struct file_server {
158210
explicit file_server(file_cache &cache) : cache_(cache) {}
159211

@@ -165,6 +217,14 @@ struct file_server {
165217
} else if (request.method == "GET") {
166218
std::shared_ptr<connection_handler> h(new connection_handler(cache_));
167219
(*h)(request.destination, connection, true);
220+
} else if (request.method == "PUT" || request.method == "POST") {
221+
auto h = std::make_shared<connection_handler>(cache_);
222+
auto c = std::make_shared<input_consumer>(h, request);
223+
connection->read([c](server::connection::input_range input,
224+
std::error_code ec, std::size_t bytes_transferred,
225+
server::connection_ptr connection) {
226+
(*c)(input, ec, bytes_transferred, connection);
227+
});
168228
} else {
169229
static server::response_header error_headers[] = {
170230
{"Connection", "close"}};
@@ -184,11 +244,10 @@ int main(int, char *[]) {
184244
file_server handler(cache);
185245
server::options options(handler);
186246
server instance(options.thread_pool(std::make_shared<utils::thread_pool>(4))
187-
.address("0.0.0.0")
188-
.port("8000"));
247+
.address("0.0.0.0")
248+
.port("8000"));
189249
instance.run();
190-
}
191-
catch (std::exception &e) {
250+
} catch (std::exception &e) {
192251
std::cerr << e.what() << std::endl;
193252
}
194253
}

0 commit comments

Comments
 (0)