0

I have taken the base program from WebServer

I am trying to split the program into a Cpp and hpp file.

server.hpp

#include <boost/asio.hpp>
#include <boost/regex.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/functional/hash.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/filesystem.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>

#include <unordered_map>
#include <thread>
#include <functional>
#include <iostream>
#include <sstream>
#include <algorithm>
#include <fstream>
#include <vector>

#define CS_WEBSERVER_PORT 0xc080

namespace WebServer {

    template < class socket_type > class WebServerBase 
    {
        public:
            virtual ~ WebServerBase();
            class Response:public std::ostream 
        {
            friend class WebServerBase < socket_type >;
            boost::asio::streambuf streambuf;
            std::shared_ptr < socket_type > socket;
            Response(std::shared_ptr < socket_type > socket):std::ostream(&streambuf), socket(socket);
            public:
            size_t size() {
                return streambuf.size();
            }
        };
            class Content:public std::istream 
        {
            friend class WebServerBase < socket_type >;

            public:
            size_t size() {
                return streambuf.size();
            } 
            const std::string string() const 
            {
                std::stringstream ss;
                ss << rdbuf();
                return ss.str();
            }
            private:
            boost::asio::streambuf & streambuf;
            Content(boost::asio::streambuf & streambuf):std::istream(&streambuf), streambuf(streambuf);
        };

            class Request 
            {
                friend class WebServerBase < socket_type >;

                class iequal_to {
                    public:
                        bool operator() (const std::string & key1, const std::string & key2)const 
                        {
                            return boost::algorithm::iequals(key1, key2);
                        }
                };
                class ihash {
                    public:
                        size_t operator() (const std::string & key)const 
                        {
                            std::size_t seed = 0;
                            for (auto & c:key)
                                boost::hash_combine(seed, std::tolower(c));
                            return seed;
                        }
                };
                public:
                std::string method, path, http_version;
                Content content;
                std::unordered_multimap < std::string, std::string, ihash, iequal_to > header;
                boost::smatch path_match;
                std::string remote_endpoint_address;
                unsigned short remote_endpoint_port;

                private:
                Request():content(streambuf); 
                boost::asio::streambuf streambuf;

                void read_remote_endpoint_data(socket_type & socket);
            };

            class Config 
            {
                friend class WebServerBase < socket_type >;

                Config(unsigned short port, size_t num_threads):num_threads(num_threads), port(port), reuse_address(true);
                size_t num_threads;
                public:
                unsigned short port;
                /*
                 * IPv4 address in dotted decimal form or IPv6 address in hexadecimal notation.
                 * If empty, the address will be any address.
                 */
                std::string address;
                /*Set to false to avoid binding the socket to an address that is already in use.*/
                bool reuse_address;
            };
            ///Set before calling start().
            Config config;

            std::unordered_map < std::string, std::unordered_map < std::string,
                std::function < void (std::shared_ptr < typename WebServerBase < socket_type >::Response >,
                        std::shared_ptr < typename WebServerBase < socket_type >::Request >) > >>resource;

            std::unordered_map < std::string,
                std::function < void (std::shared_ptr < typename WebServerBase < socket_type >::Response >,
                        std::shared_ptr < typename WebServerBase < socket_type >::Request >) > >default_resource;

        private:
            std::vector < std::pair < std::string, std::vector < std::pair < boost::regex,
                std::function < void (std::shared_ptr < typename WebServerBase < socket_type >::Response >,
                        std::shared_ptr < typename WebServerBase < socket_type >::Request >) > >>>>opt_resource;

        public:
            void start();

            void stop();

            ///Use this function if you need to recursively send parts of a longer message
            void send(std::shared_ptr < Response > response,
                    const std::function < void (const boost::system::error_code &) > &callback = nullptr) const;

        protected:
            boost::asio::io_service io_service;
            boost::asio::ip::tcp::acceptor acceptor;
            std::vector < std::thread > threads;

            long timeout_request;
            long timeout_content;

            WebServerBase(unsigned short port, size_t num_threads, long timeout_request,
                    long timeout_send_or_receive):config(port, num_threads), acceptor(io_service),
            timeout_request(timeout_request), timeout_content(timeout_send_or_receive)
        {

        }

            virtual void accept() = 0;

            std::shared_ptr < boost::asio::deadline_timer > set_timeout_on_socket(std::shared_ptr < socket_type > socket,
                    long seconds);

            void read_request_and_content(std::shared_ptr < socket_type > socket);

            bool parse_request(std::shared_ptr < Request > request, std::istream & stream) const;

            void find_resource(std::shared_ptr < socket_type > socket, std::shared_ptr < Request > request);

            void write_response(std::shared_ptr < socket_type > socket, std::shared_ptr < Request > request,
                    std::function < void (std::shared_ptr < typename WebServerBase < socket_type >::Response >,
                        std::shared_ptr < typename WebServerBase < socket_type >::Request >) >
                    &resource_function); 

            template < class socket_type > class Server:public WebServerBase < socket_type > {
            };

            typedef boost::asio::ip::tcp::socket HTTP;

            template <> class Server < HTTP >:public WebServerBase < HTTP > 
            {
                public:
                    Server(unsigned short port, size_t num_threads = 1, long timeout_request = 5, long timeout_content = 300):
                        WebServerBase < HTTP >::WebServerBase(port, num_threads, timeout_request, timeout_content) 
                {

                }

                private:
                    void accept(); 
            };
    }
}

