Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

はじめに

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", "/");
UedaTakeyuki
Mais à l’instant même où la gorgée mêlée des miettes du gâteau toucha mon palais, je tressaillis, attentif à ce qui se passait d’extraordinaire en moi. Marcel Proust - À la recherche du temps perdu
https://atelierueda.uedasoft.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away