Sunday 9 January 2011

[C++] Performing a HTTP POST request on a distant 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 perform a HTTP POST on a distant server.

Performing a HTTP POST request is not a complicated task, even without using any library. However, if you already use the Pion Network Library as a web server in your application, it would be ridiculous not to use this library to perform the POST.

class PostDataExample
{
public:
    PostDataExample(const std::string& _hostName, unsigned int _port, const std::string& _resource)
    void post(int _id);
    
private:
    std::string m_hostName;
    unsigned int m_port;
    std::string m_resource;
    PionOneToOneScheduler m_scheduler;
}

The PostDataExample class only contains a method that will post an id on a server page. I will not detail the constructor implementation, it will, as everybody expects, copy the parameters in the members. It might not be a good idea to set the host name, port and resource as private members of our class but it will be enough for the example.
Notice also the presence of the scheduler that is necessary to create the TCP connection with the server.

Now, let's see the post method implementation :
void PostDataExample::post(int _id)
{
    // Connect to remote server.
    boost::system::error_code error_code;
    pion::net::TCPConnection tcpConnection(m_scheduler.getIOService());
    error_code = tcpConnection.connect(m_hostName, m_destPort);
    if (error_code)
    {
        std::cerr << "Error during connection to distant server: "
          << error_code.message();
        return;
    }

    // Create the request.
    std::stringstream requestStream;
    requestStream << "id="<< _id;

    // Send HTTP request.
    pion::net::HTTPRequest httpRequest(m_destResource);
    httpRequest.setMethod("POST");
    httpRequest.addHeader("Host", m_hostName);
    httpRequest.addHeader("Content-Type", "application/x-www-form-urlencoded");
    httpRequest.setContent(requestStream.str());
    httpRequest.send(tcpConnection, error_code);

    if (error_code)
    {
 std::cerr << "Error while trying to post enOcean data to distant server: "
            << error_code.message();
        return;
    }

    // receive HTTP response
    pion::net::HTTPResponse httpResponse(httpRequest);
    httpResponse.receive(tcpConnection, error_code);
    if (error_code)
    {
        std::cerr << "Error while trying to read response from distant server: %s"
            << error_code.message();
    }
    else
    {
        std::cerr << "Receive response from distant server: "
          << httpResponse.getStatusMessage();
    }
}

The code is pretty straight forward so I wont explain every detail. I just want to highlight two points. First of all, don't forget to add the host name to the request or the server will not understand the request. Secondly, if you get a server response it does not mean your request is correct, just that the server received it and respond to you. For example, if you remove the host name you will get an error message.

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