• curl 中关于 CURLINFO_HEADER_SIZE 的 BUG 定位及修复


    curl 官方下载页面

    CentOS7 默认安装的 curl 版本太低了,需要升级为最新版。

    1. 问题描述

    对接了一个接口,用来下载 PDF 文件。使用 curl 下载后,文件老是报错无法打开。接口提供方直接返回的 PDF 二进制文件流,而没有放入某个字段中或经过 base64 编码。

    负责下载的部分代码如下:

        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $this->get_service_url('General'));
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); // 总是会返回原生的(Raw)内容
        curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, array("Content-Type:application/xml"));
        curl_setopt($ch, CURLOPT_POSTFIELDS, $request_xml);
        $response = curl_exec($ch);
    
        $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE); //头信息大小,curl 在这里有 BUG,需要升级到7.4 或 7.5
        $body = substr($response, $headerSize);
    
        $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);
    ...
    }

    接口返回报文:

    HTTP/1.1 200 OK
    Date: Mon, 14 May 2018 02:57:40 GMT
    Server: Apache/2.4.33 (Win64) OpenSSL/1.1.0g
    Content-Type: application/pdf
    X-Powered-By: Servlet/2.5 JSP/2.1
    Set-Cookie: JSESSIONID=HrNclQWb5KqehhTfsHl6tMAXRkdXHCuodVA3IWLnA8MbnYC2bQlJ!-2087621404; path=/; HttpOnly
    Transfer-Encoding: chunked

    通过 Fiddler 模拟请求的返回报文:

    HTTP/1.1 200 OK
    Date: Mon, 14 May 2018 01:31:12 GMT
    Server: Apache/2.4.33 (Win64) OpenSSL/1.1.0g
    Content-Type: application/pdf
    X-Powered-By: Servlet/2.5 JSP/2.1
    Set-Cookie: JSESSIONID=DGRcRdwrflVjDkUKAW-kCz6JnXKuVWFhEGXk9eNtDnfd-NNfH4jO!-2087621404; path=/; HttpOnly
    Content-Length: 955521
    
    %PDF-1.4
    %    
    2 0 obj
    <</Length 3857/Filter[/ASCII85Decode/FlateDecode]>>stream
    Gb!Sn9<0VQ&`-m)s'aK0)=.6O&Eq/V+M3`I$XP-a8Vd]C-@6[V(/J@1#mTh_)NG2FAGOZI4EcO
    ...

    2. 查找问题根源

    一番搜索后,发现了 PHP 中记录的 这个 BUG:如果使用了代理,此时 CURLINFO_HEADER_SIZE 字段会得到错误的头部长度。使用代理的情况下,返回的 HTTP Header 会多一行记录,导致头部长度计算错误:

    HTTP/1.0 200 Connection established
    
    HTTP/1.1 200 OK
    Content-Type: text/html; charset=UTF-8
    Date: Thu, 03 Jan 2013 15:54:35 GMT
    Server: Apache/2.2.22 (Amazon)
    X-Powered-By: PHP/5.3.19
    Content-Length: 15
    Connection: Keep-alive

    然后看一下下面的回复,是 curl 的问题。问题早就解决了,但是需要升级 curl 到 7.4 或更高版本。

    我的报文没有使用代理啊。没多一行,但是还是先把 curl 升级了吧。查一下 CentOS7 使用的版本:

    [root@VM_120_242_centos curl-7.59.0]# curl --version
    curl 7.29.0 (x86_64-redhat-linux-gnu) libcurl/7.29.0 NSS/3.19.1 Basic ECC zlib/1.2.7 libidn/1.28 libssh2/1.4.3
    Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtsp scp sftp smtp smtps telnet tftp 
    Features: AsynchDNS GSS-Negotiate IDN IPv6 Largefile NTLM NTLM_WB SSL libz 

    通过 CLI 调用 print_r 方法打印 curl 的版本,确认 PHP 使用的就是这个版本的 curl:了:

    [root@VM_200_22_centos 1300_al]# php -r 'print_r( curl_version());'
    Array
    (
        [version_number] => 466176
        [age] => 3
        [features] => 34493
        [ssl_version_number] => 0
        [version] => 7.29.0
        [host] => x86_64-redhat-linux-gnu
        [ssl_version] => NSS/3.19.1 Basic ECC
        [libz_version] => 1.2.7
        [protocols] => Array
            (
                [0] => dict
                ...
            )
    )

    3. 升级 curl

    升级 curl 到 7.4 或更高版本。在这里下载:curl 官方下载页面

    可以通过 yum 或源码安装,建议使用 yum,简单靠谱。

    通过 yum 安装

    1 添加 yum 仓库源

    新建文件:

    vim /etc/yum.repos.d/city-fan.repo

    复制下面内容到这个文件中:

    [CityFan]
    name=City Fan Repo
    baseurl=http://www.city-fan.org/ftp/contrib/yum-repo/rhel$releasever/$basearch/
    enabled=1
    gpgcheck=0

    2 清除 yum 缓存

    清除缓存目录下的软件包及旧的 headers:

    yum clean all

    3 安装

    yum install curl 

    4 确认

    再次执行 curl --version 检查是否更新成功:

    [root@VM_120_242_centos soft]# curl --version
    curl 7.59.0 (x86_64-redhat-linux-gnu) libcurl/7.59.0 NSS/3.28.4 zlib/1.2.7 libpsl/0.7.0 (+libicu/50.1.2) libssh2/1.8.0 nghttp2/1.31.1
    Release-Date: 2018-03-14
    Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtsp scp sftp smb smbs smtp smtps telnet tftp 
    Features: AsynchDNS IPv6 Largefile GSS-API Kerberos SPNEGO NTLM NTLM_WB SSL libz HTTP2 UnixSockets HTTPS-proxy PSL Metalink

    5 重启所有依赖 curl 的软件

    这里 PHP-FPM 需要重启:

    systemctl restart php-fpm

    通过源码安装(我是没成功)

    1 下载

    wget https://curl.haxx.se/download/curl-7.59.0.tar.gz

    2 解压

    tar -xzvf  curl-7.59.0.tar.gz

    3 覆盖安装

    cd curl-7.59.0
    ./configure
    make
    make install

    4. 根本原因

    PHP 中使用 curl 时,有这个一个选项:

    curl_setopt($ch, CURLOPT_HEADER, 1); // 启用时会将头文件的信息作为数据流输出。

    如果没有开启 CURLOPT_HEADER 这个选项,通过

    $response = curl_exec($ch);

    获取 curl 执行结果时,就是报文体了,不需要再分离 header 和 body,直接使用即可。所以代码修改如下:

            $ch = curl_init();
            curl_setopt($ch, CURLOPT_URL, $this->get_service_url('General'));
            curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); // 总是会返回原生的(Raw)内容。
            curl_setopt($ch, CURLOPT_HEADER, 1); // 启用时会将头文件的信息作为数据流输出。
            curl_setopt($ch, CURLOPT_POST, true);
            curl_setopt($ch, CURLOPT_HTTPHEADER, array("Content-Type:application/xml"));
            curl_setopt($ch, CURLOPT_POSTFIELDS, $request_xml);
            $response = curl_exec($ch);
    
            //分离 header 与 body
            $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE); //头信息 size,curl 需要升级到7.4 或 7.5
            $header = substr($response, 0, $headerSize);
            $body = substr($response, $headerSize);

            $ch = curl_init();
            curl_setopt($ch, CURLOPT_URL, $this->get_service_url('General'));
            curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); // 总是会返回原生的(Raw)内容。
            curl_setopt($ch, CURLOPT_POST, true);
            curl_setopt($ch, CURLOPT_HTTPHEADER, array("Content-Type:application/xml"));
            curl_setopt($ch, CURLOPT_POSTFIELDS, $request_xml);
            $response = curl_exec($ch);
    
            //$response 就是报文体,不需要分离 header 与 body
            $body = $response;

    总之,

    • 如果不需要 header,则不需要开启 CURLOPT_HEADER 选项,直接读 curl 的响应信息即可。
    • 如果需要 header,则开启 CURLOPT_HEADER 选项,此时需要分离 header 和 body。
  • 相关阅读:
    用实例来说明linux命令sort的用法
    shell 编程入门
    VMware sphere的使用
    linux进阶
    Windows系统下的TCP参数优化
    RFID Technology(上)——简介、市场应用与前景、工作原理
    RFID Technology(下)——面临的风险、安全与隐私策略
    TCP连接的状态与关闭方式,及其对Server与Client的影响
    Java Map遍历方式的选择
    博客处女贴
  • 原文地址:https://www.cnblogs.com/kika/p/10851619.html
Copyright © 2020-2023  润新知