4
\$\begingroup\$

I have developed a piece code to listen on a socket using Boost::asio. I would like to use that code in production. But before that, I would like to test it and get reviewed.

The code will be run inside a secure network. I guess I don't need any methods/code to check for security checks. I have removed some comments for the overview purpose.

Please review my code and give your suggestion to improve this code.

The purpose of the class:

  1. Listen on a port and accept an incoming connection. (There can be only one client).
  2. Read the message sent by a client and save it into a queue.
  3. Repeat: from 1

Listener.hpp

class Listener
{
private:

     int port;
     boost::system::error_code error;
     boost::asio::io_service io_service;
     std::shared_ptr<sharedQueue> queue_ptr;

     std::string read_exactly_socket(boost::asio::ip::tcp::socket &socket, size_t numBytesToRead);// reads given number of bytes from socket

     std::string  read_until_socket(boost::asio::ip::tcp::socket &socket, std::string complitionCondtion);// read from socket until condition meets

     std::map<std::string, std::string> parseHttpHeader(std::string toParse);// to parse http header
     std::string eraseEmptyLines(std::string str);
     void reportServer(boost::asio::ip::tcp::socket &socket, std::string responseMessage_toServer, int responseCode_toServer); // this will reply the server with given http code and message

     int hexToInt(std::string hex_str);// convert stringed hexadecimal value into integer

     std::pair<std::string /* beforeString */, std::string /* afterString */> parseChunkedTransfers(std::string str);

    void saveMessageToQueue(std::string /* the value of http Header field "SOAP Action" */,std::string/* http body */);

    pushMessageReceiver(int portToListen, std::shared_ptr<sharedQueue>  queue_unique_ptr);   // Private constructor
    pushMessageReceiver(const pushMessageReceiver&);          // Prevent copy-construction
    pushMessageReceiver& operator=(const pushMessageReceiver&);      // Prevent assignment

public:
     static pushMessageReceiver& getInstance(int portToListenOn, std::shared_ptr<sharedQueue>   queue_unique_ptr);    
     void listen();    
 };

