Refactor WebSocket, HTTP examples:

fix #297, fix #488

* Errors are checked and reported
* More comments explaining what is going on
* The connection is gracefully closed

WebSocket:

* Messages are drained before closing
This commit is contained in:
Vinnie Falco
2017-06-17 20:17:01 -07:00
parent 298bf5fbb1
commit 74891560ae
6 changed files with 260 additions and 139 deletions

View File

@@ -7,6 +7,7 @@ Version 61:
* Add message::header_part() * Add message::header_part()
* Tidy up names in error categories * Tidy up names in error categories
* Flush the output stream in the example * Flush the output stream in the example
* Clean close in Secure WebSocket client
API Changes: API Changes:

View File

@@ -144,77 +144,9 @@ The files in the repository are laid out thusly:
## Usage ## Usage
These examples are complete, self-contained programs that you can build These examples are complete, self-contained programs that you can build
and run yourself (they are in the `examples` directory). and run yourself (they are in the `example` directory).
Example WebSocket program: http://vinniefalco.github.io/beast/beast/quick_start.html
```C++
#include <beast/core.hpp>
#include <beast/websocket.hpp>
#include <boost/asio.hpp>
#include <iostream>
#include <string>
int main()
{
// Normal boost::asio setup
std::string const host = "echo.websocket.org";
boost::asio::io_service ios;
boost::asio::ip::tcp::resolver r{ios};
boost::asio::ip::tcp::socket sock{ios};
boost::asio::connect(sock,
r.resolve(boost::asio::ip::tcp::resolver::query{host, "80"}));
// WebSocket connect and send message using beast
beast::websocket::stream<boost::asio::ip::tcp::socket&> ws{sock};
ws.handshake(host, "/");
ws.write(boost::asio::buffer(std::string("Hello, world!")));
// Receive WebSocket message, print and close using beast
beast::multi_buffer b;
beast::websocket::opcode op;
ws.read(op, b);
ws.close(beast::websocket::close_code::normal);
std::cout << beast::buffers(b.data()) << "\n";
}
```
Example HTTP program:
```C++
#include <beast/core.hpp>
#include <beast/http.hpp>
#include <boost/asio.hpp>
#include <boost/lexical_cast.hpp>
#include <iostream>
#include <string>
int main()
{
// Normal boost::asio setup
std::string const host = "www.example.com";
boost::asio::io_service ios;
boost::asio::ip::tcp::resolver r{ios};
boost::asio::ip::tcp::socket sock{ios};
boost::asio::connect(sock,
r.resolve(boost::asio::ip::tcp::resolver::query{host, "http"}));
// Send HTTP request using beast
beast::http::request<beast::http::string_body> req;
req.method(beast::http::verb::get);
req.target("/");
req.version = 11;
req.insert(beast::http::field::host, host + ":" +
boost::lexical_cast<std::string>(sock.remote_endpoint().port()));
req.insert(beast::http::field::user_agent, "Beast");
req.prepare();
beast::http::write(sock, req);
// Receive and print HTTP response using beast
beast::flat_buffer b;
beast::http::response<beast::http::dynamic_body> res;
beast::http::read(sock, b, res);
std::cout << res << std::endl;
}
```
## License ## License

View File

