FastCGIを使用したC / C ++ Webアプリケーション-簡単です

こんにちは

この記事では、FastCGIプロトコルとその操作方法についてお話したいと思います。 プロトコル自体とその実装は1996年に登場したという事実にもかかわらず、このプロトコルの詳細なマニュアルはありません。開発者は独自のライブラリへの参照を作成していません。 しかし、2年前、このプロトコルを使い始めたばかりのとき、「このライブラリの使い方がよくわからない」というフレーズがよく見られました。 私が修正したいのはこの欠点です。マルチスレッドプログラムでのこのプロトコルの使用に関する詳細なガイドと、誰もが使用できるさまざまなパラメーターを選択するための推奨事項を書くことです。



良いニュースは、FastCGIとCGIでのデータのエンコード方法は同じであり、送信方法のみが変更されることです。CGIプログラムが標準のI / Oインターフェイスを使用する場合、FastCGIプログラムはソケットを使用します。 言い換えると、FastCGIを操作するためにライブラリのいくつかの機能を処理するだけで、CGIプログラムを作成した経験を活用するだけで、幸いなことに、多くの例があります。



そのため、この記事では次のことを検討します。

-FastCGIとは何ですか。CGIプロトコルとはどのように違いますか

-Web開発用にすでに多くの言語がある場合にFastCGIが必要な理由

-FastCGIプロトコルの実装は何ですか

-ソケットとは

-FastCGIライブラリの機能の説明

-マルチスレッドFastCGIプログラムの簡単な例

-シンプルなNginxの構成例

残念ながら、初心者にも同等に理解でき、経験豊富なベテランにとっても興味深い記事を書くことは非常に難しいので、可能な限り詳細にすべてのポイントをカバーするようにします。あなたが興味のないセクションを単にスキップすることができます。



FastCGIとは何ですか?



Wikipediaで FastCGIについて読むことができます。 一言で言えば、これはループで実行されるCGIプログラムです。 新しいリクエストごとに通常のCGIプログラムが再起動されると、FastCGIプログラムは順番に処理されるリクエストのキューを使用します。 今、想像してみてください:300から500の同時リクエストが4-8コアサーバーに到着しました。 これらの同じ300〜500回を実行する通常のCGIプログラムが起動されます。 明らかに、プロセスが多すぎる-サーバーは物理的に一度にすべてを処理することはできません。 そのため、プロセッサ時間の量を待つプロセスのキューを取得します。 通常、スケジューラはプロセッサを均等に分散します(この場合、すべてのプロセスの優先順位は同じです)。つまり、リクエストに対して300〜500の「ほぼ準備完了」の応答があります。 どうやら楽観的ではないように聞こえますか? FastCGIプログラムでは、これらの問題はすべて単純な要求キューによって解決されます(つまり、要求の多重化が適用されます)。



PHP、Ruby、Python、Perlなどがすでに存在するのに、なぜFastCGIが必要なのですか?



おそらく主な理由は、コンパイルされたプログラムが解釈されたプログラムよりも速く実行されることです。 たとえば、PHPの場合、APC、eAccelerator、XCacheなど、コードの解釈時間を短縮する一連のアクセラレーターがあります。 しかし、C / C ++の場合、これは単に必要ではありません。

2つ目の留意点は、動的型付けとガベージコレクターが多くのリソースを消費することです。 時々たくさん。 たとえば、PHPの整数配列は 、同じ量のデータに対してC / C ++よりも約18倍のメモリ (さまざまなPHPコンパイルオプションに応じて最大35倍)を占有するため、比較的大きなデータ構造のオーバーヘッドを考慮してください。

第三に、FastCGIプログラムは、異なる要求に共通のデータを保存できます。 たとえば、PHPが毎回リクエストを最初から処理する場合、FastCGIプログラムは、メモリの割り当て、頻繁に使用されるデータのロードなど、最初のリクエストが到着する前でも多くの準備アクションを実行できます。 -明らかに、これはすべて、システム全体のパフォーマンスを向上させることができます。

4つ目はスケーラビリティです。 mod_phpがApache WebサーバーとPHPが同じマシン上にあると想定している場合、FastCGIアプリケーションはTCPソケットを使用できます。 つまり、ネットワークを介して通信する複数のマシンのクラスタ全体を作成できます。 同時に、FastCGIはUnixドメインソケットもサポートしているため、必要に応じて同じマシンでFastCGIアプリケーションとWebサーバーを効率的に実行できます。

