如何在PHP中创建异步GET请求?

时间:2009-06-07 21:53:04

标签: php http curl asynchronous

我希望对另一台服务器上的另一个脚本发出一个简单的GET请求。我该怎么做?

在一种情况下,我只需要请求外部脚本而无需任何输出。

make_request('http://www.externalsite.com/script1.php?variable=45'); //example usage

在第二种情况下,我需要输出文本。

$output = make_request('http://www.externalsite.com/script2.php?variable=45');
echo $output; //string output

说实话,我不想乱用CURL,因为这不是CURL的工作。我也不想使用http_get,因为我没有PECL扩展。

fsockopen会工作吗?如果是这样,如何在不读取文件内容的情况下执行此操作?没有别的办法吗?

全部谢谢

更新

我应该补充一下,在第一种情况下,我不想等待脚本返回任何内容。据我所知,file_get_contents()将等待页面完全加载等?

22 个答案:

答案 0 :(得分:50)

file_get_contents会做你想做的事情

$output = file_get_contents('http://www.example.com/');
echo $output;

编辑:启动GET请求并立即返回的一种方法。

引自http://petewarden.typepad.com/searchbrowser/2008/06/how-to-post-an.html

function curl_post_async($url, $params)
{
    foreach ($params as $key => &$val) {
      if (is_array($val)) $val = implode(',', $val);
        $post_params[] = $key.'='.urlencode($val);
    }
    $post_string = implode('&', $post_params);

    $parts=parse_url($url);

    $fp = fsockopen($parts['host'],
        isset($parts['port'])?$parts['port']:80,
        $errno, $errstr, 30);

    $out = "POST ".$parts['path']." HTTP/1.1\r\n";
    $out.= "Host: ".$parts['host']."\r\n";
    $out.= "Content-Type: application/x-www-form-urlencoded\r\n";
    $out.= "Content-Length: ".strlen($post_string)."\r\n";
    $out.= "Connection: Close\r\n\r\n";
    if (isset($post_string)) $out.= $post_string;

    fwrite($fp, $out);
    fclose($fp);
}

这样做是打开一个套接字,触发一个get请求,然后立即关闭套接字并返回。

答案 1 :(得分:32)

这是如何让Marquis的回答同时适用于POST和GET请求:

  // $type must equal 'GET' or 'POST'
  function curl_request_async($url, $params, $type='POST')
  {
      foreach ($params as $key => &$val) {
        if (is_array($val)) $val = implode(',', $val);
        $post_params[] = $key.'='.urlencode($val);
      }
      $post_string = implode('&', $post_params);

      $parts=parse_url($url);

      $fp = fsockopen($parts['host'],
          isset($parts['port'])?$parts['port']:80,
          $errno, $errstr, 30);

      // Data goes in the path for a GET request
      if('GET' == $type) $parts['path'] .= '?'.$post_string;

      $out = "$type ".$parts['path']." HTTP/1.1\r\n";
      $out.= "Host: ".$parts['host']."\r\n";
      $out.= "Content-Type: application/x-www-form-urlencoded\r\n";
      $out.= "Content-Length: ".strlen($post_string)."\r\n";
      $out.= "Connection: Close\r\n\r\n";
      // Data goes in the request body for a POST request
      if ('POST' == $type && isset($post_string)) $out.= $post_string;

      fwrite($fp, $out);
      fclose($fp);
  }

答案 2 :(得分:13)

关于您的更新,关于不想等待整页加载 - 我认为您正在寻找HTTP HEAD请求。

get_headers应该这样做 - 我认为它只会请求标题,因此不会发送整页内容。

"PHP / Curl: HEAD Request takes a long time on some sites"介绍了如何使用PHP / Curl

执行HEAD请求

如果你想触发请求,而不是完全阻止脚本,有几种不同的复杂性方法。

  • 执行HTTP请求作为后台进程php execute a background process - 基本上你会执行像"wget -O /dev/null $carefully_escaped_url"这样的事情 - 这将是特定于平台的,你必须真的小心关于向命令转义参数
  • Executing a PHP script in the background - 与UNIX进程方法基本相同,但执行PHP脚本而不是shell命令
  • 拥有一个“作业队列”,使用数据库(或类似beanstalkd的东西可能有点过分)。您向队列添加URL,后台进程或cron-job定期检查新作业并对URL执行请求

答案 3 :(得分:6)

