I'm trying to create a fire and forget method in PHP so that I can POST data to a web server and not have wait for a response. I read that this could be achieved by using CURL like in the following code:
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_fields);
curl_exec($ch);
curl_close($ch);
However I don't think it works as I expect. For example if the URL I send the request to has an error it causes my script to throw an error as well. If it was fire and forget I would expect that not to happen.
Can anyone tell me whether I'm doing something wrong or offer an alternative suggestion. I'm using Windows locally and Linux for dev, staging and production environments.
UPDATE
I have found an alternative solution here: http://blog.markturansky.com/archives/205
I've cleaned it up into the code below:
function curl_post_async($url, $params = array())
{
// create POST string
$post_params = array();
foreach ($params as $key => &$val)
{
$post_params[] = $key . '=' . urlencode($val);
}
$post_string = implode('&', $post_params);
// get URL segments
$parts = parse_url($url);
// workout port and open socket
$port = isset($parts['port']) ? $parts['port'] : 80;
$fp = fsockopen($parts['host'], $port, $errno, $errstr, 30);
// create output string
$output = "POST " . $parts['path'] . " HTTP/1.1\r\n";
$output .= "Host: " . $parts['host'] . "\r\n";
$output .= "Content-Type: application/x-www-form-urlencoded\r\n";
$output .= "Content-Length: " . strlen($post_string) . "\r\n";
$output .= "Connection: Close\r\n\r\n";
$output .= isset($post_string) ? $post_string : '';
// send output to $url handle
fwrite($fp, $output);
fclose($fp);
}
This one seems to work better for me.
Is it a valid solution?
Yes, using sockets is the way to go if you don't care about the response from the URL you're calling. This is because socket connection can be terminated straight after sending the request without waiting and this is exactly what you're after - Fire and Forget.
Two notes though:
It's no longer a cURL request, so it's worth renaming the function. :)
It's definitely worth checking whether the socket could've been opened to prevent the script from complaining later when if fails:
$fp = fsockopen($parts['host'], $port, $errno, $errstr, 30);
if ( ! $fp)
{
return FALSE;
}
It's worth linking to the original source of the fsocket() script you're now using:
http://w-shadow.com/blog/2007/10/16/how-to-run-a-php-script-in-the-background/
Here is a cleaned up version of diggersworld's code that also handles other HTTP methods then POST and throws meaningful exceptions if the function fails.
/**
* Send a HTTP request, but do not wait for the response
*
* #param string $method The HTTP method
* #param string $url The url (including query string)
* #param array $params Added to the URL or request body depending on method
*/
public function sendRequest(string $method, string $url, array $params = []): void
{
$parts = parse_url($url);
if ($parts === false)
throw new Exception('Unable to parse URL');
$host = $parts['host'] ?? null;
$port = $parts['port'] ?? 80;
$path = $parts['path'] ?? '/';
$query = $parts['query'] ?? '';
parse_str($query, $queryParts);
if ($host === null)
throw new Exception('Unknown host');
$connection = fsockopen($host, $port, $errno, $errstr, 30);
if ($connection === false)
throw new Exception('Unable to connect to ' . $host);
$method = strtoupper($method);
if (!in_array($method, ['POST', 'PUT', 'PATCH'], true)) {
$queryParts = $params + $queryParts;
$params = [];
}
// Build request
$request = $method . ' ' . $path;
if ($queryParts) {
$request .= '?' . http_build_query($queryParts);
}
$request .= ' HTTP/1.1' . "\r\n";
$request .= 'Host: ' . $host . "\r\n";
$body = http_build_query($params);
if ($body) {
$request .= 'Content-Type: application/x-www-form-urlencoded' . "\r\n";
$request .= 'Content-Length: ' . strlen($body) . "\r\n";
}
$request .= 'Connection: Close' . "\r\n\r\n";
$request .= $body;
// Send request to server
fwrite($connection, $request);
fclose($connection);
}
Related
Some of our customers still use api v1.3. MailChimp PHP API Wrapper 1.3 is used. From last 10 days this stopped working for unknown reasons. I know we should upgrade to api v3, but some customers won't see our e-mail so I am trying to find out what could be wrong.
I am enclosing the part of code that is calling their server which returns "http://HTTP/1.0 302 Moved Temporarily Server: AkamaiGHost...". I am trying with their support but even they don't know anything, it would be helpful if anybody can tell any opinion what could be the reason for this response so I can push this further with their support.
$dc = "us1";
if (strstr($this->api_key, "-")) {
list($key, $dc) = explode("-", $this->api_key, 2);
if (!$dc)
$dc = "us1";
}
$host = $dc . "." . $this->apiUrl["host"];
$params["apikey"] = $this->api_key;
$this->errorMessage = "";
$this->errorCode = "";
$sep_changed = false;
//sigh, apparently some distribs change this to & by default
if (ini_get("arg_separator.output") != "&") {
$sep_changed = true;
$orig_sep = ini_get("arg_separator.output");
ini_set("arg_separator.output", "&");
}
$post_vars = http_build_query($params);
if ($sep_changed) {
ini_set("arg_separator.output", $orig_sep);
}
$payload = "POST " . $this->apiUrl["path"] . "?" . $this->apiUrl["query"] . "&method=" . $method . " HTTP/1.0\r\n";
$payload .= "Host: " . $host . "\r\n";
$payload .= "User-Agent: MCAPI/" . $this->version . "\r\n";
$payload .= "Content-type: application/x-www-form-urlencoded\r\n";
$payload .= "Content-length: " . strlen($post_vars) . "\r\n";
$payload .= "Connection: close \r\n\r\n";
$payload .= $post_vars;
ob_start();
if ($this->secure) {
$sock = fsockopen("ssl://" . $host, 443, $errno, $errstr, 30);
} else {
$sock = fsockopen($host, 80, $errno, $errstr, 30);
}
if (!$sock) {
$this->errorMessage = "Could not connect (ERR $errno: $errstr)";
$this->errorCode = "-99";
ob_end_clean();
return false;
}
$response = "";
fwrite($sock, $payload);
stream_set_timeout($sock, $this->timeout);
$info = stream_get_meta_data($sock);
while ((!feof($sock)) && (!$info["timed_out"])) {
$response .= fread($sock, $this->chunkSize);
$info = stream_get_meta_data($sock);
}
fclose($sock);
ob_end_clean();
var_dump($info);
this return array(7) { ["timed_out"]=> bool(false) ["blocked"]=> bool(true) ["eof"]=> bool(false) ["stream_type"]=> string(14) "tcp_socket/ssl" ["mode"]=> string(2) "r+" ["unread_bytes"]=> int(0) ["seekable"]=> bool(false) }
var_dump($response);
this returns "http://HTTP/1.0 302 Moved Temporarily Server: AkamaiGHost Content-Length: 0 Location: https://us8.api.mailchimp.com/1.3/?output=php&method=listSubscribe Date: Fri, 10 Aug 2018 21:51:06 GMT Connection: close"
For me setting $this->secure = true didn't do the job because of the class' __construct()
function __construct($apikey, $secure=false) {
$this->secure = $secure;
$this->apiUrl = parse_url("https://api.mailchimp.com/" . $this->version . "/?output=php");
$this->api_key = $apikey;
}
So when you instantiate the class with just the API key $secure is false by default.
Fix:
$api = new MCAPI($apiKey, true);
Hope this helps any of you guys using a 7 year old repository ( myself included )...
It appeared they don't allow none secure connections anymore, this is why request was redirected and the php wrapper I used silently passed without any error and not adding e-mail to Mailchimp. Solution if to simply set $this->secure to true.
my script is presently live and I have a problematic sandbox to test my amendments on and this is pissing off some of my customers. I'm better off testing on live site.
PayPal sent an email to warn us and keeps reminding me to jump to 1.1 but since June I've had bad luck trying various mixes and consulting various platforms for advise.
Below is my current PayPal.php code which links to a lot of classes which are necessary for my website.
I have tried many combinations of coding and it is evidently seen from the commented lines.
They doesn't seem to be able to work and I have run low on options.
Is there any kind good soul able to fix this? or advise me what is going wrong?
IPN always replies me a INVALID response or Error but in http 1.0 everything is fine and dandy...
<?php
include_once(PayPalConfig::$params[PayPalConfig::$PAYPAL_PATH]."/PayPalSettings.php");
include_once(PayPalConfig::$params[PayPalConfig::$PAYPAL_PATH]."/PayPalConstants.php");
class PayPal
{
private $settings;
private $socket;
private $data;
/**
* Constructor
*
* #param PayPalSettings $settings
*/
public function __construct(PayPalSettings $settings, array $data)
{
// Store the setting which has been passed in
$this->settings = $settings;
$this->data = $data;
}
/**
* Enter description here...
*
* #return unknown
*/
function connectToPayPal()
{
// Store the URL string
//$url = $this->settings->paypalURL;
$url = "ssl://ipnpb.paypal.com";
// Open a socket with paypal and send all the POSTed info as our request
//ORIGINAL
//$this->socket = fsockopen ($url, 80, $errno, $errstr, 30);
//AMENDED - 30/06/2013
$this->socket = fsockopen ($url, 443, $errno, $errstr, 30);
//FAILED AGAIN
//$this->socket = fsockopen ($url, 443, $errno, $errstr, 30);
return $this->socket;
}
/**
* Enter description here...
*
* #return unknown
*/
function createDataForPayPal()
{
// create a request to paypal to verify the data we have recieved
$req = 'cmd=_notify-validate';
// Build request string from each value in the $_POST
foreach ($this->data as $key => $value)
{
//Original
//$value = urlencode(stripslashes($value));
//AMENDED - 30/08/2013
$value = urlencode($value);
$req .= "&$key=$value";
}
return $req;
}
/**
* Enter description here...
*
* #param unknown_type $fp
* #param unknown_type $requestString
* #return unknown
*/
function verifyWithPaypal($fp, $requestString)
{
/* OLD CHUNK
// post back to PayPal system to validate
$header = "POST /cgi-bin/webscr HTTP/1.1\r\n";
//try this NEXT? - Not tried - 20/08/13
//$header = "POST cgi-bin/webscr HTTP/1.1\r\n";
$header .= "Host: ipnpb.paypal.com\r\n";
$header .= "Content-Type: application/x-www-form-urlencoded\r\n";
//$header .= "Content-Length: " . strlen($requestString) . "\r\n";
$header .= "Content-Length: " . strlen($requestString) . "\r\n\r\n";
$header .= "Connection: close\r\n\r\n";
*/
// post back to PayPal system to validate
$header = "POST /cgi-bin/webscr HTTP/1.1\r\n";
$header .= "Content-Length: " . strlen($requestString) . "\r\n";
$header .= "Content-Type: application/x-www-form-urlencoded\r\n";
$header .= "Host: ipnpb.paypal.com\r\n";
$header .= "Connection: close\r\n\r\n";
// If the send has failed, we just have to return false
if (!$fp)
{
return PayPalConstants::$FAILED_TO_CONNECT;
}
else
{
// Paypal responded to our request. Store the respond
//Original
fputs ($fp, $header . $requestString);
//Amended New Try Next
//fputs ($fp, $header . $requestString . "\r\n\r\n");
// Fetch respond string from PayPal
while (!feof($fp))
{
$res = fgets ($fp, 1024);
//Writes $res to LogFile
//$logger->write($res);
// Determine if the transaction has been verified with PayPal
if (strcmp (trim($res), "VERIFIED") == 0)
{
return PayPalConstants::$VERIFIED;
}
else if (strcmp (trim($res), "INVALID") == 0)
{
return PayPalConstants::$NOT_VERIFIED;
}
}
}
//return $res;
return PayPalConstants::$NOT_VERIFIED;
}
/**
* Return the payment status
*
* #return string The payment status
*/
function getPaymentStatus()
{
if ($this->socket)
{
return $this->data[self::$PAYMENT_STATUS];
}
}
function massPayment(array $payee, $environment, $emailSubject, $receiverType, $bTest=false)
{
// Setup sending data
$currency = 'USD';
$method = PayPalConstants::$MASS_PAYMENT;
$nvpStr="&EMAILSUBJECT=$emailSubject&RECEIVERTYPE=$receiverType&CURRENCYCODE=$currency";
// Encode the string
foreach($payee as $i => $receiverData) {
$receiverEmail = urlencode($receiverData['receiverEmail']);
$amount = urlencode($receiverData['amount']);
$uniqueID = urlencode($receiverData['uniqueID']);
$note = urlencode($receiverData['note']);
$nvpStr .= "&L_EMAIL$i=$receiverEmail&L_Amt$i=$amount&L_UNIQUEID$i=$uniqueID&L_NOTE$i=$note";
}
// Set end point
if ($environment != "live")
{
$API_Endpoint = "https://api-3t.$environment.paypal.com/nvp";
}
else
{
// TODO: Live API endpoint set here
$API_Endpoint = "https://api-3t.paypal.com/nvp";
}
$mode = PayPalConfig::$API;
// What mode are we using the API?
if ($bTest)
{
$mode = PayPalConfig::$API_TEST;
}
$API_Username = PayPalConfig::$params[$mode][PayPalConfig::$API_USERNAME];
$API_Password = PayPalConfig::$params[$mode][PayPalConfig::$API_PASSWORD];
$API_Sign = PayPalConfig::$params[$mode][PayPalConfig::$API_SIGN];
// Set the curl parameters.
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $API_Endpoint);
curl_setopt($ch, CURLOPT_VERBOSE, 1);
// Turn off the server and peer verification (TrustManager Concept).
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
$version = urlencode('51.0');
// Generate the request string
$nvpreq = "METHOD=$method".
"&VERSION=$version".
"&PWD=$API_Password".
"&USER=$API_Username".
"&SIGNATURE=$API_Sign$nvpStr";
// Set the request as a POST FIELD for curl.
curl_setopt($ch, CURLOPT_POSTFIELDS, $nvpreq);
// Get response from the server.
$httpResponse = curl_exec($ch);
if(!$httpResponse) {
exit("$method failed: ".curl_error($ch).'('.curl_errno($ch).')');
}
// Extract the response details.
$httpResponseAr = explode("&", $httpResponse);
$httpParsedResponseAr = array();
foreach ($httpResponseAr as $i => $value) {
$tmpAr = explode("=", $value);
if(sizeof($tmpAr) > 1) {
$httpParsedResponseAr[$tmpAr[0]] = $tmpAr[1];
}
}
if((0 == sizeof($httpParsedResponseAr)) || !array_key_exists('ACK', $httpParsedResponseAr)) {
exit("Invalid HTTP Response for POST request($nvpreq) to $API_Endpoint.");
}
return ($httpParsedResponseAr);
}
}
?>
Somehow, trimming the result straight away (as opposed to just before comparison) has worked for me:
$res = fgets ($fp, 1024);
$res = trim($res);
Good luck
I have the following function that is supposed to open up a twitter stream for me
<?php
set_time_limit(0);
$query_data = array('track' => 'facebook');
$user = 'xxx'; // replace with your account
$pass = 'xxx'; // replace with your account
$fp = fsockopen("ssl://stream.twitter.com", 443, $errno, $errstr, 30);
if(!$fp){
print "$errstr ($errno)\n";
} else {
$request = "GET /1/statuses/filter.json?" . http_build_query($query_data) . " HTTP/1.1\r\n";
$request .= "Host: stream.twitter.com\r\n";
$request .= "Authorization: Basic " . base64_encode($user . ':' . $pass) . "\r\n\r\n";
fwrite($fp, $request);
while(!feof($fp)){
$json = fgets($fp);
$data = json_decode($json, true);
if($data){
//
// Do something with the data!
//
echo $data . "<br />";
}
}
fclose($fp);
}
?>
This is my first time ever to use php at all, so I'm completely oblivious as to what to do with this $data variable, this echo line does not produce any output on the browser. I'm sure this is trivial but a first-timer for me.
EDIT: The question is, what do I do with $data to make sure the code works? do I just shoot the file in a browser? it keeps saying "waiting for localhost..." forever
Why not just check to see if it's an array after the json_decode?
if( is_array( $data ) and count( $data ) ) // Ensures that json_decode created an array and has more than one element
{
// do your processing, it should be okay
}
else
{
// it's broken, error out
}
i have been trying for the last several days to fetch a request from a website but no success.
I keep getting error 301.
Is anyone able to help me grab the content of this page: https://pre.corrupt-net.org/search.php?search=Lasse_Stefanz-Bara_Du-SE-CD-FLAC-1995-LoKET
I am looking forward to your reply.
EDIT:
This is the php function I've used:
function http_request(
$verb = 'GET', /* HTTP Request Method (GET and POST supported) */
$ip, /* Target IP/Hostname */
$port = 80, /* Target TCP port */
$uri = '/', /* Target URI */
$getdata = array(), /* HTTP GET Data ie. array('var1' => 'val1', 'var2' => 'val2') */
$postdata = array(), /* HTTP POST Data ie. array('var1' => 'val1', 'var2' => 'val2') */
$cookie = array(), /* HTTP Cookie Data ie. array('var1' => 'val1', 'var2' => 'val2') */
$custom_headers = array(), /* Custom HTTP headers ie. array('Referer: http://localhost/ */
$timeout = 1000, /* Socket timeout in milliseconds */
$req_hdr = false, /* Include HTTP request headers */
$res_hdr = false /* Include HTTP response headers */
)
{
$ret = '';
$verb = strtoupper($verb);
$cookie_str = '';
$getdata_str = count($getdata) ? '?' : '';
$postdata_str = '';
foreach ($getdata as $k => $v)
$getdata_str .= urlencode($k) .'='. urlencode($v);
foreach ($postdata as $k => $v)
$postdata_str .= urlencode($k) .'='. urlencode($v) .'&';
foreach ($cookie as $k => $v)
$cookie_str .= urlencode($k) .'='. urlencode($v) .'; ';
$crlf = "\r\n";
$req = $verb .' '. $uri . $getdata_str .' HTTP/1.1' . $crlf;
$req .= 'Host: '. $ip . $crlf;
$req .= 'User-Agent: Mozilla/5.0 Firefox/3.6.12' . $crlf;
$req .= 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' . $crlf;
$req .= 'Accept-Language: en-us,en;q=0.5' . $crlf;
$req .= 'Accept-Encoding: deflate' . $crlf;
$req .= 'Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7' . $crlf;
foreach ($custom_headers as $k => $v)
$req .= $k .': '. $v . $crlf;
if (!empty($cookie_str))
$req .= 'Cookie: '. substr($cookie_str, 0, -2) . $crlf;
if ($verb == 'POST' && !empty($postdata_str)){
$postdata_str = substr($postdata_str, 0, -1);
$req .= 'Content-Type: application/x-www-form-urlencoded' . $crlf;
$req .= 'Content-Length: '. strlen($postdata_str) . $crlf . $crlf;
$req .= $postdata_str;
}
else $req .= $crlf;
if ($req_hdr)
$ret .= $req;
if (($fp = #fsockopen($ip, $port, $errno, $errstr)) == false)
return "Error $errno: $errstr\n";
stream_set_timeout($fp, 0, $timeout * 1000);
fputs($fp, $req);
while ($line = fgets($fp)) $ret .= $line;
fclose($fp);
if (!$res_hdr)
$ret = substr($ret, strpos($ret, "\r\n\r\n") + 4);
return $ret;
}
Firstly, 301 is not an "error" as such, it indicates that you are being redirected. You need to parse the response headers, take the value of the Location: header (which the HTTP protocol specification requires be present in a redirect response) and request that URI as well.
Secondly, the function above does not appear to provide any support for accessing HTTPS URLs. You need the OpenSSL extension installed for your PHP instance to do this, and you also need to actually call it some how. You could use the above function to do so by passing ssl:// or tls:// in front of the address in the $ip parameter, but you cannot simply pass the IP.
Thirdly, the usual way to do things like this is with the cURL extension. You would do something like this:
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://pre.corrupt-net.org/search.php?search=Lasse_Stefanz-Bara_Du-SE-CD-FLAC-1995-LoKET'); // Set the URL
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE); // Follow redirects
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); // Get the result from the execution
if (($result = curl_exec($ch)) === FALSE) { // Execute the request
echo "cURL failed! Error: ".curl_error($ch);
} else {
echo "Success! Result: $result";
}
curl_close($ch);
Alternatively, if cURL is not available or you don't want to use it for some reason, you could have go with my HTTPRequest class, which is PHP4 compliant and requires no extensions (apart from OpenSSL for HTTPS requests). Documented(ish) in comments at the top of the script. You would do something like this:
$request = new httprequest(); // Create an object
// Set the request URL
if (!$request->setRequestURL('https://pre.corrupt-net.org/search.php?search=Lasse_Stefanz-Bara_Du-SE-CD-FLAC-1995-LoKET')) echo "Failed! Error: ".$request->getLastErrorStr()."<br>\r\n";
// Send the request
if (!$request->sendRequest()) echo "Failed! Error: ".$request->getLastErrorStr()."<br>\r\n";
echo "Success! Result: ".$request->getResponseBodyData(TRUE);
On a side note, a lot of the Scene PreDB managers/providers are not too keen on automated scraping, and you may get yourself banned...
I am trying to implement Paypal IPN but it never reaches the url I've set. I've written a script to log visits to this url and all I get are my visits.
How long does it take for Paypal to sent the notification?
EDIT
IPNs suddenly started to come but now I can't verify...Here is the code:
$url = 'https://www.paypal.com/cgi-bin/webscr';
$postdata = '';
foreach ($_POST as $i => $v) {
$postdata .= $i . '=' . urlencode($v) . '&';
}
$postdata .= 'cmd=_notify-validate';
$web = parse_url($url);
if ($web['scheme'] == 'https') {
$web['port'] = 443;
$ssl = 'ssl://';
} else {
$web['port'] = 80;
$ssl = '';
}
$fp = #fsockopen($ssl . $web['host'], $web['port'], $errnum, $errstr, 30);
if (!$fp) {
echo $errnum . ': ' . $errstr;
} else {
fputs($fp, "POST " . $web['path'] . " HTTP/1.1\r\n");
fputs($fp, "Host: " . $web['host'] . "\r\n");
fputs($fp, "Content-type: application/x-www-form-urlencoded\r\n");
fputs($fp, "Content-length: " . strlen($postdata) . "\r\n");
fputs($fp, "Connection: close\r\n\r\n");
fputs($fp, $postdata . "\r\n\r\n");
while (!feof($fp)) {
$info[] = #fgets($fp, 1024);
}
fclose($fp);
$info = implode(',', $info);
if (eregi('VERIFIED', $info)) {
} else {
}
}
I already commented above. But I'm pretty sure the html encoded & is messing up your callback.
There's big difference between URL encoding and HTML encoding.
Change this '&' to this '&'. & is a url/post character used to separate different sets of key/value pairs. By changing it to &, you made your whole callback a single value.
Also, just some advice, but I would ditch this
if (eregi('VERIFIED', $info)) {} else {}
and replace it with this
if (preg_match('/VERIFIED/', $info)) {} else {}
eregi is depreciated.
http://php.net/manual/en/function.eregi.php