Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++

C++ Simple BOOST Asynchronous ASIO Reverse Proxy

4.80/5 (8 votes)
3 Nov 2022CPOL2 min read 18.9K  
Reverse proxy developed using BOOST 1.75 asynchronous ASIO calls
In this tip, you will see a diagram and code to show a reverse proxy developed with BOOST async ASIO calls.

Background

C++ Boost Reverse Proxy

In a client server application, server is a central machine and one or more clients connect to the server. Clients connect to the server through a port which is usually a number or port number. A port number configured on the server side which is also called server port. All clients connect using the server port.

Image 1

What is Proxy?

It is an intermediate application that connects between client and server. Proxy acts both as a server and as a client. It is also called proxy server and proxy client. Original clients communicate directly with the Proxy servers with and proxy client will communicate with the original server. Proxy Server and proxy client exchange information among themselves. The original client connects to the proxy server through a port also called a proxy port.

Image 2

There are two types of proxy:

  1. Forward Proxy
  2. Reverse Proxy

Forward Proxy is the one that resided on every client machine. Hence, it need to be installed in every client machine.

A reverse proxy is a proxy that acts as a bridge between the client and server. Instead of client directly communicating with the server and server directly communicating with the client, they communicate directly with reverse proxy. A simple Reverse proxy is just a packet exchange between the client and server. Most proxies perform advanced tasks.

Reverse proxies themselves act as a server and client: server that listens to the external client, and client that connects to the external server. Normally, reverse proxy resides on the external server side. Internal server and internal client themselves communicate with each other for packet exchange. In the code, I have referred to the internal server as server and internal client as client.

Mostly, the reverse proxy looks as shown below. I will try to add more description or modify the diagram, since I don't have time, I am unable to do it now.

Image 3

Using the Code

The latest version of BOOST needs to be installed.

Most of the things are self explanatory.

C++
//
// Simple BOOST Asynchronous ASIO reverse proxy.
//
#define WIN32_LEAN_AND_MEAN

// Windows header declarations.
#include <Windows.h>
#include <tchar.h>
#include <strsafe.h>
#include <Winerror.h>

// C++ header declarations
#include <iostream>
using namespace std;

// BOOST ASIO declarations
#include <boost/asio.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/thread.hpp>
#include <boost/lexical_cast.hpp>
using namespace boost;
using namespace boost::asio;
using namespace boost::asio::ip;

// You can also declare the buffer inside the accept_handler to handle multiple clients.
char szServerBuffer[4096]; // Proxy Server buffer will be forwarded to client. 4K.
char szClientBuffer[4096]; // Proxy Client buffer will be forwarded to server. 4K.

// Global mutex for cout.
boost::mutex g_Mutex;

// Proxy Client write Handler.
void writeclient_handler(const boost::system::error_code& ec, 
                         std::size_t bytes_transferred)
{
    g_Mutex.lock();
    cout << "Proxy Client Sent: " << bytes_transferred << endl;
    g_Mutex.unlock();
}

// Proxy Server write Handler.
void write_handler(const boost::system::error_code& ec, std::size_t bytes_transferred)
{
    g_Mutex.lock();
    cout << "Proxy Server Sent: " << bytes_transferred << endl;
    g_Mutex.unlock();
}

// Proxy Client read handler. As soon as the proxy client receives data, 
// this function is notified.
// Data is forwarded to the proxy server and initiates for further proxy 
// client read asynchronously.
void readclient_handler(const boost::system::error_code& ec, 
                        std::size_t bytes_transferred,
    boost::shared_ptr<tcp::socket>& pClientSocket, 
    boost::shared_ptr<tcp::socket>& pPeerSocket)
{
    if (!ec || bytes_transferred != 0)
    {
        g_Mutex.lock();
        cout << "Proxy Client Received: " << bytes_transferred << endl;
        g_Mutex.unlock();
        pPeerSocket->async_write_some(boost::asio::buffer
                     (szClientBuffer, bytes_transferred),
            boost::bind(write_handler, boost::asio::placeholders::error, 
                        boost::asio::placeholders::bytes_transferred));

        pClientSocket->async_read_some(boost::asio::buffer
                       (szClientBuffer, sizeof(szClientBuffer)),
            boost::bind(readclient_handler, boost::asio::placeholders::error, 
                        boost::asio::placeholders::bytes_transferred,
                pClientSocket, pPeerSocket));
    }
    else
    {
        g_Mutex.lock();
        cout << "Proxy Client receive error: " << ec << endl;
        g_Mutex.unlock();
    }
}

// Proxy Client connect handler. When the proxy client is connected to the 
// external or actual server, this function is triggered.
void connect_handler(const boost::system::error_code& ec, 
                     boost::shared_ptr<tcp::socket>& pClientSocket,
    boost::shared_ptr<tcp::socket>& pPeerSocket)
{
    if (!ec)
    {
        g_Mutex.lock();
        cout << "Proxy CLient connected:" << endl;
        g_Mutex.unlock();
        pClientSocket->async_read_some(boost::asio::buffer
                       (szClientBuffer, sizeof(szClientBuffer)),
            boost::bind(readclient_handler, boost::asio::placeholders::error, 
                        boost::asio::placeholders::bytes_transferred,
                pClientSocket, pPeerSocket));
    }
    else
    {
        g_Mutex.lock();
        cout << "Proxy Client error: " << ec << endl;
        g_Mutex.unlock();
    }
}

