1
// ---------------------------------------------------------------------
2
// pion: a Boost C++ framework for building lightweight HTTP interfaces
3
// ---------------------------------------------------------------------
4
// Copyright (C) 2007-2012 Cloudmeter, Inc. (http://www.cloudmeter.com)
6
// Distributed under the Boost Software License, Version 1.0.
7
// See http://www.boost.org/LICENSE_1_0.txt
10
#include <boost/asio.hpp>
11
#include <boost/bind.hpp>
12
#include <boost/thread/mutex.hpp>
13
#include <pion/admin_rights.hpp>
14
#include <pion/tcp/server.hpp>
17
namespace pion { // begin namespace pion
18
namespace tcp { // begin namespace tcp
21
// tcp::server member functions
23
server::server(scheduler& sched, const unsigned int tcp_port)
24
: m_logger(PION_GET_LOGGER("pion.tcp.server")),
25
m_active_scheduler(sched),
26
m_tcp_acceptor(m_active_scheduler.get_io_service()),
28
m_ssl_context(m_active_scheduler.get_io_service(), boost::asio::ssl::context::sslv23),
32
m_endpoint(boost::asio::ip::tcp::v4(), tcp_port), m_ssl_flag(false), m_is_listening(false)
35
server::server(scheduler& sched, const boost::asio::ip::tcp::endpoint& endpoint)
36
: m_logger(PION_GET_LOGGER("pion.tcp.server")),
37
m_active_scheduler(sched),
38
m_tcp_acceptor(m_active_scheduler.get_io_service()),
40
m_ssl_context(m_active_scheduler.get_io_service(), boost::asio::ssl::context::sslv23),
44
m_endpoint(endpoint), m_ssl_flag(false), m_is_listening(false)
47
server::server(const unsigned int tcp_port)
48
: m_logger(PION_GET_LOGGER("pion.tcp.server")),
49
m_default_scheduler(), m_active_scheduler(m_default_scheduler),
50
m_tcp_acceptor(m_active_scheduler.get_io_service()),
52
m_ssl_context(m_active_scheduler.get_io_service(), boost::asio::ssl::context::sslv23),
56
m_endpoint(boost::asio::ip::tcp::v4(), tcp_port), m_ssl_flag(false), m_is_listening(false)
59
server::server(const boost::asio::ip::tcp::endpoint& endpoint)
60
: m_logger(PION_GET_LOGGER("pion.tcp.server")),
61
m_default_scheduler(), m_active_scheduler(m_default_scheduler),
62
m_tcp_acceptor(m_active_scheduler.get_io_service()),
64
m_ssl_context(m_active_scheduler.get_io_service(), boost::asio::ssl::context::sslv23),
68
m_endpoint(endpoint), m_ssl_flag(false), m_is_listening(false)
71
void server::start(void)
73
// lock mutex for thread safety
74
boost::mutex::scoped_lock server_lock(m_mutex);
76
if (! m_is_listening) {
77
PION_LOG_INFO(m_logger, "Starting server on port " << get_port());
81
// configure the acceptor service
83
// get admin permissions in case we're binding to a privileged port
84
pion::admin_rights use_admin_rights(get_port() < 1024);
85
m_tcp_acceptor.open(m_endpoint.protocol());
86
// allow the acceptor to reuse the address (i.e. SO_REUSEADDR)
87
// ...except when running not on Windows - see http://msdn.microsoft.com/en-us/library/ms740621%28VS.85%29.aspx
89
m_tcp_acceptor.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
91
m_tcp_acceptor.bind(m_endpoint);
92
if (m_endpoint.port() == 0) {
93
// update the endpoint to reflect the port chosen by bind
94
m_endpoint = m_tcp_acceptor.local_endpoint();
96
m_tcp_acceptor.listen();
97
} catch (std::exception& e) {
98
PION_LOG_ERROR(m_logger, "Unable to bind to port " << get_port() << ": " << e.what());
102
m_is_listening = true;
104
// unlock the mutex since listen() requires its own lock
105
server_lock.unlock();
108
// notify the thread scheduler that we need it now
109
m_active_scheduler.add_active_user();
113
void server::stop(bool wait_until_finished)
115
// lock mutex for thread safety
116
boost::mutex::scoped_lock server_lock(m_mutex);
118
if (m_is_listening) {
119
PION_LOG_INFO(m_logger, "Shutting down server on port " << get_port());
121
m_is_listening = false;
123
// this terminates any connections waiting to be accepted
124
m_tcp_acceptor.close();
126
if (! wait_until_finished) {
127
// this terminates any other open connections
128
std::for_each(m_conn_pool.begin(), m_conn_pool.end(),
129
boost::bind(&connection::close, _1));
132
// wait for all pending connections to complete
133
while (! m_conn_pool.empty()) {
134
// try to prun connections that didn't finish cleanly
135
if (prune_connections() == 0)
136
break; // if no more left, then we can stop waiting
137
// sleep for up to a quarter second to give open connections a chance to finish
138
PION_LOG_INFO(m_logger, "Waiting for open connections to finish");
139
scheduler::sleep(m_no_more_connections, server_lock, 0, 250000000);
142
// notify the thread scheduler that we no longer need it
143
m_active_scheduler.remove_active_user();
147
m_server_has_stopped.notify_all();
151
void server::join(void)
153
boost::mutex::scoped_lock server_lock(m_mutex);
154
while (m_is_listening) {
155
// sleep until server_has_stopped condition is signaled
156
m_server_has_stopped.wait(server_lock);
160
void server::set_ssl_key_file(const std::string& pem_key_file)
162
// configure server for SSL
165
m_ssl_context.set_options(boost::asio::ssl::context::default_workarounds
166
| boost::asio::ssl::context::no_sslv2
167
| boost::asio::ssl::context::single_dh_use);
168
m_ssl_context.use_certificate_file(pem_key_file, boost::asio::ssl::context::pem);
169
m_ssl_context.use_private_key_file(pem_key_file, boost::asio::ssl::context::pem);
173
void server::listen(void)
175
// lock mutex for thread safety
176
boost::mutex::scoped_lock server_lock(m_mutex);
178
if (m_is_listening) {
179
// create a new TCP connection object
180
tcp::connection_ptr new_connection(connection::create(get_io_service(),
181
m_ssl_context, m_ssl_flag,
182
boost::bind(&server::finish_connection,
185
// prune connections that finished uncleanly
188
// keep track of the object in the server's connection pool
189
m_conn_pool.insert(new_connection);
191
// use the object to accept a new connection
192
new_connection->async_accept(m_tcp_acceptor,
193
boost::bind(&server::handle_accept,
194
this, new_connection,
195
boost::asio::placeholders::error));
199
void server::handle_accept(tcp::connection_ptr& tcp_conn,
200
const boost::system::error_code& accept_error)
203
// an error occured while trying to a accept a new connection
204
// this happens when the server is being shut down
205
if (m_is_listening) {
206
listen(); // schedule acceptance of another connection
207
PION_LOG_WARN(m_logger, "Accept error on port " << get_port() << ": " << accept_error.message());
209
finish_connection(tcp_conn);
211
// got a new TCP connection
212
PION_LOG_DEBUG(m_logger, "New" << (tcp_conn->get_ssl_flag() ? " SSL " : " ")
213
<< "connection on port " << get_port());
215
// schedule the acceptance of another new connection
216
// (this returns immediately since it schedules it as an event)
217
if (m_is_listening) listen();
219
// handle the new connection
221
if (tcp_conn->get_ssl_flag()) {
222
tcp_conn->async_handshake_server(boost::bind(&server::handle_ssl_handshake,
224
boost::asio::placeholders::error));
227
// not SSL -> call the handler immediately
228
handle_connection(tcp_conn);
232
void server::handle_ssl_handshake(tcp::connection_ptr& tcp_conn,
233
const boost::system::error_code& handshake_error)
235
if (handshake_error) {
236
// an error occured while trying to establish the SSL connection
237
PION_LOG_WARN(m_logger, "SSL handshake failed on port " << get_port()
238
<< " (" << handshake_error.message() << ')');
239
finish_connection(tcp_conn);
241
// handle the new connection
242
PION_LOG_DEBUG(m_logger, "SSL handshake succeeded on port " << get_port());
243
handle_connection(tcp_conn);
247
void server::finish_connection(tcp::connection_ptr& tcp_conn)
249
boost::mutex::scoped_lock server_lock(m_mutex);
250
if (m_is_listening && tcp_conn->get_keep_alive()) {
252
// keep the connection alive
253
handle_connection(tcp_conn);
256
PION_LOG_DEBUG(m_logger, "Closing connection on port " << get_port());
258
// remove the connection from the server's management pool
259
ConnectionPool::iterator conn_itr = m_conn_pool.find(tcp_conn);
260
if (conn_itr != m_conn_pool.end())
261
m_conn_pool.erase(conn_itr);
263
// trigger the no more connections condition if we're waiting to stop
264
if (!m_is_listening && m_conn_pool.empty())
265
m_no_more_connections.notify_all();
269
std::size_t server::prune_connections(void)
271
// assumes that a server lock has already been acquired
272
ConnectionPool::iterator conn_itr = m_conn_pool.begin();
273
while (conn_itr != m_conn_pool.end()) {
274
if (conn_itr->unique()) {
275
PION_LOG_WARN(m_logger, "Closing orphaned connection on port " << get_port());
276
ConnectionPool::iterator erase_itr = conn_itr;
278
(*erase_itr)->close();
279
m_conn_pool.erase(erase_itr);
285
// return the number of connections remaining
286
return m_conn_pool.size();
289
std::size_t server::get_connections(void) const
291
boost::mutex::scoped_lock server_lock(m_mutex);
292
return (m_is_listening ? (m_conn_pool.size() - 1) : m_conn_pool.size());
295
} // end namespace tcp
296
} // end namespace pion