1 /* 2 * Copyright (c) 2014, Peter Thorson. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are met: 6 * * Redistributions of source code must retain the above copyright 7 * notice, this list of conditions and the following disclaimer. 8 * * Redistributions in binary form must reproduce the above copyright 9 * notice, this list of conditions and the following disclaimer in the 10 * documentation and/or other materials provided with the distribution. 11 * * Neither the name of the WebSocket++ Project nor the 12 * names of its contributors may be used to endorse or promote products 13 * derived from this software without specific prior written permission. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 * ARE DISCLAIMED. IN NO EVENT SHALL PETER THORSON BE LIABLE FOR ANY 19 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 * 26 */ 27 28 #ifndef WEBSOCKETPP_TRANSPORT_ASIO_CON_HPP 29 #define WEBSOCKETPP_TRANSPORT_ASIO_CON_HPP 30 31 #include <websocketpp/transport/asio/base.hpp> 32 33 #include <websocketpp/transport/base/connection.hpp> 34 35 #include <websocketpp/logger/levels.hpp> 36 #include <websocketpp/http/constants.hpp> 37 38 #include <websocketpp/base64/base64.hpp> 39 #include <websocketpp/error.hpp> 40 41 #include <websocketpp/common/cpp11.hpp> 42 #include <websocketpp/common/memory.hpp> 43 #include <websocketpp/common/functional.hpp> 44 #include <websocketpp/common/connection_hdl.hpp> 45 46 #include <boost/asio.hpp> 47 #include <boost/system/error_code.hpp> 48 49 #include <istream> 50 #include <sstream> 51 #include <string> 52 #include <vector> 53 54 namespace websocketpp { 55 namespace transport { 56 namespace asio { 57 58 typedef lib::function<void(connection_hdl)> tcp_init_handler; 59 60 /// Boost Asio based connection transport component 61 /** 62 * transport::asio::connection implements a connection transport component using 63 * Boost ASIO that works with the transport::asio::endpoint endpoint transport 64 * component. 65 */ 66 template <typename config> 67 class connection : public config::socket_type::socket_con_type { 68 public: 69 /// Type of this connection transport component 70 typedef connection<config> type; 71 /// Type of a shared pointer to this connection transport component 72 typedef lib::shared_ptr<type> ptr; 73 74 /// Type of the socket connection component 75 typedef typename config::socket_type::socket_con_type socket_con_type; 76 /// Type of a shared pointer to the socket connection component 77 typedef typename socket_con_type::ptr socket_con_ptr; 78 /// Type of this transport's access logging policy 79 typedef typename config::alog_type alog_type; 80 /// Type of this transport's error logging policy 81 typedef typename config::elog_type elog_type; 82 83 typedef typename config::request_type request_type; 84 typedef typename request_type::ptr request_ptr; 85 typedef typename config::response_type response_type; 86 typedef typename response_type::ptr response_ptr; 87 88 /// Type of a pointer to the ASIO io_service being used 89 typedef boost::asio::io_service* io_service_ptr; 90 /// Type of a pointer to the ASIO io_service::strand being used 91 typedef lib::shared_ptr<boost::asio::io_service::strand> strand_ptr; 92 /// Type of a pointer to the ASIO timer class 93 typedef lib::shared_ptr<boost::asio::deadline_timer> timer_ptr; 94 95 // connection is friends with its associated endpoint to allow the endpoint 96 // to call private/protected utility methods that we don't want to expose 97 // to the public api. 98 friend class endpoint<config>; 99 100 // generate and manage our own io_service connection(bool is_server,alog_type & alog,elog_type & elog)101 explicit connection(bool is_server, alog_type& alog, elog_type& elog) 102 : m_is_server(is_server) 103 , m_alog(alog) 104 , m_elog(elog) 105 { 106 m_alog.write(log::alevel::devel,"asio con transport constructor"); 107 } 108 109 /// Get a shared pointer to this component get_shared()110 ptr get_shared() { 111 return lib::static_pointer_cast<type>(socket_con_type::get_shared()); 112 } 113 is_secure() const114 bool is_secure() const { 115 return socket_con_type::is_secure(); 116 } 117 118 /// Sets the tcp pre init handler 119 /** 120 * The tcp pre init handler is called after the raw tcp connection has been 121 * established but before any additional wrappers (proxy connects, TLS 122 * handshakes, etc) have been performed. 123 * 124 * @since 0.3.0 125 * 126 * @param h The handler to call on tcp pre init. 127 */ set_tcp_pre_init_handler(tcp_init_handler h)128 void set_tcp_pre_init_handler(tcp_init_handler h) { 129 m_tcp_pre_init_handler = h; 130 } 131 132 /// Sets the tcp pre init handler (deprecated) 133 /** 134 * The tcp pre init handler is called after the raw tcp connection has been 135 * established but before any additional wrappers (proxy connects, TLS 136 * handshakes, etc) have been performed. 137 * 138 * @deprecated Use set_tcp_pre_init_handler instead 139 * 140 * @param h The handler to call on tcp pre init. 141 */ set_tcp_init_handler(tcp_init_handler h)142 void set_tcp_init_handler(tcp_init_handler h) { 143 set_tcp_pre_init_handler(h); 144 } 145 146 /// Sets the tcp post init handler 147 /** 148 * The tcp post init handler is called after the tcp connection has been 149 * established and all additional wrappers (proxy connects, TLS handshakes, 150 * etc have been performed. This is fired before any bytes are read or any 151 * WebSocket specific handshake logic has been performed. 152 * 153 * @since 0.3.0 154 * 155 * @param h The handler to call on tcp post init. 156 */ set_tcp_post_init_handler(tcp_init_handler h)157 void set_tcp_post_init_handler(tcp_init_handler h) { 158 m_tcp_post_init_handler = h; 159 } 160 161 /// Set the proxy to connect through (exception free) 162 /** 163 * The URI passed should be a complete URI including scheme. For example: 164 * http://proxy.example.com:8080/ 165 * 166 * The proxy must be set up as an explicit (CONNECT) proxy allowed to 167 * connect to the port you specify. Traffic to the proxy is not encrypted. 168 * 169 * @param uri The full URI of the proxy to connect to. 170 * 171 * @param ec A status value 172 */ set_proxy(std::string const & uri,lib::error_code & ec)173 void set_proxy(std::string const & uri, lib::error_code & ec) { 174 // TODO: return errors for illegal URIs here? 175 // TODO: should https urls be illegal for the moment? 176 m_proxy = uri; 177 m_proxy_data = lib::make_shared<proxy_data>(); 178 ec = lib::error_code(); 179 } 180 181 /// Set the proxy to connect through (exception) set_proxy(std::string const & uri)182 void set_proxy(std::string const & uri) { 183 lib::error_code ec; 184 set_proxy(uri,ec); 185 if (ec) { throw exception(ec); } 186 } 187 188 /// Set the basic auth credentials to use (exception free) 189 /** 190 * The URI passed should be a complete URI including scheme. For example: 191 * http://proxy.example.com:8080/ 192 * 193 * The proxy must be set up as an explicit proxy 194 * 195 * @param username The username to send 196 * 197 * @param password The password to send 198 * 199 * @param ec A status value 200 */ set_proxy_basic_auth(std::string const & username,std::string const & password,lib::error_code & ec)201 void set_proxy_basic_auth(std::string const & username, std::string const & 202 password, lib::error_code & ec) 203 { 204 if (!m_proxy_data) { 205 ec = make_error_code(websocketpp::error::invalid_state); 206 return; 207 } 208 209 // TODO: username can't contain ':' 210 std::string val = "Basic "+base64_encode(username + ":" + password); 211 m_proxy_data->req.replace_header("Proxy-Authorization",val); 212 ec = lib::error_code(); 213 } 214 215 /// Set the basic auth credentials to use (exception) set_proxy_basic_auth(std::string const & username,std::string const & password)216 void set_proxy_basic_auth(std::string const & username, std::string const & 217 password) 218 { 219 lib::error_code ec; 220 set_proxy_basic_auth(username,password,ec); 221 if (ec) { throw exception(ec); } 222 } 223 224 /// Set the proxy timeout duration (exception free) 225 /** 226 * Duration is in milliseconds. Default value is based on the transport 227 * config 228 * 229 * @param duration The number of milliseconds to wait before aborting the 230 * proxy connection. 231 * 232 * @param ec A status value 233 */ set_proxy_timeout(long duration,lib::error_code & ec)234 void set_proxy_timeout(long duration, lib::error_code & ec) { 235 if (!m_proxy_data) { 236 ec = make_error_code(websocketpp::error::invalid_state); 237 return; 238 } 239 240 m_proxy_data->timeout_proxy = duration; 241 ec = lib::error_code(); 242 } 243 244 /// Set the proxy timeout duration (exception) set_proxy_timeout(long duration)245 void set_proxy_timeout(long duration) { 246 lib::error_code ec; 247 set_proxy_timeout(duration,ec); 248 if (ec) { throw exception(ec); } 249 } 250 get_proxy() const251 std::string const & get_proxy() const { 252 return m_proxy; 253 } 254 255 /// Get the remote endpoint address 256 /** 257 * The iostream transport has no information about the ultimate remote 258 * endpoint. It will return the string "iostream transport". To indicate 259 * this. 260 * 261 * TODO: allow user settable remote endpoint addresses if this seems useful 262 * 263 * @return A string identifying the address of the remote endpoint 264 */ get_remote_endpoint() const265 std::string get_remote_endpoint() const { 266 lib::error_code ec; 267 268 std::string ret = socket_con_type::get_remote_endpoint(ec); 269 270 if (ec) { 271 m_elog.write(log::elevel::info,ret); 272 return "Unknown"; 273 } else { 274 return ret; 275 } 276 } 277 278 /// Get the connection handle get_handle() const279 connection_hdl get_handle() const { 280 return m_connection_hdl; 281 } 282 283 /// Call back a function after a period of time. 284 /** 285 * Sets a timer that calls back a function after the specified period of 286 * milliseconds. Returns a handle that can be used to cancel the timer. 287 * A cancelled timer will return the error code error::operation_aborted 288 * A timer that expired will return no error. 289 * 290 * @param duration Length of time to wait in milliseconds 291 * 292 * @param callback The function to call back when the timer has expired 293 * 294 * @return A handle that can be used to cancel the timer if it is no longer 295 * needed. 296 */ set_timer(long duration,timer_handler callback)297 timer_ptr set_timer(long duration, timer_handler callback) { 298 timer_ptr new_timer = lib::make_shared<boost::asio::deadline_timer>( 299 *m_io_service, 300 boost::posix_time::milliseconds(duration) 301 ); 302 303 if (config::enable_multithreading) { 304 new_timer->async_wait(m_strand->wrap(lib::bind( 305 &type::handle_timer, get_shared(), 306 new_timer, 307 callback, 308 lib::placeholders::_1 309 ))); 310 } else { 311 new_timer->async_wait(lib::bind( 312 &type::handle_timer, get_shared(), 313 new_timer, 314 callback, 315 lib::placeholders::_1 316 )); 317 } 318 319 return new_timer; 320 } 321 322 /// Timer callback 323 /** 324 * The timer pointer is included to ensure the timer isn't destroyed until 325 * after it has expired. 326 * 327 * TODO: candidate for protected status 328 * 329 * @param post_timer Pointer to the timer in question 330 * @param callback The function to call back 331 * @param ec The status code 332 */ handle_timer(timer_ptr,timer_handler callback,boost::system::error_code const & ec)333 void handle_timer(timer_ptr, timer_handler callback, 334 boost::system::error_code const & ec) 335 { 336 if (ec) { 337 if (ec == boost::asio::error::operation_aborted) { 338 callback(make_error_code(transport::error::operation_aborted)); 339 } else { 340 log_err(log::elevel::info,"asio handle_timer",ec); 341 callback(make_error_code(error::pass_through)); 342 } 343 } else { 344 callback(lib::error_code()); 345 } 346 } 347 protected: 348 /// Get a pointer to this connection's strand get_strand()349 strand_ptr get_strand() { 350 return m_strand; 351 } 352 353 /// Initialize transport for reading 354 /** 355 * init_asio is called once immediately after construction to initialize 356 * boost::asio components to the io_service 357 * 358 * The transport initialization sequence consists of the following steps: 359 * - Pre-init: the underlying socket is initialized to the point where 360 * bytes may be written. No bytes are actually written in this stage 361 * - Proxy negotiation: if a proxy is set, a request is made to it to start 362 * a tunnel to the final destination. This stage ends when the proxy is 363 * ready to forward the 364 * next byte to the remote endpoint. 365 * - Post-init: Perform any i/o with the remote endpoint, such as setting up 366 * tunnels for encryption. This stage ends when the connection is ready to 367 * read or write the WebSocket handshakes. At this point the original 368 * callback function is called. 369 */ init(init_handler callback)370 void init(init_handler callback) { 371 if (m_alog.static_test(log::alevel::devel)) { 372 m_alog.write(log::alevel::devel,"asio connection init"); 373 } 374 375 // TODO: pre-init timeout. Right now no implemented socket policies 376 // actually have an asyncronous pre-init 377 378 m_init_handler = callback; 379 380 socket_con_type::pre_init( 381 lib::bind( 382 &type::handle_pre_init, 383 get_shared(), 384 lib::placeholders::_1 385 ) 386 ); 387 } 388 389 /// initialize the proxy buffers and http parsers 390 /** 391 * 392 * @param authority The address of the server we want the proxy to tunnel to 393 * in the format of a URI authority (host:port) 394 * 395 * @return Status code indicating what errors occurred, if any 396 */ proxy_init(std::string const & authority)397 lib::error_code proxy_init(std::string const & authority) { 398 if (!m_proxy_data) { 399 return websocketpp::error::make_error_code( 400 websocketpp::error::invalid_state); 401 } 402 m_proxy_data->req.set_version("HTTP/1.1"); 403 m_proxy_data->req.set_method("CONNECT"); 404 405 m_proxy_data->req.set_uri(authority); 406 m_proxy_data->req.replace_header("Host",authority); 407 408 return lib::error_code(); 409 } 410 411 /// Finish constructing the transport 412 /** 413 * init_asio is called once immediately after construction to initialize 414 * boost::asio components to the io_service. 415 * 416 * @param io_service A pointer to the io_service to register with this 417 * connection 418 * 419 * @return Status code for the success or failure of the initialization 420 */ init_asio(io_service_ptr io_service)421 lib::error_code init_asio (io_service_ptr io_service) { 422 m_io_service = io_service; 423 424 if (config::enable_multithreading) { 425 m_strand = lib::make_shared<boost::asio::io_service::strand>( 426 lib::ref(*io_service)); 427 428 m_async_read_handler = m_strand->wrap(lib::bind( 429 &type::handle_async_read, get_shared(),lib::placeholders::_1, 430 lib::placeholders::_2)); 431 432 m_async_write_handler = m_strand->wrap(lib::bind( 433 &type::handle_async_write, get_shared(),lib::placeholders::_1, 434 lib::placeholders::_2)); 435 } else { 436 m_async_read_handler = lib::bind(&type::handle_async_read, 437 get_shared(), lib::placeholders::_1, lib::placeholders::_2); 438 439 m_async_write_handler = lib::bind(&type::handle_async_write, 440 get_shared(), lib::placeholders::_1, lib::placeholders::_2); 441 } 442 443 lib::error_code ec = socket_con_type::init_asio(io_service, m_strand, 444 m_is_server); 445 446 if (ec) { 447 // reset the handlers to break the circular reference: 448 // this->handler->this 449 lib::clear_function(m_async_read_handler); 450 lib::clear_function(m_async_write_handler); 451 } 452 453 return ec; 454 } 455 handle_pre_init(lib::error_code const & ec)456 void handle_pre_init(lib::error_code const & ec) { 457 if (m_alog.static_test(log::alevel::devel)) { 458 m_alog.write(log::alevel::devel,"asio connection handle pre_init"); 459 } 460 461 if (m_tcp_pre_init_handler) { 462 m_tcp_pre_init_handler(m_connection_hdl); 463 } 464 465 if (ec) { 466 m_init_handler(ec); 467 } 468 469 // If we have a proxy set issue a proxy connect, otherwise skip to 470 // post_init 471 if (!m_proxy.empty()) { 472 proxy_write(); 473 } else { 474 post_init(); 475 } 476 } 477 post_init()478 void post_init() { 479 if (m_alog.static_test(log::alevel::devel)) { 480 m_alog.write(log::alevel::devel,"asio connection post_init"); 481 } 482 483 timer_ptr post_timer; 484 485 if (config::timeout_socket_post_init > 0) { 486 post_timer = set_timer( 487 config::timeout_socket_post_init, 488 lib::bind( 489 &type::handle_post_init_timeout, 490 get_shared(), 491 post_timer, 492 m_init_handler, 493 lib::placeholders::_1 494 ) 495 ); 496 } 497 498 socket_con_type::post_init( 499 lib::bind( 500 &type::handle_post_init, 501 get_shared(), 502 post_timer, 503 m_init_handler, 504 lib::placeholders::_1 505 ) 506 ); 507 } 508 509 /// Post init timeout callback 510 /** 511 * The timer pointer is included to ensure the timer isn't destroyed until 512 * after it has expired. 513 * 514 * @param post_timer Pointer to the timer in question 515 * @param callback The function to call back 516 * @param ec The status code 517 */ handle_post_init_timeout(timer_ptr,init_handler callback,lib::error_code const & ec)518 void handle_post_init_timeout(timer_ptr, init_handler callback, 519 lib::error_code const & ec) 520 { 521 lib::error_code ret_ec; 522 523 if (ec) { 524 if (ec == transport::error::operation_aborted) { 525 m_alog.write(log::alevel::devel, 526 "asio post init timer cancelled"); 527 return; 528 } 529 530 log_err(log::elevel::devel,"asio handle_post_init_timeout",ec); 531 ret_ec = ec; 532 } else { 533 if (socket_con_type::get_ec()) { 534 ret_ec = socket_con_type::get_ec(); 535 } else { 536 ret_ec = make_error_code(transport::error::timeout); 537 } 538 } 539 540 m_alog.write(log::alevel::devel,"Asio transport post-init timed out"); 541 socket_con_type::cancel_socket(); 542 callback(ret_ec); 543 } 544 545 /// Post init timeout callback 546 /** 547 * The timer pointer is included to ensure the timer isn't destroyed until 548 * after it has expired. 549 * 550 * @param post_timer Pointer to the timer in question 551 * @param callback The function to call back 552 * @param ec The status code 553 */ handle_post_init(timer_ptr post_timer,init_handler callback,lib::error_code const & ec)554 void handle_post_init(timer_ptr post_timer, init_handler callback, 555 lib::error_code const & ec) 556 { 557 if (ec == transport::error::operation_aborted || 558 (post_timer && post_timer->expires_from_now().is_negative())) 559 { 560 m_alog.write(log::alevel::devel,"post_init cancelled"); 561 return; 562 } 563 564 if (post_timer) { 565 post_timer->cancel(); 566 } 567 568 if (m_alog.static_test(log::alevel::devel)) { 569 m_alog.write(log::alevel::devel,"asio connection handle_post_init"); 570 } 571 572 if (m_tcp_post_init_handler) { 573 m_tcp_post_init_handler(m_connection_hdl); 574 } 575 576 callback(ec); 577 } 578 proxy_write()579 void proxy_write() { 580 if (m_alog.static_test(log::alevel::devel)) { 581 m_alog.write(log::alevel::devel,"asio connection proxy_write"); 582 } 583 584 if (!m_proxy_data) { 585 m_elog.write(log::elevel::library, 586 "assertion failed: !m_proxy_data in asio::connection::proxy_write"); 587 m_init_handler(make_error_code(error::general)); 588 return; 589 } 590 591 m_proxy_data->write_buf = m_proxy_data->req.raw(); 592 593 m_bufs.push_back(boost::asio::buffer(m_proxy_data->write_buf.data(), 594 m_proxy_data->write_buf.size())); 595 596 m_alog.write(log::alevel::devel,m_proxy_data->write_buf); 597 598 // Set a timer so we don't wait forever for the proxy to respond 599 m_proxy_data->timer = this->set_timer( 600 m_proxy_data->timeout_proxy, 601 lib::bind( 602 &type::handle_proxy_timeout, 603 get_shared(), 604 m_init_handler, 605 lib::placeholders::_1 606 ) 607 ); 608 609 // Send proxy request 610 if (config::enable_multithreading) { 611 boost::asio::async_write( 612 socket_con_type::get_next_layer(), 613 m_bufs, 614 m_strand->wrap(lib::bind( 615 &type::handle_proxy_write, get_shared(), 616 m_init_handler, 617 lib::placeholders::_1 618 )) 619 ); 620 } else { 621 boost::asio::async_write( 622 socket_con_type::get_next_layer(), 623 m_bufs, 624 lib::bind( 625 &type::handle_proxy_write, get_shared(), 626 m_init_handler, 627 lib::placeholders::_1 628 ) 629 ); 630 } 631 } 632 handle_proxy_timeout(init_handler callback,lib::error_code const & ec)633 void handle_proxy_timeout(init_handler callback, lib::error_code const & ec) 634 { 635 if (ec == transport::error::operation_aborted) { 636 m_alog.write(log::alevel::devel, 637 "asio handle_proxy_write timer cancelled"); 638 return; 639 } else if (ec) { 640 log_err(log::elevel::devel,"asio handle_proxy_write",ec); 641 callback(ec); 642 } else { 643 m_alog.write(log::alevel::devel, 644 "asio handle_proxy_write timer expired"); 645 socket_con_type::cancel_socket(); 646 callback(make_error_code(transport::error::timeout)); 647 } 648 } 649 handle_proxy_write(init_handler callback,boost::system::error_code const & ec)650 void handle_proxy_write(init_handler callback, 651 boost::system::error_code const & ec) 652 { 653 if (m_alog.static_test(log::alevel::devel)) { 654 m_alog.write(log::alevel::devel, 655 "asio connection handle_proxy_write"); 656 } 657 658 m_bufs.clear(); 659 660 // Timer expired or the operation was aborted for some reason. 661 // Whatever aborted it will be issuing the callback so we are safe to 662 // return 663 if (ec == boost::asio::error::operation_aborted || 664 m_proxy_data->timer->expires_from_now().is_negative()) 665 { 666 m_elog.write(log::elevel::devel,"write operation aborted"); 667 return; 668 } 669 670 if (ec) { 671 log_err(log::elevel::info,"asio handle_proxy_write",ec); 672 m_proxy_data->timer->cancel(); 673 callback(make_error_code(error::pass_through)); 674 return; 675 } 676 677 proxy_read(callback); 678 } 679 proxy_read(init_handler callback)680 void proxy_read(init_handler callback) { 681 if (m_alog.static_test(log::alevel::devel)) { 682 m_alog.write(log::alevel::devel,"asio connection proxy_read"); 683 } 684 685 if (!m_proxy_data) { 686 m_elog.write(log::elevel::library, 687 "assertion failed: !m_proxy_data in asio::connection::proxy_read"); 688 m_proxy_data->timer->cancel(); 689 callback(make_error_code(error::general)); 690 return; 691 } 692 693 if (config::enable_multithreading) { 694 boost::asio::async_read_until( 695 socket_con_type::get_next_layer(), 696 m_proxy_data->read_buf, 697 "\r\n\r\n", 698 m_strand->wrap(lib::bind( 699 &type::handle_proxy_read, get_shared(), 700 callback, 701 lib::placeholders::_1, lib::placeholders::_2 702 )) 703 ); 704 } else { 705 boost::asio::async_read_until( 706 socket_con_type::get_next_layer(), 707 m_proxy_data->read_buf, 708 "\r\n\r\n", 709 lib::bind( 710 &type::handle_proxy_read, get_shared(), 711 callback, 712 lib::placeholders::_1, lib::placeholders::_2 713 ) 714 ); 715 } 716 } 717 718 /// Proxy read callback 719 /** 720 * @param init_handler The function to call back 721 * @param ec The status code 722 * @param bytes_transferred The number of bytes read 723 */ handle_proxy_read(init_handler callback,boost::system::error_code const & ec,size_t)724 void handle_proxy_read(init_handler callback, 725 boost::system::error_code const & ec, size_t) 726 { 727 if (m_alog.static_test(log::alevel::devel)) { 728 m_alog.write(log::alevel::devel, 729 "asio connection handle_proxy_read"); 730 } 731 732 // Timer expired or the operation was aborted for some reason. 733 // Whatever aborted it will be issuing the callback so we are safe to 734 // return 735 if (ec == boost::asio::error::operation_aborted || 736 m_proxy_data->timer->expires_from_now().is_negative()) 737 { 738 m_elog.write(log::elevel::devel,"read operation aborted"); 739 return; 740 } 741 742 // At this point there is no need to wait for the timer anymore 743 m_proxy_data->timer->cancel(); 744 745 if (ec) { 746 m_elog.write(log::elevel::info, 747 "asio handle_proxy_read error: "+ec.message()); 748 callback(make_error_code(error::pass_through)); 749 } else { 750 if (!m_proxy_data) { 751 m_elog.write(log::elevel::library, 752 "assertion failed: !m_proxy_data in asio::connection::handle_proxy_read"); 753 callback(make_error_code(error::general)); 754 return; 755 } 756 757 std::istream input(&m_proxy_data->read_buf); 758 759 m_proxy_data->res.consume(input); 760 761 if (!m_proxy_data->res.headers_ready()) { 762 // we read until the headers were done in theory but apparently 763 // they aren't. Internal endpoint error. 764 callback(make_error_code(error::general)); 765 return; 766 } 767 768 m_alog.write(log::alevel::devel,m_proxy_data->res.raw()); 769 770 if (m_proxy_data->res.get_status_code() != http::status_code::ok) { 771 // got an error response back 772 // TODO: expose this error in a programmatically accessible way? 773 // if so, see below for an option on how to do this. 774 std::stringstream s; 775 s << "Proxy connection error: " 776 << m_proxy_data->res.get_status_code() 777 << " (" 778 << m_proxy_data->res.get_status_msg() 779 << ")"; 780 m_elog.write(log::elevel::info,s.str()); 781 callback(make_error_code(error::proxy_failed)); 782 return; 783 } 784 785 // we have successfully established a connection to the proxy, now 786 // we can continue and the proxy will transparently forward the 787 // WebSocket connection. 788 789 // TODO: decide if we want an on_proxy callback that would allow 790 // access to the proxy response. 791 792 // free the proxy buffers and req/res objects as they aren't needed 793 // anymore 794 m_proxy_data.reset(); 795 796 // Continue with post proxy initialization 797 post_init(); 798 } 799 } 800 801 /// read at least num_bytes bytes into buf and then call handler. 802 /** 803 * 804 * 805 */ async_read_at_least(size_t num_bytes,char * buf,size_t len,read_handler handler)806 void async_read_at_least(size_t num_bytes, char *buf, size_t len, 807 read_handler handler) 808 { 809 if (m_alog.static_test(log::alevel::devel)) { 810 std::stringstream s; 811 s << "asio async_read_at_least: " << num_bytes; 812 m_alog.write(log::alevel::devel,s.str()); 813 } 814 815 if (!m_async_read_handler) { 816 m_alog.write(log::alevel::devel, 817 "async_read_at_least called after async_shutdown"); 818 handler(make_error_code(transport::error::action_after_shutdown),0); 819 return; 820 } 821 822 // TODO: safety vs speed ? 823 // maybe move into an if devel block 824 /*if (num_bytes > len) { 825 m_elog.write(log::elevel::devel, 826 "asio async_read_at_least error::invalid_num_bytes"); 827 handler(make_error_code(transport::error::invalid_num_bytes), 828 size_t(0)); 829 return; 830 }*/ 831 832 m_read_handler = handler; 833 834 if (!m_read_handler) { 835 m_alog.write(log::alevel::devel, 836 "asio con async_read_at_least called with bad handler"); 837 } 838 839 boost::asio::async_read( 840 socket_con_type::get_socket(), 841 boost::asio::buffer(buf,len), 842 boost::asio::transfer_at_least(num_bytes), 843 make_custom_alloc_handler( 844 m_read_handler_allocator, 845 m_async_read_handler 846 ) 847 ); 848 } 849 handle_async_read(boost::system::error_code const & ec,size_t bytes_transferred)850 void handle_async_read(boost::system::error_code const & ec, 851 size_t bytes_transferred) 852 { 853 m_alog.write(log::alevel::devel, "asio con handle_async_read"); 854 855 // translate boost error codes into more lib::error_codes 856 lib::error_code tec; 857 if (ec == boost::asio::error::eof) { 858 tec = make_error_code(transport::error::eof); 859 } else if (ec) { 860 // We don't know much more about the error at this point. As our 861 // socket/security policy if it knows more: 862 tec = socket_con_type::translate_ec(ec); 863 864 if (tec == transport::error::tls_error || 865 tec == transport::error::pass_through) 866 { 867 // These are aggregate/catch all errors. Log some human readable 868 // information to the info channel to give library users some 869 // more details about why the upstream method may have failed. 870 log_err(log::elevel::info,"asio async_read_at_least",ec); 871 } 872 } 873 if (m_read_handler) { 874 m_read_handler(tec,bytes_transferred); 875 // TODO: why does this line break things? 876 //m_read_handler = _WEBSOCKETPP_NULL_FUNCTION_; 877 } else { 878 // This can happen in cases where the connection is terminated while 879 // the transport is waiting on a read. 880 m_alog.write(log::alevel::devel, 881 "handle_async_read called with null read handler"); 882 } 883 } 884 async_write(const char * buf,size_t len,write_handler handler)885 void async_write(const char* buf, size_t len, write_handler handler) { 886 if (!m_async_write_handler) { 887 m_alog.write(log::alevel::devel, 888 "async_write (single) called after async_shutdown"); 889 handler(make_error_code(transport::error::action_after_shutdown)); 890 return; 891 } 892 893 m_bufs.push_back(boost::asio::buffer(buf,len)); 894 895 m_write_handler = handler; 896 897 boost::asio::async_write( 898 socket_con_type::get_socket(), 899 m_bufs, 900 make_custom_alloc_handler( 901 m_write_handler_allocator, 902 m_async_write_handler 903 ) 904 ); 905 } 906 async_write(std::vector<buffer> const & bufs,write_handler handler)907 void async_write(std::vector<buffer> const & bufs, write_handler handler) { 908 if (!m_async_write_handler) { 909 m_alog.write(log::alevel::devel, 910 "async_write (vector) called after async_shutdown"); 911 handler(make_error_code(transport::error::action_after_shutdown)); 912 return; 913 } 914 std::vector<buffer>::const_iterator it; 915 916 for (it = bufs.begin(); it != bufs.end(); ++it) { 917 m_bufs.push_back(boost::asio::buffer((*it).buf,(*it).len)); 918 } 919 920 m_write_handler = handler; 921 922 boost::asio::async_write( 923 socket_con_type::get_socket(), 924 m_bufs, 925 make_custom_alloc_handler( 926 m_write_handler_allocator, 927 m_async_write_handler 928 ) 929 ); 930 } 931 932 /// Async write callback 933 /** 934 * @param ec The status code 935 * @param bytes_transferred The number of bytes read 936 */ handle_async_write(boost::system::error_code const & ec,size_t)937 void handle_async_write(boost::system::error_code const & ec, size_t) { 938 m_bufs.clear(); 939 lib::error_code tec; 940 if (ec) { 941 log_err(log::elevel::info,"asio async_write",ec); 942 tec = make_error_code(transport::error::pass_through); 943 } 944 if (m_write_handler) { 945 m_write_handler(tec); 946 // TODO: why does this line break things? 947 //m_write_handler = _WEBSOCKETPP_NULL_FUNCTION_; 948 } else { 949 // This can happen in cases where the connection is terminated while 950 // the transport is waiting on a read. 951 m_alog.write(log::alevel::devel, 952 "handle_async_write called with null write handler"); 953 } 954 } 955 956 /// Set Connection Handle 957 /** 958 * See common/connection_hdl.hpp for information 959 * 960 * @param hdl A connection_hdl that the transport will use to refer 961 * to itself 962 */ set_handle(connection_hdl hdl)963 void set_handle(connection_hdl hdl) { 964 m_connection_hdl = hdl; 965 socket_con_type::set_handle(hdl); 966 } 967 968 /// Trigger the on_interrupt handler 969 /** 970 * This needs to be thread safe 971 */ interrupt(interrupt_handler handler)972 lib::error_code interrupt(interrupt_handler handler) { 973 if (config::enable_multithreading) { 974 m_io_service->post(m_strand->wrap(handler)); 975 } else { 976 m_io_service->post(handler); 977 } 978 return lib::error_code(); 979 } 980 dispatch(dispatch_handler handler)981 lib::error_code dispatch(dispatch_handler handler) { 982 if (config::enable_multithreading) { 983 m_io_service->post(m_strand->wrap(handler)); 984 } else { 985 m_io_service->post(handler); 986 } 987 return lib::error_code(); 988 } 989 990 /*void handle_interrupt(interrupt_handler handler) { 991 handler(); 992 }*/ 993 994 /// close and clean up the underlying socket async_shutdown(shutdown_handler callback)995 void async_shutdown(shutdown_handler callback) { 996 if (m_alog.static_test(log::alevel::devel)) { 997 m_alog.write(log::alevel::devel,"asio connection async_shutdown"); 998 } 999 1000 // Reset cached handlers now that we won't be reading or writing anymore 1001 // These cached handlers store shared pointers to this connection and 1002 // will leak the connection if not destroyed. 1003 lib::clear_function(m_async_read_handler); 1004 lib::clear_function(m_async_write_handler); 1005 lib::clear_function(m_init_handler); 1006 1007 lib::clear_function(m_read_handler); 1008 lib::clear_function(m_write_handler); 1009 1010 timer_ptr shutdown_timer; 1011 shutdown_timer = set_timer( 1012 config::timeout_socket_shutdown, 1013 lib::bind( 1014 &type::handle_async_shutdown_timeout, 1015 get_shared(), 1016 shutdown_timer, 1017 callback, 1018 lib::placeholders::_1 1019 ) 1020 ); 1021 1022 socket_con_type::async_shutdown( 1023 lib::bind( 1024 &type::handle_async_shutdown, 1025 get_shared(), 1026 shutdown_timer, 1027 callback, 1028 lib::placeholders::_1 1029 ) 1030 ); 1031 } 1032 1033 /// Async shutdown timeout handler 1034 /** 1035 * @param shutdown_timer A pointer to the timer to keep it in scope 1036 * @param callback The function to call back 1037 * @param ec The status code 1038 */ handle_async_shutdown_timeout(timer_ptr,init_handler callback,lib::error_code const & ec)1039 void handle_async_shutdown_timeout(timer_ptr, init_handler callback, 1040 lib::error_code const & ec) 1041 { 1042 lib::error_code ret_ec; 1043 1044 if (ec) { 1045 if (ec == transport::error::operation_aborted) { 1046 m_alog.write(log::alevel::devel, 1047 "asio socket shutdown timer cancelled"); 1048 return; 1049 } 1050 1051 log_err(log::elevel::devel,"asio handle_async_shutdown_timeout",ec); 1052 ret_ec = ec; 1053 } else { 1054 ret_ec = make_error_code(transport::error::timeout); 1055 } 1056 1057 m_alog.write(log::alevel::devel, 1058 "Asio transport socket shutdown timed out"); 1059 socket_con_type::cancel_socket(); 1060 callback(ret_ec); 1061 } 1062 handle_async_shutdown(timer_ptr shutdown_timer,shutdown_handler callback,boost::system::error_code const & ec)1063 void handle_async_shutdown(timer_ptr shutdown_timer, shutdown_handler 1064 callback, boost::system::error_code const & ec) 1065 { 1066 if (ec == boost::asio::error::operation_aborted || 1067 shutdown_timer->expires_from_now().is_negative()) 1068 { 1069 m_alog.write(log::alevel::devel,"async_shutdown cancelled"); 1070 return; 1071 } 1072 1073 shutdown_timer->cancel(); 1074 1075 lib::error_code tec; 1076 if (ec) { 1077 if (ec == boost::asio::error::not_connected) { 1078 // The socket was already closed when we tried to close it. This 1079 // happens periodically (usually if a read or write fails 1080 // earlier and if it is a real error will be caught at another 1081 // level of the stack. 1082 } else { 1083 // We don't know anything more about this error, give our 1084 // socket/security policy a crack at it. 1085 tec = socket_con_type::translate_ec(ec); 1086 1087 if (tec == transport::error::tls_short_read) { 1088 // TLS short read at this point is somewhat expected if both 1089 // sides try and end the connection at the same time or if 1090 // SSLv2 is being used. In general there is nothing that can 1091 // be done here other than a low level development log. 1092 } else { 1093 // all other errors are effectively pass through errors of 1094 // some sort so print some detail on the info channel for 1095 // library users to look up if needed. 1096 log_err(log::elevel::info,"asio async_shutdown",ec); 1097 } 1098 } 1099 } else { 1100 if (m_alog.static_test(log::alevel::devel)) { 1101 m_alog.write(log::alevel::devel, 1102 "asio con handle_async_shutdown"); 1103 } 1104 } 1105 callback(tec); 1106 } 1107 private: 1108 /// Convenience method for logging the code and message for an error_code 1109 template <typename error_type> log_err(log::level l,const char * msg,const error_type & ec)1110 void log_err(log::level l, const char * msg, const error_type & ec) { 1111 std::stringstream s; 1112 s << msg << " error: " << ec << " (" << ec.message() << ")"; 1113 m_elog.write(l,s.str()); 1114 } 1115 1116 // static settings 1117 const bool m_is_server; 1118 alog_type& m_alog; 1119 elog_type& m_elog; 1120 1121 struct proxy_data { proxy_datawebsocketpp::transport::asio::connection::proxy_data1122 proxy_data() : timeout_proxy(config::timeout_proxy) {} 1123 1124 request_type req; 1125 response_type res; 1126 std::string write_buf; 1127 boost::asio::streambuf read_buf; 1128 long timeout_proxy; 1129 timer_ptr timer; 1130 }; 1131 1132 std::string m_proxy; 1133 lib::shared_ptr<proxy_data> m_proxy_data; 1134 1135 // transport resources 1136 io_service_ptr m_io_service; 1137 strand_ptr m_strand; 1138 connection_hdl m_connection_hdl; 1139 1140 std::vector<boost::asio::const_buffer> m_bufs; 1141 1142 // Handlers 1143 tcp_init_handler m_tcp_pre_init_handler; 1144 tcp_init_handler m_tcp_post_init_handler; 1145 1146 handler_allocator m_read_handler_allocator; 1147 handler_allocator m_write_handler_allocator; 1148 1149 read_handler m_read_handler; 1150 write_handler m_write_handler; 1151 init_handler m_init_handler; 1152 1153 async_read_handler m_async_read_handler; 1154 async_write_handler m_async_write_handler; 1155 }; 1156 1157 1158 } // namespace asio 1159 } // namespace transport 1160 } // namespace websocketpp 1161 1162 #endif // WEBSOCKETPP_TRANSPORT_ASIO_CON_HPP 1163