發出原始 HTTP 請求時如何輕鬆解碼 HTTP 分塊編碼字符串? (How to easily decode HTTP-chunked encoded string when making raw HTTP request?)


問題描述

發出原始 HTTP 請求時如何輕鬆解碼 HTTP 分塊編碼字符串? (How to easily decode HTTP-chunked encoded string when making raw HTTP request?)

I want to make HTTP request without having dependency to cURL and allow_url_fopen = 1 by opening socket connection and send raw HTTP request:

/**
 * Make HTTP GET request
 *
 * @param   string   the URL
 * @param   int      will be filled with HTTP response status code
 * @param   string   will be filled with HTTP response header
 * @return  string   HTTP response body
 */
function http_get_request($url, &$http_code = '', &$res_head = '') 
{
  $scheme = $host = $user = $pass = $query = $fragment = '';
  $path = '/';
  $port = substr($url, 0, 5) == 'https' ? 443 : 80;

  extract(parse_url($url)); 

  $path .= ($query ? "?$query" : '').($fragment ? "#$fragment" : '');

  $head = "GET $path HTTP/1.1\r\n"
        . "Host: $host\r\n"
        . "Authorization: Basic ".base64_encode("$user:$pass")."\r\n"
        . "Connection: close\r\n\r\n";

  $fp = fsockopen($scheme == 'https' ? "ssl://$host" : $host, $port) or 
    die('Cannot connect!');

  fputs($fp, $head);
  while(!feof($fp)) {
    $res .= fgets($fp, 4096);
  }
  fclose($fp);

  list($res_head, $res_body) = explode("\r\n\r\n", $res, 2);
  list(, $http_code, ) = explode(' ', $res_head, 3);

  return $res_body;
}

The function works ok, but since I'm using HTTP/1.1, the response body usually returned in Chunked-encoded string. For example (from Wikipedia):

25
This is the data in the first chunk

1C
and this is the second one

3
con
8
sequence
0

I don't want to use http_chunked_decode() since it has PECL dependency and I want a highly portable code.

How to easily decode HTTP-chunked encoded string so my function can return the original HTML? I also have to make sure that the length of the decoded string match with the Content-Length: header. 

Any help would be appreciated. Thanks.


參考解法

方法 1:

Since the function returns the HTTP response header, you should check if 'Transfer-Encoding' is 'chunked' then decode the chunked-encoded string. In pseudocode:

CALL parse_http_header
IF 'Transfer-Encoding' IS 'chunked'
  CALL decode_chunked

Parsing HTTP response header:

Below is the function to parse HTTP response header to associative array.

function parse_http_header($str) 
{
  $lines = explode("\r\n", $str);
  $head  = array(array_shift($lines));
  foreach ($lines as $line) {
    list($key, $val) = explode(':', $line, 2);
    if ($key == 'Set-Cookie') {
      $head['Set-Cookie'][] = trim($val);
    } else {
      $head[$key] = trim($val);
    }
  }
  return $head;
}

The function will return an array like this:

Array
(
    [0] => HTTP/1.1 200 OK
    [Expires] => Tue, 31 Mar 1981 05:00:00 GMT
    [Content-Type] => text/html; charset=utf-8
    [Transfer-Encoding] => chunked
    [Set-Cookie] => Array
        (
            [0] => k=10.34; path=/; expires=Sat, 09-Jun-12 01:58:23 GMT; domain=.example.com
            [1] => guest_id=v1%3A13; domain=.example.com; path=/; expires=Mon, 02-Jun-2014 13:58:23 GMT
        )
    [Content-Length] => 43560
)

Notice how the Set-Cookie headers parsed to array. You need to parse the cookies later to associate a URL with the cookies need to be sent.


Decode the chunked-encoded string

The function below take the chunked-encoded string as the argument, and return the decoded string.

function decode_chunked($str) {
  for ($res = ''; !empty($str); $str = trim($str)) {
    $pos = strpos($str, "\r\n");
    $len = hexdec(substr($str, 0, $pos));
    $res.= substr($str, $pos + 2, $len);
    $str = substr($str, $pos + 2 + $len);
  }
  return $res;
}

// Given the string in the question, the function above will returns:
//
// This is the data in the first chunk
// and this is the second one
// consequence

方法 2:

I don't know if it's optimal for you what you need to do but, if you specify HTTP/1.0 instead of HTTP/1.1, you will not get a chunked response.

方法 3:

this Function use in Wordpress.

function decode_chunked($data) {
    if (!preg_match('/^([0-9a-f]+)(?:;(?:[\w-]*)(?:=(?:(?:[\w-]*)*|"(?:[^\r\n])*"))?)*\r\n/i', trim($data))) {
        return $data;
    }



    $decoded = '';
    $encoded = $data;

    while (true) {
        $is_chunked = (bool) preg_match('/^([0-9a-f]+)(?:;(?:[\w-]*)(?:=(?:(?:[\w-]*)*|"(?:[^\r\n])*"))?)*\r\n/i', $encoded, $matches);
        if (!$is_chunked) {
            // Looks like it's not chunked after all
            return $data;
        }

        $length = hexdec(trim($matches[1]));
        if ($length === 0) {
            // Ignore trailer headers
            return $decoded;
        }

        $chunk_length = strlen($matches[0]);
        $decoded .= substr($encoded, $chunk_length, $length);
        $encoded = substr($encoded, $chunk_length + $length + 2);

        if (trim($encoded) === '0' || empty($encoded)) {
            return $decoded;
        }
    }

    // We'll never actually get down here
    // @codeCoverageIgnoreStart
}

(by flowfreeflowfreeOkonomiyaki3000user3770797)

參考文件

  1. How to easily decode HTTP-chunked encoded string when making raw HTTP request? (CC BY-SA 3.0/4.0)

#HTTP #PHP






相關問題

POST 和 PUT HTTP 請求有什麼區別? (What's the difference between a POST and a PUT HTTP REQUEST?)

發出原始 HTTP 請求時如何輕鬆解碼 HTTP 分塊編碼字符串? (How to easily decode HTTP-chunked encoded string when making raw HTTP request?)

如何通過 HttpRequest 從連接的 Android 設備訪問本地 tomcat (How to access local tomcat via HttpRequest from connected Android device)

Python 3, http.client HTTP памылка 400 (Python 3, http.client HTTP error 400)

Tập lệnh PHP có thể tiếp tục sau khi chuyển hướng không? (Is it possible for PHP script to continue after redirect?)

兩個進程使用同一個端口? (Two processes using the same port?)

Javascript HTTP 函數 (Javascript HTTP Function)

如何通過powershell獲取請求的authtoken (How to obtain authtoken for request via powershell)

我在哪裡將 REST 客戶端身份驗證數據放在查詢中? (Where do I put the REST Client Authentication Data in the Query?)

jquery get / post請求的Apache url替換 (Apache url replacement for jquery get / post request)

在 MATLAB 中通過 HTTP 接收數據 (Receive data via HTTP in MATLAB)

在 python 中開發時,如何在 post 請求中使用“format=json&data=”? (How do I use "format=json&data=" in post requests when developing in python?)







留言討論