You can check out tutorial 1, Tutorial 2, Tutorial 3, tutorial 4.

This series would have ended, but after seeing some questions about the $_GET and $_POST values in PHP in the comments of @fingertip, I also wanted to clarify this part of the content, so I rewrote some code to do some tests and some exploration and learning about PHP source code

  • $_POST $_GET overview
  • $_POST $_GET package in PHP source code
  • Continue refining the Web server to support GET, POST requests, and try to emulate $_GET, $_POST to get data

$_POST $_GET origin

1.$_GET:

In the previous tutorial, it was easy to see that the contents of $_GET were taken from the browser access address. The argument string following the., for example:

http://localhost:8000/index.php?id=2

The address $_GET should be the array transformed by id=2

2.$_POST

In contrast to $_GET, we can use Firefox to see the value received by $_POST, such as a static interface, as a submission form





image

We enter a few fields in two input fields and then view the post through Firebug-Web





image

Form data is the post content

name=111&password=222&submit=submit

$_POST $_GET package in PHP source code

Since we need to make a server to support POST and GET, we need to analyze the origin of $_GET and $_POST, and this piece, we need to look at the knowledge involved in PHP source code. To paraphrase Linus Torvalds

talk is cheap,show me the code

1. PHP source code for get and POST analysis

Starting with SAPI, we can start reading the PHP source code. See this link to get started

www.php-internals.com/book/?p=cha…

Let’s skip the rest of the code and start with the cgi section, which we have in the php.-5.6-src/sapi/cgi directory cgi_main.c

static sapi_module_struct cgi_sapi_module = {
    "cgi-fcgi"./* name */
    "CGI/FastCGI"./* pretty name */

    php_cgi_startup,                /* startup */
    php_module_shutdown_wrapper,    /* shutdown */

    sapi_cgi_activate,                /* activate */
    sapi_cgi_deactivate,            /* deactivate */

    sapi_cgi_ub_write,                /* unbuffered write */
    sapi_cgi_flush,                    /* flush */
    NULL./* get uid */
    sapi_cgi_getenv,                /* getenv */

    php_error,                        /* error handler */

    NULL./* header handler */
    sapi_cgi_send_headers,            /* send headers handler */
    NULL./* send header handler */

    sapi_cgi_read_post,                /* read POST data */
    sapi_cgi_read_cookies,            /* read Cookies */

    sapi_cgi_register_variables,    /* register server variables */
    sapi_cgi_log_message,            /* Log message */
    NULL./* Get request time */
    NULL./* Child terminate */

    STANDARD_SAPI_MODULE_PROPERTIES
};Copy the code

Okay, so obviously we need two fields, sapi_CGI_GEtenv and sapi_Cgi_read_POST, well one is to get the environment variable, and the other is to get the data that the POST passes, and just with a little bit of knowledge, the data that get receives is stored in the environment variable, This is how it is typically stored in web servers

setenv(“QUERY_STRING”, cgi_params, 1); //cgi_params is the string following the URL

Let’s move on, how do get and POST get

  • sapi_cgi_getenv
    static char *sapi_cgi_getenv(char *name, size_t name_len TSRMLS_DC)
    {
      return getenv(name);
    }Copy the code
  • sapi_cgi_read_post

    static int sapi_cgi_read_post(char *buffer, uint count_bytes TSRMLS_DC)
    {
      uint read_bytes = 0;
      int tmp_read_bytes;
    
      count_bytes = MIN(count_bytes, SG(request_info).content_length - SG(read_post_bytes));
      while (read_bytes < count_bytes) {
          tmp_read_bytes = read(STDIN_FILENO, buffer + read_bytes, count_bytes - read_bytes);
          if (tmp_read_bytes <= 0) {
              break;
          }
          read_bytes += tmp_read_bytes;
      }
      return read_bytes;
    }Copy the code

    Well, as we guessed before, get means use

    getenv(“query_string”)

Get, and post is getting data from the input buffer, so we can use

read(STDIN_FILENO, buffer + read_bytes, count_bytes – read_bytes);

And I see a lot of examples of that

fgets(data,post_content_length+1,stdin);


Similar to cgi_main.c, we can see the same structure in FPM /php.-5.6-src/sapi/ FPM/FPM /fpm_main.c.