5番目はセキュリティです。 信じられないかもしれませんが、Apacheではデフォルトの設定で世界中のすべてのことができます。 たとえば、攻撃者が悪意のあるexploit.php.jpgスクリプトを「無実の画像」を装ってサイトにアップロードし、それをブラウザで開くと、Apacheは悪意のあるphpコードを「正直に」実行します。 おそらく唯一の信頼できる解決策は、ダウンロードされたファイルの名前(この場合はphp、php4、php5、phtmlなど)から潜在的に危険な拡張子をすべて削除または変更することです。 このような手法は、たとえばDrupalで使用されます。アンダースコアがすべての「追加」拡張機能に追加され、exploit.php_.jpgが取得されます。 確かに、システム管理者は追加のファイル拡張子をphpハンドラとして追加できるため、.htmlが突然ひどいセキュリティホールになる可能性があることに注意する必要があります。顧客が好きではなかった。 それでは、FastCGIはセキュリティに関して何を提供しますか? まず、Apacheの代わりにNginx Webサーバーを使用すると、静的ファイルが簡単に配布されます。 ポイント。 言い換えると、exploit.php.jpgファイルはサーバー側で何も処理せずに「現状のまま」提供されるため、悪意のあるスクリプトを起動しても機能しません。 第二に、FastCGIプログラムとWebサーバーは異なるユーザーから動作することができます。つまり、ファイルとフォルダーに対する異なる権限を持ちます。 たとえば、Webサーバーはダウンロードされたファイルのみを読み取ることができます-これは静的データを返すのに十分であり、FastCGIプログラムはダウンロードされたファイルを含むフォルダーの内容を読み取りおよび変更するだけですつまり、悪意のあるコードも実行できないことを意味します。 3番目に、FastCGIプログラムは、Webサーバーのchroot以外のchrootで実行できます。 chroot(ルートディレクトリの変更)自体により、プログラムの権限を厳しく制限できます。つまり、プログラムは指定されたディレクトリ外のファイルにアクセスできないため、システムの全体的なセキュリティを強化できます。



FastCGIをサポートするWebサーバーはどれを選択するのが適切ですか?



要するに、私はNginxを使用しています。 一般に、商用サーバーを含め、FastCGIをサポートするサーバーはかなりありますので、いくつかの代替案を考えてみましょう。

Apacheはおそらく最初に思い浮かぶものですが、Nginxよりも多くのリソースを消費します。 たとえば、10,000の非アクティブなHTTPキープアライブ接続の場合、Nginxは約2.5Mのメモリを消費しますが、これは比較的弱いマシンでも非常に現実的であり、Apacheは新しい接続ごとに新しいスレッドを作成することを余儀なくされるため、10,000スレッドは素晴らしいです。

Lighttpd-このWebサーバーの主な欠点は、すべての要求を単一のスレッドで処理することです。 これは、スケーラビリティに問題がある可能性があることを意味します。最新のプロセッサの4〜8コアすべてを使用することはできません。 2番目-何らかの理由でWebサーバーフローがハングした場合(たとえば、ハードディスクからの応答を長時間待機したため)、サーバー全体が "ハング"します。 言い換えれば、他のすべてのクライアントは、1つの遅い要求のために応答の受信を停止します。

別の候補はチェロキーです。 開発者によると、場合によってはNginxやLighttpdよりも高速です。



FastCGIプロトコルの実装とは何ですか?



現在、FastCGIプロトコルには2つの実装があります。FastCGIプロトコルの作成者が作成したlibfcgi.libライブラリと、 Fastcgi ++ -C ++クラスライブラリです。 Libfcgiは1996年から開発されており、公開市場によると非常に安定しており、さらに一般的であるため、この記事で使用します。 ライブラリはCで記述されているため、C ++の組み込み「ラッパー」は高レベルとは言えないため、Cインターフェイスを使用します。

ライブラリ自体のインストールを停止しても意味がないと思います。メイクファイルが含まれているので、問題はないはずです。 さらに、一般的なディストリビューションでは、このライブラリはパッケージから入手できます。



ソケットとは何ですか?