@@ -11,35 +11,75 @@
#include <beast/http.hpp> #include <beast/http.hpp>
#include <boost/asio.hpp> #include <boost/asio.hpp>
#include <boost/lexical_cast.hpp> #include <boost/lexical_cast.hpp>
#include <cstdlib>
#include <iostream> #include <iostream>
#include <string> #include <string>
int main() int main()
{ {
// Normal boost::asio setup // A helper for reporting errors
std::string const host = "www.example.com"; auto const fail =
[](std::string what, beast::error_code ec)
{
std::cerr << what << ": " << ec.message() << std::endl;
std::cerr.flush();
return EXIT_FAILURE;
};
boost::system::error_code ec;
// Set up an asio socket
boost::asio::io_service ios; boost::asio::io_service ios;
boost::asio::ip::tcp::resolver r{ios}; boost::asio::ip::tcp::resolver r{ios};
boost::asio::ip::tcp::socket sock{ios}; boost::asio::ip::tcp::socket sock{ios};
boost::asio::connect(sock,
r.resolve(boost::asio::ip::tcp::resolver::query{host, "http"}));
// Send HTTP request using beast // Look up the domain name
std::string const host = "www.example.com";
auto const lookup = r.resolve(boost::asio::ip::tcp::resolver::query{host, "http"}, ec);
if(ec)
return fail("resolve", ec);
// Make the connection on the IP address we get from a lookup
boost::asio::connect(sock, lookup, ec);
if(ec)
return fail("connect", ec);
// Set up an HTTP GET request message
beast::http::request<beast::http::string_body> req; beast::http::request<beast::http::string_body> req;
req.method(beast::http::verb::get); req.method(beast::http::verb::get);
req.target("/"); req.target("/");
req.version = 11; req.version = 11;
req.insert(beast::http::field::host, host + ":" + req.set(beast::http::field::host, host + ":" +
boost::lexical_cast<std::string>(sock.remote_endpoint().port())); boost::lexical_cast<std::string>(sock.remote_endpoint().port()));
req.insert(beast::http::field::user_agent, "Beast"); req.set(beast::http::field::user_agent, "Beast");
req.prepare(); req.prepare();
beast::http::write(sock, req);
// Receive and print HTTP response using beast // Write the HTTP request to the remote host
beast::http::write(sock, req, ec);
if(ec)
return fail("write", ec);
// This buffer is used for reading and must be persisted
beast::flat_buffer b; beast::flat_buffer b;
// Declare a container to hold the response
beast::http::response<beast::http::dynamic_body> res; beast::http::response<beast::http::dynamic_body> res;
beast::http::read(sock, b, res);
// Read the response
beast::http::read(sock, b, res, ec);
if(ec)
return fail("read", ec);
// Write the message to standard out
std::cout << res << std::endl; std::cout << res << std::endl;
// Gracefully close the socket
sock.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
if(ec)
return fail("shutdown", ec);
// If we get here then the connection is closed gracefully
return EXIT_SUCCESS;
} }
//] //]

View File

@@ -15,48 +15,77 @@
int main() int main()
{ {
using boost::asio::connect; // A helper for reporting errors
using socket = boost::asio::ip::tcp::socket; auto const fail =
using resolver = boost::asio::ip::tcp::resolver; [](std::string what, beast::error_code ec)
using io_service = boost::asio::io_service; {
namespace ssl = boost::asio::ssl; std::cerr << what << ": " << ec.message() << std::endl;
std::cerr.flush();
return EXIT_FAILURE;
};
boost::system::error_code ec;
// Normal boost::asio setup // Normal boost::asio setup
std::string const host = "localhost"; boost::asio::io_service ios;
io_service ios; boost::asio::ip::tcp::resolver r{ios};
resolver r{ios}; boost::asio::ip::tcp::socket sock{ios};
socket sock{ios};
connect(sock, r.resolve(resolver::query{host, "1007"})); // Look up the domain name
std::string const host = "www.example.com";
auto const lookup = r.resolve(boost::asio::ip::tcp::resolver::query{host, "https"}, ec);
if(ec)
return fail("resolve", ec);
// Make the connection on the IP address we get from a lookup
boost::asio::connect(sock, lookup, ec);
if(ec)
return fail("connect", ec);
// Wrap the now-connected socket in an SSL stream
boost::asio::ssl::context ctx{boost::asio::ssl::context::sslv23};
boost::asio::ssl::stream<boost::asio::ip::tcp::socket&> stream{sock, ctx};
stream.set_verify_mode(boost::asio::ssl::verify_none);
// Perform SSL handshaking // Perform SSL handshaking
ssl::context ctx{ssl::context::sslv23}; stream.handshake(boost::asio::ssl::stream_base::client, ec);
ssl::stream<socket&> stream{sock, ctx}; if(ec)
stream.set_verify_mode(ssl::verify_none); return fail("handshake", ec);
stream.handshake(ssl::stream_base::client);
// Send HTTP request over SSL using Beast // Set up an HTTP GET request message
beast::http::request<beast::http::string_body> req; beast::http::request<beast::http::string_body> req;
req.method(beast::http::verb::get); req.method(beast::http::verb::get);
req.target("/"); req.target("/");
req.version = 11; req.version = 11;
req.insert(beast::http::field::host, host + ":" + req.set(beast::http::field::host, host + ":" +
boost::lexical_cast<std::string>(sock.remote_endpoint().port())); boost::lexical_cast<std::string>(sock.remote_endpoint().port()));
req.insert(beast::http::field::user_agent, "Beast"); req.set(beast::http::field::user_agent, "Beast");
req.prepare(); req.prepare();
beast::http::write(stream, req);
// Receive and print HTTP response using Beast // Write the HTTP request to the remote host
beast::http::write(stream, req, ec);
if(ec)
return fail("write", ec);
// This buffer is used for reading and must be persisted
beast::flat_buffer b; beast::flat_buffer b;
beast::http::response<beast::http::dynamic_body> resp;
beast::http::read(stream, b, resp); // Declare a container to hold the response
std::cout << resp; beast::http::response<beast::http::dynamic_body> res;
// Read the response
beast::http::read(stream, b, res, ec);
if(ec)
return fail("read", ec);
// Write the message to standard out
std::cout << res << std::endl;
// Shut down SSL on the stream // Shut down SSL on the stream
boost::system::error_code ec;
stream.shutdown(ec); stream.shutdown(ec);
if(ec && ec != boost::asio::error::eof) if(ec && ec != boost::asio::error::eof)
std::cout << "error: " << ec.message(); fail("ssl shutdown ", ec);
// Make sure everything is written before we leave main // If we get here then the connection is closed gracefully
std::cout.flush(); return EXIT_SUCCESS;
} }