By the way, we know that phP-CLI can also request and compile PHP code. Single PHP-CLI does not support GET and post requests. Do you know why? We can easily see this through the source code

  • /php.-5.6-src/sapi/embed/php_embed. /php.-5.6-src/sapi/embed/php_embed

    extern EMBED_SAPI_API sapi_module_struct php_embed_module = {
      "embed"./* name */
      "PHP Embedded Library"./* pretty name */
    
      php_embed_startup,              /* startup */
      php_module_shutdown_wrapper,   /* shutdown */
    
      NULL./* activate */
      php_embed_deactivate,           /* deactivate */
    
      php_embed_ub_write,             /* unbuffered write */
      php_embed_flush,                /* flush */
      NULL./* get uid */
      NULL./* getenv */
    
      php_error,                     /* error handler */
    
      NULL./* header handler */
      NULL./* send headers handler */
      php_embed_send_header,          /* send header handler */
    
      NULL./* read POST data */
      php_embed_read_cookies,         /* read Cookies */
    
      php_embed_register_variables,   /* register server variables */
      php_embed_log_message,          /* Log message */
      NULL./* Get request time */
      NULL./* Child terminate */
    
      STANDARD_SAPI_MODULE_PROPERTIES
    };Copy the code

    Getenv and read POST data are both NULL, so phP-CLI can’t get get or POST requests


Okay, back to the main thread, let’s think about it a little differently. Let’s look for the definitions of these two variables from the beginning to the end of a PHP request, again in cgi_main.c

  • Continue to see cgi_sapi_module

So in this structure, the method that we’re going to implement in the middle, php_cgi_startup, is the method that when an application is going to call PHP, this function is going to be called, and we’re going to assume that GET and POST are declared in this method, and we’re going to work our way down here, as defined in this file

static int php_cgi_startup(sapi_module_struct *sapi_module)
{
    if (php_module_startup(sapi_module, &cgi_module_entry, 1) == FAILURE) {
        return FAILURE;
    }
    return SUCCESS;
}Copy the code

Well, let’s keep looking for the php_module_startup method

So we find the php_module_startup method in/main /main.c, and we find the following method in that method

php_startup_auto_globals(TSRMLS_C); / / around 2302 line

We’re going to look for that

  • /main/ php_variable. c
    void php_startup_auto_globals(TSRMLS_D)
    {
      zend_register_auto_global(ZEND_STRL("_GET"), 0, php_auto_globals_create_get TSRMLS_CC);
      zend_register_auto_global(ZEND_STRL("_POST"), 0, php_auto_globals_create_post TSRMLS_CC);
      zend_register_auto_global(ZEND_STRL("_COOKIE"), 0, php_auto_globals_create_cookie TSRMLS_CC);
      zend_register_auto_global(ZEND_STRL("_SERVER"), PG(auto_globals_jit), php_auto_globals_create_server TSRMLS_CC);
      zend_register_auto_global(ZEND_STRL("_ENV"), PG(auto_globals_jit), php_auto_globals_create_env TSRMLS_CC);
      zend_register_auto_global(ZEND_STRL("_REQUEST"), PG(auto_globals_jit), php_auto_globals_create_request TSRMLS_CC);
      zend_register_auto_global(ZEND_STRL("_FILES"), 0, php_auto_globals_create_files TSRMLS_CC);
    }Copy the code

Ha ha, we finally found the, that is to say this is a PHP – cgi and PHP – FPM PHP core to encapsulate the $_GET and $_POST method, so we realize the cgi program unable to call up the $_GET, $_POST method


Continue refining the Web server to support GET, POST requests, and try to emulate $_GET, $_POST to get data

1. Refine get requests

Go back to your web server and set the query_string environment variable in the php_cgi method of wrap_socket.c

void php_cgi(char* script_path, int fd,char *cgi_params) {
    char *emptylist[] = {script_path };
    setenv("QUERY_STRING", cgi_params, 1);
    dup2(fd, STDOUT_FILENO);
    //execl("/usr/bin/php"."php",script_path,(void *)NULL);
    //execve("./slow-cgi", emptylist, envp);
    execlp("./slow-cgi.cgi",script_path,(char *) NULL);
    //execve(script_path, emptylist, environ);
}Copy the code

Get the environment variable data from the CGI program to capture the GET request

int main(int argc, char * argv[]) {
    char *cgi_params,*script_path;
    int post_content_length;
    char content[MAXLINE],data[MAXLINE];

    printf("Content-type: text/html\r\n\r\n");
    cgi_params = getenv("QUERY_STRING");

    script_path = argv[0];
    execl("/usr/local/php56/bin/php-cgi"."php-cgi",script_path,cgi_params,(void *)NULL);
    exit(1);
}Copy the code

Here, we don’t need PHP program to request our program, because we need to print $_GET method, so according to the above analysis, we can only use php-cgi or php-fpm to request our file script_path for the request PHP file cgi_params get parameter

