引言

在PHP中进行HTTP请求时,我们经常会遇到需要处理原始响应数据的情况。特别是当使用cURL库时,如果不正确设置选项,可能会得到包含HTTP头和响应体的混合内容,这会导致JSON解析失败。本文将通过一个实际案例,讲解如何正确处理HTTP响应并转换为数组。

问题场景

假设我们有一个cURL请求,返回了以下格式的响应:

1

2

3

4

5

6

7

8

9

10

11

HTTP/1.1 200 OK

Date: Thu, 25 Dec 2025 02:30:38 GMT

Content-Type: application/json;charset=UTF-8

Transfer-Encoding: chunked

Connection: keep-alive

Keep-Alive: timeout=25

Vary: Accept-Encoding

Server: nginx/1.24.0

X-Ca-Request-Id: E7534E5F-B033-4ABB-B00D-961E4D44855D

{"msg":"成功","success":true,"code":200,"data":{"continent":"亚洲","elevation":"17","country":"中国","city":"北京市","area_code":"CN","ip":"114.249","isp":"中国联通","latitude":"39.902798","city_code":"110000","time_zone":"UTC+8","zip_code":"","country_code":"CN","weather_station":"","province":"北京市","longitude":""}}

当我们尝试直接使用json_decode()解析时,会失败!因为响应包含了HTTP头和响应体两部分。

问题分析

问题的根源在于cURL设置中的这一行:

1

curl_setopt($curl, CURLOPT_HEADER, true);

CURLOPT_HEADER设置为true时,cURL会返回完整的HTTP响应,包括响应头。而我们真正需要处理的是响应体中的JSON数据。

解决方案

方案一:最简单的修复(推荐)

直接将CURLOPT_HEADER设置为false,这样cURL只返回响应体:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

<?php

// 初始化cURL

$curl = curl_init();

curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);

curl_setopt($curl, CURLOPT_URL, $url);

curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);

curl_setopt($curl, CURLOPT_FAILONERROR, false);

curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);

// 关键修改:设置为false,只获取响应体

curl_setopt($curl, CURLOPT_HEADER, false);

// 如果是HTTPS请求,跳过SSL验证(仅测试环境使用)

if (strpos($host, "https://") === 0) {

    curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);

    curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);

}

// 执行请求

$response = curl_exec($curl);

$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);

// 检查错误

if (curl_errno($curl)) {

    $error = curl_error($curl);

    // 错误处理逻辑

}

curl_close($curl);

// 直接解析JSON

$result = json_decode($response, true);

if (json_last_error() !== JSON_ERROR_NONE) {

    // JSON解析错误处理

    echo "JSON解析失败: " . json_last_error_msg();

} else {

    // 成功获取数组

    print_r($result);

}

?>

方案二:如果需要保留响应头信息

如果确实需要获取响应头信息,可以手动分离头和体:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

<?php

// ... cURL设置部分(保持CURLOPT_HEADER为true)

// 执行请求

$rawResponse = curl_exec($curl);

$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);

$headerSize = curl_getinfo($curl, CURLINFO_HEADER_SIZE);

// 分离响应头和响应体

$headers = substr($rawResponse, 0, $headerSize);

$body = substr($rawResponse, $headerSize);

curl_close($curl);

// 解析响应体JSON

$result = json_decode($body, true);

if (json_last_error() !== JSON_ERROR_NONE) {

    echo "JSON解析失败: " . json_last_error_msg();

} else {

    // 可以同时访问头信息和响应数据

    echo "HTTP状态码: " . $httpCode . "\n";

    echo "响应头大小: " . $headerSize . " 字节\n";

    print_r($result);

}

?>

方案三:使用辅助函数处理

创建一个通用的HTTP请求函数,包含完善的错误处理:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

<?php

function makeHttpRequest($url, $method = 'GET', $headers = [], $data = null) {

    $curl = curl_init();

     

    curl_setopt_array($curl, [

        CURLOPT_URL => $url,

        CURLOPT_RETURNTRANSFER => true,

        CURLOPT_ENCODING => '',

        CURLOPT_MAXREDIRS => 10,

        CURLOPT_TIMEOUT => 30,

        CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,

        CURLOPT_CUSTOMREQUEST => $method,

        CURLOPT_HTTPHEADER => $headers,

        CURLOPT_HEADER => false, // 不包含响应头

    ]);

     

    // 如果是POST请求,设置请求体

    if ($method === 'POST' && $data) {

        curl_setopt($curl, CURLOPT_POSTFIELDS, $data);

    }

     

    // 跳过SSL验证(仅限测试环境)

    if (strpos($url, 'https://') === 0) {

        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);

        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);

    }

     

    $response = curl_exec($curl);

    $httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);

    $error = curl_error($curl);

     

    curl_close($curl);

     

    if ($error) {

        throw new Exception("cURL请求失败: " . $error);

    }

     

    // 解析JSON响应

    $decodedResponse = json_decode($response, true);

     

    if (json_last_error() !== JSON_ERROR_NONE) {

        throw new Exception("JSON解析失败: " . json_last_error_msg());

    }

     

    return [

        'status' => $httpCode,

        'data' => $decodedResponse,

        'raw' => $response // 保留原始响应,便于调试

    ];

}

// 使用示例

try {

    $result = makeHttpRequest(

        'https://api.example.com/data',

        'GET',

        ['Content-Type: application/json']

    );

     

    echo "请求成功!状态码: " . $result['status'] . "\n";

    echo "返回的数据:\n";

    print_r($result['data']);

     

} catch (Exception $e) {

    echo "请求失败: " . $e->getMessage();

}

?>

完整的错误处理示例

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

<?php

function safeJsonDecode($jsonString) {

    $decoded = json_decode($jsonString, true);

     

    switch (json_last_error()) {

        case JSON_ERROR_NONE:

            return $decoded;

        case JSON_ERROR_DEPTH:

            throw new Exception('JSON解析错误: 超出最大堆栈深度');

        case JSON_ERROR_STATE_MISMATCH:

            throw new Exception('JSON解析错误: 无效或格式错误的JSON');

        case JSON_ERROR_CTRL_CHAR:

            throw new Exception('JSON解析错误: 控制字符错误');

        case JSON_ERROR_SYNTAX:

            throw new Exception('JSON解析错误: 语法错误');

        case JSON_ERROR_UTF8:

            throw new Exception('JSON解析错误: 无效的UTF-8字符');

        default:

            throw new Exception('JSON解析错误: 未知错误');

    }

}

// 使用示例

$response = '{"msg":"成功","success":true,"code":200,"data":{...}}';

try {

    $data = safeJsonDecode($response);

    echo "解析成功:\n";

    print_r($data);

} catch (Exception $e) {

    echo "错误: " . $e->getMessage();

}

?>

最佳实践建议

生产环境设置

  • 始终启用SSL验证
  • 设置合理的超时时间
  • 记录请求日志

代码健壮性

  • 始终检查cURL错误
  • 验证HTTP状态码
  • 处理JSON解析异常
  • 使用try-catch包装关键代码

性能优化

  • 重用cURL句柄(使用curl_multi_init处理多个请求)
  • 启用HTTP/2(如果服务器支持)
  • 考虑使用更现代的HTTP客户端库(如Guzzle)

更多推荐