しかし、この「魔法」が正確にどのように発生するのか疑問に思っている人はどれくらいいますか? 標準の入力/出力機能は、画面ではなくサーバーとどのように対話しますか?
ネットワーク上の回答の検索結果は私を満足させなかったので、私は最も簡単なCGIサーバーを自分で書くことにしました。
- 子プロセスの実行-CGIスクリプト
- 環境変数とコマンドライン変数をスクリプトに渡す
- スクリプトからの応答を受け入れる
- クライアントプロセスが完了したら終了
さらに、クライアントとサーバーがWindowsとLinuxの両方でコンパイルされるようにしました。
CGIクライアント
最も単純で最もよく知られているものから始めます。CGIサーバーのクライアントについて説明します。 単純な「hello world」は私には向いていませんでした。stdout経由でメッセージを送信する機能だけでなく、環境変数やstdinからのメッセージを受信する正確さもチェックする必要があったからです。
さらに、実際のCGIインタラクションが確実に行われるようにするため、1つではなく2つのクライアントを同時に作成することにしました。 C ++およびpythonで。
C ++のソースCGIクライアント
#include <stdio.h> #include <stdlib.h> #include <iostream> #include <fcntl.h> #include <vector> #ifdef _WIN32 #include <windows.h> #define getpid() GetCurrentProcessId() #define sleep(n) Sleep(n*1000); #else #include <unistd.h> #endif using namespace std; int main(int argc, char *argv[]) { // stdout , cout << "Child process started\n"; for (int n=0; n<argc; n++) cout << "argv[" << n << "] = " << argv[n] << "\n"; // stdout , const int nContentLength = atoi(getenv("Content-Length")); cout << "\n" << "Content-Length = " << nContentLength << "\n" << "VARIABLE2 = " << getenv("VARIABLE2") << "\n" << "VARIABLE3 = " << getenv("VARIABLE3") << "\n" << "\n\n"; fflush(stdout); sleep(5); // vector<unsigned char> vBuffer(nContentLength); // stdin , const size_t nBytes = fread(&vBuffer[0], 1, nContentLength, stdin); // stdout , cout << "Request body:\n"; fwrite(&vBuffer[0], 1, nBytes, stdout); fflush(stdout); sleep(5); // return 0; }
Python CGIクライアントソース
#!/usr/bin/python import sys import os print "Content-Length = " + os.environ["Content-Length"] print "VARIABLE2 = " + os.environ["VARIABLE2"] print "VARIABLE3 = " + os.environ["VARIABLE3"] body = sys.stdin.read( int(os.environ["Content-Length"]) ) print body
顧客コードの説明
C ++でクライアントをCGIサーバーから起動すると、コマンドライン変数、「Content-Length」、「VARIABLE2」、「VARIABLE3」という名前の3つの環境変数、およびサーバーから受信したすべてのコンテンツに関する情報標準
CGIサーバーからクライアントをPythonで実行すると、「Content-Length」、「VARIABLE2」、「VARIABLE3」という名前の環境変数に関する情報と、stdinのサーバーから受信したすべてのコンテンツが画面に表示されます。
環境変数「Content-Length」は、stdinのバイト数以下の数になるようにサーバーによって形成される必要があることに注意してください。 これは、クライアントがサーバー以外からこの情報を他の方法で学習できないために必要です。
Cgiサーバー
クライアントスクリプトとは異なり、ネットワーク上のCGIサーバーコードは簡単に見つけることができないため、私のコードはさまざまな急でエラーが発生しやすい例からコンパイルされています。 彼はそれをより明確にするために自分で何かを追加しました。
C ++のCGIサーバーソース
#include <stdio.h> #include <iostream> #include <fcntl.h> #include <string> #include <vector> #ifdef _WIN32 #include <process.h> /* Required for _spawnv */ #include <windows.h> #include <io.h> #define pipe(h) _pipe(h, 1024*16, _O_BINARY|_O_NOINHERIT) #define getpid() GetCurrentProcessId() #define dup _dup #define fileno _fileno #define dup2 _dup2 #define close _close #define read _read #define write _write #else #include <errno.h> #include <unistd.h> #include <signal.h> #include <sys/wait.h> #endif using namespace std; // static const string strRequestBody = "===this is request body===\n"; static const string strRequestHeader = "Content-Length=" + to_string((long long)strRequestBody.length()); // static const char *pszChildProcessEnvVar[4] = {strRequestHeader.c_str(), "VARIABLE2=2", "VARIABLE3=3", 0}; // . - . static const char *pszChildProcessArgs[4] = {"./Main_Child.exe", "first argument", "second argument", 0}; // - . // - , - //static const char *pszChildProcessArgs[3] = {"python", "./test.py", 0}; // , int spawn_process(const char *const *args, const char * const *pEnv) { #ifdef _WIN32 return _spawnve(P_NOWAIT, args[0], args, pEnv); #else /* Create copy of current process */ int pid = fork(); /* The parent`s new pid will be 0 */ if(pid == 0) { /* We are now in a child progress Execute different process */ execvpe(args[0], (char* const*)args, (char* const*)pEnv); /* This code will never be executed */ exit(EXIT_SUCCESS); } /* We are still in the original process */ return pid; #endif } int main() { int fdStdInPipe[2], fdStdOutPipe[2]; fdStdInPipe[0] = fdStdInPipe[1] = fdStdOutPipe[0] = fdStdOutPipe[1] = -1; if (pipe(fdStdInPipe) != 0 || pipe(fdStdOutPipe) != 0) { cout << "Cannot create CGI pipe"; return 0; } // Duplicate stdin and stdout file descriptors int fdOldStdIn = dup(fileno(stdin)); int fdOldStdOut = dup(fileno(stdout)); // Duplicate end of pipe to stdout and stdin file descriptors if ((dup2(fdStdOutPipe[1], fileno(stdout)) == -1) || (dup2(fdStdInPipe[0], fileno(stdin)) == -1)) return 0; // Close original end of pipe close(fdStdInPipe[0]); close(fdStdOutPipe[1]); // , const int nChildProcessID = spawn_process(pszChildProcessArgs, pszChildProcessEnvVar); // Duplicate copy of original stdin an stdout back into stdout dup2(fdOldStdIn, fileno(stdin)); dup2(fdOldStdOut, fileno(stdout)); // Close duplicate copy of original stdin and stdout close(fdOldStdIn); close(fdOldStdOut); // write(fdStdInPipe[1], strRequestBody.c_str(), strRequestBody.length()); while (1) { // char bufferOut[100000]; int n = read(fdStdOutPipe[0], bufferOut, 100000); if (n > 0) { // fwrite(bufferOut, 1, n, stdout); fflush(stdout); } // , #ifdef _WIN32 DWORD dwExitCode; if (!::GetExitCodeProcess((HANDLE)nChildProcessID, &dwExitCode) || dwExitCode != STILL_ACTIVE) break; #else int status; if (waitpid(nChildProcessID, &status, WNOHANG) > 0) break; #endif } return 0; }
サーバーコードの説明
サーバーは、WindowsとLinuxの両方でコンパイルできるように作成されているため、最初の数行はクロスプラットフォームの定義です。
さらに、グローバル変数で設定されます:
- スクリプトに送信されるリクエスト本文(行 "===これはリクエスト本文=== \ n")、メッセージの長さはstrRequestHeader変数に保存されます
- 文字列の配列としての環境変数{strRequestHeader.c_str()、 "VARIABLE2 = 2"、 "VARIABLE3 = 3"、0}
- 配列形式のコマンドライン変数{"./Main_Child.exe"、 "最初の引数"、 "2番目の引数"、0}
この初期化により、サーバーはプロセス./Main_Child.exeと対話します。 そこで、コンパイル済みのクライアントをC ++で呼び出しました。
配列{"python"、 "./test.py"、0}がコマンドライン変数として設定されている場合、サーバーはpythonスクリプトと対話します。
グローバル変数の後、クロスプラットフォームバージョンの_spawnve関数を作成しました 。 つまり、この関数は、メモリが現在のプロセスと完全に同一であるプロセスを作成し、他のコマンドラインと環境変数を新しいプロセスに渡します。
サーバーは「メイン」機能で終了します。そのほとんどは(英語のコメントから推測できるように)さまざまなサードパーティソースから取得しました。 この関数のコードから、プロセス間のI / Oリダイレクトの「全体」が「パイプ」(チャネル)を使用して編成されていることがわかります。
チャネルメカニズムは非常にシンプルで標準的であり、 WindowsとLinuxの両方でほぼ同等に実装されています 。 これらの2つのアプローチを接続するために、ソースの最初に単純なオーバーライドを追加しました。
#ifdef _WIN32 #define pipe(h) _pipe(h, 1024*16, _O_BINARY|_O_NOINHERIT) #endif
メイン関数の終わりに、無限ループが編成され、サーバーはクライアントからの応答を受信し、この応答を画面に送信します。 クライアントプロセスが終了すると、サイクルの終了が発生します。
おわりに
かなりの年齢にもかかわらず、CGIインターフェースは依然として最も一般的なプロセス間通信インターフェースの1つです。 WebサーバーとWebサイトの大部分は、このインターフェイスと正確に対話します。 この記事が、クライアントだけでなくCGIのサーバー部分についても理解し、おそらく自分のプロジェクトに実装したい人に役立つことを願っています。
すべてのソースコードはこちらで確認できます 。
cgi_mainフォルダーと子フォルダーには、Visual Studioのプロジェクトが含まれています。
Linuxの例を実行するには、「src」フォルダーの内容をコピーして、「compile.py」スクリプトを実行するだけです。 次のようなものが得られるはずです。