Node vs. PHP - Right way to make HTTP POST web request - php

One website I have was originally done in PHP. It does a web POST request to another website every time users do a particular query on the website.
function post_request($url, $data, $referer='') {
$data = http_build_query($data);
$url = parse_url($url);
if ($url['scheme'] != 'http') {
die('Error: Only HTTP request are supported !');
}
// extract host and path:
$host = $url['host'];
$path = $url['path'];
// open a socket connection on port 80 - timeout: 7 sec
$fp = fsockopen($host, 80, $errno, $errstr, 7);
if ($fp){
// Set non-blocking mode
stream_set_blocking($fp, 0);
// send the request headers:
fputs($fp, "POST $path HTTP/1.1\r\n");
fputs($fp, "Host: $host\r\n");
if ($referer != '')
fputs($fp, "Referer: $referer\r\n");
fputs($fp, "User-Agent: Mozilla/5.0 Firefox/3.6.12\r\n");
fputs($fp, "Content-type: application/x-www-form-urlencoded\r\n");
fputs($fp, "Content-length: ". strlen($data) ."\r\n");
fputs($fp, "Connection: close\r\n\r\n");
fputs($fp, $data);
$result = '';
while(!feof($fp)) {
// receive the results of the request
$result .= fgets($fp, 128);
}
// close the socket connection:
fclose($fp);
}
else {
return array(
'status' => 'err',
'error' => "$errstr ($errno)"
);
}
// split the result header from the content
$result = explode("\r\n\r\n", $result, 2);
$header = isset($result[0]) ? $result[0] : '';
$content = isset($result[1]) ? $result[1] : '';
// return as structured array:
return array(
'status' => 'ok',
'header' => $header,
'content' => $content
);
}
This approach works trouble-free, only problem being it takes nearly 3 CPUs to support 100 concurrent users with the above code.
Thinking Node.js would be a good way to do this (web request would be async), I did the following. In terms of CPU requirements there was a definite improvement (mostly works with a single CPU, at most 2)
function postPage(postPath, postData, postReferal, onReply, out) {
var post_options = {
host: 'www.somehost.com',
port: '80',
path: postPath,
method: 'POST',
headers: {
'Referer': postReferal,
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': postData.length,
'User-Agent': 'Mozilla/5.0 Firefox/3.6.12',
'Connection': 'close'
}
};
// create request
var post_req = http.request(post_options, function (res) {
var reply = '';
res.setEncoding('utf8');
res.on('data', function (chunk) {
reply += chunk;
});
res.on('end', function () {
onReply(reply, out);
});
res.on('error', function (err) {
out.writeHead(500, { 'Content-Type': 'text/html' });
out.end('Error');
});
});
// post the data
post_req.write(postData);
post_req.end();
}
The problem in this case is that it is very fragile and around 20% of the web requests fail. If the user try the query again it works, but not a good experience.
Am using Windows Azure Websites to host both the above solutions.
Now, the questions
Is using PHP for this expected to be taking that much resources, or is it because my code isn't optimal?
What is wrong with my Node code (or Azure), that so many requests fail?

