In this article I describe the process of writing of a simple cross-platform HTTP proxy. Please, note that this code isn't production-ready - it was used as simple demonstration on how it could be implemented, and contains obvious bugs.
To develop this example (source code) I used Boost version 1.35. To build example, you can use cmake (but you can also build sources manually). To configure and build you need to run following commands (on Unix-like OSes)1:
> cmake . > make
and after compilation you'll get
proxy-asio-async executable, that you can run from
command line. This program accepts only one argument — number of threads, that will
perform request processing (by default, this value is equal 2). Port number on which
requests will be accepted is hardcoded in source code and equal to
As in previous examples, our program consists from three parts:
mainfunction, that parses command line, creates separate threads for asio services together with
serverobject, and then enters into request processing loop;
serverclass, that accepts requests, and creates
connectionobject, that implements all logic of connection handling;
connectionclass, that implements all logic, and passes data between client & web-server.
The data processing is performed in asynchronous mode, and to distribute load between
processors, we can use several independent asio services, that perform dispatching of
Note: Most hard part of the development of asynchronous code is proper design of data flow. I usually draw a state diagram and then transform each state to a separate function. Presence of such a diagram is very helpful for understanding of code by other developers.
Implementation of server (the
server class — proxy-server.hpp & proxy-server.cpp) also
not so much different from previous examples — changes were made only for method, that is
used to select a service that will implement dispatching. In our example new service is
selected from circular list of services, that allow us to get some load balancing for
Data processing is started from call to
start function from
server class, that accepts
connection and creates a new object of
connection class. This function initiates
asynchronous reading of request headers from the browser.
Reading of request headers is performed in the
handle_browser_read_headers function, that
is called when we get some part of data from the browser. I need to mention, that if we get
incomplete headers (there is no empty string (
\r\n\r\n)), then this function initiates new
reading of headers, trying to get them all.
After we get all headers, this function parses them and extracts version of HTTP protocol, used method and address of web-server (some of these data will be required to detect persistent connections).
After parsing of headers, this function calls
start_connect, that parses address of
web-server, and if we don't have opened connection to this server, then it initiates
process of name resolution. If we have opened connection, then we simply start data
handle_resolve function is called after name resolution, and if we get address of
server, then it initiates the process of connection establishing. Result of this process is
handle_connect function, that initiates the process of data transfer to the server
start_write_to_server function, that forms correct headers, and pass these data to
After transferring data to server, in function
handle_server_write we initiate reading of
response (only headers first) from server. Processing of headers is handled by
handle_server_read_headers function, that is similar to the
but it also tries to understand — should we close connection after data transfer, or not.
After processing of headers, this function initiates the process of sending data to the browser.
After sending of headers, we create a loop, that transfer body of response from server to
browser. In this loop we use two functions —
handle_browser_write, each of them calls another function until we don't finish reading
of data from server (either number of bytes, specified in headers) or don't get end of
If we'll get end of file, then we'll pass rest of data to the browser and close
connection. Or if we use persistent connection, then we'll pass control to the
function that initiates reading of new headers from the browser.
That's all. As I already mentioned above, main problem — building of right data flow sequence.
1. If cmake can't find required libraries, you can specify their location with two
<em>cmake's variables —
CMAKE_LIBRARY_PATH, by running cmake
> cmake . -DCMAKE_INCLUDE_PATH=~/exp/include -DCMAKE_LIBRARY_PATH=~/exp/lib
2. I could also implement code, that allow to specify port number in command line, but I was lazy, as this example was just a prototype to check some of my ideas.
Last change: 25.09.2021 14:21