Listener.cpp

  #include "Listener.hpp"

 Listener::Listener(int portToListen, std::shared_ptr<sharedQueue>        queue_unique_ptr):port(portToListen), queue_ptr(queue_unique_ptr){;}

 Listener& Listener::getInstance(int portToListenOn, std::shared_ptr<sharedQueue>   queue_unique_ptr){
     static Listener instance(portToListenOn,  queue_unique_ptr);
     return instance;
 }

 void Listener::saveMessageToQueue(std::string soapAction/*http header:soapAction*/, std::string/*http:actual body*/ actualBody)
 {    
     std::string actualMessage = "";
     bool dontSaveEmptyBody = false;

     /* Omited some codes here */
     if(dontSaveEmptyBody == false){
         queue_ptr->pushToQueue(actualMessage);
     }
 }

 std::map<std::string, std::string>                        Listener::parseHttpHeader(std::string toParse)
{
    std::map<std::string, std::string> _map;
    std::istringstream resp(toParse);
    std::string header;
std::string::size_type index;
while (std::getline(resp, header) && header != "\r") {
    index = header.find(':', 0);
    if(index != std::string::npos) {
        std::string key = boost::algorithm::trim_copy(header.substr(0, index));
        std::string value = boost::algorithm::trim_copy(header.substr(index + 1));
        boost::algorithm::to_lower(value);
        // key is stored as lower case entries
        _map.insert(std::make_pair(key,value));
    }
}    
return _map;
}

 std::string Listener::eraseEmptyLines(std::string str)
 {
     std::string::size_type pos = 0; // Must initialize
     while ( ( pos = str.find ("\r\n",pos) ) != std::string::npos )
     {
         str.erase ( pos, 2 );
     }
     return str;
 }

 int Listener::hexToInt(std::string hex_str){

std::istringstream buffer(hex_str);
int decimal;
buffer >> std::hex >> decimal;
return decimal;
 }

 void Listener::reportServer(boost::asio::ip::tcp::socket &socket, std::string message, int status_codes){
     std::stringstream reportStream;
     reportStream << "HTTP/1.1 " << status_codes << "  " <<message<<"\r\n";
     reportStream << "Connection: close\r\n";
     reportStream << "Content-Type: text/plain\r\n";
     reportStream << "\r\n";   
     boost::asio::write(socket,boost::asio::buffer(reportStream.str()));
 }


 std::pair<std::string /* beforeString */, std::string /* afterString */> Listener::parseChunkedTransfers(std::string str){

std::string result1, result2;
size_t position = str.find("\r\n");
result1 = str.substr(0, position);
result2 = str.substr(position + 2, std::string::npos);
return std::make_pair(result1, result2);
 }

 std::string Listener::read_until_socket(boost::asio::ip::tcp::socket &socket, std::string complitionCondtion){

std::string result;
boost::asio::streambuf buffer;
boost::asio::read_until(socket, buffer, complitionCondtion, error);
std::ostringstream stream;
stream << &buffer;
result = stream.str();
return result;
 }

 std::string Listener::read_exactly_socket(boost::asio::ip::tcp::socket &socket, size_t numBytesToRead){
std::string result;
boost::asio::streambuf buffer;
boost::asio::read(socket, buffer, boost::asio::transfer_exactly(numBytesToRead), error);
std::ostringstream stream;
stream << &buffer;
result = stream.str();
return result;
 }

 void Listener::listen()
 {

try{
    tcp::acceptor acceptor(io_service, tcp::endpoint(tcp::v4(), port));
    int responseCode_toServer;
    std::string responseMessage_toServer;
    for(;;)
    {

        long totalChunk = 0;
        std::string soapAction = "";

        std::cout<<"Server is listening on port "<<port<<std::endl;

        // create socket
        boost::asio::ip::tcp::socket socket(io_service);
        acceptor.accept(socket);

        // Read the response headers, which are terminated by a blank line.
        std::string header_data = read_until_socket(socket,"\r\n\r\n");
        size_t contentLength = 0;
        size_t hdrEnd = header_data.find("\r\n\r\n");// Header ends with empty line, mar the position to the read the body part if any.

        std::map<std::string, std::string > headerParameters = parseHttpHeader(header_data);
        soapAction = headerParameters.at("soapaction");// collect the value of soap action header filed

        std::map<std::string, std::string >::iterator searchContentLength    = headerParameters.find("content-length");
        // check whether transfer encoding field present in the header or not.
        std::map<std::string, std::string >::iterator searchTransferEncoding = headerParameters.find("transfer-encoding");

        std::string alreadyReadBodyPart(header_data.substr(hdrEnd + 4, std::string::npos)); // body data collected from header part, may be just a one part of body
        std::string actualBody;
        if(searchTransferEncoding != headerParameters.end()){
            // Checking the header filed "Transfer-Encoding" should be the first condition because Content-Length value does not make any sense if the encoding is "chunked".
            // Each chunk would look like this '7\r\n  my_data\r\n'  a content size followed by data. End of message would be a chunk of '0\r\n\r\n'

            if(searchTransferEncoding->second == "chunked")
            {
                // Header may or may not contain chunk size. If header includes chunk size then read exactly 'chunk size' of charectors from socket if else read until "\r\n" to find out the 'chunk size'. Repeat until "chunk size"  0.
                std::string chunkSizeInHex;
                std::string content;
                do
                {
                    // To read chunked message, first read chunk size and read that many numbers of charactors form a stream. Repeat this until you encounter 0 sized chunks.
                    // Make sure for the next loop iteration that the new hexadecimal number is read.
                    // This is the case if the alreadyReadBodyPart contains a \r\n.

                    if (alreadyReadBodyPart.find("\r\n") == std::string::npos)
                    {
                        // The hexadecimal number of bytes is unknown, so we read'm out.
                        // read until first chunk header ends. Chunk starts with length of the chuck in hexa decimal format.
                        alreadyReadBodyPart = read_until_socket(socket,"\r\n");
                    }
                    // The case that the carrage return is already present.
                    std::pair<std::string /* beforeString */, std::string /* afterString */> lPair = parseChunkedTransfers( alreadyReadBodyPart);
                    std::string firstPart  = lPair.first;
                    std::string secondPart = lPair.second;
                    alreadyReadBodyPart = secondPart;

                    long bytesOfChunk = hexToInt(firstPart);

                    totalChunk = totalChunk+bytesOfChunk;

                    if (bytesOfChunk == 0)
                    {
                        // End condition. Data transfer complete.
                        break;
                    }
                    // Read the bytesToRead number of remaining chars.
                    long bytesToRead = bytesOfChunk - secondPart.size();

                    if (bytesOfChunk + 2 <= (long) alreadyReadBodyPart.size())
                    {
                        // Process the data.
                        content            += alreadyReadBodyPart.substr(0, bytesOfChunk);
                        alreadyReadBodyPart = alreadyReadBodyPart.substr(bytesOfChunk + 2, std::string::npos);
                    }
                    // Perhaps the content is not fully there, then read until \r\n.
                    else if (bytesOfChunk + 2 > (long) alreadyReadBodyPart.size())
                    {
                        // Read the remaining number of bytes + 2 for the \r\n from the stream.
                        // read exactly the nextChunkSize charectors from socket
                        std::string newData = read_exactly_socket(socket, bytesToRead + 2);
                        newData = newData.substr(0, newData.size() - 2);
                        // Append the new data to the content.
                        content            += alreadyReadBodyPart;
                        content            += newData;
                        alreadyReadBodyPart = "";
                    }
                } while (true);

                actualBody = content;
                contentLength = content.size();
            }
        }
        else if(searchContentLength != headerParameters.end()) {
            //Read the content length from header if specified it not check for chunked transfers
            contentLength = boost::lexical_cast<size_t>(searchContentLength->second );
            alreadyReadBodyPart = eraseEmptyLines(alreadyReadBodyPart);

            if(contentLength > alreadyReadBodyPart.size())
            {
                // only part of the body is read so far. one more read operation is needed to read full body .
                size_t pendingContent_length = contentLength - alreadyReadBodyPart.size(); /// this the size of the body which is yet to be read from socket
                std::string body_trailer = read_exactly_socket(socket,pendingContent_length);
                actualBody = alreadyReadBodyPart + body_trailer;
                responseCode_toServer = 200;
            }
            else if( contentLength == alreadyReadBodyPart.size()){
                // body fully already read, no further read needed
                actualBody = alreadyReadBodyPart;
                responseCode_toServer = 200;
            }
            else if(contentLength < alreadyReadBodyPart.size()){
                // the body string might contain some more trailing zeros. which needs to be removed
            }
        }
        else if(searchContentLength == headerParameters.end())
        {
            // Header neither contain Content-Length field nor Transfer encoding then we assume that this message has been sent for testing purposes.
            responseCode_toServer = 200;
            responseMessage_toServer = "Listening";
        }
        reportServer(socket, responseMessage_toServer, responseCode_toServer);
        saveMessageToQueue(soapAction, actualBody);
    }

}
catch (std::exception& e)
{
    std::cerr <<"Listener Exception : "<< e.what() << std::endl;
    exit(0);
}

}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.