2016-07-13 8 views
0

私は、CPPおよびHPPファイルにプログラムを分割しようとしていますWebServer分割C++テンプレートファイル

から基本プログラムをとっています。

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(); 
      }; 
    } 
} 

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; 
        }); 
     } 
    } 
} 

次のように私はそれをコンパイルします。

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

は、しかし、私は次のエラーが

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 
} 
^ 

私はこれらのエラーは何を意味するのかを把握することはできません取得します。角括弧は私の目に正確に一致しているようです。もし誰かが私を助けることができれば素晴らしいだろう。

+0

でなければなりません。たとえば、cppで 'socket_type'を使用することはできません。 –

+0

@ dau_sama-それ以外の方法は何ですか? – liv2hak

+0

テンプレートを宣言し、同じ翻訳単位で実装する必要があります。別々の '.h'ファイルと' .cpp'ファイルに分割することはできず、あなたが思っているように '.cpp'をそれ自身の翻訳単位としてコンパイルできると期待しています。しかし、あなたができることは、同じ翻訳単位内のテンプレート実装からテンプレート宣言を分割し、そのテンプレートを宣言した後で '.h'ファイル'#include'sを実行する別のファイルに実装を移すことができます。 –

答えて

1

ラインライン165:template <class socket_type> class Server:public WebserverBase<socket_type>を使用すると、テンプレートの種類に依存している場合、あなたはCPPファイルで実装を置くことができない名前空間Webserverない内部クラスでWebserverBase