見出し画像

[Drogon]でプロジェクトを作る

前回の記事の中で、drogonフレームワークのインストールについて解説しました。

今回は、drogonフレームワークを使用して実際にプロジェクトを作成し、examplesの中にあるhelloworldと同じものを作成、動作させていきたいと思います。

drogon_ctl

Drogonフレームワークのビルドが成功していれば、drogon_ctl コマンドが使用可能となっているはずです。入っているかどうかわからない場合は以下のコマンドで確認しておきましょう。

$ which drogon_ctl

デフォルトでは以下のディレクトリの中に、別名ショートカットである dg_ctl と共にインストールされているかと思います。

$ which drogon_ctl
/usr/local/bin/gd_ctl
$ ls /usr/local/bin/ -l
total 3420
lrwxrwxrwx 1 root root      12 May 31 03:29 dg_ctl -> ./drogon_ctl
-rwxr-xr-x 1 root root 3501912 May 31 05:56 drogon_ctl

Drogonフレームワークを使用した開発はこのコマンド無しではできませんので、もしもインストールがうまくいっていないようであれば前回の記事に従ってインストールをやり直してみてください。

drogon_ctl の使用方法は、大まかにはオプションなしでこのコマンドを実行することで確認できます。

$ drogon_ctl
usage: drogon_ctl [-v | --version] [-h | --help] <command> [<args>]
commands list:
version                 display version of this tool
help                    display this message
press                   Do stress testing(Use 'drogon_ctl help press' for more information)
create                  create some source files(Use 'drogon_ctl help create' for more information)

-v または --version はバージョン表示ですが、前回の記事の中でも実行してみたとおり、使用可能なモジュールなどを一覧してみることができます。
-h または --help はオプションなしで実行した場合と同様、この画面を表示します。また、サブコマンドを指定するとそれぞれの使用方法についても参照可能です。
重要になってくるのが、第二センテンスの <command> と第三センテンスの <args> です。
<command> は drgon_clt コマンドを使用して作成する何某を指定するサブコマンドですが、drogon_ctl -h の実行結果からわかる通り、ここに指定することができるのはpressとcreateです。
press ストレステストの実行
create ソースコードの作成とありますが、プロジェクトの作成にも使用します。
<args> はpress サブコマンドと、createサブコマンドに与える引数を指定します。指定できる内容はそれぞれ違います。

実際に指定できる値を以下のコマンドで見てみましょう。

$ drogon_ctl -h create
$ drogon_ctl -h create
Use create command to create some source files of drogon webapp

Usage:drogon_ctl create <view|controller|filter|project|model> [-options] <object name>

drogon_ctl create view <csp file name> [-o <output path>] [-n <namespace>]|[--path-to-namespace]//create HttpView source files from csp files

drogon_ctl create controller [-s] <[namespace::]class_name> //create HttpSimpleController source files

drogon_ctl create controller -h <[namespace::]class_name> //create HttpController source files

drogon_ctl create controller -w <[namespace::]class_name> //create WebSocketController source files

drogon_ctl create controller -r <[namespace::]class_name> [--resource=...]//create restful controller source files

drogon_ctl create filter <[namespace::]class_name> //create a filter named class_name

drogon_ctl create plugin <[namespace::]class_name> //create a plugin named class_name

drogon_ctl create project <project_name> //create a project named project_name

drogon_ctl create model <model_path> [--table=<table_name>] [-f]//create model classes in model_path

create で指定できるのは
view
controller
filter
project
model
 
の五つだと判ります。

project 以外のサブコマンドは、オプションに応じてスキャフォールド(足場、最低限のソースコードのテンプレート)が記載されたソースコードを作成してくれます。
今回の記事では、これらのうち、project controller view の三つのサブコマンドを使用します。

pressの方も見ていきましょう。

$ drogon_ctl -h press
Use press command to do stress testing
Usage:drogon_ctl press <options> <url>
-n num    number of requests(default : 1)
-t num    number of threads(default : 1)
-c num    concurrent connections(default : 1)
-q        no progress indication(default: no)
example: drogon_ctl press -n 10000 -c 100 -t 4 -q http://localhost:8080/index.html

指定されたURLに対してストレステスト(実際に接続やリクエストの負荷をかけてサーバの動作を確認するテスト)を実施することのできるコマンドだと判ります。

こちらはサブコマンド無しでオプションだけでの指定です。

-n はリクエスト数、実際にサーバに処理させたいリクエストを何回投げるかの設定です。
-t はスレッド数、同時に処理するスレッド数です、公式ドキュメントによると、動作しているCPUの数で設定するとパフォーマンスが最大となるそうです。
-c 同時接続数、対象urlに対して同時に何人が接続するかの想定を設定します。
-q テストの進捗状況を表示しないオプションです。