Use the request library
Buffering the entire response
The most basic way is to make a request, buffer the entire response from the remote service (indianrail.gov.in) into memory, and then send that back to the client. However it is worth looking at the streaming example below
Install the needed dependencies
npm install request eyespect
var request = require('request');
var inspect = require('eyespect').inspector({maxLength: 99999999}); // nicer console logging
var url = 'http://www.indianrail.gov.in';
var postData = {
fooKey: 'foo value'
};
var postDataString = JSON.stringify(postData);
var opts = {
method: 'post',
body: postDataString // postData must be a string here..request can handle encoding key-value pairs, see documentation for details
};
inspect(postDataString, 'post data body as a string');
inspect(url, 'posting to url');
request(url, function (err, res, body) {
if (err) {
inspect('error posting request');
console.log(err);
return;
}
var statusCode = res.statusCode;
inspect(statusCode, 'statusCode from remote service');
inspect(body,'body from remote service');
});
Streaming
If you have a response stream to work with you can stream the post data without having to buffer everything into memory first. I am guessing in your example this is the out parameter.
To add some error handling, you can use the async module and repeatedly try the post request until it either completes successfully or the maximum number of attempts is reached
npm install request filed temp eyespect async
var request = require('request');
var inspect = require('eyespect').inspector({maxLength: 99999999}); // nicer console logging
var filed = require('filed');
var temp = require('temp');
var rk = require('required-keys');
var async = require('async');
function postToService(data, cb) {
// make sure the required key-value pairs were passed in the data parameter
var keys = ['url', 'postData'];
var err = rk.truthySync(data, keys);
if (err) { return cb(err); }
var url = data.url;
var postData = data.postData;
var postDataString = JSON.stringify(postData);
var opts = {
method: 'post',
body: postDataString // postData must be a string here..request can handle encoding key-value pairs, see documentation for details
};
var filePath = temp.path({suffix: '.html'});
// open a writable stream to a file on disk. You could however replace this with any writeable stream such as "out" in your example
var file = filed(filePath);
// stream the response to disk just as an example
var r = request(url).pipe(file);
r.on('error', function (err) {
inspect(err, 'error streaming response to file on disk');
cb(err);
});
r.on('end', function (err) {
cb();
});
}
function keepPostingUntilSuccess(callback) {
var url = 'http://www.google.com';
var postData = {
fooKey: 'foo value'
};
var data = {
url: url,
postData: postData
};
var complete = false;
var maxAttemps = 50;
var attempt = 0;
async.until(
function () {
if (complete) {
return true;
}
if (attempt >= maxAttemps) {
return true;
}
return false;
},
function (cb) {
attempt++;
inspect(attempt, 'posting to remote service, attempt number');
postToService(data, function (err) {
// simulate the request failing 3 times, then completing correctly
if (attempt < 3) {
err = 'desired number of attempts not yet reached';
}
if (!err) {
complete = true;
}
cb();
});
},
function (err) {
inspect(complete, 'done with posting, did we complete successfully?');
if (complete) {
return callback();
}
callback('failed to post data, maximum number of attempts reached');
});
}
keepPostingUntilSuccess(function (err) {
if (err) {
inspect(err, 'error posting data');
return;
}
inspect('posted data successfully');
});

Related

Tableau Trusted Authentication Ticket with PHP

I am trying to understand how the Trusted Authentication ticket is meant to work with PHP. I've been looking up different questions and came up with this code
$url = 'https://tableau.godigitally.io/trusted/';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, "username=" . $userid . ""); // define what you want to post
$response = curl_exec($ch);
curl_close($ch);
//echo 'Test: ' . $response;
echo '<iframe src=', $url, $ticket, 'views/Dashboard2_0/Dashboard1?', $urlparams, '"
width="700" height="400">
</iframe>';
But I get the following error when I run this code
I have no idea where I'm going wrong. I have confirmed that my server configuration is correct by using the testing techniques described in https://help.tableau.com/current/server/en-us/trusted_auth_testing.htm
To Work with Tableau and web UI , the best way, you should add a Tableau js library to your web application and follow the steps
Generate the Ticket from tableau server call ,
To generate the ticket from tableau server ,first you should add your user to tableau server and follow the below steps
this is the code sample which you can use to generate the ticket
function getTabTicket(tableauServer, username, site){
return new Promise((resolve,reject)=>{
let url = new URL(tableauServer + '/trusted');
let body = {
username: username,
};
if (site) {
body['target_site'] = site;
}
let postData = querystring.stringify(body);
let proto = http;
if (url.protocol === 'https:') {
proto = https;
}
let req = proto.request({
method: 'POST',
hostname: url.hostname,
port:url.port,
path: '/trusted',
headers: {'Content-Type': 'application/x-www-form-urlencoded'}
}, function (response) {
let ticketData = '';
response.on('data', function (chunk) {
ticketData += chunk;
});
response.on('end', function () {
let contents = {ticket: ticketData};
resolve(contents);
});
});
req.on('error', function (error) {
reject(error);
console.log('ERROR: ' + error);
});
req.write(postData);
req.end();
})
}
You can check this library which is open source you can use to generate the Ticket.
https://github.com/aalteirac/jwt-tableau-broker
https://anthony-alteirac.medium.com/tableau-trusted-authentication-the-ticket-broker-cloud-friendly-709789942aa3
After generating Ticket , you have to call the tableau server to get the report .
I am using js library for that .
Now 2 step is include js using NPM or by reference
and then you can call the function
var tableuReport = new tableau.Viz(this.reportContainerDiv, fullTableauDashboardUrl, Constants.tableauReportUISettings);
reportContainerDiv // the div element in which you have to render the component
fullTableauDashboardURL = {tableau_server_url}+"trusted"/{ticektId}/reportsuburl // the tableau URL it is in the format
for example your ,
tableau_server_url = "https://tableau-dev-abc.com"
tableauDashboardUrl = "#/site/CovidApp/views/IndiaReport/IndiaReport_v2?:iid=1/"
ticketId = "fjdjadheuihrieywfiwnfih"
So your final URL will be like
fullTableauDashboardUrl = "https://tableau-dev-abc.com/trusted/fjdjadheuihrieywfiwnfih/t/CovidApp/views/IndiaReport/IndiaReport_v2?:iid=1
fullTableauDashboardUrl.replace("#/site", "t");
PS: we have to replace the #/site to t