你没有。虽然PHP提供了许多调用URL的方法,但它并不提供对每个请求/执行周期进行任何类型的异步/线程处理的开箱即用支持。发送URL(或SQL语句等)请求的任何方法都将等待某种类型的响应。您需要在本地计算机上运行某种辅助系统才能实现此目的(谷歌周围的“PHP作业队列”)

答案 4 :(得分:6)

我建议你测试一下PHP库:curl-easy

<?php
$request = new cURL\Request('http://www.externalsite.com/script2.php?variable=45');
$request->getOptions()
    ->set(CURLOPT_TIMEOUT, 5)
    ->set(CURLOPT_RETURNTRANSFER, true);

// add callback when the request will be completed
$request->addListener('complete', function (cURL\Event $event) {
    $response = $event->response;
    $content = $response->getContent();
    echo $content;
});

while ($request->socketPerform()) {
    // do anything else when the request is processed
}

答案 5 :(得分:4)

function make_request($url, $waitResult=true){
    $cmi = curl_multi_init();

    $curl = curl_init();
    curl_setopt($curl, CURLOPT_URL, $url);
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);

    curl_multi_add_handle($cmi, $curl);

    $running = null;
    do {
        curl_multi_exec($cmi, $running);
        sleep(.1);
        if(!$waitResult)
        break;
    } while ($running > 0);
    curl_multi_remove_handle($cmi, $curl);
    if($waitResult){
        $curlInfos = curl_getinfo($curl);
        if((int) $curlInfos['http_code'] == 200){
            curl_multi_close($cmi);
            return curl_multi_getcontent($curl);
        }
    }
    curl_multi_close($cmi);
}

答案 6 :(得分:3)

有趣的问题。我猜你只是想在其他服务器上触发一些进程或操作,但不关心结果是什么,并希望你的脚本继续。 cURL中可能存在可以实现此目的的一些内容,但是如果cURL无法执行此操作,您可能需要考虑使用exec()在执行调用的服务器上运行另一个脚本。 (通常人们想要脚本调用的结果,所以我不确定PHP是否能够只触发该过程。)使用exec(),您可以运行wget甚至另一个PHP脚本请求file_get_conents()

答案 7 :(得分:2)

您最好考虑使用Message Queues而不是建议的方法。 我相信这将是更好的解决方案,尽管它需要的工作量不仅仅是发送请求。

答案 8 :(得分:2)

让我告诉你我的方式:)

需要在服务器上安装nodejs

(我的服务器发送1000 https get请求仅需2秒)

url.php:

<?
$urls = array_fill(0, 100, 'http://google.com/blank.html');

