fopen on URL阻止PHP中的脚本执行

时间:2014-09-11 13:33:04

标签: php stream nonblocking

我试图在PHP中打开非阻塞流(5.3.2和5.4.4)。我做了以下事情:

$fp = fopen($url, 'r');
if ($fp === false)
    return false;
print('stream opened'.PHP_EOL);
stream_set_blocking($fp, 0);

网址指向php文件:

<?php sleep(10); ?>
<html><body>Hello</body></html>

问题是fopen()似乎在我甚至无法将流设置为非阻塞之前阻塞。实际上,stream opened消息在10秒后打印而不是直接打印。

1 个答案:

答案 0 :(得分:1)

在网址上执行fopen时,会在此时发送HTTP标头。由于没有上下文被定义(并且无法使用非阻塞选项配置上下文),fopen等待发送http报头并阻塞。 解决方法是使用fsockopen,它只打开tcp连接,不再执行任何操作。这种方法的缺点是必须手动创建HTTP请求。

这是一个(可优化的)实现,它以非阻塞的方式从URL中读取数据。

function parse_http_url($url)
{
    $parts = parse_url($url);
    if ($parts === false) return false;
    if (!isset($parts['scheme']))
        $parts['scheme'] = 'http';
    if ($parts['scheme'] !== 'http' && $parts['scheme'] !== 'https')
        return false;
    if (!isset($parts['port']))
        $parts['port'] = ($parts['scheme'] === 'http') ? 80 : 443;
    if(!isset($parts['path']))
        $parts['path'] = '/';
    $parts['uri'] = $parts['path'];
    if (!empty($parts['query']))
        $parts['uri'] .= '?'.$parts['query'];
    return $parts;
}

function url_get_contents($url, $options = null) {
    if(!($url_parts = parse_http_url($url))) return false;
    $timeout = intval(@$options['http']['timeout']);
    if (!($fp = fsockopen($url_parts['host'], $url_parts['port'], $errno, $errstr, $timeout))) return false;
    stream_set_blocking($fp, 0);
    if($timeout > 0) {
        stream_set_timeout($fp, $timeout);
        $sleep_time = (($timeout * 1000000) / 100); # 1% of timeout in ms
        $stop_time = microtime(true) + $timeout;
    } else {
        $sleep_time = 10000; # 10 ms
    }
    if (!isset($options['http']['method'])) $options['http']['method'] = 'GET';
    if (!isset($options['http']['header'])) $options['http']['header'] = '';
    $request = "{$options['http']['method']} {$url_parts['uri']} HTTP/1.1\r\n{$options['http']['header']}\r\n";
    if (fwrite($fp, $request) === false) {
        fclose($fp);
        return false;
    }
    $content = '';
    $buff_size = 4096;
    do {
        $rd = fread($fp, $buff_size);
        if ($rd === false) {
            fclose($fp);
            return false;
        }

        $content .= $rd;

        $meta = stream_get_meta_data($fp);
        if ($meta['eof']) {
            fclose($fp);
            if(empty($content)) return false;
            // HTTP headers should be separated with \r\n only but lets be safe
            $content = preg_split('/\r\n|\r|\n/', $content);
            $resp = explode(' ', array_shift($content));
            $code = isset($resp[1]) ? intval($resp[1]) : 0;
            if ($code < 200 || $code >= 300) {
                $message = isset($resp[2]) ? $resp[2] : 'Unknown error';
                trigger_error("Error {$code} {$message}", E_USER_WARNING);
                return false;
            }
            // Skip headers
            while (!empty($content) && array_shift($content) !== '');
            return implode("\n", $content);
        }
        if ($meta['timed_out']) {
            fclose($fp);
            return false;
        }
        if (isset($stop_time) && microtime(true) >= $stop_time) {
            fclose($fp);
            return false;
        }

        if ($meta['unread_bytes'] === 0) {
            usleep($sleep_time);
        }
    } while(true);
}