Kea 1.5.0
connection.cc
Go to the documentation of this file.
1// Copyright (C) 2017-2018 Internet Systems Consortium, Inc. ("ISC")
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7#include <config.h>
8
10#include <http/connection.h>
12#include <http/http_log.h>
13#include <http/http_messages.h>
14#include <boost/bind.hpp>
15
16using namespace isc::asiolink;
17
18namespace {
19
23constexpr size_t MAX_LOGGED_MESSAGE_SIZE = 1024;
24
25}
26
27namespace isc {
28namespace http {
29
30void
31HttpConnection::
32SocketCallback::operator()(boost::system::error_code ec, size_t length) {
33 if (ec.value() == boost::asio::error::operation_aborted) {
34 return;
35 }
36 callback_(ec, length);
37}
38
40 HttpAcceptor& acceptor,
41 HttpConnectionPool& connection_pool,
42 const HttpResponseCreatorPtr& response_creator,
43 const HttpAcceptorCallback& callback,
44 const long request_timeout,
45 const long idle_timeout)
46 : request_timer_(io_service),
47 request_timeout_(request_timeout),
48 idle_timeout_(idle_timeout),
49 socket_(io_service),
50 acceptor_(acceptor),
51 connection_pool_(connection_pool),
52 response_creator_(response_creator),
53 request_(response_creator_->createNewHttpRequest()),
54 parser_(new HttpRequestParser(*request_)),
55 acceptor_callback_(callback),
56 buf_(),
57 output_buf_() {
58 parser_->initModel();
59}
60
62 close();
63}
64
65void
67 request_timer_.cancel();
68 socket_.close();
69}
70
71void
72HttpConnection::stopThisConnection() {
73 try {
75 HTTP_CONNECTION_STOP)
76 .arg(getRemoteEndpointAddressAsText());
77 connection_pool_.stop(shared_from_this());
78 } catch (...) {
79 LOG_ERROR(http_logger, HTTP_CONNECTION_STOP_FAILED);
80 }
81}
82
83void
85 // Create instance of the callback. It is safe to pass the local instance
86 // of the callback, because the underlying boost functions make copies
87 // as needed.
88 HttpAcceptorCallback cb = boost::bind(&HttpConnection::acceptorCallback,
89 shared_from_this(),
90 boost::asio::placeholders::error);
91 try {
92 acceptor_.asyncAccept(socket_, cb);
93
94 } catch (const std::exception& ex) {
95 isc_throw(HttpConnectionError, "unable to start accepting TCP "
96 "connections: " << ex.what());
97 }
98}
99
100void
102 try {
103 TCPEndpoint endpoint;
104 // Create instance of the callback. It is safe to pass the local instance
105 // of the callback, because the underlying boost functions make copies
106 // as needed.
107 SocketCallback cb(boost::bind(&HttpConnection::socketReadCallback,
108 shared_from_this(),
109 boost::asio::placeholders::error,
110 boost::asio::placeholders::bytes_transferred));
111 socket_.asyncReceive(static_cast<void*>(buf_.data()), buf_.size(),
112 0, &endpoint, cb);
113
114 } catch (...) {
115 stopThisConnection();
116 }
117}
118
119void
120HttpConnection::doWrite() {
121 try {
122 if (!output_buf_.empty()) {
123 // Create instance of the callback. It is safe to pass the local instance
124 // of the callback, because the underlying boost functions make copies
125 // as needed.
126 SocketCallback cb(boost::bind(&HttpConnection::socketWriteCallback,
127 shared_from_this(),
128 boost::asio::placeholders::error,
129 boost::asio::placeholders::bytes_transferred));
130 socket_.asyncSend(output_buf_.data(),
131 output_buf_.length(),
132 cb);
133 } else {
134 if (!request_->isPersistent()) {
135 stopThisConnection();
136
137 } else {
138 reinitProcessingState();
139 doRead();
140 }
141 }
142 } catch (...) {
143 stopThisConnection();
144 }
145}
146
147void
148HttpConnection::asyncSendResponse(const ConstHttpResponsePtr& response) {
149 output_buf_ = response->toString();
150 doWrite();
151}
152
153
154void
155HttpConnection::acceptorCallback(const boost::system::error_code& ec) {
156 if (!acceptor_.isOpen()) {
157 return;
158 }
159
160 if (ec) {
161 stopThisConnection();
162 }
163
164 acceptor_callback_(ec);
165
166 if (!ec) {
168 HTTP_REQUEST_RECEIVE_START)
169 .arg(getRemoteEndpointAddressAsText())
170 .arg(static_cast<unsigned>(request_timeout_/1000));
171
172 setupRequestTimer();
173 doRead();
174 }
175}
176
177void
178HttpConnection::socketReadCallback(boost::system::error_code ec, size_t length) {
179 if (ec) {
180 // IO service has been stopped and the connection is probably
181 // going to be shutting down.
182 if (ec.value() == boost::asio::error::operation_aborted) {
183 return;
184
185 // EWOULDBLOCK and EAGAIN are special cases. Everything else is
186 // treated as fatal error.
187 } else if ((ec.value() != boost::asio::error::try_again) &&
188 (ec.value() != boost::asio::error::would_block)) {
189 stopThisConnection();
190
191 // We got EWOULDBLOCK or EAGAIN which indicate that we may be able to
192 // read something from the socket on the next attempt. Just make sure
193 // we don't try to read anything now in case there is any garbage
194 // passed in length.
195 } else {
196 length = 0;
197 }
198 }
199
200 // Receiving is in progress, so push back the timeout.
201 setupRequestTimer();
202
203 if (length != 0) {
205 HTTP_DATA_RECEIVED)
206 .arg(length)
207 .arg(getRemoteEndpointAddressAsText());
208
209 std::string s(&buf_[0], buf_[0] + length);
210 parser_->postBuffer(static_cast<void*>(buf_.data()), length);
211 parser_->poll();
212 }
213
214 if (parser_->needData()) {
215 doRead();
216
217 } else {
218 try {
219 request_->finalize();
220
222 HTTP_CLIENT_REQUEST_RECEIVED)
223 .arg(getRemoteEndpointAddressAsText());
224
226 HTTP_CLIENT_REQUEST_RECEIVED_DETAILS)
227 .arg(getRemoteEndpointAddressAsText())
228 .arg(parser_->getBufferAsString(MAX_LOGGED_MESSAGE_SIZE));
229
230 } catch (const std::exception& ex) {
232 HTTP_BAD_CLIENT_REQUEST_RECEIVED)
233 .arg(getRemoteEndpointAddressAsText())
234 .arg(ex.what());
235
237 HTTP_BAD_CLIENT_REQUEST_RECEIVED_DETAILS)
238 .arg(getRemoteEndpointAddressAsText())
239 .arg(parser_->getBufferAsString(MAX_LOGGED_MESSAGE_SIZE));
240 }
241
242 // Don't want to timeout if creation of the response takes long.
243 request_timer_.cancel();
244
245 HttpResponsePtr response = response_creator_->createHttpResponse(request_);
247 HTTP_SERVER_RESPONSE_SEND)
248 .arg(response->toBriefString())
249 .arg(getRemoteEndpointAddressAsText());
250
252 HTTP_SERVER_RESPONSE_SEND_DETAILS)
253 .arg(getRemoteEndpointAddressAsText())
254 .arg(HttpMessageParserBase::logFormatHttpMessage(response->toString(),
255 MAX_LOGGED_MESSAGE_SIZE));
256
257 // Response created. Active timer again.
258 setupRequestTimer();
259
260 asyncSendResponse(response);
261 }
262}
263
264void
265HttpConnection::socketWriteCallback(boost::system::error_code ec, size_t length) {
266 if (ec) {
267 // IO service has been stopped and the connection is probably
268 // going to be shutting down.
269 if (ec.value() == boost::asio::error::operation_aborted) {
270 return;
271
272 // EWOULDBLOCK and EAGAIN are special cases. Everything else is
273 // treated as fatal error.
274 } else if ((ec.value() != boost::asio::error::try_again) &&
275 (ec.value() != boost::asio::error::would_block)) {
276 stopThisConnection();
277
278 // We got EWOULDBLOCK or EAGAIN which indicate that we may be able to
279 // read something from the socket on the next attempt.
280 } else {
281 // Sending is in progress, so push back the timeout.
282 setupRequestTimer();
283
284 doWrite();
285 }
286 }
287
288
289 if (length <= output_buf_.size()) {
290 // Sending is in progress, so push back the timeout.
291 setupRequestTimer();
292
293 output_buf_.erase(0, length);
294 doWrite();
295
296 } else {
297 output_buf_.clear();
298
299 if (!request_->isPersistent()) {
300 stopThisConnection();
301
302 } else {
303 reinitProcessingState();
304 doRead();
305 }
306 }
307}
308
309void
310HttpConnection::reinitProcessingState() {
311 request_ = response_creator_->createNewHttpRequest();
312 parser_.reset(new HttpRequestParser(*request_));
313 parser_->initModel();
314 setupIdleTimer();
315}
316
317void
318HttpConnection::setupRequestTimer() {
319 // Pass raw pointer rather than shared_ptr to this object,
320 // because IntervalTimer already passes shared pointer to the
321 // IntervalTimerImpl to make sure that the callback remains
322 // valid.
323 request_timer_.setup(boost::bind(&HttpConnection::requestTimeoutCallback,
324 this),
325 request_timeout_, IntervalTimer::ONE_SHOT);
326}
327
328void
329HttpConnection::setupIdleTimer() {
330 request_timer_.setup(boost::bind(&HttpConnection::idleTimeoutCallback,
331 this),
332 idle_timeout_, IntervalTimer::ONE_SHOT);
333}
334
335void
336HttpConnection::requestTimeoutCallback() {
338 HTTP_CLIENT_REQUEST_TIMEOUT_OCCURRED)
339 .arg(getRemoteEndpointAddressAsText());
340 HttpResponsePtr response =
341 response_creator_->createStockHttpResponse(request_,
343 asyncSendResponse(response);
344}
345
346void
347HttpConnection::idleTimeoutCallback() {
349 HTTP_IDLE_CONNECTION_TIMEOUT_OCCURRED)
350 .arg(getRemoteEndpointAddressAsText());
351 stopThisConnection();
352}
353
354std::string
355HttpConnection::getRemoteEndpointAddressAsText() const {
356 try {
357 if (socket_.getASIOSocket().is_open()) {
358 return (socket_.getASIOSocket().remote_endpoint().address().to_string());
359 }
360 } catch (...) {
361 }
362 return ("(unknown address)");
363}
364
365
366} // end of namespace isc::http
367} // end of namespace isc
368
virtual const char * what() const
Returns a C-style character string of the cause of the exception.
Generic error reported within HttpConnection class.
Definition: connection.h:26
Pool of active HTTP connections.
void stop(const HttpConnectionPtr &connection)
Stops a connection and removes it from the pool.
HttpConnection(asiolink::IOService &io_service, HttpAcceptor &acceptor, HttpConnectionPool &connection_pool, const HttpResponseCreatorPtr &response_creator, const HttpAcceptorCallback &callback, const long request_timeout, const long idle_timeout)
Constructor.
Definition: connection.cc:39
void doRead()
Starts asynchronous read from the socket.
Definition: connection.cc:101
void close()
Closes the socket.
Definition: connection.cc:66
void asyncAccept()
Asynchronously accepts new connection.
Definition: connection.cc:84
~HttpConnection()
Destructor.
Definition: connection.cc:61
static std::string logFormatHttpMessage(const std::string &message, const size_t limit=0)
Formats provided HTTP message for logging.
A generic parser for HTTP requests.
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
#define LOG_ERROR(LOGGER, MESSAGE)
Macro to conveniently test error output and log it.
Definition: macros.h:32
#define LOG_DEBUG(LOGGER, LEVEL, MESSAGE)
Macro to conveniently test debug output and log it.
Definition: macros.h:14
boost::shared_ptr< const HttpResponse > ConstHttpResponsePtr
Pointer to the const HttpResponse object.
Definition: response.h:84
boost::function< void(const boost::system::error_code &)> HttpAcceptorCallback
Type of the callback for the TCP acceptor used in this library.
Definition: http_acceptor.h:19
isc::log::Logger http_logger("http")
Defines the logger used within libkea-http library.
Definition: http_log.h:18
boost::shared_ptr< HttpResponseCreator > HttpResponseCreatorPtr
Pointer to the HttpResponseCreator object.
boost::shared_ptr< HttpResponse > HttpResponsePtr
Pointer to the HttpResponse object.
Definition: response.h:81
const int DBGLVL_TRACE_BASIC
Trace basic operations.
Definition: log_dbglevels.h:65
const int DBGLVL_TRACE_DETAIL_DATA
Trace data associated with detailed operations.
Definition: log_dbglevels.h:74
const int DBGLVL_TRACE_BASIC_DATA
Trace data associated with the basic operations.
Definition: log_dbglevels.h:68
const int DBGLVL_TRACE_DETAIL
Trace detailed operations.
Definition: log_dbglevels.h:71
Defines the logger used by the top-level component of kea-dhcp-ddns.