function execinbackground($cmd) { 
    if (substr(php_uname(), 0, 7) == "Windows"){ 
        pclose(popen("start /B ". $cmd, "r"));  
    } 
    else { 
        exec($cmd . " > /dev/null &");   
    } 
} 
fwite(fopen("urls.txt","w"),implode("\n",$urls);
execinbackground("nodejs urlscript.js urls.txt");
// { do your work while get requests being executed.. }
?>

urlscript.js&gt;

var https = require('https');
var url = require('url');
var http = require('http');
var fs = require('fs');
var dosya = process.argv[2];
var logdosya = 'log.txt';
var count=0;
http.globalAgent.maxSockets = 300;
https.globalAgent.maxSockets = 300;

setTimeout(timeout,100000); // maximum execution time (in ms)

function trim(string) {
    return string.replace(/^\s*|\s*$/g, '')
}

fs.readFile(process.argv[2], 'utf8', function (err, data) {
    if (err) {
        throw err;
    }
    parcala(data);
});

function parcala(data) {
    var data = data.split("\n");
    count=''+data.length+'-'+data[1];
    data.forEach(function (d) {
        req(trim(d));
    });
    /*
    fs.unlink(dosya, function d() {
        console.log('<%s> file deleted', dosya);
    });
    */
}


function req(link) {
    var linkinfo = url.parse(link);
    if (linkinfo.protocol == 'https:') {
        var options = {
        host: linkinfo.host,
        port: 443,
        path: linkinfo.path,
        method: 'GET'
    };
https.get(options, function(res) {res.on('data', function(d) {});}).on('error', function(e) {console.error(e);});
    } else {
    var options = {
        host: linkinfo.host,
        port: 80,
        path: linkinfo.path,
        method: 'GET'
    };        
http.get(options, function(res) {res.on('data', function(d) {});}).on('error', function(e) {console.error(e);});
    }
}


process.on('exit', onExit);

function onExit() {
    log();
}

function timeout()
{
console.log("i am too far gone");process.exit();
}

function log() 
{
    var fd = fs.openSync(logdosya, 'a+');
    fs.writeSync(fd, dosya + '-'+count+'\n');
    fs.closeSync(fd);
}

答案 9 :(得分:2)

如果您使用的是Linux环境,则可以使用PHP的exec命令来调用linux curl。这是一个示例代码,它将生成异步HTTP帖子。

function _async_http_post($url, $json_string) {
  $run = "curl -X POST -H 'Content-Type: application/json'";
  $run.= " -d '" .$json_string. "' " . "'" . $url . "'";
  $run.= " > /dev/null 2>&1 &";
  exec($run, $output, $exit);
  return $exit == 0;
}

此代码不需要任何额外的PHP库,它可以在不到10毫秒的时间内完成http帖子。

答案 10 :(得分:2)

对我来说,出现了关于异步GET请求的问题,因为我遇到了需要做数百个请求的情况,在每次请求时获取并处理结果数据并且每个请求都需要执行毫秒执行,这导致使用简单file_get_contents完成执行的分钟(!)。

在这种情况下,在php.net上的 w_haigh 对函数http://php.net/manual/en/function.curl-multi-init.php进行了非常有用的评论

所以,这是我同时提出大量请求的升级和清理版本。 对于我的情况,它相当于“异步”方式。可能对某人有帮助!

// Build the multi-curl handle, adding both $ch
$mh = curl_multi_init();

// Build the individual requests, but do not execute them
$chs = [];
$chs['ID0001'] = curl_init('http://webservice.example.com/?method=say&word=Hello');
$chs['ID0002'] = curl_init('http://webservice.example.com/?method=say&word=World');
// $chs[] = ...
foreach ($chs as $ch) {
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,  // Return requested content as string
        CURLOPT_HEADER => false,         // Don't save returned headers to result
        CURLOPT_CONNECTTIMEOUT => 10,    // Max seconds wait for connect
        CURLOPT_TIMEOUT => 20,           // Max seconds on all of request
        CURLOPT_USERAGENT => 'Robot YetAnotherRobo 1.0',
    ]);

    // Well, with a little more of code you can use POST queries too
    // Also, useful options above can be  CURLOPT_SSL_VERIFYHOST => 0  
    // and  CURLOPT_SSL_VERIFYPEER => false ...

    // Add every $ch to the multi-curl handle
    curl_multi_add_handle($mh, $ch);
}

// Execute all of queries simultaneously, and continue when ALL OF THEM are complete
$running = null;
do {
    curl_multi_exec($mh, $running);
} while ($running);

// Close the handles
foreach ($chs as $ch) {
    curl_multi_remove_handle($mh, $ch);
}
curl_multi_close($mh);

// All of our requests are done, we can now access the results
// With a help of ids we can understand what response was given
// on every concrete our request
$responses = [];
foreach ($chs as $id => $ch) {
    $responses[$id] = curl_multi_getcontent($ch);
    curl_close($ch);
}
unset($chs); // Finita, no more need any curls :-)

print_r($responses); // output results

很容易重写它来处理POST或其他类型的HTTP(S)请求或它们的任何组合。和Cookie支持,重定向,http-auth等

答案 11 :(得分:1)

我发现这个有趣的链接可以进行异步处理(获取请求)。

askapache

此外,您可以使用消息队列(例如beanstalkd。

)进行异步处理

答案 12 :(得分:1)

尝试:

//Your Code here
$pid = pcntl_fork();
if ($pid == -1) {
     die('could not fork');
}
else if ($pid)
{
echo("Bye")  
}
else
{
     //Do Post Processing
}

这不能用作apache模块,你需要使用CGI。

答案 13 :(得分:1)

以下是对执行简单GET请求的已接受答案的修改。

有一点需要注意,如果服务器进行任何网址重写,这将无法正常工作。您需要使用功能更全面的http客户端。

  /**
   * Performs an async get request (doesn't wait for response)
   * Note: One limitation of this approach is it will not work if server does any URL rewriting
   */
  function async_get($url)
  {
      $parts=parse_url($url);

      $fp = fsockopen($parts['host'],
          isset($parts['port'])?$parts['port']:80,
          $errno, $errstr, 30);

      $out = "GET ".$parts['path']." HTTP/1.1\r\n";
      $out.= "Host: ".$parts['host']."\r\n";
      $out.= "Connection: Close\r\n\r\n";
      fwrite($fp, $out);
      fclose($fp);
  }

