PHP cURL required only to send and not wait for response

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.


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


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:


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.

Tags:

Php

Curl