View File

@@ -5,43 +5,102 @@
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
// //
#include <beast/core/ostream.hpp> #include <beast/core.hpp>
#include <beast/websocket.hpp> #include <beast/websocket.hpp>
#include <beast/websocket/ssl.hpp> #include <beast/websocket/ssl.hpp>
#include <boost/asio.hpp> #include <boost/asio.hpp>
#include <boost/asio/ssl.hpp> #include <boost/asio/ssl.hpp>
#include <cstdlib>
#include <iostream> #include <iostream>
#include <string> #include <string>
int main() int main()
{ {
using boost::asio::connect; // A helper for reporting errors
using socket = boost::asio::ip::tcp::socket; auto const fail =
using resolver = boost::asio::ip::tcp::resolver; [](std::string what, beast::error_code ec)
using io_service = boost::asio::io_service; {
namespace ssl = boost::asio::ssl; std::cerr << what << ": " << ec.message() << std::endl;
std::cerr.flush();
return EXIT_FAILURE;
};
// Normal boost::asio setup boost::system::error_code ec;
// Set up an asio socket to connect to a remote host
boost::asio::io_service ios;
boost::asio::ip::tcp::resolver r{ios};
boost::asio::ip::tcp::socket sock{ios};
// Look up the domain name
std::string const host = "echo.websocket.org"; std::string const host = "echo.websocket.org";
io_service ios; auto const lookup = r.resolve(boost::asio::ip::tcp::resolver::query{host, "https"}, ec);
resolver r{ios}; if(ec)
socket sock{ios}; return fail("resolve", ec);
connect(sock, r.resolve(resolver::query{host, "https"}));
// Make the connection on the IP address we get from a lookup
boost::asio::connect(sock, lookup, ec);
if(ec)
return fail("connect", ec);
// Wrap the now-connected socket in an SSL stream
using stream_type = boost::asio::ssl::stream<boost::asio::ip::tcp::socket&>;
boost::asio::ssl::context ctx{boost::asio::ssl::context::sslv23};
stream_type stream{sock, ctx};
stream.set_verify_mode(boost::asio::ssl::verify_none);
// Perform SSL handshaking // Perform SSL handshaking
using stream_type = ssl::stream<socket&>; stream.handshake(boost::asio::ssl::stream_base::client, ec);
ssl::context ctx{ssl::context::sslv23}; if(ec)
stream_type stream{sock, ctx}; return fail("ssl handshake", ec);
stream.set_verify_mode(ssl::verify_none);
stream.handshake(ssl::stream_base::client);
// Secure WebSocket connect and send message using Beast // Now wrap the handshaked SSL stream in a websocket stream
beast::websocket::stream<stream_type&> ws{stream}; beast::websocket::stream<stream_type&> ws{stream};
ws.handshake(host, "/");
ws.write(boost::asio::buffer("Hello, world!"));
// Receive Secure WebSocket message, print and close using Beast // Perform the websocket handshake
ws.handshake(host, "/", ec);
if(ec)
return fail("handshake", ec);
// Send a message
ws.write(boost::asio::buffer("Hello, world!"), ec);
if(ec)
return fail("write", ec);
// This buffer will hold the incoming message
beast::multi_buffer b; beast::multi_buffer b;
ws.close(beast::websocket::close_code::normal);
std::cout << beast::buffers(b.data()) << "\n"; // Read the message into our buffer
ws.read(b, ec);
if(ec)
return fail("read", ec);
// Send a "close" frame to the other end, this is a websocket thing
ws.close(beast::websocket::close_code::normal, ec);
if(ec)
return fail("close", ec);
// The buffers() function helps print a ConstBufferSequence
std::cout << beast::buffers(b.data()) << std::endl;
// WebSocket says that to close a connection you have
// to keep reading messages until you receive a close frame.
// Beast delivers the close frame as an error from read.
//
beast::drain_buffer drain; // Throws everything away efficiently
for(;;)
{
// Keep reading messages...
ws.read(drain, ec);
// ...until we get the special error code
if(ec == beast::websocket::error::closed)
break;
// Some other error occurred, report it and exit.
if(ec)
return fail("close", ec);
}
return EXIT_SUCCESS;
} }