Is there a way in PHP to stop a curl request once a string match is found?

I currently have a PHP script that downloads a website's html, then preg_match is run on the curl_exec() result. The webpage is around 2 Mb in size and the matching string is usually towards to the beginning of the page, so a large mount of the download time appears to be unnecessary. I am wondering if there is a way to kill a curl session once the string has been found. Would pipes work? I am also open to trying other frameworks like BASH and Javascript. Thanks.
I'm pretty sure it can't be done in PHP because the thread cant let curl know to stop if it's doing something else.
In PHP, you could use fsockopen, and then break out of loop early once has matched:
<?php
$host = "stackoverflow.com";
$page = "/questions/62504744/is-there-a-way-in-php-to-stop-a-curl-request-once-a-string-match-is-found/62505031";
$fp = fsockopen("ssl://$host", 443, $errno, $errdesc);
if (!$fp)
die("Couldn't connect to $host:\nError: $errno\nDesc: $errdesc\n");
stream_set_blocking($fp, 0);
$request = "GET $page HTTP/1.1\r\n";
$request .= "Host: $host\r\n";
$request .= "User-Agent: Mozilla/5.0\r\n";
$request .= "Accept: text/xml,application/xml,application/xhtml+xml,";
$request .= "text/html;q=0.9,text/plain;q=0.8,video/x-mng,image/png,";
$request .= "image/jpeg,image/gif;q=0.2,text/css,*/*;q=0.1\r\n\r\n";
fputs ($fp, $request);
$content = '';
while (!feof($fp)) {
$body = fgets($fp);
if (stristr($body, "PHP script that downloads a website's html")) {
echo 'Was found';
$content = $body;
break;
}
}
fclose($fp);
echo $content;
Alternatively if you wanted to use nodejs, you can do the same.
const https = require("https");
const req = https.request({
host: "stackoverflow.com",
port: 443,
path:
"/questions/62504744/is-there-a-way-in-php-to-stop-a-curl-request-once-a-string-match-is-found"
}, function(res) {
let found = false;
res.on("data", function(chunk) {
// change PHP script... to DOCTYPE, which will show it aborts after first chunk
if (chunk.includes("PHP script that downloads a website's html")) {
found = true;
req.abort();
}
console.log(chunk);
});
res.on("end", () => console.log(found));
});
req.end();
Edit:
Do something with matched string.
const https = require("https");
// callback function when a match is found
function doSomthing(str){
console.log('matched partial dom:', str)
}
const req = https.request({
host: "stackoverflow.com",
port: 443,
path:
"/questions/62504744/is-there-a-way-in-php-to-stop-a-curl-request-once-a-string-match-is-found"
}, function(res) {
let body = ''
res.on("data", function(chunk) {
// change PHP script... to DOCTYPE, which will show it aborts after first chunk
if (chunk.includes("PHP script that downloads a website's html")) {
body = chunk.toString();
req.abort();
}
});
res.on("end", () => doSomthing(body));
});
req.end();

