シンプルなcgiサーバーの例

多くの人がクライアントとサーバー間のCGI相互作用の仕組みを知っていると思います。クライアントはサーバーから受信し、標準のstdinとstdoutを介してサーバーにデータを送信します。 実際、WebサーバーのスクリプトはすべてCGIクライアントであるため、多くの人が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の両方でコンパイルできるように作成されているため、最初の数行はクロスプラットフォームの定義です。

さらに、グローバル変数で設定されます:





この初期化により、サーバーはプロセス./Main_Child.exeと対話します。 そこで、コンパイル済みのクライアントをC ++で呼び出しました。

配列{"python"、 "./test.py"、0}がコマンドライン変数として設定されている場合、サーバーはpythonスクリプトと対話します。



グローバル変数の後、クロスプラットフォームバージョンの_spawnve関数を作成しました 。 つまり、この関数は、メモリが現在のプロセスと完全に同一であるプロセスを作成し、他のコマンドラインと環境変数を新しいプロセスに渡します。



サーバーは「メイン」機能で終了します。そのほとんどは(英語のコメントから推測できるように)さまざまなサードパーティソースから取得しました。 この関数のコードから、プロセス間のI / Oリダイレクトの「全体」が「パイプ」(チャネル)を使用して編成されていることがわかります。

チャネルメカニズムは非常にシンプルで標準的であり、 WindowsLinuxの両方でほぼ同等に実装されています 。 これらの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」スクリプトを実行するだけです。 次のようなものが得られるはずです。








All Articles