答案 14 :(得分:1)

上面发布的脚本只做了一些修改。以下是为我工作

function curl_request_async($url, $params, $type='GET')
    {
        $post_params = array();
        foreach ($params as $key => &$val) {
            if (is_array($val)) $val = implode(',', $val);
            $post_params[] = $key.'='.urlencode($val);
        }
        $post_string = implode('&', $post_params);

        $parts=parse_url($url);
        echo print_r($parts, TRUE);
        $fp = fsockopen($parts['host'],
            (isset($parts['scheme']) && $parts['scheme'] == 'https')? 443 : 80,
            $errno, $errstr, 30);

        $out = "$type ".$parts['path'] . (isset($parts['query']) ? '?'.$parts['query'] : '') ." HTTP/1.1\r\n";
        $out.= "Host: ".$parts['host']."\r\n";
        $out.= "Content-Type: application/x-www-form-urlencoded\r\n";
        $out.= "Content-Length: ".strlen($post_string)."\r\n";
        $out.= "Connection: Close\r\n\r\n";
        // Data goes in the request body for a POST request
        if ('POST' == $type && isset($post_string)) $out.= $post_string;
        fwrite($fp, $out);
        fclose($fp);
    }

答案 15 :(得分:1)

似乎没有人提到Guzzle,这是一个PHP HTTP客户端,可以轻松发送HTTP请求。它可以使用或不使用data _null_; if eof then call symputx('num_or',_n_-1); set CondensedOverrides4 end=eof ; call symputx(cats('Item',_n_),rule,'g'); run; 。它可以发送同步和异步请求。

Curl

答案 16 :(得分:0)

基于这个线程,我为我的codeigniter项目做了这个。它工作得很好。您可以在后台处理任何功能。

接受异步调用的控制器。

class Daemon extends CI_Controller
{
    // Remember to disable CI's csrf-checks for this controller

    function index( )
    {
        ignore_user_abort( 1 );
        try
        {
            if ( strcmp( $_SERVER['REMOTE_ADDR'], $_SERVER['SERVER_ADDR'] ) != 0 && !in_array( $_SERVER['REMOTE_ADDR'], $this->config->item( 'proxy_ips' ) ) )
            {
                log_message( "error", "Daemon called from untrusted IP-address: " . $_SERVER['REMOTE_ADDR'] );
                show_404( '/daemon' );
                return;
            }

            $this->load->library( 'encrypt' );
            $params = unserialize( urldecode( $this->encrypt->decode( $_POST['data'] ) ) );
            unset( $_POST );
            $model = array_shift( $params );
            $method = array_shift( $params );
            $this->load->model( $model );
            if ( call_user_func_array( array( $this->$model, $method ), $params ) === FALSE )
            {
                log_message( "error", "Daemon could not call: " . $model . "::" . $method . "()" );
            }
        }
        catch(Exception $e)
        {
            log_message( "error", "Daemon has error: " . $e->getMessage( ) . $e->getFile( ) . $e->getLine( ) );
        }
    }
}

执行异步调用的库

class Daemon
{
    public function execute_background( /* model, method, params */ )
    {
        $ci = &get_instance( );
        // The callback URL (its ourselves)
        $parts = parse_url( $ci->config->item( 'base_url' ) . "/daemon" );
        if ( strcmp( $parts['scheme'], 'https' ) == 0 )
        {
            $port = 443;
            $host = "ssl://" . $parts['host'];
        }
        else 
        {
            $port = 80;
            $host = $parts['host'];
        }
        if ( ( $fp = fsockopen( $host, isset( $parts['port'] ) ? $parts['port'] : $port, $errno, $errstr, 30 ) ) === FALSE )
        {
            throw new Exception( "Internal server error: background process could not be started" );
        }
        $ci->load->library( 'encrypt' );
        $post_string = "data=" . urlencode( $ci->encrypt->encode( serialize( func_get_args( ) ) ) );
        $out = "POST " . $parts['path'] . " HTTP/1.1\r\n";
        $out .= "Host: " . $host . "\r\n";
        $out .= "Content-Type: application/x-www-form-urlencoded\r\n";
        $out .= "Content-Length: " . strlen( $post_string ) . "\r\n";
        $out .= "Connection: Close\r\n\r\n";
        $out .= $post_string;
        fwrite( $fp, $out );
        fclose( $fp );
    }
}

可以调用此方法来处理'background'中的任何model :: method()。它使用变量参数。

