PHP cURL required only to send and not wait for response

All we need is an easy explanation of the problem, so here it is.

I need a PHP cURL configuration so that my script is able to send requests and ignore the answers sent by the API.

curl_setopt($ch,CURLOPT_URL,$url);
curl_setopt($ch,CURLOPT_POST,count($fields));
curl_setopt($ch,CURLOPT_POSTFIELDS,$fields_string);
// curl_setopt($ch, CURLOPT_RETURNTRANSFER, false);
//curl_setopt($ch, CURLOPT_TIMEOUT_MS, 100);
$result = curl_exec($ch);
echo $result;
curl_close ($ch);

I tried adding:
// curl_setopt($ch, CURLOPT_RETURNTRANSFER, false);
//curl_setopt($ch, CURLOPT_TIMEOUT_MS, 100);

But its not working properly and the API webserver is not receiving the requests.

The reason for this is I am sending large amount of requests to the API therefore my script is very slow because it waits for each and every request.

Any help is appreciated.

How to solve :

I know you bored from this bug, So we are here to help you! Take a deep breath and look at the explanation of your problem. We have many solutions to this problem, But we recommend you to use the first method because it is tested & true method that will 100% work for you.

Method 1

Sender file example ./ajax/sender.php

Script sending POST -> it makes full request to host, but it doesn’t wait on answer from server : CURLOPT_HEADER(0) we dont needs headers from server) and CURLOPT_RETURNTRANSFER (false) we don’t needs data from server.CURLOPT_TIMEOUT – Extra procteted : We waiting after sent only 1ms on respond server, this is extra quaranty to not wait any more ms if server keep us. ### NOTE ### HTTP1.1 has one package max 16kb. HTTP2 has 36kb one pacakge. If POST are more biggest, server will be send with many packages in series = $SIZE%16kb

    $url = 'https://127.0.0.1/ajax/received.php';
    $curl = curl_init();                
    $post['test'] = 'examples daata'; // our data todo in received
    curl_setopt($curl, CURLOPT_URL, $url);
    curl_setopt ($curl, CURLOPT_POST, TRUE);
    curl_setopt ($curl, CURLOPT_POSTFIELDS, $post); 
    
    curl_setopt($curl, CURLOPT_USERAGENT, 'api');

    //curl_setopt($curl, CURLOPT_TIMEOUT, 1); //if your connect is longer than 1s it lose data in POST better is finish script in recevie
    curl_setopt($curl, CURLOPT_HEADER, 0);
    curl_setopt($curl,  CURLOPT_RETURNTRANSFER, false);
    curl_setopt($curl, CURLOPT_FORBID_REUSE, true);
    curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 1);
    curl_setopt($curl, CURLOPT_DNS_CACHE_TIMEOUT, 100); 
    
    curl_setopt($curl, CURLOPT_FRESH_CONNECT, true);
    
    curl_exec($curl);   
    
    curl_close($curl);  

Received file example ./ajax/received.php

    ignore_user_abort(true); //if connect is close, we continue php script in background up to script will be end
    
            header("Connection: close\r\n"); 
            header("Content-Encoding: none\r\n"); 
            header("Content-Length: 1"); 
    ### we just close connect above if webbrowser, or request waiting on answer ( we know we set CURLOP to not wait) ###
ob_end_clean(); //just flush all content if exists to request. If server still waiting on answer.
            //HERE all script doing in background: Example
            $this->db->query('UPDATE new_hook_memory SET new=new+1 WHERE id=1');
 

EDIT 2019 if you using fastcgi just finish fastcgi and browser close connection but script still will be working up to end.

How finish script: PHP mod_fcgi with fastcgi_finish_request();

For Apache2:

ob_end_flush();
flush();

For php-fpm

fastcgi_finish_request(); $this->db->query('UPDATE new_hook_memory SET new=new+1 WHERE id=1');

Old version:

Method 2

These two solutions work well for me

( Of course it has been a long time, but I don’t think this question is outdated )

using file_get_contents:

  //url of php to be called

$url = "example.php/test?id=1";

  //this will set the minimum time to wait before proceed to the next line to 1 second

$ctx = stream_context_create(['http'=> ['timeout' => 1]]);

file_get_contents($url,null,$ctx);

  //the php will read this after 1 second 

using cURL:

  //url of php to be called

$url = "example.php/test?id=1";

$test = curl_init();

  //this will set the minimum time to wait before proceed to the next line to 100 milliseconds

curl_setopt_array($test,[CURLOPT_URL=>$url,CURLOPT_TIMEOUT_MS=>100,CURLOPT_RETURNTRANSFER=>TRUE]);

curl_exec($test);

  //this line will be executed after 100 milliseconds

curl_close ($test); 

in both case the called php must set ignore_user_abort(true).

