用C一步步開發(fā)web服務(wù)器(5)

大家可以教程1胶惰,教程2傻工,教程3教程4中查看之前內(nèi)容孵滞。

本來這個系列告一段落了中捆,但是看到@指尖流年的評論中提到的關(guān)于PHP中$_GET以及$_POST取值的一些疑問,我也想搞清楚這塊的內(nèi)容坊饶,故我重新寫了一些代碼做了些測試以及一部分關(guān)于php源碼的探究與學(xué)習(xí)

  • $_POST $_GET 概述
  • $_POST $_GET php源碼中的封裝
  • 繼續(xù)完善web服務(wù)器泄伪,使其支持get,post請求匿级,并嘗試模擬$_GET蟋滴,$_POST方式獲取數(shù)據(jù)

$_POST $_GET 由來

1.$_GET:

在之前的教程中,我們可以很容易的知道$_GET的內(nèi)容是取自瀏覽器訪問地址?號后面的參數(shù)串痘绎,例如:

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

這個地址$_GET 應(yīng)該是id=2所轉(zhuǎn)化的數(shù)組

2.$_POST

相比較$_GET不同津函,我們可以用firefox來看到$_POST所接收的值,例如是一個靜態(tài)界面,是一個提交表單

image

我們在2個輸入框輸入一些字段孤页,然后通過firebug-網(wǎng)絡(luò)查看post內(nèi)容

image

中看到form data 就是post內(nèi)容

name=111&password=222&submit=submit

$_POST $_GET php源碼中的封裝

既然我們需要做出一個服務(wù)器來支持post跟get傳遞尔苦,我們需要分析$_GET跟$_POST的由來,而這一塊散庶,我們就需要去查看PHP源碼中涉及到的知識了蕉堰。套用Linus Torvalds的一句話

talk is cheap,show me the code

1.php源碼中對于get跟post的分析

一切從sapi開始,我們可以開始閱讀相關(guān)的php源碼悲龟,具體可以參考這個鏈接開始閱讀

http://www.php-internals.com/book/?p=chapt02/02-02-00-overview

我們跳過其他的代碼屋讶,直線從cgi部分入手,我們在php.-5.6-src/sapi/cgi 這個目錄下的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
};

好的 這塊很顯然我們需要2個字段,sapi_cgi_getenv以及sapi_cgi_read_post须教,嗯 一個是獲取環(huán)境變量斩芭,另一個是獲取post傳遞的數(shù)據(jù)的乐疆,借助一些之前掌握的知識,get接收的數(shù)據(jù)他是存儲在環(huán)境變量中,web服務(wù)器中通常是這樣存儲的

setenv("QUERY_STRING", cgi_params, 1);//cgi_params為url后面跟著的字符串

我們繼續(xù)往下看,看get跟post是怎么獲取的

  • sapi_cgi_getenv
static char *sapi_cgi_getenv(char *name, size_t name_len TSRMLS_DC)
{
    return getenv(name);
}
  • 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;
}

嗯猾骡,跟我們之前猜想的一樣,get就是用

getenv("query_string")

獲取的,而post就是獲取輸入緩沖區(qū)的data庆寺,我們可以用

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

而我看到很多例子是用

fgets(data,post_content_length+1,stdin);


與cgi_main.c類似的懦尝,我們可以在fpm中看到相同的結(jié)構(gòu)
/php.-5.6-src/sapi/fpm/fpm/fpm_main.c中看到一樣的。

借著說一句陵霉,我們知道php-cli模式也是可以進(jìn)行PHP代碼的請求與編譯的,單身php-cli模式并不支持get埃唯,post請求撩匕,大家知道原因嗎?我們通過源碼就能很容易看出來

  • php-cli模式也稱為embed模式墨叛,中文名叫做嵌入式模式,我們查看這塊源碼/php.-5.6-src/sapi/embed/php_embed.c中有這么一段
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
};

我們可以看到getenv以及read POST data這2個部分都是NULL止毕,所以可以得出php-cli模式是無法獲取get,post請求的


好了漠趁,回到主線扁凛,我們換個思路看,我們從一個php請求開始到結(jié)束中去尋找這2個變量的定義闯传,還是在cgi_main.c中去尋找

  • 繼續(xù)看cgi_sapi_module

在這個結(jié)構(gòu)體中谨朝,中間要實現(xiàn)的方法php_cgi_startup,這個方法是當(dāng)一個應(yīng)用要調(diào)用PHP的時候甥绿,這個函數(shù)會被調(diào)用字币,我們就會估計GET跟POST是在這個方法中聲明的,我們順著這個往下找共缕,在這個文件中是這樣定義的

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;
}

嗯洗出,我們繼續(xù)去找php_module_startup這個方法

于是在/main/main.c中的找到這個php_module_startup方法,我們在這個方法中找到下面這個方法

php_startup_auto_globals(TSRMLS_C);//2302行左右

我們?nèi)ふ疫@個方法

  • /main/php_variables.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);
}

哈哈,我們終于找到了图谷,那也就是說這個是php-cgi以及php-fpm等PHP內(nèi)核才封裝的這個$_GET以及$_POST 方法翩活,所以我們實現(xiàn)的cgi程序無法調(diào)用起$_GET,$_POST方法


繼續(xù)完善web服務(wù)器阱洪,使其支持get,post請求菠镇,并嘗試模擬$_GET冗荸,$_POST方式獲取數(shù)據(jù)

1.完善get請求