View File

@@ -10,29 +10,89 @@
#include <beast/core.hpp> #include <beast/core.hpp>
#include <beast/websocket.hpp> #include <beast/websocket.hpp>
#include <boost/asio.hpp> #include <boost/asio.hpp>
#include <cstdlib>
#include <iostream> #include <iostream>
#include <string> #include <string>
int main() int main()
{ {
// Normal boost::asio setup // A helper for reporting errors
std::string const host = "echo.websocket.org"; auto const fail =
[](std::string what, beast::error_code ec)
{
std::cerr << what << ": " << ec.message() << std::endl;
std::cerr.flush();
return EXIT_FAILURE;
};
boost::system::error_code ec;
// Set up an asio socket
boost::asio::io_service ios; boost::asio::io_service ios;
boost::asio::ip::tcp::resolver r{ios}; boost::asio::ip::tcp::resolver r{ios};
boost::asio::ip::tcp::socket sock{ios}; boost::asio::ip::tcp::socket sock{ios};
boost::asio::connect(sock,
r.resolve(boost::asio::ip::tcp::resolver::query{host, "80"}));
// WebSocket connect and send message using beast // Look up the domain name
std::string const host = "echo.websocket.org";
auto const lookup = r.resolve(boost::asio::ip::tcp::resolver::query{host, "http"}, ec);
if(ec)
return fail("resolve", ec);
// Make the connection on the IP address we get from a lookup
boost::asio::connect(sock, lookup, ec);
if(ec)
return fail("connect", ec);
// Wrap the now-connected socket in a websocket stream
beast::websocket::stream<boost::asio::ip::tcp::socket&> ws{sock}; beast::websocket::stream<boost::asio::ip::tcp::socket&> ws{sock};
ws.handshake(host, "/");
ws.write(boost::asio::buffer(std::string("Hello, world!")));
// Receive WebSocket message, print and close using beast // Perform the websocket handhskae
ws.handshake(host, "/", ec);
if(ec)
return fail("handshake", ec);
// Send a message
ws.write(boost::asio::buffer(std::string("Hello, world!")), ec);
if(ec)
return fail("write", ec);
// This buffer will hold the incoming message
beast::multi_buffer b; beast::multi_buffer b;
ws.read(b);
ws.close(beast::websocket::close_code::normal); // Read the message into our buffer
std::cout << beast::buffers(b.data()) << "\n"; ws.read(b, ec);
if(ec)
return fail("read", ec);
// Send a "close" frame to the other end, this is a websocket thing
ws.close(beast::websocket::close_code::normal, ec);
if(ec)
return fail("close", ec);
// The buffers() function helps print a ConstBufferSequence
std::cout << beast::buffers(b.data()) << std::endl;
// WebSocket says that to close a connection you have
// to keep reading messages until you receive a close frame.
// Beast delivers the close frame as an error from read.
//
beast::drain_buffer drain; // Throws everything away efficiently
for(;;)
{
// Keep reading messages...
ws.read(drain, ec);
// ...until we get the special error code
if(ec == beast::websocket::error::closed)
break;
// Some other error occurred, report it and exit.
if(ec)
return fail("close", ec);
}
// If we get here the connection was cleanly closed
return EXIT_SUCCESS;
} }
//] //]