11
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

boost.asioでSSL

Last updated at Posted at 2017-02-01

とりあえずサンプルプロジェクトつくった。HTTPやHTTPSのデータをクローリングしようと思ったが
めんどくさくて力尽きた。需要なさそうだし
https://github.com/YukiMiyatake/AsyncBotCrawler

簡単なのでさらっといきます!

boost.asioのSSL概要

内部でOpenSSLを使っているので、libeay32.lib、ssleay32.libが必要
(MacやLinuxでは名前違うかもしれない)

基本的に普通のTCPと同じように通信ができる! すごい!

違いは大雑把に下記

1、

boost::asio::ip::tcp::socket を
boost::asio::ssl::stream<boost::asio::ip::tcp::socket> に変更

2、boost::asio::ssl::context を作成し load_verify_fileで証明書を読み込む(Verifyしない場合は読まなくて良い)
3、上記のssl contextを socketのコンストラクタに渡す
4、socketのset_verify_mode、set_verify_callback を設定し、verifyのコールバックを待つ

あとは通常Socketと同じように通信できる

#サンプルコード
Boost.asioにサンプルあります! このままでOKです!
http://www.boost.org/doc/libs/1_62_0/doc/html/boost_asio/example/cpp03/ssl/client.cpp
http://www.boost.org/doc/libs/1_62_0/doc/html/boost_asio/example/cpp03/ssl/server.cpp

##サンプルの注意点
非同期ゆえに、オブジェクトのスコープを気にする必要がある
例えば上記 clientの作成部を 下記のように {}を入れてスコープつけるだけで 落ちる

    {
        client c(io_service, ctx, iterator);
    }

理由は、cのコンストラクタを実行した時、非同期なので実行終了を待たずにコンストラクタを抜ける
その後 スコープを抜けて cはデストラクトされる
その後 非同期処理がおわりコールバックを呼んだ時には cが消えているため thisポインタがなく落ちる

解決法は 非同期処理が終了するまで cを生存させることである。例えば リストに入れてコールバックもらったらdeleteするとかもあるが
asioでスマートな解決法は shared_ptrを使い管理する事である
具体的には コールバック関数を呼ぶときに、shared_from_this を使い shared_ptrをキャプチャする

clientのサンプルでは、 cのコンストラクト時を下記に変え shared_ptrにする

 make_shared<client>(io_service, ctx, iterator);

そして、clientクラスに shared_from_thisを使えるように enable_shared_from_thisをpublic継承する

class client : public std::enable_shared_from_this<client>

最後に、非同期のコールバック関数に share_from_thisを使い shared_ptrをキャプチャする
サンプルは C++03なので bindを使ってキャプチャを行っている
具体的には bindでthisを渡している部分をすべて shared_from_this() にすればよい

resolver_.async_resolve(query,
    boost::bind(&httpx_client::handle_resolve, shared_from_this(),
        boost::asio::placeholders::error,
        boost::asio::placeholders::iterator));

ちなみに上記はC++03時点の書き方で、boost::bindを使って関数オブジェクトを作り shared_ptrをキャプチャしているが
C++11以降では ラムダ関数を使いキャプチャすることが出来る
上記より 少しきれいなコードになると思うので 覚えておくと良い

auto self(shared_from_this());
resolver_.async_resolve(query,
		[this,self](const boost::system::error_code& err,tcp::resolver::iterator endpoint_iterator)
		{
   		    handle_resolve(err, endpoint_iterator);
		}
);

#サンプル
HTTPSを使って HTMLをコンソール出力します

今回はVerifyが不要なので、証明書のロードやVerifyチェックを省略します
HTTPのGetリクエストを投げて、レスポンスをコンソール出力

#include <cstdlib>
#include <iostream>
#include <memory>
#include <boost/bind.hpp>
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>

class client: public std::enable_shared_from_this<client>
{
public:
	client(boost::asio::io_service& io_service,
		boost::asio::ssl::context& context,
		boost::asio::ip::tcp::resolver::iterator endpoint_iterator)
		: socket_(io_service, context), endpoint_iterator_(endpoint_iterator)
	{
		// constructorでは shared_from_thisが取れないので、実際の実行は下記で行う
	}

	void run() {

		boost::asio::async_connect(socket_.lowest_layer(), endpoint_iterator_,
			boost::bind(&client::handle_connect, shared_from_this(),
				boost::asio::placeholders::error));
	}