and server.cpp

#define WEBSERVER_PORT 0x8080

namespace WebServer {

void WebServerBase::Request::read_remote_endpoint_data(socket_type & socket) 
{
    try {
        remote_endpoint_address = socket.lowest_layer().remote_endpoint().address().to_string();
        remote_endpoint_port = socket.lowest_layer().remote_endpoint().port();
    }
    catch(const std::exception &) 
    {

    }
}

void WebServerBase::start() 
{
    /*Copy the resources to opt_resource for more efficient request processing*/
    opt_resource.clear();
    for (auto & res:resource) 
    {
        for (auto & res_method:res.second) 
        {
            auto it = opt_resource.end();
            for (auto opt_it = opt_resource.begin(); opt_it != opt_resource.end(); opt_it++) 
            {
                if (res_method.first == opt_it->first) 
                {
                    it = opt_it;
                    break;
                }
            }
            if (it == opt_resource.end()) 
            {
                opt_resource.emplace_back();
                it = opt_resource.begin() + (opt_resource.size() - 1);
                it->first = res_method.first;
            }
            it->second.emplace_back(boost::regex(res.first), res_method.second);
        }
    }

    if (io_service.stopped())
        io_service.reset();

    boost::asio::ip::tcp::endpoint endpoint;
    if (config.address.size() > 0)
        endpoint =
            boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(config.address), config.port);
    else
        endpoint = boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), config.port);
    acceptor.open(endpoint.protocol());
    acceptor.set_option(boost::asio::socket_base::reuse_address(config.reuse_address));
    acceptor.bind(endpoint);
    acceptor.listen();

    accept();

    //If num_threads>1, start m_io_service.run() in (num_threads-1) threads for thread-pooling
    threads.clear();
    for (size_t c = 1; c < config.num_threads; c++) 
    {
        threads.emplace_back([this] () {
                io_service.run();});
    }

    //Main thread
    io_service.run();

    //Wait for the rest of the threads, if any, to finish as well
    for (auto & t:threads) {
        t.join();
    }
}

void WebServerBase::stop() {
    acceptor.close();
    io_service.stop();
}

///Use this function if you need to recursively send parts of a longer message
void WebServerBase::send(std::shared_ptr < Response > response,
        const std::function < void (const boost::system::error_code &) > &callback = nullptr) const 
{
    boost::asio::async_write(*response->socket, response->streambuf,
            [this, response, callback] (const boost::system::error_code & ec,
                size_t /*bytes_transferred */ ) 
            {
            if (callback)
            callback(ec);
            }) ;
}