$this->load->library('daemon');
$this->daemon->execute_background( 'model', 'method', $arg1, $arg2, ... );

答案 17 :(得分:0)

建议:格式化一个FRAMESET HTML页面,其中包含内部9帧。每个框架将获取myapp.php页面的不同“实例”。 Web服务器上将同时运行9个不同的线程。

答案 18 :(得分:0)

对于PHP5.5 +, mpyw/co 是最终的解决方案。它的工作方式就像JavaScript中的tj/co一样。

实施例

假设您要下载指定的多个GitHub用户的头像。每个用户都需要执行以下步骤。

  1. 获取http://github.com/mpyw(GET HTML)
  2. 的内容
  3. 查找<img class="avatar" src="...">并请求(获取图片)
  4. ---:等待我的回复
    ...:在并行流程中等待其他响应

    许多着名的基于curl_multi的脚本已经为我们提供了以下流程。

            /-----------GET HTML\  /--GET IMAGE.........\
           /                     \/                      \ 
    [Start] GET HTML..............----------------GET IMAGE [Finish]
           \                     /\                      /
            \-----GET HTML....../  \-----GET IMAGE....../
    

    然而,这还不够有效。您想减少无用的等待时间...吗?

            /-----------GET HTML--GET IMAGE\
           /                                \            
    [Start] GET HTML----------------GET IMAGE [Finish]
           \                                /
            \-----GET HTML-----GET IMAGE.../
    

    是的,mpyw / co很容易。有关更多详细信息,请访问存储库页面。

答案 19 :(得分:-1)

当我对任何页面的特定URL进行POST时,这是我自己的PHP函数....

示例: * 使用我的函数...

<?php
    parse_str("email=myemail@ehehehahaha.com&subject=this is just a test");
    $_POST['email']=$email;
    $_POST['subject']=$subject;
    echo HTTP_Post("http://example.com/mail.php",$_POST);***

    exit;
?>
<?php
    /*********HTTP POST using FSOCKOPEN **************/
    // by ArbZ

    function HTTP_Post($URL,$data, $referrer="") {

    // parsing the given URL
    $URL_Info=parse_url($URL);

    // Building referrer
    if($referrer=="") // if not given use this script as referrer
      $referrer=$_SERVER["SCRIPT_URI"];

    // making string from $data
    foreach($data as $key=>$value)
      $values[]="$key=".urlencode($value);
    $data_string=implode("&",$values);

    // Find out which port is needed - if not given use standard (=80)
    if(!isset($URL_Info["port"]))
      $URL_Info["port"]=80;

    // building POST-request: HTTP_HEADERs
    $request.="POST ".$URL_Info["path"]." HTTP/1.1\n";
    $request.="Host: ".$URL_Info["host"]."\n";
    $request.="Referer: $referer\n";
    $request.="Content-type: application/x-www-form-urlencoded\n";
    $request.="Content-length: ".strlen($data_string)."\n";
    $request.="Connection: close\n";
    $request.="\n";
    $request.=$data_string."\n";

    $fp = fsockopen($URL_Info["host"],$URL_Info["port"]);
    fputs($fp, $request);
    while(!feof($fp)) {
        $result .= fgets($fp, 128);
    }
    fclose($fp); //$eco = nl2br();

    function getTextBetweenTags($string, $tagname) {
        $pattern = "/<$tagname ?.*>(.*)<\/$tagname>/";
        preg_match($pattern, $string, $matches);
        return $matches[1]; }
    //STORE THE FETCHED CONTENTS to a VARIABLE, because its way better and fast...
    $str = $result;
    $txt = getTextBetweenTags($str, "span"); $eco = $txt;  $result = explode("&",$result);
    return $result[1];
<span style=background-color:LightYellow;color:blue>".trim($_GET['em'])."</span>
</pre> "; 
}
</pre>

答案 20 :(得分:-2)

试试这个代码......

$chu = curl_init();

curl_setopt($chu, CURLOPT_URL, 'http://www.myapp.com/test.php?someprm=xyz');

curl_setopt($chu, CURLOPT_FRESH_CONNECT, true);
curl_setopt($chu, CURLOPT_TIMEOUT, 1);

curl_exec($chu);
curl_close($chu);

请不要忘记启用CURL php扩展。

答案 21 :(得分:-5)

这对我来说很好,遗憾的是你无法从你的请求中检索回复:

<?php
header("http://mahwebsite.net/myapp.php?var=dsafs");
?>

它工作得非常快,不需要原始tcp套接字:)