コマンドとして提供されているので、JenkinsなどのCIサーバと連携させて、テストを自動化させるのにも向いていますね。

ここでは使用する分だけのざっくりとした内容をまとめていますので、より詳しい内容が必要な方は公式ドキュメントをご覧ください。

プロジェクトの作成

それでは、Drogonフレームワークを使用してサービスを立ち上げる手順に入っていきましょう。

まず初めにすることは、Projectの作成です。
これには以下のコマンドを使用します。

$ drogon_ctl create my_first_project

コピー&ペーストで実行できるよう、適当に名前を付けましたが、任意のプロジェクト名を付ける場合にはmy_first_project を好きなプロジェクト名に書き換えてください。

$ drogon_ctl create project my_first_project
create a project named my_first_project

コマンドの実行に成功するとこのようなメッセージが表示されるとともに、以下のようにプロジェクトディレクトリが作成されます。

$ ls
my_first_project
$ cd my_first_project/
/my_first_project$ ls
build  CMakeLists.txt  config.json  controllers  filters  main.cc  models  plugins  test  views

それぞれのディレクトリの解説については別の記事で解説することにして、この記事では割愛します。
移行の手順は全てプロジェクトディレクトリの中で行います。

コントローラの作成

プロジェクトを作成した後にまず初めにするのがコントローラの作成です。
コントローラはhttpを通じてサーバに届いたリクエストを解析して、それぞれのドメインに割り当てられたアプリケーション内のプログラムを呼び出す役割をします。

コントローラの作成には以下のコマンドを使用します。

$ drogon_ctl create controller my_first_controller

drogon_ctl コマンドは、実行されたカレントディレクトリでコントローラのソースコードを作成します。

Drogonプロジェクトのビルドはプロジェクトディレクトリ直下のCMakeLists.txtによって設定された手順でプロジェクトをビルドするのですが、この中でコントローラのソースディレクトリがcontrollersとして設定されています。

プロジェクトディレクトリ直下に置いたり、自身でビルドルールを書き換えたりするのであれば必要ありませんが、
大抵の場合はプロジェクトのディレクトリ構成に従って以下のようにcontrollersディレクトリで作成するのが望ましいでしょう。

/my_first_project/$ cd controllers/
/my_first_project/controllers$ ls
/my_first_project/controllers$ drogon_ctl create controller my_first_controller
Create a http simple controller: my_first_controller
/my_first_project/controllers$ ls
my_first_controller.cc  my_first_controller.h

実際に作成されたコントローラの内容を見ると、以下のようなソースコードが作成されています。

my_first_controller.h

 #pragma  once
 #include  <drogon/HttpSimpleController.h>

using namespace drogon;
class my_first_controller : public drogon::HttpSimpleController<my_first_controller>
{
    public:
    virtual void asyncHandleHttpRequest(const HttpRequestPtr& req, std::function<void (const HttpResponsePtr &)> &&callback) override;
    PATH_LIST_BEGIN
    // list path definitions here;
    // PATH_ADD("/path", "filter1", "filter2", HttpMethod1, HttpMethod2...);
    PATH_LIST_END
};

my_first_controller.cc

 #include  "my_first_controller.h"

void my_first_controller::asyncHandleHttpRequest(const HttpRequestPtr& req, std::function<void (const HttpResponsePtr &)> &&callback)
{
    // write your application logic here
}

単純なRESTfulAPIを実装するだけであれば、my_first_controller.hにルーティングを設定し、my_first_controller.cc にJsonをレスポンスするコードを書いてやれば、実現することができます。

ビューの作成

次にViewを作成します。

ViewはControllerが作成したデータを使用して、フロントサイドレンダリングで動的にHTMLを生成する部分です。

DrogonではCSP(C++ Server Page)というHTMLの中に要素としてC++のコードを実装できる独自形式のファイルによって、どのようなwebページを表示するか定義し、それを基にC++のソースコードを生成します。

CSPファイルに関する解説はまた別で行います。

まずはプロジェクトディレクトリ内にあるviewディレクトリに、以下の内容を書き込んだHelloView.cspを作成しましょう。

<!DOCTYPE html>
<html>
<%c++
	// name 変数にパラメータからnameで識別される値を指定の型で取得
    auto viewName=@@.get<std::string>("name");
	bool nameIsEmpty = viewName == "";
	if (nameIsEmpty)
		viewName = "anonymous";
	auto message = "Hello, " + viewName + " from a CSP template";