And the result will not be printed in both case, but be careful with the timeout you will set, it needs to be greater than the time that the called php needs to start yielding results.

Method 3

If possible you can run wget in background (using exec)

Method 4

How can you tell if the request succeeded or not? You need to wait for at least the status code from the server to determine that. If latency is the issue, look at the curl multi API to perform multiple requests in parallel. You should be able to set a write callback function to abort reception of returned data once the status code has been returned.

Method 5

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 0);
curl_setopt($ch, CURLOPT_TIMEOUT_MS, 1);
curl_exec($ch);
curl_close($ch);

That works well for me.
Tested on PHP 7.1.14 Windows

Method 6

There was some frustration in finding a solution that actually works, so I ended up building a service based on fsockopen() that can handle both GET and POST requests, without being blocking.

Below is the service class:

class NonBlockingHttpClientService {

    private $method = 'GET';
    private $params = [];
    private $port = 80;

    private $host;
    private $path;
    private $post_content;

    public function isPost(): bool
    {
        return ($this->method === 'POST');
    }

    public function setMethodToPost(): NonBlockingHttpClientService
    {
        $this->method = 'POST';

        return $this;
    }

    public function setPort(int $port): NonBlockingHttpClientService
    {
        $this->port = $port;

        return $this;
    }

    public function setParams(array $params): NonBlockingHttpClientService
    {
        $this->params = $params;

        return $this;
    }

    private function handleUrl(string $url): void
    {
        $url = str_replace(['https://', 'http://'], '', $url);

        $url_parts = explode('/', $url);

        if(count($url_parts) < 2) {
            $this->host = $url_parts[0];
            $this->path = '/';
        } else {
            $this->host = $url_parts[0];
            $this->path = str_replace($this->host, '', $url);
        }
    }

    private function handleParams(): void
    {
        if(empty($this->params)) return;

        if($this->isPost()) {
            $this->post_content = http_build_query($this->params);

        } else {
            /*
            if you want to specify the params as an array for GET request, they will just be
            appended to the path as a query string
            */
            if(strpos($this->path, '?') === false) {
                $this->path .= '?' . ltrim($this->arrayToQueryString($this->params), '&');
            } else {
                $this->path .= $this->arrayToQueryString($this->params);
            }
        }
    }

    private function arrayToQueryString(array $params): string
    {
        $string = '';

        foreach($params as $name => $value) {
            $string .= "&$name=" . urlencode($value);
        }

        return $string;
    }

    public function doRequest(string $url): bool
    {
        $this->handleUrl($url);
        $this->handleParams();

        $host = $this->host;
        $path = $this->path;

        $fp = fsockopen($host,  $this->port, $errno, $errstr, 1);

        if (!$fp) {
            $error_message = __CLASS__ . ": cannot open connection to $host$path : $errstr ($errno)";
            echo $error_message;
            error_log($error_message);

            return false;

        } else {
            fwrite($fp, $this->method . " $path HTTP/1.1\r\n");
            fwrite($fp, "Host: $host\r\n");

            if($this->isPost()) fwrite($fp, "Content-Type: application/x-www-form-urlencoded\r\n");
            if($this->isPost()) fwrite($fp, "Content-Length: " . strlen($this->post_content) . "\r\n");

            fwrite($fp, "Connection: close\r\n");
            fwrite($fp, "\r\n");

            if($this->isPost()) fwrite($fp, $this->post_content);

            return true;
        }
    }
}

It can be used like this:

$req = new NonBlockingHttpClientService();
$req->setMethodToPost(); //default is GET, so just omit this for GET requests
$req->setParams([
    'test2' => 'aaaa', //if parameters are specified both with setParams() and in the query string, for GET requests, params specified with setParams() will take precedence
    'test3' => 'bbbb',
    'time' => date('H:i:s')
]);

$req->doRequest('test.localhost/some_path/slow_api.php?test1=value1&test2=value2');

And the slow_api.php file, can be something like this.

<?php
error_log('start');
sleep(10);
error_log(print_r($_REQUEST, 1) . 'end');

I find it easier to monitor (tail -f) the error log in order to see what is happening.

Method 7

A bit late now but the solution to this for anyone interested is that CURLOPT_RETURNTRANSFER needs to be set to TRUE, not false. That way the curl_exec function returns a value immediately rather than waiting for the request to complete before returning – i.e. it acts asynchronously rather than synchronously.

Example:

curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

Method 8

If you use Linux, you can use shell_exec in your PHP and send the result of curl to dev/null. By this method, you don’t receive any answer and after sending PHP goes to the next line and Linux executes your command in the background. You should use this method if the answer is not important to you.

This is an example:

shell_exec("curl 'http://domian.com/message?text=$text' > /dev/null 2>/dev/null &")

Note: You can add any curl option like header or post method or proxy as your need.

Note: Use and implement method 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply