📜 ⬆️ ⬇️

An example of the simplest cgi server

I think many people know how CGI works between the client and the server: the client receives from the server and gives the server data through standard stdin and stdout. Many people probably even wrote CGI clients themselves, because in fact, any script for a web server is a CGI client.
And how many people wondered how exactly this "magic"? How do the standard I / O functions interact with the server instead of the screen?

The results of the search for an answer on the network did not satisfy me, and I decided to write the simplest CGI server myself, which could:

In addition, I wanted the client and server to be compiled in both Windows and Linux.


CGI client


I will begin all the same with the simplest and well-known: I will describe my client for the CGI server. A simple “hello world” did not suit me, because it was necessary to check not only the possibility of passing messages through stdout, but also the correctness of receiving environment variables and messages from stdin.
In addition, in order to make sure that the most genuine CGI interaction came out, it was decided to write not one, but two clients at once. On with ++ and on python.
')
C ++ CGI client source
#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 client source
 #!/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 


Explanation of customer code

If you start the client in C ++ from the CGI server, then information about the command line variables, three environment variables named Content-Length, VARIABLE2 and VARIABLE3, as well as all content received from the server in the stdin
If you run the client in Python from the CGI server, then information about the environment variables with the names Content-Length, VARIABLE2 and VARIABLE3, as well as all the content received from the server in stdin is displayed.

It should be noted that the “Content-Length” environment variable must be formed by the server in such a way that it is less than or equal to the number of bytes in stdin. This is necessary because the client cannot in any other way know this information except from the server.

CGI server


Unlike client scripts, the CGI server code on the network is not at all easy to find, so my code is compiled from various steep and often error-prone examples. Something added on my own to make it more visible.

C ++ CGI server source
 #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; } 



Explanation of the server code

The server was written so that it could be compiled both in Windows and Linux, so the first few lines are cross-platform definitions.
Further in global variables it is set:


With this initialization, the server will interact with the process "./Main_Child.exe". So I called the compiled client in C ++.
If you specify {{python ', "./test.py", 0} as command line variables, the server will interact with the python script.

After global variables, I wrote a cross-platform version of the _spawnve function . In short, this function creates a process whose memory is completely identical to the current process and passes other command line variables and environments to the new process.

The server ends with the "main" function, most of which (as you can guess from the English comments) I took from various third-party sources. From the code of this function, it can be seen that the "whole salt" of redirecting I / O between processes is organized using "pipe" (channels).
The channel mechanism is fairly simple and standard, it is almost equally implemented in both Windows and Linux . To connect these two approaches, at the very beginning of the source, I added a simple override:

 #ifdef _WIN32 #define pipe(h) _pipe(h, 1024*16, _O_BINARY|_O_NOINHERIT) #endif 


At the end of the “main” function, an endless loop is organized in which the server receives a response from the client and transmits this response to the screen. The exit from the loop will occur when the client process ends.

Conclusion


Despite its age, the CGI interface remains one of the most common interfaces for interprocess communication. The vast majority of web servers and websites interact using this interface. I hope this article will be useful to those who want to understand, and can implement in their own projects not only the client, but also the server part of CGI.

All sources can be found: here .
The cgi_main and child folders contain projects for Visual Studio.
To run the Linux example, just copy the contents of the “src” folder and run the “compile.py” script. It should get something like this:

Source: https://habr.com/ru/post/254621/


All Articles