ソケットの一般的な概念は、 Wikipediaで入手できます。 一言で言えば、ソケットはプロセス間通信の方法です。

私たちが思い出すように、現代のすべてのオペレーティングシステムでは、各プロセスは独自のアドレス空間を使用します。 オペレーティングシステムのカーネルは、RAMへの直接アクセスを担当し、プログラムが存在しない(このプログラムのコンテキスト内の)メモリアドレスにアクセスすると、カーネルはセグメンテーションエラー(セグメンテーションエラー)を返し、プログラムを閉じます。 これはすばらしいことです。現在、あるプログラムのエラーは他のプログラムに害を及ぼすことはできません。 しかし、プログラムには異なるアドレス空間があるため、共有データからのデータ交換やデータ交換もできません。 あるプログラムから別のプログラムにデータを本当に転送する必要がある場合は、どうすればいいですか? 実際、この問題を解決するために、ソケットが開発されました。2つ以上のプロセス(読み取り:プログラム)が同じソケットに接続し、データ交換を開始します。 それは別の世界への一種の「窓」であることがわかります-それを通して、あなたは他のストリームにデータを送受信できます。

接続の使用タイプに応じて、ソケットは異なります。 たとえば、TCPソケットがあります。通常のネットワークを使用してデータを交換します。つまり、プログラムは異なるコンピューターで実行できます。 2番目に一般的なオプション-Unixドメインソケット(Unixドメインソケット)-は、同じマシン内でのみデータ交換に適しており、ファイルシステムの通常のパスのように見えますが、実際のハードドライブは使用されません-すべてのデータ交換はRAMで行われます。 ネットワークスタックを使用する必要がないという事実により、TCPソケットよりも多少速く(約10%)動作します。 Windowsの場合、このソケットオプションは名前付きパイプと呼ばれます。

GNU / Linuxのソケットの使用例は、 この記事に記載されています 。 ソケットをまだ使用していない場合は、ソケットをよく理解することをお勧めします。これは必須ではありませんが、ここで説明する事項の理解を深めることができます。



libfcgiライブラリの使用方法は?



そこで、マルチスレッドFastCGIアプリケーションを作成したいので、最も重要な機能のいくつかを説明します。

まず、ライブラリを初期化する必要があります。

int FCGX_Init(void);
      
      





注意! この関数は、このライブラリの他の関数の前に一度だけ呼び出す必要があります(任意の数のスレッドに対して一度だけ)。



次に、リスニングソケットを開く必要があります。

 int FCGX_OpenSocket(const char *path, int backlog);
      
      





パス変数には、ソケット接続文字列が含まれています。 UnixドメインソケットとTCPソケットの両方がサポートされており、ライブラリはパラメータの準備と関数自体の呼び出しに必要なすべての作業を行います。

Unixドメインソケットの接続文字列の例:

 "/tmp/fastcgi/mysocket" "/tmp/fcgi_example.bare.sock"
      
      





ここではすべてが明確だと思います:ソケットとやり取りするすべてのプロセスがそれにアクセスできる必要がありますが、文字列の形で一意のパスを渡すだけです。 繰り返しますが、この方法は1台のコンピューターのフレームワーク内でのみ機能しますが、TCPソケットよりも多少高速です。

TCPソケットの接続文字列の例:

 ":5000" ":9000"
      
      





この場合、指定されたポート(この場合はそれぞれ5000または9000)でTCPソケットが開かれ、すべてのIPアドレスから要求が受け入れられます。 注意! この方法は潜在的に安全ではありません。サーバーがインターネットに接続されている場合、FastCGIプログラムは他のコンピューターからの要求を受け入れます。 これは、攻撃者がデスパッケージをFastCGIプログラムに送信できることを意味します。 もちろん、これには良いことは何もありません-最良の場合、プログラムは単にクラッシュし、サービス拒否(必要に応じてDoS攻撃)、最悪の場合、リモートコード実行(これがまったく幸運でない場合)になる可能性があるため、常に制限しますファイアウォール(ファイアウォール)を使用してこのようなポートにアクセスし、FastCGIプログラムの通常の操作中に実際に使用されるIPアドレスにのみアクセスを許可する必要があります(「明示的に許可されていないすべてが禁止」という原則)。