%>
<head>
    <meta charset="UTF-8">
    <title>{% viewName %}</title>
</head>
<body>
    <%c++ $$<<message; %>
	<%c++
	if (nameIsEmpty)
	{
		$$ << "<br>"
			<< "You can revisit the same page and append ?request_name=<i>your_name</i> to change the name";
	}
	%>
</body>
</html>

DrogonにおけるViewの作成はこれで完了です。
内部的にはこのCSPファイルからC++のクラスを生成した上で、そのソースコードでビルドを行っていますが、通常Viewにこれ以外のファイルで定義を行う必要はありません。

コントローラとビューをつなぐ

ここまで生成したらコントローラとビューの連携を書いていきます。

まずはviewへのルーティングを、コントローラのヘッダに定義します。

my_first_controller.h

 #pragma  once
 #include  <drogon/HttpSimpleController.h>

using namespace drogon;
class my_first_controller : public drogon::HttpSimpleController<my_first_controller>
{
    public:
    virtual void asyncHandleHttpRequest(
    const HttpRequestPtr& req,
    std::function<void (const HttpResponsePtr &)> &&callback) override;
    // ルートへのアクセスに対して、Getリクエストへのレスポンスを定義します。
    PATH_LIST_BEGIN
    PATH_ADD( "/", Get);
    PATH_LIST_END
};

Drogonフレームワークにおいて、HTTPリクエストの内容への参照は自動生成されるメソッド asyncHandleHttpRequest の第一引数、 HttpRequestPtr& req に渡ってきます。
名前からわかる通り、これはHttpRequestオブジェクトに対するポインタです。

何気ない内容ではあるのですが、これこそが C++ を利用して開発されている美点で、HttpRequestによってわたってきた情報がサーバ内でメモリに展開された後は、ポインタによって直接その情報を参照するため、情報の複製などのリソースを消費する処理が行われないのです。
これによりコンピュータのリソース、特にメモリの使用を抑制することが出来るわけです。

コントローラでHTTPリクエストの内容を展開する際には、渡ってきたこのポインタを介して参照することができます。

my_first_controller.cc

 #include  "my_first_controller.h"

void my_first_controller::asyncHandleHttpRequest(const HttpRequestPtr& req, std::function<void (const HttpResponsePtr &)> &&callback)
{
    // viewに渡すデータはHttpViewData型に格納します
    drogon::HttpViewData viewData;
    // HttpViewData へのデータの格納は、key - valueのペアで行います。
    // ここでは name というキーワードに対して、リクエストで渡ってきた request_name 引数の値を取得し設定しています。
    viewData.insert(
        "name", 
        req->getParameter("request_name")
        );

    //"HelloView.csp"から構築されたviewへの呼び出しを作成し、コールバックに登録します。
    callback(
        drogon::HttpResponse::newHttpViewResponse("HelloView.csp", viewData)
        );
}

これでコントローラとビューをつなぐコーディングは完了です。

プロジェクトをビルドする

一通りのコーディングが済んだら、プロジェクトをビルドして実行、アクセスしてみます。

ビルド方法は プロジェクトディレクトリ/build ディレクトリに移動して実行します。
大抵の場合はプロジェクトディレクトリで以下のコマンドを打っていくだけです。

/my_first_project$ cd build/
/my_first_project/build$ cmake ..
/my_first_project/build$ make

プロジェクトがビルドされ、以下のように100%まで完了すればプロジェクトのビルドは完了です。

Scanning dependencies of target my_first_project
[ 14%] Building CXX object CMakeFiles/my_first_project.dir/controllers/my_first_controller.cc.o
[ 28%] Linking CXX executable my_first_project
[ 71%] Built target my_first_project
[100%] Built target my_first_project_test

アクセスしてみる

プロジェクトのビルドが成功すると、build ディレクトリにプロジェクト名そのままの実行ファイルが作成されます。
この実行ファイルを叩くことでサーバが立ち上がり、作成したアプリケーションへのアクセスが可能になります。

試してみましょう。

まずは以下のコマンドでアプリケーションを起動します。

/my_first_project/build$ ./my_first_project

次にブラウザを開いて、アドレスバーに以下のアドレスを入力します。

localhost:80

以下のような画面が表示されれば成功です。

パラメータなしでの実行結果

ここではパラメータを与えずに実行したため、View の中に記載されたデフォルトのメッセージが表示されます。

次に、実際にきちんとコントローラとビューが繋がっているか、パラメータを与えて実行してみましょう