回到之前做的web服務(wù)器,在wrap_socket.c中php_cgi方法中利耍,設(shè)置query_string環(huán)境變量

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);
}

在cgi程序中獲取環(huán)境變量數(shù)據(jù)蚌本,就可以捕獲get請求啦

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);
}

這里,我們不用php程序去請求我們的程序堂竟,因為我們要打印$_GET 方法魂毁,所以根據(jù)上文分析的玻佩,我們只能用php-cgi 或者php-fpm去請求我們的文件
script_path 為請求的php文件
cgi_params 獲取到的get參數(shù)

這里為什么是這樣的請求方式在php源碼中也是有跡可循的出嘹,可以看這里/sapi/cgi/cgi_main.c 2280行左右

/* 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;
}

好的,我們看看展示請求的PHP頁面以及瀏覽器展示的結(jié)果
index.php

<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<title>鵬哥的第一個web服務(wù)器</title>
</head>
<body>
<?php
$array = array(
    "id" => "1",
    "name"=> "pengge",
    "aaa" => "sdsdd",
    "yes" => "sdsdfsfsff"
);
echo "<pre>";
var_dump($_GET);
var_dump($_SERVER);
var_dump($array);
?>
</body>
</html>

我們?yōu)g覽器請求一下


iamge

大致的數(shù)據(jù)可以得到咬崔,也可以模擬到$_GET方式的接收

2.糾結(jié)的post請求

實話說垮斯,post請求我一直沒有調(diào)試好,所以我就簡單的寫了這塊的代碼兜蠕,只實現(xiàn)了從瀏覽器獲取到post數(shù)據(jù),但是讓cgi程序從緩沖區(qū)獲取post數(shù)據(jù)一直沒有實現(xiàn)曙旭,這也是隔了好久沒有更新的原因桂躏,希望有大神可以吧這塊調(diào)試出來川陆。较沪。
獲取post數(shù)據(jù)的過程

/*
 * 處理客戶端的http請求.
 * cfd      : 客戶端文件描述符
 * path     : 請求的文件路徑
 * query    : 請求發(fā)送的過來的數(shù)據(jù), url ? 后面那些數(shù)據(jù)
 */
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; //報文長度
    char c;
    while(getfdline(fd, buf, sizeof(buf))>0){
        buf[15] = '\0';
        if(!strcasecmp(buf, "Content-Length:"))
            contlen = atoi(buf + 16);
    }
    if(contlen == -1){ //錯誤的報文,直接返回錯誤結(jié)果
        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);
  
}

通過read(fd, data, contlen);就可以獲取到post請求的內(nèi)容,截圖為


image

后面的調(diào)試不通過们何,我就不寫了骡苞,最后展示下cgi程序獲取get以及post請求的全代碼

#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");
    //獲取get數(shù)據(jù)
    cgi_params = getenv("QUERY_STRING");
    
    if(getenv("CONTENT-LENGTH") != NULL) {
        //獲取post長度
        post_content_length = atol(getenv("CONTENT-LENGTH"));
        printf("post_content_length=%d\n",post_content_length);
        
        //獲取緩沖區(qū)內(nèi)容,也就是獲取post內(nèi)容
        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);
}

具體的所有代碼我會放在github上烘苹,希望大家能夠得到些許幫助镣衡,也希望大家能夠幫我解決上面的問題原文章鏈接

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末廊鸥,一起剝皮案震驚了整個濱河市惰说,隨后出現(xiàn)的幾起案子缘回,更是在濱河造成了極大的恐慌酥宴,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件授滓,死亡現(xiàn)場離奇詭異般堆,居然都是意外死亡擎宝,警方通過查閱死者的電腦和手機绍申,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進(jìn)店門极阅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人仆百,你說我怎么就攤上這事奔脐。” “怎么了峦朗?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵波势,是天一觀的道長。 經(jīng)常有香客問我拴曲,道長凛忿,這世上最難降的妖魔是什么侄非? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任逞怨,我火速辦了婚禮叠赦,結(jié)果婚禮上革砸,老公的妹妹穿的比我還像新娘。我一直安慰自己册踩,他們只是感情好效拭,可當(dāng)我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布缎患。 她就那樣靜靜地躺著挤渔,像睡著了一般。 火紅的嫁衣襯著肌膚如雪嫉父。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天稽鞭,我揣著相機與錄音引镊,去河邊找鬼。 笑死吩抓,一個胖子當(dāng)著我的面吹牛赴恨,可吹牛的內(nèi)容都是我干的伦连。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼额港,長吁一口氣:“原來是場噩夢啊……” “哼移斩!你這毒婦竟也來了绢馍?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤猖任,失蹤者是張志新(化名)和其女友劉穎朱躺,沒想到半個月后哺徊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡盈滴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年病苗,在試婚紗的時候發(fā)現(xiàn)自己被綠了硫朦。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片背镇。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡瞒斩,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出祷舀,到底是詐尸還是另有隱情烹笔,我是刑警寧澤,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布饰豺,位于F島的核電站哟忍,受9級特大地震影響陷寝,放射性物質(zhì)發(fā)生泄漏凤跑。R本人自食惡果不足惜叛复,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一褐奥、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧儿倒,春花似錦、人聲如沸彻犁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽森篷。三九已至仲智,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間坎藐,已是汗流浹背哼绑。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工抖韩, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人双谆。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓顽馋,卻偏偏與公主長得像幌羞,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子熊痴,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內(nèi)容