次の接続文字列の例:

 "*:5000" "*:9000"
      
      





この方法は以前の方法とまったく同じです。TCPソケットは任意のIPアドレスからの接続で開かれるため、この場合はファイアウォールを慎重に構成する必要もあります。 この接続文字列からの唯一のプラスは純粋に管理者です-構成ファイルを読み取るプログラマーまたはシステム管理者は、プログラムが任意のIPアドレスからの接続を受け入れることを理解するため、他のすべてが等しい場合は、前のバージョンよりもデータを優先することをお勧めします。

より安全なオプションは、接続文字列でIPアドレスを明示的に指定することです。

 "5.5.5.5:5000" "127.0.0.1:9000"
      
      





この場合、指定されたIPアドレス(この場合、それぞれ5.5.5.5または127.0.0.1)からのみ要求が受け入れられ、他のすべてのIPアドレス(この場合、それぞれ5000または9000)は閉じられます。 これにより、システムの全体的なセキュリティが向上するため、可能な場合は常にこの形式の文字列をTCPソケットへの接続に使用します。システム管理者がファイアウォールの設定を「忘れる」場合はどうなりますか? 2番目の例に注意してください。同じマシン(localhost)のアドレスが表示されます。 これにより、何らかの理由でUnixドメインソケットを使用できない場合に、同じマシン上にTCPソケットを作成できます(たとえば、chroot Webサーバーとchroot FastCGIプログラムは異なるフォルダーにあり、共通のファイルパスがないため) ) 残念ながら、2つ以上の異なるIPアドレスを指定することはできません。したがって、異なるコンピューターにある複数のWebサーバーからの要求を本当に受け入れる必要がある場合は、ポートを完全に開いて(前の方法を参照)、設定に依存する必要がありますファイアウォール、または異なるポートで複数のソケットを使用します。 また、libfcgiライブラリはIPv6アドレスをサポートしていません。1996年にこの標準が生まれたばかりなので、食欲を通常のIPv4アドレスに制限する必要があります。 確かに、本当にIPv6サポートが必要な場合は、FCGX_OpenSocket関数にパッチを適用することで比較的簡単に追加できます。ライブラリライセンスではこれが許可されています。

注意! ソケットを作成するときにIPアドレスを指定する機能を使用しても十分な保護ではありません-IPスプーフィング攻撃が可能(パケットの送信者のIPアドレスをスプーフィング)するため、ファイアウォールの設定が必要です。 通常、IPスプーフィングに対する保護として、ファイアウォールは、ローカルネットワーク上のすべてのホスト(より正確には、ホストとのブロードキャストドメイン)のパケットのIPアドレスとネットワークカードのMACアドレス間の対応をチェックし、リターンアドレスを持つインターネットからのすべてのパケットを破棄しますプライベートIPアドレスのゾーンまたはローカルホスト(マスク10.0.0.0/8、172.16.0.0/12、192.168.0.0/16、fc00 :: / 7、127.0.0.0 / 8および:: 1/128)にあります。 それでも、ライブラリのこの機能を使用することはなお良いです-ファイアウォールが正しく構成されていない場合、TCPプロトコルにはIPスプーフィングに対する保護が組み込まれているため、偽造IPアドレスから「死のパケット」を送信することは誰よりもはるかに困難です。

接続文字列の最後の種類は、ホストドメイン名を使用することです。

 "example.com:5000" "localhost:9000"
      
      





この場合、指定したホストのドメイン名に基づいてIPアドレスが自動的に取得されます。 制限は同じです-ホストには1つのIPv4アドレスが必要です。そうでない場合、エラーが発生します。 ただし、FastCGIの操作の最初にソケットが一度作成されると、この方法はあまり役に立ちません-IPアドレスを動的に変更することはできません(より正確には、IPアドレスを変更するたびにFastCGIプログラムを再起動する必要があります)。 一方、比較的大規模なネットワークでは便利な場合があります。ドメイン名を覚えておくことは、IPアドレスよりも簡単です。



バックログ関数の2番目のパラメーターは、ソケット要求キューの長さを決定します。 特別な値0(ゼロ)は、このオペレーティングシステムのデフォルトのキューの長さを意味します。