How to use PHP proxy for CORS?

I am trying to use a weather API which returns the data in xml format. I was using a third-party proxy server to get around CORS issues but that service is no longer running. I am using jquery/ajax to get the data like this:
function getMetarToTable(){
var stationt="KJFK";
$.ajax({
type: "GET",
url: 'proxy.php',
crossDomain: true,
data: {'stationString': stationt},
datatype: "xml",
headers: {
'X-Proxy-URL': 'https://www.aviationweather.gov/adds/dataserver_current/httpparam?datasource=metars&requestType=retrieve&format=xml&mostRecentForEachStation=constraint&hoursBeforeNow=1.25',
},
error: function(jqXHR, textStatus, errorThrown) {
console.log('Error: ' + errorThrown);
},
success: function(xml) {
$("#metarweatherpage").html('');
$(xml).find('METAR').each(function(){
var sRawtext = $(this).find('raw_text').text();
var sStationid = $(this).find('station_id').text();
$("#metarweatherpage").append('<p><h2>'+sStationid+'</h2></p><p>'+sRawtext+'</p>');
});
},
});
};
PHP - proxy.php
<?php
if( ! isset($whitelist))
$whitelist = [];
if( ! isset($curl_maxredirs))
$curl_maxredirs = 10;
if( ! isset($curl_timeout))
$curl_timeout = 30;
// Get stuff
$headers = getallheaders();
$method = __('REQUEST_METHOD', $_SERVER);
$url = __('X-Proxy-Url', $headers);
$cookie = __('X-Proxy-Cookie', $headers);
// Check that we have a URL
if( ! $url)
http_response_code(400) and exit("X-Proxy-Url header missing");
// Check that the URL looks like an absolute URL
if( ! parse_url($url, PHP_URL_SCHEME))
http_response_code(403) and exit("Not an absolute URL: $url");
// Check referer hostname
if( ! parse_url(__('Referer', $headers), PHP_URL_HOST) == $_SERVER['HTTP_HOST'])
http_response_code(403) and exit("Invalid referer");
// Check whitelist, if not empty
if( ! empty($whitelist) and ! array_reduce($whitelist, 'whitelist', [$url, false]))
http_response_code(403) and exit("Not whitelisted: $url");
// Remove ignored headers and prepare the rest for resending
$ignore = ['Cookie', 'Host', 'X-Proxy-URL'];
$headers = array_diff_key($headers, array_flip($ignore));
if($cookie)
$headers['Cookie'] = $cookie;
foreach($headers as $key => &$value)
$value = "$key: $value";
// Init curl
$curl = curl_init();
do
{
// Set generic options
curl_setopt_array($curl, [
CURLOPT_URL => $url,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_HEADER => TRUE,
CURLOPT_TIMEOUT => $curl_timeout,
CURLOPT_FOLLOWLOCATION => TRUE,
CURLOPT_MAXREDIRS => $curl_maxredirs,
]);
// Method specific options
switch($method)
{
case 'HEAD':
curl_setopt($curl, CURLOPT_NOBODY, TRUE);
break;
case 'GET':
break;
case 'PUT':
case 'POST':
case 'DELETE':
default:
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);
curl_setopt($curl, CURLOPT_POSTFIELDS, file_get_contents('php://input'));
break;
}
// Perform request
ob_start();
curl_exec($curl) or http_response_code(500) and exit(curl_error($curl));
$out = ob_get_clean();
// HACK: If for any reason redirection doesn't work, do it manually...
$url = curl_getinfo($curl, CURLINFO_REDIRECT_URL);
}
while($url and --$maxredirs > 0);
// Get curl info and close handler
$info = curl_getinfo($curl);
curl_close($curl);
// Remove any existing headers
header_remove();
// Use gz, if acceptable
ob_start('ob_gzhandler');
// Output headers
$header = substr($out, 0, $info['header_size']);
array_map('header', explode("\r\n", $header));
// And finally the body
echo substr($out, $info['header_size']);
// Helper functions
function __($key, array $array, $default = null)
{
return array_key_exists($key, $array) ? $array[$key] : $default;
}
function whitelist($carry, $item)
{
static $url;
if(is_array($carry))
{
$url = parse_url($carry[0]);
$url['raw'] = $carry[0];
$carry = $carry[1];
}
// Equals the full URL
if(isset($item[0]))
return $carry or $url['raw'] == $item[0];
// Regex matches the full URL
if(isset($item['regex']))
return $carry or preg_match($item['regex'], $url['raw']);
// Select components matches same components in the URL
return $carry or $item == array_intersect_key($url, $item);
}
I get a 400 Bad Request error and the response is "X-Proxy-Url header missing".
Bit late to the party, but it's pretty obvious what's going on.
PHP array indices act as hash tables, so they are case sensitive.
In your client side js you set the request header as 'X-Proxy-URL':, however, in your server-side php you check for 'X-Proxy-Url', notice the URL vs Url part.

