/*
 * 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 <boost/algorithm/string.hpp>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/scoped_ptr.hpp>
#include <boost/thread.hpp>
#include <boost/thread/recursive_mutex.hpp>

#include <iostream>
#include <stdexcept>

#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<boost::asio::streambuf::const_buffers_type> bufIterator;

static std::pair<bufIterator, bool> match_header(boost::shared_ptr<std::string> 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 = "<policy-file-request/>";

	// 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 <typename T> struct endpoint_traits;
    
namespace role {

template <class endpoint>
class server {
public:
    // Connection specific details
    template <typename connection_type>
    class connection : boost::noncopyable {
    public:
        typedef connection<connection_type> 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<std::string>& get_subprotocols() const {
            return m_requested_subprotocols;
        }
        const std::vector<std::string>& 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<std::string>, 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<std::string>    m_requested_subprotocols;
        std::vector<std::string>    m_requested_extensions;
        std::string                 m_subprotocol;
        std::vector<std::string>    m_extensions;
        
        http::parser::request       m_request;
        http::parser::response      m_response;
        blank_rng                   m_rng;
    };
    
    // types
    typedef server<endpoint> type;
    typedef endpoint endpoint_type;
    
    typedef typename endpoint_traits<endpoint>::connection_ptr connection_ptr;
    typedef typename endpoint_traits<endpoint>::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 <typename InternetProtocol> 
    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 <typename InternetProtocol> 
    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<boost::thread> > m_listening_threads;
};

template <class endpoint>
void server<endpoint>::start_listen(const boost::asio::ip::tcp::endpoint& e,size_t num_threads) {
    {
        boost::unique_lock<boost::recursive_mutex> 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<boost::thread> 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 <class endpoint>
void server<endpoint>::stop_listen(bool join) {
    {
    	boost::unique_lock<boost::recursive_mutex> 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 <class endpoint>
void server<endpoint>::start_listen(uint16_t port, size_t num_threads) {
    start_listen(boost::asio::ip::tcp::v6(), port, num_threads);
}

template <class endpoint>
void server<endpoint>::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 <class endpoint>
void server<endpoint>::start_accept() {
    boost::lock_guard<boost::recursive_mutex> 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 <class endpoint>
void server<endpoint>::handle_accept(connection_ptr con, 
                                     const boost::system::error_code& error)
{
	bool delay = false;

    boost::lock_guard<boost::recursive_mutex> 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<endpoint>::connection<connnection_type> Implimentation

template <class endpoint>
template <class connection_type>
void server<endpoint>::connection<connection_type>::select_subprotocol(
                                                    const std::string& value)
{
    // TODO: should this be locked?
    
    std::vector<std::string>::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 <class endpoint>
template <class connection_type>
void server<endpoint>::connection<connection_type>::select_extension(
                                                    const std::string& value)
{
    // TODO: should this be locked?
    
    if (value == "") {
        return;
    }
    
    std::vector<std::string>::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 <class endpoint>
template <class connection_type>
void server<endpoint>::connection<connection_type>::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 <class endpoint>
template <class connection_type>
void server<endpoint>::connection<connection_type>::async_init() {
    boost::lock_guard<boost::recursive_mutex> 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<std::string> stringPtr = boost::make_shared<std::string>();
	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 <class endpoint>
template <class connection_type>
void server<endpoint>::connection<connection_type>::handle_read_request(
	boost::shared_ptr<std::string> 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("<policy-file-request/>") != std::string::npos)
            { // set m_version to -1, call async_write->handle_write_response
                std::string reply =
                    "<?xml version=\"1.0\"?><cross-domain-policy>"
                    "<allow-access-from domain=\"*\" to-ports=\"";
				reply += beast::lexicalCastThrow <std::string>(m_connection.get_raw_socket().local_endpoint().port());
				reply += "\"/></cross-domain-policy>";
				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<connection_type>(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<size_t>(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<connection_type>(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<char> sep(",");
                boost::tokenizer< boost::char_separator<char> > tokens(subprotocols, sep);
                for(boost::tokenizer< boost::char_separator<char> >::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 <class endpoint>
template <class connection_type>
void server<endpoint>::connection<connection_type>::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 <class endpoint>
template <class connection_type>
void server<endpoint>::connection<connection_type>::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<processor::hybi_legacy<connection_type> >(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 <class endpoint>
template <class connection_type>
void server<endpoint>::connection<connection_type>::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 <class endpoint>
template <class connection_type>
void server<endpoint>::connection<connection_type>::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