Webサーバーから要求が来るたびに、FastCGIプログラムによる処理を待機している間、この接続に新しい接続が置かれます。 キューが完全にいっぱいの場合、後続の接続要求はすべて失敗します。Webサーバーは、接続拒否応答を受信します(接続は拒否されます)。 原則として、これには何も問題はありません-Nginx Webサーバーには独自の要求キューがあり、空きリソースがない場合、新しい要求はWebサーバーキューで既に処理されている順番を待ちます(少なくともタイムアウト)。 さらに、FastCGIプログラムを実行しているサーバーが複数ある場合、Nginxはそのような要求を負荷の少ないサーバーに転送できます。

それでは、どのキューの長さが最適かを考えてみましょう。 一般に、ストレステストのデータに基づいてこのパラメーターを個別に構成することをお勧めしますが、この値に最適な範囲を評価しようとします。 最初に知っておくべきことは、キューの最大長が制限されていることです(オペレーティングシステムのカーネル設定、通常は1024接続以下で決定されます)。 2番目-キューはリソースを消費しますが、安価ですが、それでもリソースなので、不当に長くする価値はありません。 さらに、FastCGIプログラムには8つのワークフローがあり(最新の4-8コアプロセッサにとっては非常に現実的)、各スレッドには独自の接続が必要であり、タスクは並列に処理されます。 したがって、理想的には、Webサーバーから既に8つのリクエストがあり、不要な遅延なしにすべてのスレッドが機能することを保証する必要があります。 つまり、最小リクエストキューサイズは、FastCGIプログラムのワークスレッドの数です。 ネットワーク上でのデータ転送時間は有限であるため、この値を50%-100%増やして、読み込みのためのマージンを確保することができます。

次に、この数量の上限を定義しましょう。 ここでは、実際に処理できるリクエストの数を把握し、リクエストのキューをこの値に制限する必要があります。 このキューを大きくしすぎて、顧客が順番を待つのに飽き飽きし、答えを待たずにサイトを離れるだけだと想像してください。 明らかに、これには何も良いことはありません-Webサーバーは接続を開く要求を送信する必要がありましたが、それはそれ自体が高価であり、FastCGIプログラムがこの要求を処理するのに十分な時間を持っていなかったためだけにこの接続を閉じます 一言で言えば、CPU時間を浪費しているだけですが、それだけでは十分ではありません。 しかし、これは最悪のことではありません。クライアントが、リクエストの処理を開始するためのフィールドであるサイトからの情報の受信を拒否した場合、さらに悪化します。 本質的に不必要な要求を完全に処理する必要があることがわかりますが、それは状況を悪化させるだけです。 理論的には、ほとんどのクライアントがプロセッサの100%の負荷で応答を待たない場合に状況が発生する可能性があります。 良くない

したがって、300ミリ秒(つまり0.3秒)で処理できる1つの要求があるとします。 さらに、Webページが30秒以上読み込まれると、訪問者の平均50%がリソースを離れることがわかります。 明らかに、不満を持っている人の50%が多すぎるため、最大ページ読み込み時間を5秒に制限します。 これは、カスケードスタイルシートを適用してJavaScriptを実行した後の完全に完成したWebページを意味します。平均的なサイトでのこの段階は、Webページの合計読み込み時間の70%を占めることがあります。 したがって、ネットワーク経由でデータをダウンロードするために残されるのは5 * 0.3 = 1.5秒以下です。 さらに、htmlコード、スタイルシート、スクリプト、およびグラフィックスは異なるファイルで送信され、最初にhtmlコードが送信され、次にすべてが送信されることに注意してください。 ただし、htmlコードを受信した後、ブラウザーは残りのリソースのリクエストを並行して開始するため、htmlコードの読み込み時間はデータを受信する合計時間の50%と見積もることができます。 したがって、1つのリクエストを処理するのに1.5 * 0.5 = 0.75秒以下しか残っていません。 平均して1つのスレッドが0.3秒でリクエストを処理する場合、キューにはスレッドあたり0.75 / 0.3 = 2.5のリクエストが必要です。 8つのワークフローがあるため、結果のキューサイズは2.5 * 8 = 20リクエストになります。 上記の計算の規則に注意してください-特定のサイトがある場合、計算で使用される値はより正確に決定できますが、それでもより最適なパフォーマンスチューニングの出発点となります。



