既存のC ++アプリケーションにWeb UIを実装するさまざまな方法の評価の一環として、
Fastcgi Containerフレームワークは、
Habréでよく知られている
Fastcgi Daemonフレームワークに基づいて作成されました。
プロトタイプのすべての機能を維持しながら、新しいフレームワークと主な違いは次のとおりです。
- C ++ 11で書き直されたフレームワーク
- フィルターのサポートを追加
- クライアントの認証と承認のサポートが追加されました
- セッションサポートの追加
- サーブレットのサポートを追加(元のフレームワークからのリクエストハンドラの拡張)
- JSPのようなページからC ++サーブレットを生成するページコンパイラを追加しました
プロトタイプの機能と実装の詳細は、Habréで数回議論されました(たとえば、
こちら )。 この記事では、新しい
Fastcgi Containerフレームワークの機能について説明します。
C ++ 11の使用
Fastcgi Daemonプロトタイプフレームワークは、Boostライブラリを広範囲に使用します。 Boostの使用を新しい標準構造に置き換えて、派生フレームワークをC ++ 11で書き直すことが決定されました。 例外はBoost.Anyライブラリで、C ++ 11には同等のライブラリがありません。 必要な機能は、
MNMLSTCコアライブラリを使用して追加されました。
フィルター
FastCGIプロトコルは、対応する機能を整理するためのFilterおよびAuthorizerロールを提供しますが、一般的な実装(たとえば、Apache HTTPDおよびNGINXのモジュール)はResponderロールのみをサポートします。
その結果、Fastcgi Containerにフィルターサポートが直接追加されました。
アプリケーションでは、
fastcgi::Filter
クラスの拡張としてフィルターが作成され
fastcgi::Filter
。
class Filter { public: Filter(); virtual ~Filter(); Filter(const Filter&) = delete; Filter& operator=(const Filter&) = delete; virtual void onThreadStart(); virtual void doFilter(Request *req, HandlerContext *context, std::function<void(Request *req, HandlerContext *context)> next) = 0; };
コンテナへのロードは、アプリケーションの他のコンポーネントと同様に動的に実行されます。
<modules> <module name="example" path="./example.so"/> ... </modules> <components> <component name="example_filter_1" type="example:filter1"> <logger>daemon-logger</logger> </component> <component name="example_filter_2" type="example:filter2"> <logger>daemon-logger</logger> </component> ... </components>
フィルタは、特定のアプリケーションに対してグローバルにすることができます。
<handlers urlPrefix="/myapp"> <filter> <component name="example_filter_1"/> </filter> ... </handlers>
または、指定された正規表現に一致するURLを持つリクエストのグループを処理するように設計されています。
<handlers urlPrefix="/myapp"> <filter url="/.*"> <component name="example_filter_2"/> </filter> ... </handlers>
コンテナーが現在の要求に対して複数のフィルターを検出した場合、それらは構成ファイルに追加されたのと同じ順序で実行されます。
制御を次のフィルター(またはフィルターがキュー内の唯一のものまたは最後のものである場合はターゲットハンドラー/サーブレット)に順番に制御を移すために、現在のフィルターはパラメーターのリストを通じて渡された
next
関数を呼び出します。 チェーンを中断するために、フィルターは
next
関数を呼び出さずに制御を返すことができます。
一般に、各フィルターは2回制御を受け取ります。次のフィルターまたはターゲットハンドラー/サーブレットに制御を移す前と、次のフィルターまたはハンドラー/サーブレットが動作を終了した後です。 ボディとヘッダーがまだクライアントに送信されている場合、フィルターは、ターゲットハンドラー/サーブレットの作業の前後に、応答本文および/またはヘッダー(HTTPヘッダー)を変更できます。
認証
認証は特別なフィルターによって実行されます。 Fastcgi Containerには、
Basic access authentication
、
Form authentication
、および
Delegated authentication
認証タイプ用のフィルターが含まれています。
これらのタイプの最後は、認証プロセスをHTTPサーバーに委任し、そこから標準CGI変数
REMOTE_USER
として渡されたユーザー識別子を期待します。
他の2つのタイプは、提供された
Security Realm
を使用して認証します。
従来のフィルターの場合と同様に、コンテナーへのロードは動的に実行されます。
<modules> <module name="auth" path="/usr/local/lib64/fastcgi3/fastcgi3-authenticator.so"/> ... </modules> <components> <component name="form_authenticator" type="auth:form-authenticator"> <form-page>/login</form-page> <realm>example_realm</realm> <logger>daemon-logger</logger> <store-request>true</store-request> </component> <component name="basic_authenticator" type="auth:basic-authenticator"> <realm>example_realm</realm> <logger>daemon-logger</logger> </component> <component name="delegated_authenticator" type="auth:delegated-authenticator"> <realm>example_realm</realm> <logger>daemon-logger</logger> </component> ... </components>
通常、認証フィルターはフィルターチェーンの最初に示されます。
<handlers urlPrefix="/myapp"> <filter url="/.*"> <component name="form_authenticator"/> </filter> ... </handlers>
認証フィルターを使用するには、
Security Realm
必要です。これは、
fastcgi::security::Realm
クラスの拡張としてアプリケーションに実装する必要があります。
class Realm : public fastcgi::Component { public: Realm(std::shared_ptr<fastcgi::ComponentContext> context); virtual ~Realm(); virtual void onLoad() override; virtual void onUnload() override; virtual std::shared_ptr<Subject> authenticate(const std::string& username, const std::string& credentials); virtual std::shared_ptr<Subject> getSubject(const std::string& username); const std::string& getName() const; protected: std::string name_; std::shared_ptr<fastcgi::Logger> logger_; };
構成ファイルに直接ユーザーのリストを含む
Security Realm
簡単な実装の例:
セキュリティレルム class ExampleRealm : virtual public fastcgi::security::Realm { public: ExampleRealm(std::shared_ptr<fastcgi::ComponentContext> context); virtual ~ExampleRealm(); virtual void onLoad() override; virtual void onUnload() override; virtual std::shared_ptr<fastcgi::security::Subject> authenticate(const std::string& username, const std::string& credentials) override; virtual std::shared_ptr<fastcgi::security::Subject> getSubject(const std::string& username) override; private: std::unordered_map<std::string, std::shared_ptr<UserData>> users_; }; ExampleRealm::ExampleRealm(std::shared_ptr<fastcgi::ComponentContext> context) : fastcgi::security::Realm(context) { const fastcgi::Config *config = context->getConfig(); const std::string componentXPath = context->getComponentXPath(); std::vector<std::string> users; config->subKeys(componentXPath+"/users/user[count(@name)=1]", users); for (auto& u : users) { std::string username = config->asString(u + "/@name", ""); std::shared_ptr<UserData> data = std::make_shared<UserData>(); data->password = config->asString(u + "/@password", ""); std::vector<std::string> roles; config->subKeys(u+"/role[count(@name)=1]", roles); for (auto& r : roles) { data->roles.push_back(config->asString(r + "/@name", "")); } users_.insert({username, std::move(data)}); } } ExampleRealm::~ExampleRealm() { ; } void ExampleRealm::onLoad() { fastcgi::security::Realm::onLoad(); } void ExampleRealm::onUnload() { fastcgi::security::Realm::onUnload(); } std::shared_ptr<fastcgi::security::Subject> ExampleRealm::authenticate(const std::string& username, const std::string& credentials) { std::shared_ptr<fastcgi::security::Subject> subject; auto it = users_.find(username); if (users_.end()!=it && it->second && credentials==it->second->password) { subject = std::make_shared<fastcgi::security::Subject>(); for (auto &r : it->second->roles) { subject->setPrincipal(std::make_shared<fastcgi::security::Principal>(r)); } subject->setReadOnly(); } return subject; } std::shared_ptr<fastcgi::security::Subject> ExampleRealm::getSubject(const std::string& username) { std::shared_ptr<fastcgi::security::Subject> subject; auto it = users_.find(username); if (users_.end()!=it && it->second) { subject = std::make_shared<fastcgi::security::Subject>(); for (auto &r : it->second->roles) { subject->setPrincipal(std::make_shared<fastcgi::security::Principal>(r)); } subject->setReadOnly(); } return subject; }
コンテナにロードすることは、アプリケーションの他のコンポーネントをロードすることに似ています。
<modules> <module name="example" path="./example.so"/> ... </modules> <components> <component name="example_realm" type="example:example-realm"> <name>Example Realm</name> <logger>daemon-logger</logger> <users> <user name="test1" password="1234"> <role name="ROLE1"/> <role name="ROLE2"/> <role name="ROLE3"/> </user> <user name="test2" password="5678"> <role name="ROLE1"/> <role name="ROLE4"/> </user> </users> </component> ... </components>
適切に操作するには、
Form Authentication
フィルターでセッションサポートをアクティブにする必要があります。 同時に、セッションは認証されていないクライアントからの最初のリクエストを保存するために使用されます。
ログイン
宣言的な承認の場合、構成ファイルの
<security-constraints>
要素が使用されます。
<security-constraints> <constraint url=".*" role="ROLE1"/> <constraint url="/servlet" role="ROLE2"/> </security-constraints>
承認はプログラムで実行できます。 このために、
fastcgi::Request
および
fastcgi::HttpRequest
クラスはメソッドを提供します:
std::shared_ptr<security::Subject> Request::getSubject() const; bool Request::isUserInRole(const std::string& roleName) const; template<class T> bool Request::isUserInRole(const std::string &roleName) { return getSubject()->hasPrincipal<T>(roleName); }
クライアントが認証されない場合、
getSubject()
メソッドは、空のロールセットを持つ匿名オブジェクトへのポインターを返し、次のメソッドが呼び出されたときに
true
を返し
true
。
bool security::Subject::isAnonymous() const;
セッション
コンテナは、
Simple Session Manager
実装を提供します。 有効にするには、構成ファイルに次を追加します。
<modules> <module name="manager" path="/usr/local/lib64/fastcgi3/fastcgi3-session-manager.so"/> ... </modules> <components> <component name="session-manager" type="manager:simple-session-manager"> <logger>daemon-logger</logger> </component> ... </components> <session attach="true" component="session-manager"> <timeout>30</timeout> </session>
現在のセッションにアクセスするために、
fastcgi::Request
クラスはメソッドを提供します:
std::shared_ptr<Session> Request::getSession();
とりわけ、
fastcgi::Session
クラスは以下のメソッドを提供します:
virtual void setAttribute(const std::string &name, const core::any &value); virtual core::any getAttribute(const std::string &name) const; virtual bool hasAttribute(const std::string &name) const; virtual void removeAttribute(const std::string& name); virtual void removeAllAttributes(); std::type_info const& type(const std::string &name) const; std::size_t addListener(ListenerType f); void removeListener(std::size_t index);
Simple Session Manager
はコンテナーのクラスターをサポートしていないため、ロードバランサーで複数のコンテナーを使用する場合は、スティッキーセッションモードを構成する必要があります。
一般に、セッションソリューションは適切にスケーリングされないため、高負荷下で高いパフォーマンスが期待されるシステムでは、セッションの使用を避ける必要があります。
サーブレット
fastcgi::Request
および
fastcgi::Handler
クラスに加えて、コンテナは
fastcgi::HttpRequest
、
fastcgi::HttpResponse
および
fastcgi::Servlet
ます。
アプリケーションでは、「古い」クラスと「新しい」クラスの両方を使用できます。
C ++サーバーページおよびページコンパイラ
Page Compilerは、
POCOプロジェクトのフォークであり、特別なディレクティブ(C ++サーバーページ、CPSP)を含むHTMLページをサーブレットに変換するように設計されています。
簡単なC ++サーバーページの例:
<%@ page class="TimeHandler" %> <%@ component name="TestServlet" %> <%! #include <chrono> %> <% auto p = std::chrono::system_clock::now(); auto t = std::chrono::system_clock::to_time_t(p); %> <html> <head> <title>Time Sample</title> </head> <body> <h1>Time Sample</h1> <p><%= std::ctime(&t) %></p> </body> </html>
ディレクティブの詳細な説明は
、GitHubプロジェクトで入手できます。