std::shared_ptr < boost::asio::deadline_timer > WebServerBase::set_timeout_on_socket(std::shared_ptr < socket_type > socket,
        long seconds) 
{
    std::shared_ptr < boost::asio::deadline_timer > timer(new boost::asio::deadline_timer(io_service));
    timer->expires_from_now(boost::posix_time::seconds(seconds));
    timer->async_wait([socket] (const boost::system::error_code & ec) 
            {
            if (!ec) 
            {
            boost::system::error_code ec;
            socket->lowest_layer().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
            socket->lowest_layer().close();}
            }) ;
    return timer;
}

void WebServerBase::read_request_and_content(std::shared_ptr < socket_type > socket) 
{
    //Create new streambuf (Request::streambuf) for async_read_until()
    //shared_ptr is used to pass temporary objects to the asynchronous functions
    std::shared_ptr < Request > request(new Request());
    request->read_remote_endpoint_data(*socket);

    //Set timeout on the following boost::asio::async-read or write function
    std::shared_ptr < boost::asio::deadline_timer > timer;
    if (timeout_request > 0)
        timer = set_timeout_on_socket(socket, timeout_request);

    boost::asio::async_read_until(*socket, request->streambuf, "\r\n\r\n",
            [this, socket, request, timer] (const boost::system::error_code & ec,
                size_t bytes_transferred) 
            {
            if (timeout_request > 0)
            timer->cancel(); 
            if (!ec) 
            {
            /**
             * request->streambuf.size() is not necessarily the same as bytes_transferred, from Boost-docs:
             * "After a successful async_read_until operation, the streambuf may contain additional data beyond the delimiter"
             * The chosen solution is to extract lines from the stream directly when parsing the header. What is left of the
             * streambuf (maybe some bytes of the content) is appended to in the async_read-function below (for retrieving content).
             */
            size_t num_additional_bytes = request->streambuf.size() - bytes_transferred;
            if (!parse_request(request, request->content))
            return;
            //If content, read that as well
            auto it = request->header.find("Content-Length");
            if (it != request->header.end()) 
            {
                //Set timeout on the following boost::asio::async-read or write function
                std::shared_ptr < boost::asio::deadline_timer > timer;
                if (timeout_content > 0)
                    timer = set_timeout_on_socket(socket, timeout_content);
                unsigned long long content_length; 
                try {
                    content_length = stoull(it->second);
                }
                catch(const std::exception &) 
                {
                    return;
                }
                if (content_length > num_additional_bytes) 
                {
                    boost::asio::async_read(*socket, request->streambuf,
                            boost::asio::transfer_exactly(content_length -
                                num_additional_bytes),
                            [this, socket, request, timer]
                            (const boost::system::error_code & ec,
                             size_t /*bytes_transferred */ ) 
                            {
                            if (timeout_content > 0)
                            timer->cancel(); 
                            if (!ec)
                            find_resource(socket, request);
                            });
                }
                else 
                {
                    if (timeout_content > 0)
                        timer->cancel(); 
                    find_resource(socket, request);
                }
            }
            else 
            {
                find_resource(socket, request);}
            }
            }) ;
}

bool WebServerBase::parse_request(std::shared_ptr < Request > request, std::istream & stream) const 
{
    std::string line;
    getline(stream, line);
    size_t method_end;
    if ((method_end = line.find(' ')) != std::string::npos) 
    {
        size_t path_end;
        if ((path_end = line.find(' ', method_end + 1)) != std::string::npos) 
        {
            request->method = line.substr(0, method_end);
            request->path = line.substr(method_end + 1, path_end - method_end - 1);

            size_t protocol_end;
            if ((protocol_end = line.find('/', path_end + 1)) != std::string::npos) 
            {
                if (line.substr(path_end + 1, protocol_end - path_end - 1) != "HTTP")
                    return false;
                request->http_version = line.substr(protocol_end + 1, line.size() - protocol_end - 2);
            } 
            else
                return false;

            getline(stream, line);
            size_t param_end;
            while ((param_end = line.find(':')) != std::string::npos) 
            {
                size_t value_start = param_end + 1;
                if ((value_start) < line.size()) 
                {
                    if (line[value_start] == ' ')
                        value_start++;
                    if (value_start < line.size())
                        request->header.insert(std::make_pair(line.substr(0, param_end),
                                    line.substr(value_start, line.size() - value_start - 1)));
                }

                getline(stream, line);
            }
        } else
            return false;
    } else
        return false;
    return true;
}