そのため、ソケット記述子を取得しました。その後、リクエスト構造にメモリを割り当てる必要があります。 この構造の説明は次のとおりです。

 typedef struct FCGX_Request { int requestId; int role; FCGX_Stream *in; FCGX_Stream *out; FCGX_Stream *err; char **envp; struct Params *paramsPtr; int ipcFd; int isBeginProcessed; int keepConnection; int appStatus; int nWriters; int flags; int listen_sock; int detached; } FCGX_Request;
      
      





注意! 新しいリクエストを受信すると、以前のデータはすべて失われるため、データの長期保存が必要な場合は、ディープコピーを使用します(データへのポインターではなく、データ自体をコピーします)。

この構造について次のことを知っておく必要があります。

-変数in、out、およびerrは、それぞれ入力、出力、およびエラーフローの役割を果たします。 入力ストリームにはPOST要求のデータが含まれ、FastCGIプログラムの応答(たとえば、Webページのhttp-headersおよびhtml-code)は出力ストリームに送信する必要があり、エラーストリームは単にWebサーバーをエラーログに追加します。 同時に、エラーストリームをまったく使用する必要はありません。エラーをログに記録する必要がある場合は、おそらく別のファイルを使用することをお勧めします。ネットワーク経由のデータ転送とWebサーバーによる後続の処理は追加のリソースを消費します。

-envp変数には、たとえば、SERVER_PROTOCOL、REQUEST_METHOD、REQUEST_URI、QUERY_STRING、CONTENT_LENGTH、HTTP_USER_AGENT、HTTP_COOKIE、HTTP_REFERERなど、Webサーバーとhttpヘッダーによって設定された環境変数の値が含まれます。 これらのヘッダーは、それぞれCGIおよびHTTPプロトコル標準によって定義されています;それらの使用例は、CGIプログラムで見つけることができます。 データ自体は文字列の配列に格納され、配列の最後の要素には、配列の終わりを示すヌルポインター(NULL)が含まれます。 各行(文字列配列の各要素)には、TITLE_VARIABLE = VALUEの形式の変数値が1つ含まれています。たとえば、CONTENT_LENGTH = 0(この場合、このリクエストの長さはゼロであるため、POSTデータはありません)。 envp文字列配列に必要なヘッダーがない場合、渡されていません。 FastCGIプログラムに渡される変数のすべての値を取得する場合は、NULLへのポインターが見つかるまで、ループ内のenvp配列のすべての行を読み取ります。

実際、この構造の説明でこれを終了しました。他のすべての変数は必要ありません。



メモリが割り当てられたので、要求構造を初期化する必要があります。

 int FCGX_InitRequest(FCGX_Request *request, int sock, int flags);
      
      





関数パラメーターは次のとおりです。

request-初期化されるデータ構造へのポインタ

sockは、FCGX_OpenSocket関数を呼び出した後に受け取ったソケット記述子です。 既製の記述子の代わりに、0(ゼロ)を渡してデフォルト設定でソケットを取得できますが、この方法はまったく面白くないことに注意してください-ソケットはランダムな空きポートで開かれるため、Webを適切に構成できません-server-データの送信先を正確に事前に知りません。

フラグ-フラグ。 実際、この関数に渡すことができるフラグは1つだけです-FCGI_FAIL_ACCEPT_ON_INTR-中断時にFCGX_Accept_rを呼び出さないでください。



その後、新しいリクエストを取得する必要があります。

 int FCGX_Accept_r(FCGX_Request *request);
      
      





その中で、最後の段階ですでに初期化されているリクエスト構造を転送する必要があります。 注意! マルチスレッドプログラムでは、この関数を呼び出すときに同期を使用する必要があります。

実際、この関数は、ソケットの処理に関するすべての作業を行います。最初に、以前の要求(存在する場合)でWebサーバーに応答を送信し、以前のデータ転送チャネルを閉じて、それに関連するすべてのリソース(要求構造変数を含む)を解放します、次に、新しい要求を受信し、新しいデータ送信チャネルを開き、後続の処理のために要求構造に新しいデータを準備します。 新しいリクエストの受信でエラーが発生した場合、関数はゼロ未満のエラーコードを返します。



次に、おそらく環境変数を取得する必要があります。そのためには、request-> envp配列を個別に処理するか、関数を使用します

 char *FCGX_GetParam(const char *name, FCGX_ParamArray envp);
      
      





