~ubuntu-branches/debian/sid/pion/sid

« back to all changes in this revision

Viewing changes to src/tcp_server.cpp

  • Committer: Package Import Robot
  • Author(s): Roberto C. Sanchez
  • Date: 2013-07-06 18:04:35 UTC
  • Revision ID: package-import@ubuntu.com-20130706180435-kejjzc1qpyz3qv6c
Tags: upstream-5.0.1+dfsg
ImportĀ upstreamĀ versionĀ 5.0.1+dfsg

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// ---------------------------------------------------------------------
 
2
// pion:  a Boost C++ framework for building lightweight HTTP interfaces
 
3
// ---------------------------------------------------------------------
 
4
// Copyright (C) 2007-2012 Cloudmeter, Inc.  (http://www.cloudmeter.com)
 
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
        
 
79
        before_starting();
 
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() < 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