void WebServerBase::find_resource(std::shared_ptr < socket_type > socket, std::shared_ptr < Request > request) 
{
    //Find path- and method-match, and call write_response
    for (auto & res:opt_resource) 
    {
        if (request->method == res.first) 
        {
            for (auto & res_path:res.second) 
            {
                boost::smatch sm_res;
                if (boost::regex_match(request->path, sm_res, res_path.first)) 
                {
                    request->path_match = std::move(sm_res);
                    write_response(socket, request, res_path.second);
                    return;
                }
            }
        }
    }
    auto it_method = default_resource.find(request->method);
    if (it_method != default_resource.end()) 
    {
        write_response(socket, request, it_method->second);
    }
}

void WebServerBase::write_response(std::shared_ptr < socket_type > socket, std::shared_ptr < Request > request,
        std::function < void (std::shared_ptr < typename WebServerBase < socket_type >::Response >,
            std::shared_ptr < typename WebServerBase < socket_type >::Request >) >
        &resource_function) 
{
    //Set timeout on the following boost::asio::async-read or write function
    std::shared_ptr < boost::asio::deadline_timer > timer;
    if (timeout_content > 0)
        timer = set_timeout_on_socket(socket, timeout_content);

    auto response = std::shared_ptr < Response > (new Response(socket),[this, request, timer] (Response * response_ptr) 
            {
            auto response = std::shared_ptr < Response > (response_ptr);
            send(response,[this, response, request,timer] (const boost::system::error_code & ec) 
                    {
                    if (!ec) 
                    {
                    if (timeout_content > 0)
                    timer->cancel(); float http_version; 
                    try {
                    http_version = stof(request->http_version);}
                    catch(const std::exception &) 
                    {
                    return;
                    }

                    auto range = request->header.equal_range("Connection");
                    for (auto it = range.first; it != range.second; it++) 
                    {
                    if (boost::iequals(it->second, "close"))
                    return;
                    }
                    if (http_version > 1.05)
                    read_request_and_content(response->socket);
                    }
                    }
            );}
    );

    try {
        resource_function(response, request);
    }
    catch(const std::exception &) 
    {
        return;
    }
}

    template < class socket_type > class Server:public WebServerBase < socket_type > {
    };

    typedef boost::asio::ip::tcp::socket HTTP;

    template <> class Server < HTTP >:public WebServerBase < HTTP > 
    {
        void Server::accept() 
        {
            //Create new socket for this connection
            //Shared_ptr is used to pass temporary objects to the asynchronous functions
            std::shared_ptr < HTTP > socket(new HTTP(io_service));

            acceptor.async_accept(*socket,[this, socket] (const boost::system::error_code & ec) 
                    {
                    //Immediately start accepting a new connection
                    accept(); if (!ec) {
                    boost::asio::ip::tcp::no_delay option(true);
                    socket->set_option(option); read_request_and_content(socket);}
                    }) ;
        }
    };
}

typedef WebServer::Server < WebServer::HTTP > HttpServer;
void default_resource_send(const HttpServer &server, std::shared_ptr<HttpServer::Response> response,
        std::shared_ptr<std::ifstream> ifs, std::shared_ptr<std::vector<char> > buffer);