nameは、値を受け取りたい環境変数またはhttp-headerの名前を含む文字列です。

envp-request-> envp変数に含まれる環境変数の配列

この関数は、必要な環境変数の値を文字列として返します。 注意深い読者がchar **とFCGX_ParamArrayの型の不一致を恐れないようにしてください-これらの型は同義語として宣言されています(typedef char ** FCGX_ParamArray)。

さらに、おそらくWebサーバーに応答を送信する必要があります。 これを行うには、request-> out出力ストリームと関数を使用します

 int FCGX_PutStr(const char *str, int n, FCGX_Stream *stream);
      
      





strは、出力用のデータを含むバッファで、終端ゼロは含まれません(つまり、バッファにはバイナリデータが含まれる場合があります)。

n-バイト単位のバッファ長、

stream-データを出力するストリーム(request-> outまたはrequest-> err)。



終端のゼロでCストリング標準を使用する場合、関数を使用する方が便利です。

 int FCGX_PutS(const char *str, FCGX_Stream *stream);
      
      





strlen(str)関数を使用して文字列の長さを決定し、前の関数を呼び出します。 したがって、文字列の長さを事前に知っている場合(たとえば、C ++ std ::文字列文字列を使用する場合)、効率上の理由から前の関数を使用することをお勧めします。

これらの関数はUTF-8文字列で正常に機能するため、多言語Webアプリケーションで問題が発生することはありません。

同じリクエストの処理中にこれらの関数を複数回呼び出すこともできます。これにより、パフォーマンスが向上する場合があります。 たとえば、大きなファイルを送信する必要があります。 ハードドライブからファイル全体をダウンロードしてから「1ピース」で送信する代わりに、すぐにデータの送信を開始できます。 その結果、ブラウザの白い画面の代わりにクライアントは興味のあるデータを受信し始めます。これは純粋に心理的に彼をもう少し長く待たせます。 言い換えれば、ページをロードする時間をいくらか取っているようです。 また、ほとんどのリソース(カスケードスタイルシート、JavaScriptなど)はWebページの先頭に示されています。つまり、ブラウザーはhtmlコードの一部を分析し、これらのリソースをより早く読み込むことができます-データを表示するもう1つの理由部分的に。



次に必要になるのは、POSTリクエストの処理です。 値を取得するには、関数を使用してストリームのリクエストからデータを読み取る必要があります

 int FCGX_GetStr(char * str, int n, FCGX_Stream *stream);
      
      





strはバッファへのポインタです。

nは、バイト単位のバッファのサイズです。

stream-データの読み取り元のストリーム。

POST要求で送信されるデータのサイズ(バイト単位)は、環境変数CONTENT_LENGTHを使用して決定できます。その値は、思い出すように、FCGX_GetParam関数を使用して取得できます。 注意! CONTENT_LENGTH変数の値に基づいて制限なしでstrバッファーを作成することは非常に悪い考えです。攻撃者は任意の大きなPOSTリクエストを送信でき、サーバーは単に空きRAMを使い果たす可能性があります(必要に応じてDoS攻撃を受けます)。 代わりに、バッファーのサイズを適切な量(数キロバイトから数メガバイト)に制限し、FCGX_GetStr関数を数回呼び出すことをお勧めします。



最後の重要な関数は、出力ストリームとエラーストリームをフラッシュし(まだ送信されていないクライアントデータを送信します。これを出力ストリームとエラーストリームに入れることができます)、接続を閉じます。

 void FCGX_Finish_r(FCGX_Request *request);
      
      





この関数はオプションであることを強調したいと思います。FCGX_Accept_r関数は、クライアントにデータを送信し、新しい要求を受信する前に現在の接続を閉じます。 問題は、なぜそれが必要なのかということです。必要なすべてのデータをクライアントにすでに送信し、最終的な操作を実行する必要があることを想像してください。データベースへの統計の書き込み、ログファイルへのエラーの書き込みなど。明らかに、クライアントとの接続はもはや必要ではありませんが、クライアントは(ブラウザの意味で)まだ私たちからの情報を待っています。事前にFCGX_Accept_rを呼び出すことができないことは明らかです。その後、次のリクエストの処理を開始する必要があります。この場合、FCGX_Finish_r関数が必要になります-新しいリクエストを受信する前に現在の接続を閉じることができます。はい、この機能を使用しない場合と同じ単位時間あたりのリクエスト数を処理できますが、クライアントはより早く回答を受け取ります-最終操作が終了するまで待つ必要はありません。FastCGIを使用するのは、要求の処理速度が速いためです。

