Sunday 9 January 2011

[C++] Creating a simple web server with Pion Network Library

The Pion Network Library is a great library to add a web server to your application. Sadly it is not very documented and it is sometimes hard to find how to do some really simple things.

Here is an example on how to create a simple web server.

The class containing our web server will have a really simple interface :

using namespace pion::net; // to simplify the example syntax, REMOVE IN REAL CODE.

class WebServer
{
public:
    /**
     * Start the web server
     * @param _port the port to listen on.
     */
    void start(unsigned int _port);

    /**
     * Stop the web server.
     */
    void stop();

private:
    /**
     * Handle http requests.
     * @param _httpRequest the request
     * @param _tcpConn the connection
     */
    void requestHandler(HTTPRequestPtr& _httpRequest, TCPConnectionPtr& _tcpConn)

    pion::net::HTTPServerPtr m_httpServer;
};

The only two public method of our web server are the start and stop method that will, as everybody expect, start and stop the server. The web server also define a private method that will be used as the web server request callback.
Let's continue with the real Pion Network Library code, the implementation of the start and stop methods:

/**
 * Start the web server
 * @param _port the port to listen on.
 */
void WebServer::start(unsigned int _port)
{
    // Create a web server and specify the port on witch it will listen.
    m_httpServer = pion::net::HTTPServerPtr(
        new pion::net::HTTPServer(_port));
    // Add a resource.
    m_httpServer->addResource("/", 
        boost::bind(&WebServer::requestHandler, this, _1, _2));
    // Start the web server.
    m_httpServer->start();
}

/**
 * Stop the web server.
 */
void WebServer::stop()
{
    // Just check that the http server has been created before stopping it.
    if (m_httpServer.get() != NULL)
    {
        m_httpServer->stop();
    }
}

Once again the code is really simple. The most interesting part here is the addition of the resource. It will defined the callback executed when a client send a request on the given page.
Here "/" is the root resource and will correspond to "http://server_ip:_port/". Every time a client will make a request to this resource the WebServer::requestHandler will be executed.

The only subtlety is the use of the boost::bind function to call the web server instance method and not a static or global function. The boost::bind will call the method with two arguments _1 and _2 that correspond to the request and the tcp connection defined in the requestHandler method and with a pointer to this instance that is hidden in every instance method.

The only thing left is to write the request handler method :

/**
 * Handle http requests.
 * @param _httpRequest the request
 * @param _tcpConn the connection
 */
void WebServer::requestHandler(HTTPRequestPtr& _httpRequest, TCPConnectionPtr& _tcpConn)
{
    static const std::string kHTMLStart("<html><body>\n");
    static const std::string kHTMLEnd("</body></html>\n");
 
    HTTPResponseWriterPtr writer(
        HTTPResponseWriter::create(
            _tcpConn, 
            *_httpRequest, 
            boost::bind(&TCPConnection::finish, _tcpConn)));
    HTTPResponse& r = writer->getResponse();
 
    HTTPTypes::QueryParams& params = 
        _httpRequest->getQueryParams();
 
    writer->writeNoCopy(kHTMLStart);

    HTTPTypes::QueryParams::const_iterator paramIter = params.find("id");
    if (paramIter != params.end())
    {
        r.setStatusCode(pion::net::HTTPTypes::RESPONSE_CODE_OK);
        r.setStatusMessage(pion::net::HTTPTypes::RESPONSE_MESSAGE_OK);

        std::string id = paramIter->second;
        writer->write("Request id : ");
        writer->write(id);
    }
    else
    {
        r.setStatusCode(pion::net::HTTPTypes::RESPONSE_CODE_BAD_REQUEST);
        r.setStatusMessage(pion::net::HTTPTypes::RESPONSE_MESSAGE_BAD_REQUEST);
    }

    writer->writeNoCopy(kHTMLEnd);
    writer->send();
}

This method search for the id parameter in the parameter list and write it back on the response. Of course this is not very useful and you should add your application logic instead of this method. It is for example possible to look for certain parameters and send them back to another method (eventually with the boost::bind mechanism) that will do the real real logic.

To test this example you can use the following main function:
int main()
{
 WebServer server;
 server.start(12345);

 while (1)
 {
  sleep(5000);
 }

 return 0;
}

13 comments:

  1. I'm wondering - what is your main function?

    I've tried using this example and I'm getting segfaults at writer->send(), deep down in the boost asio library.

    ReplyDelete
    Replies
    1. Hello Impaler.
      I edited the post above to include the main function. I also corrected a bug in the requestHandler. I was using writer->writeNoCopy instead of write for object that went out of scope at the end of the method. As the response is sent after the end of the method call this leads to undefined behavior. This was probably the cause of your segfault.

      Delete
  2. This article is the only source I found about the Pion Network Lib. This lib looks very powerful. Thanks for sharing !

    ReplyDelete
    Replies
    1. I also had trouble finding information on how to use this library. That's what motivated me to create this blog. I also came across cpp-netlib but did not have the opportunity to test it. You might want to look at it.

      Delete
  3. Good afternoon. Firstly, thank you for your nice tutorial. But I'm having trouble compiling due to the libraries included. Could you provide the complete code? greetings

    ReplyDelete
  4. Do you know if there is anyway to define patters for the URLs in a similar way to what frameworks like webpy (http://webpy.org/cookbook/url_handling), tornado, or Jersy do?

    That feature is a must if one wants to develop REST APIs easily. I know I could get the resource and parse it manually, but that is obviously tedious and error prone.

    ReplyDelete
  5. The pion documentation is still not the strong point about the project as of today.

    And when I searched for pion documentation in Google, this post (from 2011) is still in the first page. Evern worse, the code here is outdated and doesn't work. I modernized the code for current API and uploaded at https://gist.github.com/vinipsmaker/9460241

    ReplyDelete
  6. Having attempted this sometime ago. I became frustrated with the available options, so ran off and whipped together Restbed (https://github.com/corvusoft/restbed).

    The main objective is to reach HTTP 2.0 compliance.

    ReplyDelete
  7. Having attempted this sometime ago. I became frustrated with the available options, so ran off and whipped together Restbed (https://github.com/corvusoft/restbed).

    The main objective is to reach HTTP 2.0 compliance.

    ReplyDelete