33

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.

1
  • 1
    It seems like it's harder than one would expect. Here's a nice article about the problem and three possible solutions. segment.com/blog/how-to-make-async-requests-in-php TLDR: You can either use pfsockopen to open persistent socket connection or fork curl process or if you don't care about delay of actual request (eg. in a log scenario) you can log your request to a log file and have background process like cron send requests out of band using the log entries. Commented Jun 19, 2020 at 4:31

8 Answers 8

32

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:

Sign up to request clarification or add additional context in comments.

6 Comments

Dont't you mid adding any coments to the code? This is useless.
This works, but if you set too small timeout (10 milliseconds in my case), it gets interrupted before request is completely sent.
Some explanations : this code produces a cURL error code 28 : « Operation timed out after 1000 milliseconds with 0 out of -1 bytes received ». ($data is false). The tip used by this snippet consists in simply ignoring this error because it is not propagated to PHP. The query sent by cURL continues its life apart. But you don't know if the query has been accepted by the host.
@skrol29 this example is for one way stream with consists ip or host, you need trust in your host, or to ip. Is simple solutions to not waiting on respond. If you want using important data and have ensure, you should using diffrent method like rabitmq to have more controlled. Even you don't need know what curl result. Is just like ping to php, to make consists operations like count+1
Isn't it possible to just send an async request with php7 nowadays or use threads?
|
11

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.

1 Comment

Thanks, CURLOPT_TIMEOUT_MS makes the trick
5

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

2 Comments

This does not address the problem. It is equivalent to backgrounding curl, and will still have a process waiting for the response, consuming computational resources.
Not the best idea
4

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.

Comments

1
$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

4 Comments

This one print the response, how to disable it?
@Hassaan: curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE)
@MarcoDemaio this will make script wait for response as far as I know
@l00k, I thought Hassaan in his comment had asked how to not print out the result of the call to curl_exec($ch);
0

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.

1 Comment

Really old answer, but the point with this pattern is that you don't care. Think of low priority tracking calls, you want to be able to send requests without caring about a response in order to minimize server load for lots of tiny low importance requests.
-2

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.

Comments

-3

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);

8 Comments

Reza asked something else
How can you get value straight away if the server you are calling is lagging for 30 seconds to give you the value and response?
Curl_setopt always returns immediately, as options need to be set before the connection is initiated. This answer doesn't make any sense, is wrong and doesn't answer OP's question
It's curl_exec (not curl_setopt) that will return immediately or wait for the external server response depending upon the value of the CURLOPT_RETURNTRANSFER set using curl_setopt. Thanks for pointing that out - I've edited my answer.
For anyone reading, this is totally wrong: CURLOPT_RETURNTRANSFER instructs cURL to return the server's response when you call curl_exec, instead of just returning true|false on success/failure. It has nothing to do with waiting or not waiting for the request to complete.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.