2016年5月8日星期日

PHP fsockopen 發送 HTTP Request

fsockopen 是 PHP 的低階操作,能發送 HTTP Request 操作

一般使用 HTTP 連接是不需要給予 http 的協定描述
$handle = fsockopen($host, $port);
但使用 HTTPS 連接則需要使用 ssl 的協定描述
$handle = fsockopen("ssl://{$host}", 443);
留意 HTTPS 的連接埠必須使用 443

確保 fsockopen 能連接到才繼續 HTTP Request 的步驟,可以使用
if ($handle = fsockopen($host, $port)){
//    do something if connected
}

連接至伺服器後可以編寫 request 的 標頭及資料以傳送至伺服器
使用 fwrite, fputs 等方法傳送標頭及資料
fwrite($handle, "POST /path HTTP/1.1\n");
fwrite($handle, "Host: {$host}\n");
fwrite($handle, "User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:46.0) Gecko/20100101 Firefox/46.0\n");
fwrite($handle, "Connection: close\n");
fwrite($handle, "\n");
fwrite($handle, 'request-body');
傳送的 HTTP Request 必須符合 HTTP/1.1 格式
第一句是標明使用的方法,如: GET, POST, PUT, DELETE 及 來源位置 等標準 HTTP/1.1 方法
第二句開始是標頭,如 Host, User-Agent, Connection 等標頭,每個標頭的結束都需要加上 \n 換行
完成標頭,需要一個空行加上 \n 換行,確定標頭的部分已經完結
若有需要傳送 request 資料,在標頭完結後追加
若沒有 Connection: close 的標頭,會令傳送 HTTP Request 以為未完結,而結束標頭沒有使用 2個 \n 亦不能得知標頭已經完成
但 Connection: close 標頭則不一定是最後
(User-Agent 標頭可以到 ifconfig.me 尋找)

完成傳送標頭及資料後,需要使用 fread, fgets 等方法傳回 response 標頭及資料
$response = '';
while (!feof($handle)){
    $response .= fgets($handle, 1024);
}
fclose($handle);
echo $response;
傳回內容基本上是不分訊息、標頭及資料,必須自己區分
正常來說,訊息是第一行例如 HTTP/1.1 200 OK
之後是標頭,每個標頭會以 \r\n 結束,直至 \r\n\r\n (2組 \r\n)
第一組 \r\n\r\n 之後是資料
因此可以使用
$headers = explode("\r\n\r\n", $response, 2);
$responseBody = $headers[1];
$responseHeaders = explode("\r\n", $headers[0]);
$heads = explode(' ', array_shift($responseHeaders), 3);
$responseCode = $heads[1];
$responseMessage = $heads[2];
先以 \r\n\r\n 分割標頭及資料,但因為資料有機會存在 \r\n\r\n ,因此只分割 2組,所以 $headers[1] 便是資料
以 explode("\r\n", $headers[0]) 分割成 $responseHeaders
再以 array_shift($responseHeaders[0]) 獲得第一組資料並從原本的 $responseHeaders[0] 中移除,以 ' ' (1格空格) 區分訊息的編號及內容
同樣訊息有機會存在超過 2個 空格,因此只分割 3組, $heads[1] 是編號, $heads[2] 是資訊

若果閣下有留意,會發現在下發送的標頭只是使用 \n ,而傳回的標頭則是使用 \r\n
傳統的 HTTP/1.1 是使用 \r\n 作為標頭結束符號
通過 HTTP/1.1 19.3 Tolerant Applications 新修訂的 HTTP/1.1 是建議不再使用 \r\n 改為使用 \n
但若使用 \r\n 仍然有效,因此傳統的 HTTP/1.1 Response 資料,仍然會使用 \r\n
而且在下是指定使用 \n 而不是使用 PHP_EOL
由於不同作業系統的 PHP_EOL 會有分別:Windows 使用 \r\n 、 Mac 使用 \r 、 *nix 使用 \n ,若閣下使用 Mac 便會出錯

其實 PHP 還有 HTTP Request 操作如 file_get_contentsCURL
file_get_contents 除了能讀取本機的文件內容,其實都可以進行發送 HTTP Request 但必須使用 stream_context_create 才能根據 array 的內容發送
而 php-curl 還需要安裝 libcurl 才能讓 PHP 操作 CURL ,還需要記著一堆 CURL 欄位名稱,極不方便
因此才使用 fsockopen 這種低階操作又不需要記著太多額外資料就能發送 HTTP Request

沒有留言 :

發佈留言