これにより、実際には、ライブラリー関数の記述が終了し、受信データの処理が開始されます。



マルチスレッドFastCGIプログラムの簡単な例



この例ではすべてが明らかになると思います。唯一のことは、デバッグメッセージの印刷とワークフローの「眠りに落ちる」ことは、デモンストレーションのみを目的として行われたことです。プログラムをコンパイルするときは、libfcgiおよびlibpthreadライブラリ(gccコンパイラオプション:-lfcgiおよび-lpthread)を含めるようにしてください。



 #include <pthread.h> #include <sys/types.h> #include <stdio.h> #include "fcgi_config.h" #include "fcgiapp.h" #define THREAD_COUNT 8 #define SOCKET_PATH "127.0.0.1:9000" //    static int socketId; static void *doit(void *a) { int rc, i; FCGX_Request request; char *server_name; if(FCGX_InitRequest(&request, socketId, 0) != 0) { //     printf("Can not init request\n"); return NULL; } printf("Request is inited\n"); for(;;) { static pthread_mutex_t accept_mutex = PTHREAD_MUTEX_INITIALIZER; //    printf("Try to accept new request\n"); pthread_mutex_lock(&accept_mutex); rc = FCGX_Accept_r(&request); pthread_mutex_unlock(&accept_mutex); if(rc < 0) { //    printf("Can not accept new request\n"); break; } printf("request is accepted\n"); //   server_name = FCGX_GetParam("SERVER_NAME", request.envp); //  HTTP- (    ) FCGX_PutS("Content-type: text/html\r\n", request.out); //         FCGX_PutS("\r\n", request.out); //   ( - html- -) FCGX_PutS("<html>\r\n", request.out); FCGX_PutS("<head>\r\n", request.out); FCGX_PutS("<title>FastCGI Hello! (multi-threaded C, fcgiapp library)</title>\r\n", request.out); FCGX_PutS("</head>\r\n", request.out); FCGX_PutS("<body>\r\n", request.out); FCGX_PutS("<h1>FastCGI Hello! (multi-threaded C, fcgiapp library)</h1>\r\n", request.out); FCGX_PutS("<p>Request accepted from host <i>", request.out); FCGX_PutS(server_name ? server_name : "?", request.out); FCGX_PutS("</i></p>\r\n", request.out); FCGX_PutS("</body>\r\n", request.out); FCGX_PutS("</html>\r\n", request.out); //"" -    sleep(2); //   FCGX_Finish_r(&request); //  -  ,    .. } return NULL; } int main(void) { int i; pthread_t id[THREAD_COUNT]; //  FCGX_Init(); printf("Lib is inited\n"); //   socketId = FCGX_OpenSocket(SOCKET_PATH, 20); if(socketId < 0) { //    return 1; } printf("Socket is opened\n"); //   for(i = 0; i < THREAD_COUNT; i++) { pthread_create(&id[i], NULL, doit, NULL); } //    for(i = 0; i < THREAD_COUNT; i++) { pthread_join(id[i], NULL); } return 0; }
      
      







簡単なNginxの構成例



実際、設定の最も単純な例は次のようになります。



 サーバー{ 
	server_name localhost; 

	場所/ { 
		fastcgi_pass 127.0.0.1:9000; 
		#fastcgi_pass unix:/ tmp / fastcgi / mysocket; 
		#fastcgi_pass localhost:9000; 
		 
		fastcgi_paramsを含めます。 
	 } 
 } 




この場合、FastCGIプログラムを正しく動作させるには、この構成で十分です。コメントされた行は、それぞれUnixドメインソケットで動作し、IPアドレスの代わりにホストドメイン名を指定する例です。

プログラムをコンパイルして実行し、Nginxを構成した後、ローカルホストで

FastCGI Hello!(マルチスレッドC、fcgiappライブラリ)



最後まで読んでくれたすべての人に感謝します。



All Articles