// Proxy Server read handler. As soon as the proxy server receives data, 
// this function is notified.
// Data is forwarded to the proxy client and initiates 
// for further proxy server read asynchronously.
void read_handler(const boost::system::error_code& ec, std::size_t bytes_transferred,
    boost::shared_ptr<tcp::socket>& pPeerSocket, 
    boost::shared_ptr<tcp::socket>& pClientSocket)
{
    if (!ec || bytes_transferred != 0)
    {
        g_Mutex.lock();
        cout << "Proxy Server Received: " << bytes_transferred << endl;
        g_Mutex.unlock();

        pClientSocket->async_write_some(boost::asio::buffer
                                       (szServerBuffer, bytes_transferred),
            boost::bind(writeclient_handler, boost::asio::placeholders::error, 
                        boost::asio::placeholders::bytes_transferred));

        pPeerSocket->async_read_some(boost::asio::buffer
                                    (szServerBuffer, sizeof(szServerBuffer)),
            boost::bind(read_handler, boost::asio::placeholders::error, 
                        boost::asio::placeholders::bytes_transferred,
                        pPeerSocket, pClientSocket));
    }
    else
    {
        g_Mutex.lock();
        cout << "Proxy Server receive error: " << ec << endl;
        g_Mutex.unlock();
    }
}

// Proxy Server accept handler. When the external/actual client 
// connects the proxy server,
// this function is triggered.
void accept_handler(const boost::system::error_code& ec, 
                    boost::shared_ptr<io_context>& pIOContext,
    boost::shared_ptr<tcp::acceptor>& pAcceptor, 
                      boost::shared_ptr<tcp::socket>& pPeerSocket)
{
    if (!ec)
    {
        g_Mutex.lock();
        cout << "Proxy Server accepted:" << endl;
        g_Mutex.unlock();
        boost::shared_ptr<tcp::socket> pNewPeerSocket(new tcp::socket(*pIOContext));
        pAcceptor->async_accept(*pNewPeerSocket,
            boost::bind(accept_handler, boost::asio::placeholders::error, 
                        pIOContext, pAcceptor, pNewPeerSocket));

        // Proxy Client
        boost::shared_ptr<tcp::socket> pCientSocket1(new tcp::socket(*pIOContext));
        boost::shared_ptr<tcp::resolver> 
               pClientResolver(new tcp::resolver(*pIOContext));
        boost::shared_ptr<tcp::resolver::query> pClientResolverQuery(
            new tcp::resolver::query("127.0.0.1", boost::lexical_cast<string>(30000)));
        tcp::resolver::iterator itrEndPoint = 
                       pClientResolver->resolve(*pClientResolverQuery);
        pCientSocket1->async_connect(*itrEndPoint, 
                       boost::bind(connect_handler, boost::asio::placeholders::error,
            pCientSocket1, pPeerSocket));

        Sleep(2000);

        pPeerSocket->async_read_some(boost::asio::buffer
                                    (szServerBuffer, sizeof(szServerBuffer)),
            boost::bind(read_handler, boost::asio::placeholders::error, 
                        boost::asio::placeholders::bytes_transferred,
                pPeerSocket, pCientSocket1));
    }
    else
    {
        g_Mutex.lock();
        cout << "Server accept error: " << ec << endl;
        g_Mutex.unlock();
    }
}

// Keep the proxy server running.
void ReverProxyThread(boost::shared_ptr<io_context>& pIOContext)
{
    while (1)
    {
        try
        {
            boost::system::error_code ec;
            pIOContext->run(ec);
            break;
        }
        catch (std::runtime_error& ex)
        {

        }
    }
}

void _tmain()
{
    // IOContext
    boost::shared_ptr<io_context> pIOContext1(new io_context());
    boost::shared_ptr<io_context::strand> 
                      pStrand(new io_context::strand(*pIOContext1));

    // Worker Thread
    boost::shared_ptr<io_context::work> pWork(new io_context::work(*pIOContext1));
    boost::shared_ptr<boost::thread_group> pTG(new boost::thread_group());
    for (int i = 0; i < 2; i++)
    {
        pTG->create_thread(boost::bind(ReverProxyThread, pIOContext1));
    }

    // Resolver
    boost::shared_ptr<tcp::resolver> pResolver(new tcp::resolver(*pIOContext1));
    boost::shared_ptr<tcp::resolver::query>
        pQuery1(new tcp::resolver::query(tcp::v4(), 
                boost::lexical_cast<string>(20000)));
    tcp::resolver::iterator pIter1 = pResolver->resolve(*pQuery1);
    boost::shared_ptr<tcp::acceptor> pAcceptor(new tcp::acceptor(*pIOContext1));

    // Acceptor
    tcp::endpoint Endpoint = (*pIter1);
    pAcceptor->open(Endpoint.protocol());
    pAcceptor->bind(*pIter1);
    pAcceptor->listen();
    boost::shared_ptr<tcp::socket> pPeerSocket(new tcp::socket(*pIOContext1));
    pAcceptor->async_accept(*pPeerSocket,
        boost::bind(accept_handler, boost::asio::placeholders::error, 
                    pIOContext1, pAcceptor, pPeerSocket));

    cin.get();

    system::error_code ec;
    pPeerSocket->close(ec);
    pPeerSocket->shutdown(tcp::socket::shutdown_both, ec);

    pIOContext1->stop();

    pTG->join_all();
}
//

History

  • 9th May, 2021: Initial version
  • 21st March, 2022: Article updated

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)