int main()
{
    //HTTP-server at port c080 using 1 thread
    HttpServer server(WEBSERVER_PORT, 1);

    server.resource["^/match/([0-9]+)$"]["GET"] =
        [&server] (std::shared_ptr < HttpServer::Response > response, std::shared_ptr < HttpServer::Request > request) {
            std::string number = request->path_match[1];
            *response << "HTTP/1.1 200 OK\r\nContent-Length: " << number.length() << "\r\n\r\n" << number;
        };

    using namespace boost::property_tree;
    server.default_resource["GET"]=
        [&server](std::shared_ptr<HttpServer::Response> response, std::shared_ptr<HttpServer::Request> request) 
        {
            const auto web_root_path=boost::filesystem::canonical("web");
            boost::filesystem::path path=web_root_path;
            path/=request->path;
            if(boost::filesystem::exists(path)) 
            {
                path=boost::filesystem::canonical(path);
                //Check if path is within web_root_path
                if(std::distance(web_root_path.begin(), web_root_path.end())<=std::distance(path.begin(), path.end()) &&
                        std::equal(web_root_path.begin(), web_root_path.end(), path.begin())) 
                {
                    if(boost::filesystem::is_directory(path))
                        path/="index.html";
                    if(boost::filesystem::exists(path) && boost::filesystem::is_regular_file(path)) 
                    {
                        auto ifs=std::make_shared<std::ifstream>();
                        ifs->open(path.string(), std::ifstream::in | std::ios::binary);

                        if(*ifs) 
                        {
                            //read and send 128 KB at a time
                            std::streamsize buffer_size=131072;
                            auto buffer=std::make_shared<std::vector<char> >(buffer_size);

                            ifs->seekg(0, std::ios::end);
                            auto length=ifs->tellg();

                            ifs->seekg(0, std::ios::beg);

                            *response << "HTTP/1.1 200 OK\r\nContent-Length: " << length << "\r\n\r\n";
                            default_resource_send(server, response, ifs, buffer);
                            return;
                        }
                    }
                }
            }
            std::string content="Could not open path "+request->path;
            *response << "HTTP/1.1 400 Bad Request\r\nContent-Length: " << content.length() << "\r\n\r\n" << content;
        };

    std::thread server_thread([&server] ()
            {
            //Start server
            server.start();
            });

    server_thread.join();

    return 0;
}

void default_resource_send(const HttpServer &server, std::shared_ptr<HttpServer::Response> response,
        std::shared_ptr<std::ifstream> ifs, std::shared_ptr<std::vector<char> > buffer) 
{
    std::streamsize read_length;
    if((read_length=ifs->read(&(*buffer)[0], buffer->size()).gcount())>0) 
    {
        response->write(&(*buffer)[0], read_length);
        if(read_length==static_cast<std::streamsize>(buffer->size())) 
        {
            server.send(response, [&server, response, ifs, buffer](const boost::system::error_code &ec) 
                    {
                    if(!ec)
                    default_resource_send(server, response, ifs, buffer);
                    else
                    std::cerr << "Connection interrupted" << std::endl;
                    });
        }
    }
}

I compile it as follows.

g++ -std=c++11 server.cpp  -lboost_system -lboost_thread -lboost_filesystem -lboost_regex -lpthread -o server

However I get the following errors

In file included from httpserver.cpp:18:0:
httpserver.hpp:165:24: error: declaration of ‘class socket_type’
             template < class socket_type > class Server:public WebServerBase < socket_type > {
                        ^
server.hpp:23:16: error:  shadows template parm ‘class socket_type’
     template < class socket_type > class WebServerBase 
                ^
server.hpp:170:23: error: explicit specialization in non-namespace scope ‘class WebServer::WebServerBase<socket_type>’
             template <> class Server < HTTP >:public WebServerBase < HTTP > 
                       ^
server.hpp:170:31: error: template parameters not deducible in partial specialization:
             template <> class Server < HTTP >:public WebServerBase < HTTP > 
                               ^
server.hpp:170:31: note:         ‘socket_type’
server.hpp:182:5: error: expected ‘;’ after class definition
     }
     ^