AJAX/PHP Not Working After Web Host upgrade

My web hosting company recently upgraded to Apache 2.2.22 and PHP 5.3.13 and since then a piece of script will not work correctly. The webpage is a radio streamer and now the part that updates the track info from a text file does not display at all. The streamer is working fine and so are other third-party widgets.
Here is part of the script to display the album cover:
updateNowPlayingInfo = function() {
var d = new Date();
$.ajax( '/php_proxy_simple.php?url=playingnow.txt&_=' + d.getTime(), {
complete: function( jqXHR, textStatus) { console.log( 'RMX Player XHR completed: ' +textStatus ); },
error: function( jqXHR, textStatus, errorThrown) { console.log( 'RMX Player XHR error:' + textStatus + ':' + errorThrown ); },
xhr: (window.ActiveXObject) ?
function() {
try {
return new window.ActiveXObject("Microsoft.XMLHTTP");
} catch(e) {}
} :
function() {
return new window.XMLHttpRequest();
},
cache: true,
type: 'GET',
crossDomain: true,
dataType: 'text',
global: false, // #note was using false
ifModified: true,
success: function( data, textStatus, jqXHR ) {
//alert( playingData );
playingData = data.split("\n");
if ( playingData[2] && ! playingData[2].match( /no-image-no-ciu/ ) ) {
//playingData[2] = playingData[2].replace( 'SS110', 'AA280' ); // swap small image for medium
//console.log( playingData[2] );
playingData[2] = playingData[2].replace( '_SL160_', '_SX200_' ); // swap small image for large
$( "#nowplaying_album_cover img" ).attr( "src" , playingData[2] );
$( "#nowplaying_album_cover").show();
}
else $( "#nowplaying_album_cover").attr("src" , playingData[2] );
$( "#nowplaying_album_cover").show();
},
failure: function() { alert('failed to get play data') ; }
} );
And the php code:
<?php
// PHP Proxy example for Yahoo! Web services.
// Responds to both HTTP GET and POST requests
// Allowed hostname
define ('HOSTNAME', 'http://www.mysite.co/');
// Get the REST call path from the AJAX application
// Is it a POST or a GET?
ini_set( 'error_reporting', 0);
$path = ($_POST['url']) ? $_POST['url'] : $_GET['url'];
$url = HOSTNAME.$path.'?timestamp=' . time();
// Open the Curl session
$session = curl_init($url);
// If it's a POST, put the POST data in the body
if ($_POST['url']) {
$postvars = '';
while ($element = current($_POST)) {
$postvars .= urlencode(key($_POST)).'='.urlencode($element).'&';
next($_POST);
}
curl_setopt ($session, CURLOPT_POST, true);
curl_setopt ($session, CURLOPT_POSTFIELDS, $postvars);
}
// Don't return HTTP headers. Do return the contents of the call
curl_setopt($session, CURLOPT_HEADER, false);
curl_setopt($session, CURLOPT_RETURNTRANSFER, true);
// Make the call
$response = curl_exec($session);
// possibly include expires header to bust aggresive caching -expires=>’+1s’
header('Content-Type: text/html;charset=utf-8');
echo $response;
curl_close($session);
?>
I grabbed this from the raw log files:
"GET /playingnow.txt HTTP/1.1" 304
Not sure if that is relevant. Any help would be appreciated. Thanks
Fixed it, the file permission for the PHP file needed to be at 0644. Thanks.

How do I make an asynchronous GET request in PHP?

I wish to make a simple GET request to another script on a different server. How do I do this?
In one case, I just need to request an external script without the need for any output.
make_request('http://www.externalsite.com/script1.php?variable=45'); //example usage
In the second case, I need to get the text output.
$output = make_request('http://www.externalsite.com/script2.php?variable=45');
echo $output; //string output
To be honest, I do not want to mess around with CURL as this isn't really the job of CURL. I also do not want to make use of http_get as I do not have the PECL extensions.
Would fsockopen work? If so, how do I do this without reading in the contents of the file? Is there no other way?
Thanks all
Update
I should of added, in the first case, I do not want to wait for the script to return anything. As I understand file_get_contents() will wait for the page to load fully etc?
file_get_contents will do what you want
$output = file_get_contents('http://www.example.com/');
echo $output;
Edit: One way to fire off a GET request and return immediately.
Quoted from 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);
}
What this does is open a socket, fire off a get request, and immediately close the socket and return.
This is how to make Marquis' answer work with both POST and GET requests:
// $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);
}
Regarding your update, about not wanting to wait for the full page to load - I think a HTTP HEAD request is what you're looking for..
get_headers should do this - I think it only requests the headers, so will not be sent the full page content.
"PHP / Curl: HEAD Request takes a long time on some sites" describes how to do a HEAD request using PHP/Curl
If you want to trigger the request, and not hold up the script at all, there are a few ways, of varying complexities..
Execute the HTTP request as a background process, php execute a background process - basically you would execute something like "wget -O /dev/null $carefully_escaped_url" - this will be platform specific, and you have to be really careful about escaping parameters to the command
Executing a PHP script in the background - basically the same as the UNIX process method, but executing a PHP script rather than a shell command
Have a "job queue", using a database (or something like beanstalkd which is likely overkill). You add a URL to the queue, and a background process or cron-job routinely checks for new jobs and performs requests on the URL
You don't. While PHP offers lots of ways to call a URL, it doesn't offer out of the box support for doing any kind of asynchronous/threaded processing per request/execution cycle. Any method of sending a request for a URL (or a SQL statement, or a etc.) is going to wait for some kind of response. You'll need some kind of secondary system running on the local machine to achieve this (google around for "php job queue")
I would recommend you well tested PHP library: 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
}
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);
}
If you are using Linux environment then you can use the PHP's exec command to invoke the linux curl. Here is a sample code, which will make a Asynchronous HTTP post.
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;
}
This code does not need any extra PHP libs and it can complete the http post in less than 10 milliseconds.
Interesting problem. I'm guessing you just want to trigger some process or action on the other server, but don't care what the results are and want your script to continue. There is probably something in cURL that can make this happen, but you may want to consider using exec() to run another script on the server that does the call if cURL can't do it. (Typically people want the results of the script call so I'm not sure if PHP has the ability to just trigger the process.) With exec() you could run a wget or even another PHP script that makes the request with file_get_conents().
Nobody seems to mention Guzzle, which is a PHP HTTP client that makes it easy to send HTTP requests. It can work with or without Curl. It can send both synchronous and asynchronous requests.
$client = new GuzzleHttp\Client();
$promise = $client->requestAsync('GET', 'http://httpbin.org/get');
$promise->then(
function (ResponseInterface $res) {
echo $res->getStatusCode() . "\n";
},
function (RequestException $e) {
echo $e->getMessage() . "\n";
echo $e->getRequest()->getMethod();
}
);
You'd better consider using Message Queues instead of advised methods.
I'm sure this will be better solution, although it requires a little more job than just sending a request.
let me show you my way :)
needs nodejs installed on the server
(my server sends 1000 https get request takes only 2 seconds)
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 >
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);
}
For me the question about asynchronous GET request is appeared because of I met with situation when I need to do hundreds of requests, get and deal with result data on every request and every request takes significant milliseconds of executing that leads to minutes(!) of total executing with simple file_get_contents.
In this case it was very helpful comment of w_haigh at php.net on function http://php.net/manual/en/function.curl-multi-init.php
So, here is my upgraded and cleaned version of making lot of requests simultaneously.
For my case it's equivalent to "asynchronous" way. May be it helps for someone!
// 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
It's easy to rewrite this to handle POST or other types of HTTP(S) requests or any combinations of them. And Cookie support, redirects, http-auth, etc.
Try:
//Your Code here
$pid = pcntl_fork();
if ($pid == -1) {
die('could not fork');
}
else if ($pid)
{
echo("Bye")
}
else
{
//Do Post Processing
}
This will NOT work as an apache module, you need to be using CGI.
I found this interesting link to do asynchronous processing(get request).
askapache
Furthermore you could do asynchronous processing by using a message queue like for instance beanstalkd.
Here's an adaptation of the accepted answer for performing a simple GET request.
One thing to note if the server does any url rewriting, this will not work. You'll need to use a more full featured http client.
/**
* 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);
}
Just a few corrections on scripts posted above. The following is working for me
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);
}
Based on this thread I made this for my codeigniter project. It works just fine. You can have any function processed in the background.
A controller that accepts the async calls.
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( ) );
}
}
}
And a library that does the async calls
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 );
}
}
This method can be called to process any model::method() in the 'background'. It uses variable arguments.
$this->load->library('daemon');
$this->daemon->execute_background( 'model', 'method', $arg1, $arg2, ... );
Suggestion: format a FRAMESET HTML page which contains, let´s say, 9 frames inside. Each frame will GET a different "instance" of your myapp.php page. There will be 9 different threads running on the Web server, in parallel.
For PHP5.5+, mpyw/co is the ultimate solution. It works as if it is tj/co in JavaScript.
Example
Assume that you want to download specified multiple GitHub users' avatars. The following steps are required for each user.
Get content of http://github.com/mpyw (GET HTML)
Find <img class="avatar" src="..."> and request it (GET IMAGE)
---: Waiting my response
...: Waiting other response in parallel flows
Many famous curl_multi based scripts already provide us the following flows.
/-----------GET HTML\ /--GET IMAGE.........\
/ \/ \
[Start] GET HTML..............----------------GET IMAGE [Finish]
\ /\ /
\-----GET HTML....../ \-----GET IMAGE....../
However, this is not efficient enough. Do you want to reduce worthless waiting times ...?
/-----------GET HTML--GET IMAGE\
/ \
[Start] GET HTML----------------GET IMAGE [Finish]
\ /
\-----GET HTML-----GET IMAGE.../
Yes, it's very easy with mpyw/co. For more details, visit the repository page.
Here is my own PHP function when I do POST to a specific URL of any page....
Sample: * usage of my Function...
<?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>
Try this code....
$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);
Please dont forget to enable CURL php extension.
This works fine for me, sadly you cannot retrieve the response from your request:
<?php
header("http://mahwebsite.net/myapp.php?var=dsafs");
?>
It works very fast, no need for raw tcp sockets :)

Categories