大家可以教程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ù)串痘绎,例如:
這個地址$_GET 應(yīng)該是id=2所轉(zhuǎn)化的數(shù)組
2.$_POST
相比較$_GET不同津函,我們可以用firefox來看到$_POST所接收的值,例如是一個靜態(tài)界面,是一個提交表單
我們在2個輸入框輸入一些字段孤页,然后通過firebug-網(wǎng)絡(luò)查看post內(nèi)容
中看到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覽器請求一下
大致的數(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);
}