server.hpp: In constructor ‘WebServer::WebServerBase<socket_type>::Response::Response(std::shared_ptr<_Tp1>)’:
server.hpp:32:101: error: expected ‘{’ at end of input
             Response(std::shared_ptr < socket_type > socket):std::ostream(&streambuf), socket(socket);
                                                                                                     ^
server.hpp: In constructor ‘WebServer::WebServerBase<socket_type>::Content::Content(boost::asio::streambuf&)’:
server.hpp:54:102: error: expected ‘{’ at end of input
             Content(boost::asio::streambuf & streambuf):std::istream(&streambuf), streambuf(streambuf);
                                                                                                      ^
server.hpp: In constructor ‘WebServer::WebServerBase<socket_type>::Request::Request()’:
server.hpp:87:44: error: expected ‘{’ at end of input
                 Request():content(streambuf); 
                                            ^
server.hpp: In constructor ‘WebServer::WebServerBase<socket_type>::Config::Config(short unsigned int, size_t)’:
server.hpp:97:121: error: expected ‘{’ at end of input
                 Config(unsigned short port, size_t num_threads):num_threads(num_threads), port(port), reuse_address(true);
                                                                                                                         ^
server.cpp: At global scope:
server.cpp:24:6: error: ‘template<class socket_type> class WebServer::WebServerBase’ used without template parameters
 void WebServerBase::Request::read_remote_endpoint_data(socket_type & socket) 
      ^
server.cpp:24:56: error: variable or field ‘read_remote_endpoint_data’ declared void
 void WebServerBase::Request::read_remote_endpoint_data(socket_type & socket) 
                                                        ^
server.cpp:24:56: error: ‘socket_type’ was not declared in this scope
server.cpp:24:56: note: suggested alternative:
‘boost::asio::detail::socket_type’
 typedef int socket_type;
             ^
server.cpp:435:1: error: expected ‘}’ at end of input
 }
 ^

I can't figure out what these errors mean. The brackets seem to be matched correctly to my eye. It would be great if someone could help me.

liv2hak
  • 14,472
  • 53
  • 157
  • 270
  • if you rely on a templated type, you can't put the implementation in a cpp file. for example you can't use `socket_type` on the cpp. – dau_sama Jul 13 '16 at 03:53
  • @dau_sama- what is the other way around? – liv2hak Jul 13 '16 at 04:00
  • A template must be declared AND implemented in the same translation unit. You cannot split it into separate `.h` and `.cpp` files and expect to be able to compile the `.cpp` as its own translation unit, like you are thinking. But, what you CAN do is split the template declaration from the template implementation within the same translation unit, and then you can move the implementation into a separate file that the `.h` file `#include`s after declaring the template. – Remy Lebeau Jul 13 '16 at 04:05
  • 2
    `WebServerBase` is a class template - but in the .cpp file you are trying to pretend as if it's a regular non-template class. The correct syntax goes like this `template void WebServerBase::start() { ... }` Further, implementation of a template class must be in a header - the compiler needs to see the actual function bodies at the point where they are called. – Igor Tandetnik Jul 13 '16 at 04:05
  • Did you mean `Server` to be a member class of `WebServerBase`? That makes little sense - I suspect you might have miscounted your braces. You also miss a semicolon after the closing brace that ends `WebServerBase` definition. – Igor Tandetnik Jul 13 '16 at 04:08
  • @IgorTandetnik - can I split the files to baseserver.hpp and then server.hpp and server.cpp to have the derived classes? – liv2hak Jul 13 '16 at 04:25
  • The specialization `Server < HTTP >` could be declared in the header and implemented in a .cpp file. Most everything else in the code you show would have to be in some header or other (you can spread it across several headers if you are so inclined). – Igor Tandetnik Jul 13 '16 at 04:38

1 Answers1

1

The line Line 165: template <class socket_type> class Server:public WebserverBase<socket_type> should be in namespace Webserver not inside class WebserverBase

P0W
  • 46,614
  • 9
  • 72
  • 119