pion  5.0.6
tcp_server.cpp
1 // ---------------------------------------------------------------------
2 // pion: a Boost C++ framework for building lightweight HTTP interfaces
3 // ---------------------------------------------------------------------
4 // Copyright (C) 2007-2014 Splunk Inc. (https://github.com/splunk/pion)
5 //
6 // Distributed under the Boost Software License, Version 1.0.
7 // See http://www.boost.org/LICENSE_1_0.txt
8 //
9 
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>
15 
16 
17 namespace pion { // begin namespace pion
18 namespace tcp { // begin namespace tcp
19 
20 
21 // tcp::server member functions
22 
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()),
27 #ifdef PION_HAVE_SSL
28  m_ssl_context(m_active_scheduler.get_io_service(), boost::asio::ssl::context::sslv23),
29 #else
30  m_ssl_context(0),
31 #endif
32  m_endpoint(boost::asio::ip::tcp::v4(), tcp_port), m_ssl_flag(false), m_is_listening(false)
33 {}
34 
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()),
39 #ifdef PION_HAVE_SSL
40  m_ssl_context(m_active_scheduler.get_io_service(), boost::asio::ssl::context::sslv23),
41 #else
42  m_ssl_context(0),
43 #endif
44  m_endpoint(endpoint), m_ssl_flag(false), m_is_listening(false)
45 {}
46 
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()),
51 #ifdef PION_HAVE_SSL
52  m_ssl_context(m_active_scheduler.get_io_service(), boost::asio::ssl::context::sslv23),
53 #else
54  m_ssl_context(0),
55 #endif
56  m_endpoint(boost::asio::ip::tcp::v4(), tcp_port), m_ssl_flag(false), m_is_listening(false)
57 {}
58 
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()),
63 #ifdef PION_HAVE_SSL
64  m_ssl_context(m_active_scheduler.get_io_service(), boost::asio::ssl::context::sslv23),
65 #else
66  m_ssl_context(0),
67 #endif
68  m_endpoint(endpoint), m_ssl_flag(false), m_is_listening(false)
69 {}
70 
71 void server::start(void)
72 {
73  // lock mutex for thread safety
74  boost::mutex::scoped_lock server_lock(m_mutex);
75 
76  if (! m_is_listening) {
77  PION_LOG_INFO(m_logger, "Starting server on port " << get_port());
78 
80 
81  // configure the acceptor service
82  try {
83  // get admin permissions in case we're binding to a privileged port
84  pion::admin_rights use_admin_rights(get_port() > 0 && 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
88 #ifndef _MSC_VER
89  m_tcp_acceptor.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
90 #endif
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();
95  }
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());
99  throw;
100  }
101 
102  m_is_listening = true;
103 
104  // unlock the mutex since listen() requires its own lock
105  server_lock.unlock();
106  listen();
107 
108  // notify the thread scheduler that we need it now
109  m_active_scheduler.add_active_user();
110  }
111 }
112 
113 void server::stop(bool wait_until_finished)
114 {
115  // lock mutex for thread safety
116  boost::mutex::scoped_lock server_lock(m_mutex);
117 
118  if (m_is_listening) {
119  PION_LOG_INFO(m_logger, "Shutting down server on port " << get_port());
120 
121  m_is_listening = false;
122 
123  // this terminates any connections waiting to be accepted
124  m_tcp_acceptor.close();
125 
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));
130  }
131 
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);
140  }
141 
142  // notify the thread scheduler that we no longer need it
143  m_active_scheduler.remove_active_user();
144 
145  // all done!
146  after_stopping();
147  m_server_has_stopped.notify_all();
148  }
149 }
150 
151 void server::join(void)
152 {
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);
157  }
158 }
159 
160 void server::set_ssl_key_file(const std::string& pem_key_file)
161 {
162  // configure server for SSL
163  set_ssl_flag(true);
164 #ifdef PION_HAVE_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);
170 #endif
171 }
172 
173 void server::listen(void)
174 {
175  // lock mutex for thread safety
176  boost::mutex::scoped_lock server_lock(m_mutex);
177 
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,
183  this, _1)));
184 
185  // prune connections that finished uncleanly
186  prune_connections();
187 
188  // keep track of the object in the server's connection pool
189  m_conn_pool.insert(new_connection);
190 
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));
196  }
197 }
198 
199 void server::handle_accept(tcp::connection_ptr& tcp_conn,
200  const boost::system::error_code& accept_error)
201 {
202  if (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());
208  }
209  finish_connection(tcp_conn);
210  } else {
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());
214 
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();
218 
219  // handle the new connection
220 #ifdef PION_HAVE_SSL
221  if (tcp_conn->get_ssl_flag()) {
222  tcp_conn->async_handshake_server(boost::bind(&server::handle_ssl_handshake,
223  this, tcp_conn,
224  boost::asio::placeholders::error));
225  } else
226 #endif
227  // not SSL -> call the handler immediately
228  handle_connection(tcp_conn);
229  }
230 }
231 
232 void server::handle_ssl_handshake(tcp::connection_ptr& tcp_conn,
233  const boost::system::error_code& handshake_error)
234 {
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);
240  } else {
241  // handle the new connection
242  PION_LOG_DEBUG(m_logger, "SSL handshake succeeded on port " << get_port());
243  handle_connection(tcp_conn);
244  }
245 }
246 
247 void server::finish_connection(tcp::connection_ptr& tcp_conn)
248 {
249  boost::mutex::scoped_lock server_lock(m_mutex);
250  if (m_is_listening && tcp_conn->get_keep_alive()) {
251 
252  // keep the connection alive
253  handle_connection(tcp_conn);
254 
255  } else {
256  PION_LOG_DEBUG(m_logger, "Closing connection on port " << get_port());
257 
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);
262 
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();
266  }
267 }
268 
269 std::size_t server::prune_connections(void)
270 {
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;
277  ++conn_itr;
278  (*erase_itr)->close();
279  m_conn_pool.erase(erase_itr);
280  } else {
281  ++conn_itr;
282  }
283  }
284 
285  // return the number of connections remaining
286  return m_conn_pool.size();
287 }
288 
289 std::size_t server::get_connections(void) const
290 {
291  boost::mutex::scoped_lock server_lock(m_mutex);
292  return (m_is_listening ? (m_conn_pool.size() - 1) : m_conn_pool.size());
293 }
294 
295 } // end namespace tcp
296 } // end namespace pion
static boost::shared_ptr< connection > create(boost::asio::io_service &io_service, ssl_context_type &ssl_context, const bool ssl_flag, connection_handler finished_handler)
Definition: connection.hpp:94
void join(void)
the calling thread will sleep until the server has stopped listening for connections ...
Definition: tcp_server.cpp:151
unsigned int get_port(void) const
returns tcp port number that the server listens for connections on
Definition: server.hpp:64
boost::asio::io_service & get_io_service(void)
returns an async I/O service used to schedule work
Definition: server.hpp:156
void start(void)
starts listening for new connections
Definition: tcp_server.cpp:71
void set_ssl_key_file(const std::string &pem_key_file)
Definition: tcp_server.cpp:160
void remove_active_user(void)
unregisters an active user with the thread scheduler
Definition: scheduler.cpp:95
void close(void)
closes the tcp socket and cancels any pending asynchronous operations
Definition: connection.hpp:151
static void sleep(boost::uint32_t sleep_sec, boost::uint32_t sleep_nsec)
Definition: scheduler.hpp:107
virtual void before_starting(void)
called before the TCP server starts listening for new connections
Definition: server.hpp:150
void set_ssl_flag(bool b=true)
sets value of SSL flag (true if the server uses SSL to encrypt connections)
Definition: server.hpp:85
server(const unsigned int tcp_port)
Definition: tcp_server.cpp:47
virtual void after_stopping(void)
called after the TCP server has stopped listing for new connections
Definition: server.hpp:153
logger m_logger
primary logging interface used by this class
Definition: server.hpp:160
void add_active_user(void)
Definition: scheduler.cpp:88
void stop(bool wait_until_finished=false)
Definition: tcp_server.cpp:113
virtual void handle_connection(tcp::connection_ptr &tcp_conn)
Definition: server.hpp:144
std::size_t get_connections(void) const
returns the number of active tcp connections
Definition: tcp_server.cpp:289