LoginSignup
2
3

More than 5 years have passed since last update.

Boost.Beast で websocket の handshake に custom request header をつける方法

Last updated at Posted at 2019-02-23

はじめに

Beast の ドキュメント を見ていると、websocket client で handshake をリクエストする時に handshake_ex() を使って Decorator という関数オブジェクトを渡すとリクエストヘッダを追加できるみたいです

こんな例がでてます

ws.handshake_ex("localhost", "/",
    [](request_type& m)
    {
        m.insert(http::field::sec_websocket_protocol, "xmpp;ws-chat");
    });

m.insert の最初の引数は こちら で定義されてる enum class で、リクエストヘッダフィールドの名前がポケモンの一覧なみにたくさん定義されてます
見たことのないのがほとんどで、リクエストヘッダの世界って奥が深かったんだなと自分の知識の浅さを思い知らされます

第一引数でヘッダフィールドを enum class で指定して、第二引数の文字列を設定してくれるみたいで、例えば Authorization とかは

m.insert(boost::beast::http::field::authorization, "Bearer 1234");

でうまくいきました

ここで自然に疑問がわいてきます。enmu で指定するんだったら カスタムヘッダ ってどうしたらいいんでしょ?

正解1

同じ疑問を持つ人はやっぱりいらっしゃっいまして、カスタムヘッダを指定できるようにインターフェースを拡張してよって issue が上がってて、それに対してこれでできるでしょ という回答がまたかっこいい

class set_subprotocols
{
    std::string s_;

public:
    explicit
    set_subprotocols(std::string s)
        : s_(s) {}

    template<bool isRequest, class Body, class Headers>
    void
    operator()(beast::http::message<isRequest, Body, Headers>& m) const
    {
        m.fields.replace("Sec-WebSocket-Protocol", s_);
    }
};

using namespace beast::websocket;

stream<boost::ip::tcp::socket> ws;
...
ws.set_option(decorate(set_subprotocols{"mqtt"}));

早速、こんなコード書いて X-Custome-Id がつくかためしてみます
あ、いま気がついたんだけどカスタムって e いりましたっけ?

sample.cpp
#include <boost/beast/core.hpp>
#include <boost/beast/websocket.hpp>
#include <boost/beast/http.hpp>
#include <boost/asio/connect.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <cstdlib>
#include <iostream>
#include <string>

#include <boost/beast/websocket/ssl.hpp>
#include <boost/asio/ssl.hpp>

#include <boost/beast/websocket/stream.hpp>

using tcp = boost::asio::ip::tcp;               // from <boost/asio/ip/tcp.hpp>
namespace websocket = boost::beast::websocket;  // from <boost/beast/websocket.hpp>

// https://github.com/boostorg/beast/issues/70
class set_subprotocols
{
    std::string s_;

public:
    explicit
    set_subprotocols(std::string s)
        : s_(s) {}

    template<bool isRequest, class Body, class Headers>
    void
    operator()(boost::beast::http::message<isRequest, Body, Headers>& m) const
    {
        m.fields.replace("X-Custome-Id", s_);
    }
};

// Sends a WebSocket message and prints the response
int main(int argc, char** argv)
{
    try
    {
        // The io_context is required for all I/O
        boost::asio::io_context ioc;

        // These objects perform our I/O
        tcp::resolver resolver{ioc};
//        websocket::stream<tcp::socket> ws{ioc};
        boost::asio::ssl::context ctx{boost::asio::ssl::context::sslv23};
        ctx.set_verify_mode(boost::asio::ssl::verify_none);
        websocket::stream<boost::asio::ssl::stream<boost::asio::ip::tcp::socket>> ws{ioc, ctx};


        boost::asio::ip::tcp::endpoint ep{boost::asio::ip::address::from_string("127.0.0.1"), 8888};

        // add custome header X-Custome-Id
        ws.set_option(websocket::decorate(set_subprotocols{"1234"}));

    }
    catch(std::exception const& e)
    {
        std::cerr << "Error: " << e.what() << std::endl;
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}

なんとコンパイル・エラー...

g++-8.1.0 -std=c++17 sample.cpp -o wc -I ../boost_1_69_0 -lstdc++ -lpthread -latomic -lcrypto -lssl
sample.cpp: In function ‘int main(int, char**)’:
sample.cpp:55:34: error: ‘decorate’ is not a member of ‘websocket’
         ws.set_option(websocket::decorate(set_subprotocols{"1234"}));
                                  ^~~~~~~~

decorate が websocket のメンバじゃないって???
じゃ、どこのメンバなのよ???

boost 1.69 の場合

コード読んでもよくわからない(達人のコードはヤワ C++er にはとってもハード)ので諦めて聞いてみたら 5分も待たないうちに回答してもらえてびっくり、なんて親切な人なんだろう
decorate はインターフェース変えたよ ってお茶目なおっしゃられぶりに好感が持てます

なんだかんだで boost 1.69 での正解はこんな感じになりました

sample.cpp
#include <boost/beast/core.hpp>
#include <boost/beast/websocket.hpp>
#include <boost/beast/http.hpp>
#include <boost/asio/connect.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <cstdlib>
#include <iostream>
#include <string>

#include <boost/beast/websocket/ssl.hpp>
#include <boost/asio/ssl.hpp>

#include <boost/beast/websocket/stream.hpp>

using tcp = boost::asio::ip::tcp;               // from <boost/asio/ip/tcp.hpp>
namespace websocket = boost::beast::websocket;  // from <boost/beast/websocket.hpp>

// https://github.com/boostorg/beast/issues/70
class set_subprotocols
{
    std::string s_;

public:
    explicit
    set_subprotocols(std::string s)
        : s_(s) {}

    template<bool isRequest, class Body, class Headers>
    void
    operator()(boost::beast::http::message<isRequest, Body, Headers>& m) const
    {
        m.set("X-Custome-Id", s_);
    }
};

// Sends a WebSocket message and prints the response
int main(int argc, char** argv)
{
    try
    {
        // The io_context is required for all I/O
        boost::asio::io_context ioc;

        // These objects perform our I/O
        tcp::resolver resolver{ioc};
//        websocket::stream<tcp::socket> ws{ioc};
        boost::asio::ssl::context ctx{boost::asio::ssl::context::sslv23};
        ctx.set_verify_mode(boost::asio::ssl::verify_none);
        websocket::stream<boost::asio::ssl::stream<boost::asio::ip::tcp::socket>> ws{ioc, ctx};


        boost::asio::ip::tcp::endpoint ep{boost::asio::ip::address::from_string("127.0.0.1"), 8888};

        // add custome header X-Custome-Id
//        ws.set_option(websocket::decorate(set_subprotocols{"1234"}));

        // connect the underlying TCP/IP socket
        ws.next_layer().next_layer().connect(ep);

        // perform SSL handshake
        ws.next_layer().handshake(boost::asio::ssl::stream_base::client);

        ws.handshake_ex(
            "localhost",
            "/",
            set_subprotocols{"1234"});
    }
    catch(std::exception const& e)
    {
        std::cerr << "Error: " << e.what() << std::endl;
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}

boost 1.70 の場合

さらに、boost 1.70 ではまた変えるからね、との事ですのでこの場を借りて皆様にも御連絡しておきますね
ごめんね、産みの苦しみってもんさ(Sorry about that, growing pains!!!)との事でございます

ws.set_option(websocket::stream_base::decorator(set_subprotocols{"1234"}));
ws.handshake("localhost", "/");
2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3