/sapi/cgi/cgi_main.c 2280 /sapi/cgi/cgi_main.c

/* all remaining arguments are part of the query string * this section of code concatenates all remaining arguments * into a single string, separating args with a & * this allows command lines like: * * test.php v1=test v2=hello+world! * test.php "v1=test&v2=hello world!" * test.php v1=test "v2=hello world!" * /
if(! SG(request_info).query_string && argc > php_optind) {int slen = strlen(PG(arg_separator).input);
    len = 0;
    for (i = php_optind; i < argc; i++) {
        if (i < (argc - 1)) {
            len += strlen(argv[i]) + slen;
        } else {
            len+= strlen(argv[i]); }}len+ =2;
    s = malloc(len);
    *s = '\ 0';            /* we are pretending it came from the environment */
    for (i = php_optind; i < argc; i++) {
        strlcat(s, argv[i], len);
        if (i < (argc - 1)) {
            strlcat(s, PG(arg_separator).input, len);
        }
    }
    SG(request_info).query_string = s;
    free_query_string = 1;
}Copy the code

Ok, let’s take a look at the PHP page that displays the request and the browser that displays the result index.php

<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>Pengo's first Web server</title>
</head>
<body>

        
$array = array(
    "id"= >"1"."name"= >"pengge"."aaa"= >"sdsdd"."yes"= >"sdsdfsfsff"
);
echo "<pre>";
var_dump($_GET);
var_dump($_SERVER);
var_dump($array);
? >
</body>
</html>Copy the code

Let’s do a browser request





iamge

Rough data can be obtained or simulated to receive in $_GET mode

2. Tangled POST requests

To be honest, I never debugged the POST request properly, so I simply wrote this piece of code, which only implemented to get the POST data from the browser, but the CGI program to get the POST data from the buffer has never been implemented, which is why it has not been updated for a long time. I hope there is a god to debug this piece of code. The process of obtaining POST data

* CFD: client file descriptor * path: the requested file path * query: the data sent by the request, url? The latter data */
void request_cgi(int fd, const char* path, const char* query)
{
    char buf[MAXLINE],data[MAXLINE];
    char contlen_string[MAXLINE];
    int p[2];
    pid_t pid;
    int contlen = - 1; // The length of the packet
    char c;
    while(getfdline(fd, buf, sizeof(buf))>0){
        buf[15] = '\ 0';
        if(! strcasecmp(buf,"Content-Length:"))
            contlen = atoi(buf + 16);
    }
    if(contlen == - 1) {// Error packets are directly returned
        p_error("contlen error");
        return;
    }

    sprintf(buf, "HTTP / 1.0 200 OK \ r \ n");
    sprintf(buf, "%sServer: Tiny Web Server\r\n",buf);
    sprintf(buf, "%sContent-Type:text/html\r\n",buf);

    sprintf(contlen_string, "%d", contlen);
    setenv("CONTENT-LENGTH",contlen_string , 1);


    read(fd, data, contlen);
    printf("post data= %s\n",data);

    write(fd, buf, strlen(buf));
    dup2(fd,STDOUT_FILENO);
    execlp("./slow-cgi.cgi", path, (char *) NULL);
    exit(1);

}Copy the code

Read (fd, data, contlen); You can obtain the content of the POST request, as shown in the screenshot





image

I won’t write it until I pass the debugging, but I’ll show you the full code of the CGI program that gets the GET and POST requests

#define MAXLINE 1024

int main(int argc, char * argv[]) {
    char *cgi_params,*script_path;
    int post_content_length;
    char content[MAXLINE],data[MAXLINE];

    printf("Content-type: text/html\r\n\r\n");
    //Cgi_params = getenv("QUERY_STRING");

    if(getenv("CONTENT-LENGTH") != NULL) {
        //Post_content_length = atol(getenv("CONTENT-LENGTH"));
        printf("post_content_length=%d\n",post_content_length);

        //Get buffer content, that is, get post content fflush(stdin);while((fgets(data,post_content_length+1,stdin)) ! = NULL) { sprintf(content,"Info:%s\r\n",data);
            printf("Content-length: %lu\r\n", strlen(content));
            printf("Content-type: text/html\r\n\r\n");
            printf("%s", content);
            exit(1);
        }
        fflush(stdout);
        exit(0);
        exit(1);
        script_path = argv[0];
    }
    script_path = argv[0];
    execl("/usr/local/php56/bin/php-cgi"."php-cgi",script_path,cgi_params,(void *)NULL);
    exit(1);
}Copy the code

I will post all the code on Github, hoping you can get some help, and also hope you can help me solve the above problems