/* * Copyright (c) 2011, Peter Thorson. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the WebSocket++ Project nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL PETER THORSON BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ #ifndef WEBSOCKETPP_ROLE_SERVER_HPP #define WEBSOCKETPP_ROLE_SERVER_HPP #include "../processors/hybi.hpp" #include "../processors/hybi_legacy.hpp" #include "../rng/blank_rng.hpp" #include "../shared_const_buffer.hpp" #include #include #include #include #include #include #include #include #include #ifdef _MSC_VER // Disable "warning C4355: 'this' : used in base member initializer list". # pragma warning(push) # pragma warning(disable:4355) #endif namespace websocketpp { typedef boost::asio::buffers_iterator bufIterator; static std::pair match_header(boost::shared_ptr string, bufIterator nBegin, bufIterator nEnd) { if (nBegin == nEnd) return std::make_pair(nEnd, false); (*string) += std::string(nBegin, nEnd); std::string::const_iterator begin = string->begin(); std::string::const_iterator end = string->end(); static const std::string eol_match = "\n"; static const std::string header_match = "\r\n\r\n"; static const std::string flash_match = ""; // Do we have a complete HTTP request std::string::const_iterator it = std::search(begin, end, header_match.begin(), header_match.end()); if (it != end) { int leftOver = (end - (it + header_match.size())); return std::make_pair(nEnd - leftOver, true); } // If we don't have a flash policy request, we're done it = std::search(begin, end, flash_match.begin(), flash_match.end()); if (it == end) // No match return std::make_pair(nEnd, false); // If we have a line ending before the flash policy request, treat as http std::string::const_iterator it2 = std::search(begin, end, eol_match.begin(), eol_match.end()); if ((it2 != end) && (it2 < it)) return std::make_pair(nEnd, false); // Treat as flash policy request int leftOver = (end - (it + flash_match.size())); return std::make_pair(nEnd - leftOver, true); } // Forward declarations template struct endpoint_traits; namespace role { template class server { public: // Connection specific details template class connection : boost::noncopyable { public: typedef connection type; typedef endpoint endpoint_type; // Valid always int get_version() const { return m_version; } std::string get_request_header(const std::string& key) const { return m_request.header(key); } std::string get_origin() const { return m_origin; } // Information about the requested URI // valid only after URIs are loaded // TODO: check m_uri for NULLness bool get_secure() const { return m_uri->get_secure(); } std::string get_host() const { return m_uri->get_host(); } std::string get_resource() const { return m_uri->get_resource(); } uint16_t get_port() const { return m_uri->get_port(); } // Valid for CONNECTING state void add_response_header(const std::string& key, const std::string& value) { m_response.add_header(key,value); } void replace_response_header(const std::string& key, const std::string& value) { m_response.replace_header(key,value); } void remove_response_header(const std::string& key) { m_response.remove_header(key); } const std::vector& get_subprotocols() const { return m_requested_subprotocols; } const std::vector& get_extensions() const { return m_requested_extensions; } void select_subprotocol(const std::string& value); void select_extension(const std::string& value); // Valid if get_version() returns -1 (ie this is an http connection) void set_body(const std::string& value); int32_t rand() { return 0; } bool is_server() const { return true; } // should this exist? boost::asio::io_service& get_io_service() { return m_endpoint.get_io_service(); } protected: connection(endpoint& e) : m_endpoint(e), m_connection(static_cast< connection_type& >(*this)), m_version(-1), m_uri() {} // initializes the websocket connection void async_init(); void handle_read_request(boost::shared_ptr, const boost::system::error_code& error, std::size_t bytes_transferred); void handle_short_key3(const boost::system::error_code& error, std::size_t bytes_transferred); void write_response(); void handle_write_response(const boost::system::error_code& error); void log_open_result(); private: endpoint& m_endpoint; connection_type& m_connection; int m_version; uri_ptr m_uri; std::string m_origin; std::vector m_requested_subprotocols; std::vector m_requested_extensions; std::string m_subprotocol; std::vector m_extensions; http::parser::request m_request; http::parser::response m_response; blank_rng m_rng; }; // types typedef server type; typedef endpoint endpoint_type; typedef typename endpoint_traits::connection_ptr connection_ptr; typedef typename endpoint_traits::handler_ptr handler_ptr; // handler interface callback base class class handler_interface { public: virtual ~handler_interface() {} virtual void on_handshake_init(connection_ptr con) {} virtual void validate(connection_ptr con) {} virtual void on_open(connection_ptr con) {} virtual void on_close(connection_ptr con) {} virtual void on_fail(connection_ptr con) {} virtual void on_message(connection_ptr con,message::data_ptr) {} virtual bool on_ping(connection_ptr con,std::string) {return true;} virtual void on_pong(connection_ptr con,std::string) {} virtual void on_pong_timeout(connection_ptr con,std::string) {} virtual bool http(connection_ptr con) { return true; } virtual void on_send_empty(connection_ptr con) {} }; server(boost::asio::io_service& m) : m_endpoint(static_cast< endpoint_type& >(*this)), m_io_service(m), // the only way to set an endpoint address family appears to be using // this constructor, which also requires a port. This port number can be // ignored, as it is always overwriten later by the listen() member func m_acceptor(m), m_state(IDLE), m_timer(m,boost::posix_time::seconds(0)) {} void start_listen(uint16_t port, size_t num_threads = 1); void start_listen(const boost::asio::ip::tcp::endpoint& e, size_t num_threads = 1); // uses internal resolver void start_listen(const std::string &host, const std::string &service, size_t num_threads = 1); template void start_listen(const InternetProtocol &internet_protocol, uint16_t port, size_t num_threads = 1) { m_endpoint.m_alog->at(log::alevel::DEVEL) << "role::server listening on port " << port << log::endl; boost::asio::ip::tcp::endpoint e(internet_protocol, port); start_listen(e,num_threads); } void stop_listen(bool join); // legacy interface void listen(uint16_t port, size_t num_threads = 1) { start_listen(port,num_threads>1 ? num_threads : 0); if(num_threads > 1) stop_listen(true); } void listen(const boost::asio::ip::tcp::endpoint& e, size_t num_threads = 1) { start_listen(e,num_threads>1 ? num_threads : 0); if(num_threads > 1) stop_listen(true); } void listen(const std::string &host, const std::string &service, size_t num_threads = 1) { start_listen(host,service,num_threads>1 ? num_threads : 0); if(num_threads > 1) stop_listen(true); } template void listen(const InternetProtocol &internet_protocol, uint16_t port, size_t num_threads = 1) { start_listen(internet_protocol,port,num_threads>1 ? num_threads : 0); if(num_threads > 1) stop_listen(true); } protected: bool is_server() { return true; } private: enum state { IDLE = 0, LISTENING = 1, STOPPING = 2, STOPPED = 3 }; // start_accept creates a new connection and begins an async_accept on it void start_accept(); // handle_accept will begin the connection's read/write loop and then reset // the server to accept a new connection. Errors returned by async_accept // are logged and ingored. void handle_accept(connection_ptr con, const boost::system::error_code& error); endpoint_type& m_endpoint; boost::asio::io_service& m_io_service; boost::asio::ip::tcp::acceptor m_acceptor; state m_state; boost::asio::deadline_timer m_timer; std::vector< boost::shared_ptr > m_listening_threads; }; template void server::start_listen(const boost::asio::ip::tcp::endpoint& e,size_t num_threads) { { boost::unique_lock lock(m_endpoint.m_lock); if (m_state != IDLE) { throw exception("listen called from invalid state."); } m_acceptor.open(e.protocol()); m_acceptor.set_option(boost::asio::socket_base::reuse_address(true)); m_acceptor.bind(e); m_acceptor.listen(); this->start_accept(); } if (num_threads > MAX_THREAD_POOL_SIZE) { throw exception("listen called with invalid num_threads value"); } m_state = LISTENING; for (std::size_t i = 0; i < num_threads; ++i) { boost::shared_ptr thread( new boost::thread(boost::bind( &endpoint_type::run_internal, &m_endpoint )) ); m_listening_threads.push_back(thread); } if(num_threads == 0) { for (;;) { try { m_endpoint.run_internal(); break; } catch (const std::exception & e) { // Don't let an exception trash the endpoint DJS m_endpoint.elog().at(log::elevel::RERROR) << "run_internal exception: " << e.what() << log::endl; } } m_state = IDLE; } } template void server::stop_listen(bool join) { { boost::unique_lock lock(m_endpoint.m_lock); if (m_state != LISTENING) { throw exception("stop_listen called from invalid state"); } m_acceptor.close(); } if(join) { for (std::size_t i = 0; i < m_listening_threads.size(); ++i) { m_listening_threads[i]->join(); } } m_listening_threads.clear(); m_state = IDLE; } template void server::start_listen(uint16_t port, size_t num_threads) { start_listen(boost::asio::ip::tcp::v6(), port, num_threads); } template void server::start_listen(const std::string &host, const std::string &service, size_t num_threads) { boost::asio::ip::tcp::resolver resolver(m_io_service); boost::asio::ip::tcp::resolver::query query(host, service); boost::asio::ip::tcp::resolver::iterator endpoint_iterator = resolver.resolve(query); boost::asio::ip::tcp::resolver::iterator end; if (endpoint_iterator == end) { throw std::invalid_argument("Can't resolve host/service to listen"); } const boost::asio::ip::tcp::endpoint &ep = *endpoint_iterator; start_listen(ep,num_threads); } template void server::start_accept() { boost::lock_guard lock(m_endpoint.m_lock); connection_ptr con = m_endpoint.create_connection(); if (con == connection_ptr()) { // the endpoint is no longer capable of accepting new connections. m_endpoint.m_alog->at(log::alevel::CONNECT) << "Connection refused because endpoint is out of resources or closing." << log::endl; return; } m_acceptor.async_accept( con->get_raw_socket(), boost::bind( &type::handle_accept, this, con, boost::asio::placeholders::error ) ); } // handle_accept will begin the connection's read/write loop and then reset // the server to accept a new connection. Errors returned by async_accept // are logged and ingored. template void server::handle_accept(connection_ptr con, const boost::system::error_code& error) { bool delay = false; boost::lock_guard lock(m_endpoint.m_lock); try { if (error) { con->m_fail_code = fail::status::SYSTEM; con->m_fail_system = error; if (error == boost::system::errc::too_many_files_open) { con->m_fail_reason = "too many files open"; delay = true; } else if (error == boost::asio::error::operation_aborted) { con->m_fail_reason = "io_service operation canceled"; // the operation was canceled. This was probably due to the // io_service being stopped. } else { con->m_fail_reason = "unknown"; } m_endpoint.m_elog->at(log::elevel::RERROR) << "async_accept returned error: " << error << " (" << con->m_fail_reason << ")" << log::endl; con->terminate(false); } else { con->start(); } } catch (const std::exception & e) { // We must call start_accept, even if we throw DJS m_endpoint.m_elog->at(log::elevel::RERROR) << "handle_accept caught exception: " << e.what() << log::endl; } if (delay) { // Don't spin if too many files are open DJS m_timer.expires_from_now(boost::posix_time::milliseconds(500)); m_timer.async_wait(boost::bind(&type::start_accept,this)); } else this->start_accept(); } // server::connection Implimentation template template void server::connection::select_subprotocol( const std::string& value) { // TODO: should this be locked? std::vector::iterator it; it = std::find(m_requested_subprotocols.begin(), m_requested_subprotocols.end(), value); if (value != "" && it == m_requested_subprotocols.end()) { throw std::invalid_argument("Attempted to choose a subprotocol not proposed by the client"); } m_subprotocol = value; } template template void server::connection::select_extension( const std::string& value) { // TODO: should this be locked? if (value == "") { return; } std::vector::iterator it; it = std::find(m_requested_extensions.begin(), m_requested_extensions.end(), value); if (it == m_requested_extensions.end()) { throw std::invalid_argument("Attempted to choose an extension not proposed by the client"); } m_extensions.push_back(value); } // Valid if get_version() returns -1 (ie this is an http connection) template template void server::connection::set_body( const std::string& value) { // TODO: should this be locked? if (m_connection.m_version != -1) { // TODO: throw exception throw std::invalid_argument("set_body called from invalid state"); } m_response.set_body(value); } /// initiates an async read for an HTTP header /** * Thread Safety: locks connection */ template template void server::connection::async_init() { boost::lock_guard lock(m_connection.m_lock); m_connection.get_handler()->on_handshake_init(m_connection.shared_from_this()); // TODO: make this value configurable m_connection.register_timeout(5000,fail::status::TIMEOUT_WS, "Timeout on WebSocket handshake"); static boost::arg<1> pl1; static boost::arg<2> pl2; boost::shared_ptr stringPtr = boost::make_shared(); m_connection.get_socket().async_read_until( m_connection.buffer(), boost::bind(&match_header, stringPtr, pl1, pl2), m_connection.get_strand().wrap(boost::bind( &type::handle_read_request, m_connection.shared_from_this(), stringPtr, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred )) ); } /// processes the response from an async read for an HTTP header /** * Thread Safety: async asio calls are not thread safe */ template template void server::connection::handle_read_request( boost::shared_ptr header, const boost::system::error_code& error, std::size_t /*bytes_transferred*/) { if (error) { // log error m_endpoint.m_elog->at(log::elevel::RERROR) << "Error reading HTTP request. code: " << error << log::endl; m_connection.terminate(false); return; } m_connection.buffer().consume(header->size()); try { std::istringstream request(*header); if (!m_request.parse_complete(request)) { // not a valid HTTP request/response if (m_request.method().find("") != std::string::npos) { // set m_version to -1, call async_write->handle_write_response std::string reply = "" "(m_connection.get_raw_socket().local_endpoint().port()); reply += "\"/>"; reply.append("\0", 1); m_version = -1; shared_const_buffer buffer(reply); m_connection.get_socket().async_write( shared_const_buffer(reply), boost::bind( &type::handle_write_response, m_connection.shared_from_this(), boost::asio::placeholders::error ) ); return; } throw http::exception("Received invalid HTTP Request",http::status_code::BAD_REQUEST); } // TODO: is there a way to short circuit this or something? // This is often useful but slow to generate. //m_endpoint.m_alog.at(log::alevel::DEBUG_HANDSHAKE) << m_request.raw() << log::endl; std::string h = m_request.header("Upgrade"); if (boost::ifind_first(h,"websocket")) { // Version is stored in the Sec-WebSocket-Version header for all // versions after draft Hybi 00/Hixie 76. The absense of a version // header is assumed to mean Hybi 00. h = m_request.header("Sec-WebSocket-Version"); if (h == "") { m_version = 0; } else { m_version = atoi(h.c_str()); if (m_version == 0) { throw(http::exception("Unable to determine connection version",http::status_code::BAD_REQUEST)); } } // Choose an appropriate websocket processor based on the version if (m_version == 0) { m_connection.m_processor = processor::ptr( new processor::hybi_legacy(m_connection) ); // Hybi legacy requires some extra out of band bookkeeping that // future versions wont. We need to pull off an additional eight // bytes after the /r/n/r/n and store them somewhere that the // processor can find them. char foo[8]; request.read(foo,8); if (request.gcount() != 8) { size_t left = 8 - static_cast(request.gcount()); // This likely occurs because the full key3 wasn't included // in the asio read. It is likely that the extra bytes are // actually on the wire and another asio read would get them // Fixing this will require a way of restarting the // handshake read and storing the existing bytes until that // comes back. Issue #101 m_endpoint.m_elog->at(log::elevel::RERROR) << "Short Key 3: " << zsutil::to_hex(std::string(foo)) << " bytes missing: " << left << " eofbit: " << (request.eof() ? "true" : "false") << " failbit: " << (request.fail() ? "true" : "false") << " badbit: " << (request.bad() ? "true" : "false") << " goodbit: " << (request.good() ? "true" : "false") << log::endl; throw http::exception("Full Key3 not found in first chop", http::status_code::INTERNAL_SERVER_ERROR); /*m_request.add_header("Sec-WebSocket-Key3",std::string(foo)); boost::asio::async_read( m_connection.get_socket(), m_connection.buffer(), boost::asio::transfer_at_least(left), m_connection.get_strand().wrap(boost::bind( &type::handle_short_key3, m_connection.shared_from_this(), boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred )) ); return;*/ } m_request.add_header("Sec-WebSocket-Key3",std::string(foo)); } else if (m_version == 7 || m_version == 8 || m_version == 13) { m_connection.m_processor = processor::ptr( new processor::hybi(m_connection) ); } else { // version does not match any processor we have avaliable. Send // an HTTP error and return the versions we do support in a the // appropriate response header. m_response.add_header("Sec-WebSocket-Version","13, 8, 7"); throw(http::exception("Unsupported WebSocket version",http::status_code::BAD_REQUEST)); } m_connection.m_processor->validate_handshake(m_request); // Extract subprotocols std::string subprotocols = m_request.header("Sec-WebSocket-Protocol"); if(subprotocols.length() > 0) { boost::char_separator sep(","); boost::tokenizer< boost::char_separator > tokens(subprotocols, sep); for(boost::tokenizer< boost::char_separator >::iterator it = tokens.begin(); it != tokens.end(); ++it){ std::string proto = *it; boost::trim(proto); if (proto.length() > 0){ m_requested_subprotocols.push_back(proto); } } } m_origin = m_connection.m_processor->get_origin(m_request); m_uri = m_connection.m_processor->get_uri(m_request); m_endpoint.get_handler()->validate(m_connection.shared_from_this()); m_response.set_status(http::status_code::SWITCHING_PROTOCOLS); } else { // should there be a more encapsulated http processor here? m_origin = m_request.header("Origin"); // Set URI std::string h = m_request.header("Host"); size_t last_colon = h.rfind(":"); size_t last_sbrace = h.rfind("]"); if (last_colon == std::string::npos || (last_sbrace != std::string::npos && last_sbrace > last_colon)) { // TODO: this makes the assumption that WS and HTTP // default ports are the same. m_uri.reset(new uri(m_connection.is_secure(),h,m_request.uri())); } else { m_uri.reset(new uri(m_connection.is_secure(), h.substr(0,last_colon), h.substr(last_colon+1), m_request.uri())); } // continue as HTTP? if (m_endpoint.get_handler()->http(m_connection.shared_from_this())) m_response.set_status(http::status_code::OK); else m_response.set_status(http::status_code::INTERNAL_SERVER_ERROR); } } catch (const http::exception& e) { m_endpoint.m_elog->at(log::elevel::RERROR) << e.what() << log::endl; m_response.set_status(e.m_error_code,e.m_error_msg); m_response.set_body(e.m_body); } catch (const uri_exception& e) { // there was some error building the uri m_endpoint.m_elog->at(log::elevel::RERROR) << e.what() << log::endl; m_response.set_status(http::status_code::BAD_REQUEST); } write_response(); } template template void server::connection::handle_short_key3 ( const boost::system::error_code& error, std::size_t /*bytes_transferred*/) { if (error) { // log error m_endpoint.m_elog->at(log::elevel::RERROR) << "Error reading HTTP request. code: " << error << log::endl; m_connection.terminate(false); return; } try { std::istream request(&m_connection.buffer()); std::string foo = m_request.header("Sec-WebSocket-Key3"); size_t left = 8-foo.size(); if (left == 0) { // ????? throw http::exception("handle_short_key3 called without short key", http::status_code::INTERNAL_SERVER_ERROR); } char foo2[9]; request.get(foo2,left); if (request.gcount() != left) { // ????? throw http::exception("Full Key3 not found", http::status_code::INTERNAL_SERVER_ERROR); } m_endpoint.m_elog->at(log::elevel::RERROR) << "Recovered from Short Key 3: " << left << log::endl; foo.append(std::string(foo2)); m_request.replace_header("Sec-WebSocket-Key3",foo2); m_connection.m_processor->validate_handshake(m_request); m_origin = m_connection.m_processor->get_origin(m_request); m_uri = m_connection.m_processor->get_uri(m_request); m_endpoint.get_handler()->validate(m_connection.shared_from_this()); m_response.set_status(http::status_code::SWITCHING_PROTOCOLS); } catch (const http::exception& e) { m_endpoint.m_elog->at(log::elevel::RERROR) << e.what() << log::endl; m_response.set_status(e.m_error_code,e.m_error_msg); m_response.set_body(e.m_body); } catch (const uri_exception& e) { // there was some error building the uri m_endpoint.m_elog->at(log::elevel::RERROR) << e.what() << log::endl; m_response.set_status(http::status_code::BAD_REQUEST); } write_response(); } template template void server::connection::write_response() { bool ws_response = true; m_response.set_version("HTTP/1.1"); if (m_response.get_status_code() == http::status_code::SWITCHING_PROTOCOLS) { // websocket response m_connection.m_processor->handshake_response(m_request,m_response); if (m_subprotocol != "") { m_response.replace_header("Sec-WebSocket-Protocol",m_subprotocol); } // TODO: return negotiated extensions } else { // TODO: HTTP response ws_response = false; } m_response.replace_header("Server",USER_AGENT); std::string raw = m_response.raw(); // Hack for legacy HyBi if (ws_response && m_version == 0) { raw += boost::dynamic_pointer_cast >(m_connection.m_processor)->get_key3(); } shared_const_buffer buffer(raw); m_endpoint.m_alog->at(log::alevel::DEBUG_HANDSHAKE) << raw << log::endl; m_connection.get_socket().async_write( //boost::asio::buffer(raw), buffer, boost::bind( &type::handle_write_response, m_connection.shared_from_this(), boost::asio::placeholders::error ) ); } template template void server::connection::handle_write_response( const boost::system::error_code& error) { if (error) { m_endpoint.m_elog->at(log::elevel::RERROR) << "Network error writing handshake respons. code: " << error << log::endl; m_connection.terminate(false); return; } if (m_response.get_status_code() != http::status_code::SWITCHING_PROTOCOLS) { if (m_version == -1) { // if this was not a websocket connection, we have written // the expected response and the connection can be closed. } else { // this was a websocket connection that ended in an error m_endpoint.m_elog->at(log::elevel::RERROR) << "Handshake ended with HTTP error: " << m_response.get_status_code() << " " << m_response.get_status_msg() << log::endl; } m_connection.terminate(true); return; } m_connection.cancel_timeout(); log_open_result(); m_connection.m_state = session::state::OPEN; m_connection.get_handler()->on_open(m_connection.shared_from_this()); get_io_service().post( m_connection.m_strand.wrap(boost::bind( &connection_type::handle_read_frame, m_connection.shared_from_this(), boost::system::error_code() )) ); //m_connection.handle_read_frame(boost::system::error_code()); } template template void server::connection::log_open_result() { std::stringstream version; version << "v" << m_version << " "; std::string remote; boost::system::error_code ec; boost::asio::ip::tcp::endpoint ep = m_connection.get_raw_socket().remote_endpoint(ec); if (ec) { m_endpoint.m_elog->at(log::elevel::WARN) << "Error getting remote endpoint. code: " << ec << log::endl; } m_endpoint.m_alog->at(log::alevel::CONNECT) << (m_version == -1 ? "HTTP" : "WebSocket") << " Connection "; if (ec) { m_endpoint.m_alog->at(log::alevel::CONNECT) << "Unknown"; } else { m_endpoint.m_alog->at(log::alevel::CONNECT) << ep; } m_endpoint.m_alog->at(log::alevel::CONNECT) << " " << (m_version == -1 ? "" : version.str()) << (get_request_header("User-Agent") == "" ? "NULL" : get_request_header("User-Agent")) << " " << (m_uri ? m_uri->get_resource() : "uri is NULL") << " " << m_response.get_status_code() << log::endl; } } // namespace role } // namespace websocketpp #ifdef _MSC_VER # pragma warning(pop) #endif #endif // WEBSOCKETPP_ROLE_SERVER_HPP