├── .gitignore ├── CMakeLists.txt ├── README.md ├── common.h ├── proxy-conn.cpp ├── proxy-conn.hpp ├── proxy-server.cpp ├── proxy-server.hpp └── proxy.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | Debug 3 | build 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.6) 2 | PROJECT(boost-asio-proxy) 3 | 4 | SET(Boost_USE_STATIC_LIBS OFF) 5 | SET(Boost_USE_MULTITHREAD ON) 6 | FIND_PACKAGE(Boost 1.42.0 REQUIRED COMPONENTS system thread regex) 7 | IF(Boost_FOUND) 8 | INCLUDE_DIRECTORIES(${Boost_INCLUDE_DIRS}) 9 | LINK_DIRECTORIES(${Boost_LIBRARY_DIRS}) 10 | ENDIF(Boost_FOUND) 11 | 12 | SET(USED_LIBS ${Boost_SYSTEM_LIBRARY} ${Boost_THREAD_LIBRARY} ${Boost_REGEX_LIBRARY}) 13 | 14 | ADD_EXECUTABLE(proxy proxy.cpp proxy-server.cpp proxy-conn.cpp) 15 | TARGET_LINK_LIBRARIES(proxy ${USED_LIBS}) 16 | 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | boost-asio-proxy 2 | ================ 3 | 4 | Source code for examples from article [How to write simple HTTP proxy with Boost.Asio](http://alexott.net/en/cpp/BoostAsioProxy.html) 5 | 6 | Source code requires Boost >= 1.42 & CMake >= 2.6. Use following commands to configure & 7 | build example: 8 | 9 | mkdir build 10 | cd build 11 | cmake .. 12 | make 13 | 14 | This work is licensed under a Creative Commons Attribution 3.0 Unported License. 15 | -------------------------------------------------------------------------------- /common.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file common.h 3 | * @author Alex Ott 4 | * 5 | * @brief Declarations and includes, common for all boost::asio based programs 6 | * 7 | * 8 | */ 9 | 10 | #ifndef _COMMON_H 11 | #define _COMMON_H 1 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | #include 19 | 20 | #include 21 | 22 | #include 23 | #include 24 | 25 | #include 26 | #include 27 | 28 | namespace ba=boost::asio; 29 | namespace bs=boost::system; 30 | 31 | typedef boost::shared_ptr socket_ptr; 32 | typedef boost::shared_ptr io_service_ptr; 33 | 34 | #endif /* _COMMON_H */ 35 | 36 | -------------------------------------------------------------------------------- /proxy-conn.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file proxy-conn.cpp 3 | * @author Alex Ott 4 | * 5 | * @brief 6 | * 7 | * 8 | */ 9 | 10 | #include "proxy-conn.hpp" 11 | 12 | /** 13 | * 14 | * 15 | * @param io_service 16 | */ 17 | connection::connection(ba::io_service& io_service) : io_service_(io_service), 18 | bsocket_(io_service), 19 | ssocket_(io_service), 20 | resolver_(io_service), 21 | proxy_closed(false), 22 | isPersistent(false), 23 | isOpened(false) 24 | { 25 | fHeaders.reserve(8192); 26 | } 27 | 28 | /** 29 | * Start read data of request from browser 30 | * 31 | */ 32 | void connection::start() { 33 | // std::cout << "start" << std::endl; 34 | fHeaders.clear(); 35 | reqHeaders.clear(); 36 | respHeaders.clear(); 37 | 38 | handle_browser_read_headers(bs::error_code(), 0); 39 | } 40 | 41 | /** 42 | * Read header of HTTP request from browser 43 | * 44 | * @param err 45 | * @param len 46 | */ 47 | void connection::handle_browser_read_headers(const bs::error_code& err, size_t len) { 48 | // std::cout << "handle_browser_read_headers. Error: " << err << ", len=" << len << std::endl; 49 | if(!err) { 50 | if(fHeaders.empty()) 51 | fHeaders=std::string(bbuffer.data(),len); 52 | else 53 | fHeaders+=std::string(bbuffer.data(),len); 54 | if(fHeaders.find("\r\n\r\n") == std::string::npos) { // going to read rest of headers 55 | async_read(bsocket_, ba::buffer(bbuffer), ba::transfer_at_least(1), 56 | boost::bind(&connection::handle_browser_read_headers, 57 | shared_from_this(), 58 | ba::placeholders::error, 59 | ba::placeholders::bytes_transferred)); 60 | } else { // analyze headers 61 | //std::cout << "fHeaders:\n" << fHeaders << std::endl; 62 | std::string::size_type idx=fHeaders.find("\r\n"); 63 | std::string reqString=fHeaders.substr(0,idx); 64 | fHeaders.erase(0,idx+2); 65 | 66 | idx=reqString.find(" "); 67 | if(idx == std::string::npos) { 68 | std::cout << "Bad first line: " << reqString << std::endl; 69 | return; 70 | } 71 | 72 | fMethod=reqString.substr(0,idx); 73 | reqString=reqString.substr(idx+1); 74 | idx=reqString.find(" "); 75 | if(idx == std::string::npos) { 76 | std::cout << "Bad first line of request: " << reqString << std::endl; 77 | return; 78 | } 79 | fURL=reqString.substr(0,idx); 80 | fReqVersion=reqString.substr(idx+1); 81 | idx=fReqVersion.find("/"); 82 | if(idx == std::string::npos) { 83 | std::cout << "Bad first line of request: " << reqString << std::endl; 84 | return; 85 | } 86 | fReqVersion=fReqVersion.substr(idx+1); 87 | 88 | // string outputs to console completely, even when using multithreading 89 | //std::cout << std::string("\n fMethod: " + fMethod + ", fURL: " + fURL + ", fReqVersion: " + fReqVersion + "\n"); 90 | // analyze headers, etc 91 | parseHeaders(fHeaders,reqHeaders); 92 | // 93 | start_connect(); 94 | } 95 | } else { 96 | shutdown(); 97 | } 98 | } 99 | 100 | /** 101 | * Start connecting to the web-server, initially to resolve the DNS-name of web-server into the IP address 102 | * 103 | */ 104 | void connection::start_connect() { 105 | std::string server=""; 106 | std::string port="80"; 107 | boost::regex rHTTP("http://(.*?)(:(\\d+))?(/.*)"); 108 | boost::smatch m; 109 | 110 | if(boost::regex_search(fURL, m, rHTTP, boost::match_extra)) { 111 | server=m[1].str(); 112 | if(m[2].str() != "") { 113 | port=m[3].str(); 114 | } 115 | fNewURL=m[4].str(); 116 | } 117 | if(server.empty()) { 118 | std::cout << "Can't parse URL "<< std::endl; 119 | return; 120 | } 121 | // std::cout << server << " " << port << " " << fNewURL << std::endl; 122 | 123 | if(!isOpened || server != fServer || port != fPort) { 124 | fServer=server; 125 | fPort=port; 126 | ba::ip::tcp::resolver::query query(server, port); 127 | resolver_.async_resolve(query, 128 | boost::bind(&connection::handle_resolve, shared_from_this(), 129 | boost::asio::placeholders::error, 130 | boost::asio::placeholders::iterator)); 131 | } else { 132 | start_write_to_server(); 133 | } 134 | } 135 | 136 | /** 137 | * If successful, after the resolved DNS-names of web-server into the IP addresses, try to connect 138 | * 139 | * @param err 140 | * @param endpoint_iterator 141 | */ 142 | void connection::handle_resolve(const boost::system::error_code& err, 143 | ba::ip::tcp::resolver::iterator endpoint_iterator) { 144 | // std::cout << "handle_resolve. Error: " << err.message() << "\n"; 145 | if (!err) { 146 | const bool first_time = true; 147 | handle_connect(boost::system::error_code(), endpoint_iterator, first_time); 148 | }else { 149 | shutdown(); 150 | } 151 | } 152 | 153 | /** 154 | * Try to connect to the web-server 155 | * 156 | * @param err 157 | * @param endpoint_iterator 158 | */ 159 | void connection::handle_connect(const boost::system::error_code& err, 160 | ba::ip::tcp::resolver::iterator endpoint_iterator, const bool first_time) { 161 | // std::cout << "handle_connect. Error: " << err << "\n"; 162 | if (!err && !first_time) { 163 | isOpened=true; 164 | start_write_to_server(); 165 | } else if (endpoint_iterator != ba::ip::tcp::resolver::iterator()) { 166 | //ssocket_.close(); 167 | ba::ip::tcp::endpoint endpoint = *endpoint_iterator; 168 | ssocket_.async_connect(endpoint, 169 | boost::bind(&connection::handle_connect, shared_from_this(), 170 | boost::asio::placeholders::error, 171 | ++endpoint_iterator, false)); 172 | } else { 173 | shutdown(); 174 | } 175 | } 176 | 177 | /** 178 | * Write data to the web-server 179 | * 180 | */ 181 | void connection::start_write_to_server() { 182 | fReq=fMethod; 183 | fReq+=" "; 184 | fReq+=fNewURL; 185 | fReq+=" HTTP/"; 186 | fReq+="1.0"; 187 | // fReq+=fReqVersion; 188 | fReq+="\r\n"; 189 | fReq+=fHeaders; 190 | // std::cout << "Request: " << Req << std::endl; 191 | ba::async_write(ssocket_, ba::buffer(fReq), 192 | boost::bind(&connection::handle_server_write, shared_from_this(), 193 | ba::placeholders::error, 194 | ba::placeholders::bytes_transferred)); 195 | 196 | fHeaders.clear(); 197 | } 198 | 199 | /** 200 | * If successful, read the header that came from a web server 201 | * 202 | * @param err 203 | * @param len 204 | */ 205 | void connection::handle_server_write(const bs::error_code& err, size_t len) { 206 | // std::cout << "handle_server_write. Error: " << err << ", len=" << len << std::endl; 207 | if(!err) { 208 | handle_server_read_headers(bs::error_code(), 0); 209 | }else { 210 | shutdown(); 211 | } 212 | } 213 | 214 | /** 215 | * Read header of data returned from the web-server 216 | * 217 | * @param err 218 | * @param len 219 | */ 220 | void connection::handle_server_read_headers(const bs::error_code& err, size_t len) { 221 | // std::cout << "handle_server_read_headers. Error: " << err << ", len=" << len << std::endl; 222 | if(!err) { 223 | std::string::size_type idx; 224 | if(fHeaders.empty()) 225 | fHeaders=std::string(sbuffer.data(),len); 226 | else 227 | fHeaders+=std::string(sbuffer.data(),len); 228 | idx=fHeaders.find("\r\n\r\n"); 229 | if(idx == std::string::npos) { // going to read rest of headers 230 | async_read(ssocket_, ba::buffer(sbuffer), ba::transfer_at_least(1), 231 | boost::bind(&connection::handle_server_read_headers, 232 | shared_from_this(), 233 | ba::placeholders::error, 234 | ba::placeholders::bytes_transferred)); 235 | } else { // analyze headers 236 | // std::cout << "Response: " << fHeaders << std::endl; 237 | RespReaded=len-idx-4; 238 | idx=fHeaders.find("\r\n"); 239 | std::string respString=fHeaders.substr(0,idx); 240 | RespLen = -1; 241 | parseHeaders(fHeaders.substr(idx+2),respHeaders); 242 | std::string reqConnString="",respConnString=""; 243 | 244 | std::string respVersion=respString.substr(respString.find("HTTP/")+5,3); 245 | 246 | headersMap::iterator it=respHeaders.find("Content-Length"); 247 | if(it != respHeaders.end()) 248 | RespLen=boost::lexical_cast(it->second); 249 | it=respHeaders.find("Connection"); 250 | if(it != respHeaders.end()) 251 | respConnString=it->second; 252 | it=reqHeaders.find("Connection"); 253 | if(it != reqHeaders.end()) 254 | reqConnString=it->second; 255 | 256 | isPersistent=( 257 | ((fReqVersion == "1.1" && reqConnString != "close") || 258 | (fReqVersion == "1.0" && reqConnString == "keep-alive")) && 259 | ((respVersion == "1.1" && respConnString != "close") || 260 | (respVersion == "1.0" && respConnString == "keep-alive")) && 261 | RespLen != -1); 262 | // std::cout << "RespLen: " << RespLen << " RespReaded: " << RespReaded 263 | // << " isPersist: " << isPersistent << std::endl; 264 | 265 | // sent data 266 | ba::async_write(bsocket_, ba::buffer(fHeaders), 267 | boost::bind(&connection::handle_browser_write, 268 | shared_from_this(), 269 | ba::placeholders::error, 270 | ba::placeholders::bytes_transferred)); 271 | } 272 | } else { 273 | shutdown(); 274 | } 275 | } 276 | 277 | /** 278 | * Writing data to the browser, are recieved from web-server 279 | * 280 | * @param err 281 | * @param len 282 | */ 283 | void connection::handle_browser_write(const bs::error_code& err, size_t len) { 284 | // std::cout << "handle_browser_write. Error: " << err << " " << err.message() 285 | // << ", len=" << len << std::endl; 286 | if(!err) { 287 | if(!proxy_closed && (RespLen == -1 || RespReaded < RespLen)) 288 | async_read(ssocket_, ba::buffer(sbuffer,len), ba::transfer_at_least(1), 289 | boost::bind(&connection::handle_server_read_body, 290 | shared_from_this(), 291 | ba::placeholders::error, 292 | ba::placeholders::bytes_transferred)); 293 | else { 294 | // shutdown(); 295 | if(isPersistent && !proxy_closed) { 296 | std::cout << "Starting read headers from browser, as connection is persistent" << std::endl; 297 | start(); 298 | } 299 | } 300 | } else { 301 | shutdown(); 302 | } 303 | } 304 | 305 | /** 306 | * Reading data from a Web server, for the writing them to the browser 307 | * 308 | * @param err 309 | * @param len 310 | */ 311 | void connection::handle_server_read_body(const bs::error_code& err, size_t len) { 312 | // std::cout << "handle_server_read_body. Error: " << err << " " << err.message() 313 | // << ", len=" << len << std::endl; 314 | if(!err || err == ba::error::eof) { 315 | RespReaded+=len; 316 | // std::cout << "len=" << len << " resp_readed=" << RespReaded << " RespLen=" << RespLen<< std::endl; 317 | if(err == ba::error::eof) 318 | proxy_closed=true; 319 | ba::async_write(bsocket_, ba::buffer(sbuffer,len), 320 | boost::bind(&connection::handle_browser_write, 321 | shared_from_this(), 322 | ba::placeholders::error, 323 | ba::placeholders::bytes_transferred)); 324 | } else { 325 | shutdown(); 326 | } 327 | } 328 | 329 | /** 330 | * Close both sockets: for browser and web-server 331 | * 332 | */ 333 | void connection::shutdown() { 334 | if (ssocket_.is_open()) 335 | { 336 | ssocket_.shutdown(boost::asio::ip::tcp::socket::shutdown_both); 337 | ssocket_.close(); 338 | } 339 | if (bsocket_.is_open()) 340 | { 341 | bsocket_.shutdown(boost::asio::ip::tcp::socket::shutdown_both); 342 | bsocket_.close(); 343 | } 344 | } 345 | 346 | 347 | void connection::parseHeaders(const std::string& h, headersMap& hm) { 348 | std::string str(h); 349 | std::string::size_type idx; 350 | std::string t; 351 | while((idx=str.find("\r\n")) != std::string::npos) { 352 | t=str.substr(0,idx); 353 | str.erase(0,idx+2); 354 | if(t == "") 355 | break; 356 | idx=t.find(": "); 357 | if(idx == std::string::npos) { 358 | std::cout << "Bad header line: " << t << std::endl; 359 | break; 360 | } 361 | // std::cout << "Name: " << t.substr(0,idx) 362 | // << " Value: " << t.substr(idx+2) << std::endl; 363 | hm.insert(std::make_pair(t.substr(0,idx),t.substr(idx+2))); 364 | } 365 | } 366 | -------------------------------------------------------------------------------- /proxy-conn.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file proxy-conn.hpp 3 | * @author Alex Ott 4 | * 5 | * @brief 6 | * 7 | * 8 | */ 9 | 10 | #ifndef _PROXY_CONN_H 11 | #define _PROXY_CONN_H 1 12 | 13 | #include "common.h" 14 | #include 15 | 16 | class connection : public boost::enable_shared_from_this { 17 | public: 18 | typedef boost::shared_ptr pointer; 19 | 20 | static pointer create(ba::io_service& io_service) { 21 | return pointer(new connection(io_service)); 22 | } 23 | 24 | ba::ip::tcp::socket& socket() { 25 | return bsocket_; 26 | } 27 | 28 | /// Start read data of request from browser 29 | void start(); 30 | 31 | private: 32 | connection(ba::io_service& io_service); 33 | 34 | /// Read header of HTTP request from browser 35 | void handle_browser_read_headers(const bs::error_code& err, size_t len); 36 | 37 | /// Start connecting to the web-server, initially to resolve the DNS-name of Web server into the IP address 38 | void start_connect(); 39 | void handle_resolve(const boost::system::error_code& err, 40 | ba::ip::tcp::resolver::iterator endpoint_iterator); 41 | void handle_connect(const boost::system::error_code& err, 42 | ba::ip::tcp::resolver::iterator endpoint_iterator, const bool first_time); 43 | 44 | /// Write data to the web-server 45 | void start_write_to_server(); 46 | void handle_server_write(const bs::error_code& err, size_t len); 47 | 48 | /// Read header of data returned from the web-server 49 | void handle_server_read_headers(const bs::error_code& err, size_t len); 50 | 51 | /// Reading data from a Web server, and writing it to the browser 52 | void handle_browser_write(const bs::error_code& err, size_t len); 53 | void handle_server_read_body(const bs::error_code& err, size_t len); 54 | 55 | /// Close both sockets: for browser and web-server 56 | void shutdown(); 57 | 58 | 59 | 60 | ba::io_service& io_service_; 61 | ba::ip::tcp::socket bsocket_; 62 | ba::ip::tcp::socket ssocket_; 63 | ba::ip::tcp::resolver resolver_; 64 | bool proxy_closed; 65 | bool isPersistent; 66 | int32_t RespLen; 67 | int32_t RespReaded; 68 | 69 | boost::array bbuffer; 70 | boost::array sbuffer; 71 | 72 | std::string fURL; 73 | std::string fHeaders; 74 | std::string fNewURL; 75 | std::string fMethod; 76 | std::string fReqVersion; 77 | std::string fServer; 78 | std::string fPort; 79 | bool isOpened; 80 | 81 | std::string fReq; 82 | 83 | typedef boost::unordered_map headersMap; 84 | headersMap reqHeaders, respHeaders; 85 | 86 | void parseHeaders(const std::string& h, headersMap& hm); 87 | }; 88 | 89 | 90 | #endif /* _PROXY-CONN_H */ 91 | 92 | -------------------------------------------------------------------------------- /proxy-server.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file proxy-server.cpp 3 | * @author Alex Ott 4 | * 5 | * @brief 6 | * 7 | * 8 | */ 9 | 10 | #include "proxy-server.hpp" 11 | 12 | server::server(const ios_deque& io_services, int port, std::string interface_address) 13 | : io_services_(io_services), 14 | endpoint_(interface_address.empty()? 15 | (ba::ip::tcp::endpoint(ba::ip::tcp::v4(), port)): // INADDR_ANY for v4 (in6addr_any if the fix to v6) 16 | ba::ip::tcp::endpoint(ba::ip::address().from_string(interface_address), port) ), // specified ip address 17 | acceptor_(*io_services.front(), endpoint_) // By default set option to reuse the address (i.e. SO_REUSEADDR) 18 | { 19 | std::cout << endpoint_.address().to_string() << ":" << endpoint_.port() << std::endl; 20 | // std::cout << "server::server" << std::endl; 21 | start_accept(); 22 | } 23 | 24 | void server::start_accept() { 25 | // std::cout << "server::start_accept" << std::endl; 26 | // Round robin. 27 | io_services_.push_back(io_services_.front()); 28 | io_services_.pop_front(); 29 | connection::pointer new_connection = connection::create(*io_services_.front()); 30 | 31 | acceptor_.async_accept(new_connection->socket(), 32 | boost::bind(&server::handle_accept, this, new_connection, 33 | ba::placeholders::error)); 34 | } 35 | 36 | void server::handle_accept(connection::pointer new_connection, const bs::error_code& error) { 37 | // std::cout << "server::handle_accept" << std::endl; 38 | if (!error) { 39 | new_connection->start(); 40 | start_accept(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /proxy-server.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file proxy-server.hpp 3 | * @author Alex Ott 4 | * 5 | * @brief 6 | * 7 | * 8 | */ 9 | 10 | #ifndef _PROXY_SERVER_H 11 | #define _PROXY_SERVER_H 1 12 | 13 | #include "common.h" 14 | #include "proxy-conn.hpp" 15 | 16 | #include 17 | 18 | typedef std::deque ios_deque; 19 | 20 | class server { 21 | public: 22 | server(const ios_deque& io_services, int port=10001, std::string interface_address = ""); 23 | 24 | private: 25 | void start_accept(); 26 | void handle_accept(connection::pointer new_connection, const bs::error_code& error); 27 | 28 | ios_deque io_services_; 29 | const ba::ip::tcp::endpoint endpoint_; /**< object, that points to the connection endpoint */ 30 | ba::ip::tcp::acceptor acceptor_; /**< object, that accepts new connections */ 31 | }; 32 | 33 | 34 | #endif /* _PROXY-SERVER_H */ 35 | 36 | -------------------------------------------------------------------------------- /proxy.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file proxy.cpp 3 | * @author Alex Ott 4 | * 5 | * @brief 6 | * 7 | * 8 | */ 9 | 10 | #include "proxy-server.hpp" 11 | 12 | int main(int argc, char** argv) { 13 | try { 14 | int thread_num = 2, port = 10001; 15 | std::string interface_address; 16 | if(argc > 1) 17 | thread_num = boost::lexical_cast(argv[1]); 18 | // read port number from command line, if provided 19 | if(argc > 2) 20 | port = boost::lexical_cast(argv[2]); 21 | // read local interface address from command line, if provided 22 | if(argc > 3) 23 | interface_address = argv[3]; 24 | ios_deque io_services; 25 | std::deque io_service_work; 26 | 27 | boost::thread_group thr_grp; 28 | 29 | for (int i = 0; i < thread_num; ++i) { 30 | io_service_ptr ios(new ba::io_service); 31 | io_services.push_back(ios); 32 | io_service_work.push_back(ba::io_service::work(*ios)); 33 | thr_grp.create_thread(boost::bind(&ba::io_service::run, ios)); 34 | } 35 | server server(io_services, port, interface_address); 36 | thr_grp.join_all(); 37 | } catch (std::exception& e) { 38 | std::cerr << e.what() << std::endl; 39 | } 40 | 41 | 42 | return 0; 43 | } 44 | 45 | --------------------------------------------------------------------------------