Watching Vinnie Falco's excellent CppCon 2017 talk “Make Classes Great Again! (Using Concepts for Customization Points” inspired me to try using Boost Beast, a library for working for with HTTP and WebSocket's in C++. Unfortunately since this is C++ I spent most of my time building all of the libraries I needed in Windows, which for some reason I still use. This post is a summary of my findings for my own future reference.
Building Boost
First off, I hope you're sitting down because I must reveal to you that Boost is still a total pain in the ass to build on Windows with the latest Visual Studio 2017 release.
In the past, I considered Boost Build my friend, but now we are enemies due to reasons I keep wanting to write a blog post about; in short I realized it was a time vampire that was draining me of my precious mortality. I liked a lot about Boost Build compared to the alternatives, but unfortunately it suffers from a lack of quality which caused me to spend more time maintaining it than working on my projects. In comparison CMake is rock solid once you get it working.
Everyone calls Microsoft's C++ compiler “Visual Studio” but the compiler itself is named “Microsoft Visual C++”. Boost Build bucks these social norms by referring to it as “msvc” which I always admired. With Visual Studio 2015, the corresponding compiler version was 14.0, but with Visual Studio 2017 it's changed to 14.1 which is surprising (of course since Boost Build is built on the worse than TCL stringly-typed programming language bjam you can pass in “msvc-15.0” as the tool chain and have no idea you've done something wrong until the build totally finishes, you get linker errors, and you finally realize the libraries were built with a different compiler).
Another thing that surprised me is that Boost Build doesn't do a good job of automatically finding the Microsoft tools it needs to call, meaning you have to run the “vcvars” batch file included with Visual Studio or open up the shortcut labeled “Visual Studio 2017's Developer Command Prompt”. In the past this wasn't necessary, so I never did it. I also liked keeping my shell clean from all the strange paths MS added to it. However since both Boost and OpenSSL required me to do this I've since added CALL “C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat” to my bash.rc-like batch file that executes for my go-to Command Prompt session. Time will tell if this creates other problems. Side note: I don't bother using mingw or any other compiler on Windows anymore, since Linux let's me do that without the stress ulcer and is run on my laptop natively and on my desktop using the Windows Subsystem for Linx (aka Bash for Ubuntu for Windows). Part of the fall out of my divorce with Boost Build also means using multiple compilers is even harder than it was before in Windows and there's no point when easier operating systems exist and most of my code is portable.
Another big change to my go-to shell was adding “C:\Program Files\7-Zip”, which gives me access to the excellent 7-zip tool from the command line (AppVeyor makes this available on it's command line which is a sign it's a good idea).
Finally, it's worth noting I have long put all of the typical unix tools (grep, sed, ls, etc) on my command prompt by installing Cygwin (which I used to need for Dreamcast development) and adding it's bin directory to my path. In the script below I use curl and also sha256sum pretty often.
Next, I realized I had an ancient site-config.jam file with all my toolchains set up (one of Boost Build's best features). Unfortunately I also had a statement in there that said “using msvc : 14.0 ;”. In the end I commented all of this out.
Manuel Gustavo has written a series of scripts for building Boost versions 1.63 and 1.64 in Windows. I didn't use them other than to crib the commands he used to build the 64-bit variants of the libraries.
First, make sure you have this stuff in your command prompt:
set PATH=%PATH%;C:\Program Files\7-Zip
set PATH=%PATH%;C:\Cygwin\bin\curl.exe
CALL "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat"
Then run this:
cd C:\Tools\boost
curl -o boost_1_65_1.7z -L https://dl.bintray.com/boostorg/release/1.65.1/source/boost_1_65_1.7z
REM should be "df7bea4ce58076bd8e9cd63966562d8cf969b431e1c587969e6aa408e51de606"
sha256sum boost_1_65_1.7z
7z x boost_1_65_1.7z
cd boost_1_65_1
bootstrap.bat
b2.exe --toolset=msvc-14.1 --clean-all
b2.exe --toolset=msvc-14.1 architecture=x86 address-model=64 --stagedir=".\stage_x64" threading=multi --build-type=complete stage -j8
b2.exe --toolset=msvc-14.1 --clean-all
b2.exe --toolset=msvc-14.1 architecture=x86 address-model=32 --stagedir=".\stage_x86" threading=multi --build-type=complete stage -j8
Notes: change ‘-j8’ above to ‘-jn’, where ‘n’ is the number of cores on your machine.
Manuel Gustavo has more code that places the libraries into a “bin” directory, but I've skipped that.
Once this runs, you need to set the environment variable BOOST_ROOT to the location of the unzipped boost_1_65_1 stuff from above.
Building OpenSSL
If you want to make websites or service, you're going to want to use SSL. It's a huge headache but given how much the world relies on it you can't really avoid being initiated.
OpenSSL turns out to not be too difficult to build if you follow the exact necessary steps, much like every other problem faced by man. My main issues were caused by the myriad of out-dated documentation that's out there.
First off, building OpenSSL in Windows requires Perl. I used Strawberry Perl, because I'm a fan of any any programming tool or language named after food. It may feel annoying to have to install a language just to build a thing but cheer up by imagining all the totally siiiiick Perl 5 code you'll be able to write after this.
Also, there's some difference between OpenSSL distributions. 1.1.0f is the newest version, but turns out the 1.1.0 version doesn't work with Boost; see bugs like this for more info. So if you value your time on Earth stick with 1.0.2.
Also, building with the ASM stuff is probably better but adds the need to download NASM. So I just skipped it with the “no-asm” option.
mkdir C:\Tools\openssl-64
mkdir C:\Tools\openssl-src
cd C:\Tools\openssl-src
curl -o openssl-1.0.2l.tar.gz -L https://www.openssl.org/source/openssl-1.0.2l.tar.gz
7z x openssl-1.0.2l.tar.gz
7z x openssl-1.0.2l.tar
cd openssl-1.0.2l
perl Configure VC-WIN64A no-asm --prefix=C:\Tools\openssl-64 --openssldir=C:\Tools\openssl-64
ms\do_win64a
nmake -f ms\nt.mak
nmake -f ms\nt.mak install
Building some Code
To start using Boost and OpenSSL from CMake you'll need to set some environment variables so CMake knows where you put that stuff.
set BOOST_LIBRARYDIR=C:\Tools\boost\boost_1_65_1\stage_x64\lib
set OPENSSL_ROOT_DIR=C:\Tools\openssl-64
SET PATH=%PATH%;%BOOST_LIBRARYDIR%
At this point you should be able to create simple CMake projects that can find and use OpenSSL and Boost (including stuff like coroutines).
As painful as it is, the result is worth it. Here is what some asynchronous client code looks like using the Boost Beast library (it's header only so no nasty build step required).
boost::asio::spawn(ios, [&](boost::asio::yield_context yield)
{
tcp::resolver resolver{ios};
ssl::stream<tcp::socket> stream{ios, ctx};
{
auto const begin = resolver.async_resolve(
tcp::resolver::query{ host, port }, yield);
tcp::resolver::iterator end;
boost::asio::async_connect(
stream.next_layer(), begin, end, yield);
}
stream.async_handshake(ssl::stream_base::client, yield);
http::request<http::string_body> req{http::verb::get, target, version};
req.set(http::field::host, host);
req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
http::async_write(stream, req, yield);
boost::beast::flat_buffer b;
http::response<http::dynamic_body> res;
http::async_read(stream, b, res, yield);
std::cout << res << std::endl;
boost::system::error_code ec;
// This is currently failing every single time due to this bug:
// https://svn.boost.org/trac10/ticket/12710
stream.async_shutdown(yield[ec]);
if(ec == boost::asio::error::eof) {
ec.assign(0, ec.category());
} else {
boost::asio::detail::throw_error(ec);
}
});
(Note: I stole this from Vinnie Falco's example code, but I put the coroutine function into a lambda and rely on exceptions rather than error checking to make the code look nicer :)).
I think it's further proof that coroutines are the sexiest language feature ever- everyplace you see “yield” getting passed in would normally require a call to a separate function in classic Boost ASIO style, but here the coroutine yields, bringing the same advantages but without the fuss.