	void handle_connect(const boost::system::error_code& error)
	{
		if (!error)
		{
			socket_.async_handshake(boost::asio::ssl::stream_base::client,
				boost::bind(&client::handle_handshake, shared_from_this(),
					boost::asio::placeholders::error));
		}
		else
		{
			std::cout << "Connect failed: " << error.message() << "\n";
		}
	}

	void handle_handshake(const boost::system::error_code& error)
	{
		if (!error)
		{
			std::ostream request_stream(&request_);
			{
				request_stream << "GET /"  << " HTTP/1.0\r\n";
				request_stream << "Host: www.google.co.jp" << "\r\n";
				request_stream << "Accept: */*\r\n";

				request_stream << "Connection: close\r\n\r\n";
			}
			boost::asio::async_write(socket_, request_,
				boost::bind(&client::handle_write_request, shared_from_this(),
					boost::asio::placeholders::error));

		}
		else
		{
			std::cout << "Handshake failed: " << error.message() << "\n";
		}
	}

	void handle_write_request(const boost::system::error_code& err)
	{
		if (!err)
		{
			boost::asio::async_read_until(socket_, response_, "\r\n",
				boost::bind(&client::handle_read_status_line, shared_from_this(),
					boost::asio::placeholders::error));
		}
		else
		{
			std::cerr << "Error: " << err.message() << "\n";
		}
	}

	void handle_read_status_line(const boost::system::error_code& err)
	{
		if (!err)
		{
			std::istream response_stream(&response_);
			std::string http_version;
			response_stream >> http_version;
			response_stream >> status_code_;
			std::string status_message;
			std::getline(response_stream, status_message);
			if (!response_stream || http_version.substr(0, 5) != "HTTP/")
			{
				std::cerr << "Invalid response\n";
				return;
			}
			if (status_code_ != 200)
			{
				std::cout << "Response returned with status code ";
				std::cout << status_code_ << "\n";
				return;
			}

			// Read the response headers, which are terminated by a blank line.
			boost::asio::async_read_until(socket_, response_, "\r\n\r\n",
				boost::bind(&client::handle_read_headers, shared_from_this(),
					boost::asio::placeholders::error));
		}
		else
		{
			std::cerr << "Error: " << err << "\n";
		}
	}


	void handle_read_headers(const boost::system::error_code& err)
	{
		if (!err)
		{
			// 
			std::istream response_stream(&response_);
			std::string header;
			while (std::getline(response_stream, header) && header != "\r")
			{
				message_header_ << header << "\n";
			}

			boost::asio::async_read(socket_, response_,
				boost::asio::transfer_at_least(1),
				boost::bind(&client::handle_read_content, shared_from_this(),
					boost::asio::placeholders::error));
		}
		else
		{
			std::cerr << "Error: " << err << "\n";
		}
	}

	void handle_read_content(const boost::system::error_code& err)
	{
		//		if (err == boost::asio::error::eof)
		if (err)
		{
			auto  self(shared_from_this());

			message_body_ << &response_;

			
			// 終了したのでここで表示。実際はコールバックを呼ぶことが多い
//			[this, self]() {callback_(*this); }();
			std::cout << status_code_ << std::endl << message_header_.str() << std::endl << message_body_.str() << std::endl;

		}
		else if (!err)
		{
			message_body_ << &response_;

			response_.consume(response_.size());

			boost::asio::async_read(socket_, response_,
				boost::asio::transfer_at_least(1),
				boost::bind(&client::handle_read_content, shared_from_this(),
					boost::asio::placeholders::error));

		}
	}



private:
	boost::asio::ssl::stream<boost::asio::ip::tcp::socket> socket_;
	boost::asio::ip::tcp::resolver::iterator endpoint_iterator_;
	unsigned int status_code_ = 0;
	std::ostringstream message_header_;
	std::ostringstream message_body_;
	boost::asio::streambuf request_;
	boost::asio::streambuf response_;
};

int main(int argc, char* argv[])
{
	try
	{

		boost::asio::io_service io_service;

		boost::asio::ip::tcp::resolver resolver(io_service);
		boost::asio::ip::tcp::resolver::query query("www.google.co.jp", "https");
		boost::asio::ip::tcp::resolver::iterator iterator = resolver.resolve(query);

		boost::asio::ssl::context ctx(boost::asio::ssl::context::sslv23);

		std::make_shared<client>(io_service, ctx, iterator)->run();

		io_service.run();
	}
	catch (std::exception& e)
	{
		std::cerr << "Exception: " << e.what() << "\n";
	}

	return 0;
}


11
11
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
11
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?