localhost:80/?request_name=Mr.Tanutaro

以下のような画面が表示されるかと思います。

パラメータ有での実行結果

しっかりとリクエストパラメータとして名前が渡っていることがわかりました。

さいごに

今回はここまで。
大半のコードはdrgon_ctlによって生成されますので、開発言語がC++であるにもかかわらず開発難易度はそれほど高くないように思います。

ただ、Drogonはリリースから間もないということもあり、Vue.jsなどと違って見た目をどうにかするフレームワークが(探せばあるのかもしれませんが)ないので、見目麗しく書くのはなかなかコツがいりそうです。

とはいえ、C++でMVCという特徴は、前回書いたIoTへの利用であったり、旧来のシステムのコードをそのまま利用しつつ省コストでRESTfulAPI化するには、非常に適したものです。

私のような社内SEとして既存の資産が多くある企業に勤めている方であれば確実に何かの役に立つかと思いますので、皆様もぜひ試してみてください。

参考:CSPから生成されるのはどんなコード?

このCSPファイルを読み込んでviewを構築します。 viewを構築するには、create viewサブコマンドでCSPファイルを指定して構築します。

/my_first_project$ cd views/
/my_first_project/views$ drogon_ctl create view HelloView.csp
create view:HelloView.csp
create HttpView Class file by HelloView.csp
className=HelloView

HelloView.h

//this file is generated by program automatically,don't modify it! #include  <drogon/DrTemplate.h>
class HelloView:public drogon::DrTemplate<HelloView>
{
    public:
    HelloView(){};
    virtual ~HelloView(){};
    virtual std::string genText(const drogon::DrTemplateData &) override;
};

地味にCRTPでインターフェースを定義しておる……良き。

HelloView.cc

//this file is generated by program(drogon_ctl) automatically,don't modify it! #include  "HelloView.h" #include  <drogon/utils/OStringStream.h> #include  <string> #include  <map> #include  <vector> #include  <set> #include  <iostream> #include  <unordered_map> #include  <unordered_set> #include  <algorithm> #include  <list> #include  <deque> #include  <queue>
using namespace drogon;
std::string HelloView::genText(const DrTemplateData& HelloView_view_data)
{
	drogon::OStringStream HelloView_tmp_stream;
	std::string layoutName{""};
	HelloView_tmp_stream << "<!DOCTYPE html>\n";
	HelloView_tmp_stream << "<html>\n";
    auto name=HelloView_view_data.get<std::string>("name");
	bool nameIsEmpty = name == "";
	if (nameIsEmpty)
		name = "anonymous";
	auto message = "Hello, " + name + " from a CSP template";
	HelloView_tmp_stream << "<head>\n";
	HelloView_tmp_stream << "    <meta charset=\"UTF-8\">\n";
	HelloView_tmp_stream << "    <title>";
{
    auto & val=HelloView_view_data["name"];
    if(val.type()==typeid(const char *)){
        HelloView_tmp_stream<<*any_cast<const char *>(&val);
    }else if(val.type()==typeid(std::string)||val.type()==typeid(const std::string)){
        HelloView_tmp_stream<<*any_cast<const std::string>(&val);
    }
}
	HelloView_tmp_stream << "</title>\n";
	HelloView_tmp_stream << "</head>\n";
	HelloView_tmp_stream << "<body>\n";
	HelloView_tmp_stream << "    ";
 HelloView_tmp_stream<<message; 
	HelloView_tmp_stream << "	";
	if (nameIsEmpty)
	{
		HelloView_tmp_stream << "<br>"
			<< "You can revisit the same page and append ?name=<i>your_name</i> to change the name";
	}
	
	HelloView_tmp_stream << "</body>\n";
	HelloView_tmp_stream << "</html>\n";
if(layoutName.empty())
{
std::string ret{std::move(HelloView_tmp_stream.str())};
return ret;
}else
{
auto templ = DrTemplateBase::newTemplate(layoutName);
if(!templ) return "";
HttpViewData data = HelloView_view_data;
auto str = std::move(HelloView_tmp_stream.str());
if(!str.empty() && str[str.length()-1] == '\n') str.resize(str.length()-1);
data[""] = std::move(str);
return templ->genText(data);
}
}

生成されたそれぞれの冒頭のコメントにある通り、viewのソースはCSPファイルを基にdrgon_ctlコマンドが自動で生成するので、開発者がファイルの中身をどうこうする必要はありません。

C++の文字列にHTMLを追記していくような形のコードが出てきますし、あまり見たりいじったりしたいものではないですね。。。

この記事が気に入ったらサポートをしてみませんか?