I didn't find any example of how to make several web-service request at the same time (simultaneous).
For instant, this is request I send in order to get hotel details:
$s = new soapclient("http://www.wb-service-address.com",array('wsdl'));
$HotelInfo = new stdClass;
<HotelInfo softwareID="123" SessionId="153" Lang="EN" InfoType="">
<Hotel ID="103" />
</HotelInfo>
$HotelInfo->xmlRequest = $paramsStr;
$result = $s->__call("SubmitXmlString",array($HotelInfo));
$obj_pros = get_object_vars($result);
$hotel_full_xml = $obj_pros['SubmitXmlStringResult'];
$hotel_full_xml = simplexml_load_string($hotel_full_xml);
(The XML "HotelInfo" is the request)
I would like to send the same request to number of systems (urls) at the same time.
I'll appreciate your help
php by nature does not do that but you can using another library like GuzzleHttp
Exemple:
public function index()
{
$promises = call_user_func(function () {
foreach ($this->usernames as $username) {
(yield $this->client->requestAsync('GET', 'https://api.github.com/users/' . $username));
}
});
// Wait till all the requests are finished.
\GuzzleHttp\Promise\all($promises)->then(function (array $responses) {
$this->profiles = array_map(function ($response) {
return json_decode($response->getBody(), true);
}, $responses);
})->wait();
// Return JSON response
$response = new Response();
// StreamInterface objects are not immutable!
$response->getBody()->write($this->html());
return $response->withHeader('Content-type', 'text/html');
}
Also you can using this solution: https://github.com/amphp/amp
another solution with python: ThreadPoolExecutor
see that in python documentation: Concurrent Execution
Related
https://github.com/paquettg/php-html-parser
Anybody knows how to to follow redirects in this library?
For example:
require "vendor/autoload.php";
use PHPHtmlParser\Dom;
$dom = new Dom;
$dom->loadFromUrl($html);
Versions:
guzzlehttp/guzzle: "7.2.0"
paquettg/php-html-parser: "3.1.1"
Why does the library not natively allow redirects?
The loadFromUrl method has the following signature (at the time is 3.1.1)
public function loadFromUrl(string $url, ?Options $options = null, ?ClientInterface $client = null, ?RequestInterface $request = null): Dom
{
if ($client === null) {
$client = new Client();
}
if ($request === null) {
$request = new Request('GET', $url);
}
$response = $client->sendRequest($request);
$content = $response->getBody()->getContents();
return $this->loadStr($content, $options);
}
Looking at the line $response = $client->sendRequest($request); it goes to Guzzle's Client - https://github.com/guzzle/guzzle/blob/master/src/Client.php#L131
/**
* The HttpClient PSR (PSR-18) specify this method.
*
* #inheritDoc
*/
public function sendRequest(RequestInterface $request): ResponseInterface
{
$options[RequestOptions::SYNCHRONOUS] = true;
$options[RequestOptions::ALLOW_REDIRECTS] = false;
$options[RequestOptions::HTTP_ERRORS] = false;
return $this->sendAsync($request, $options)->wait();
}
The $options[RequestOptions::ALLOW_REDIRECTS] = false; will automatically turn off redirects. No matter what you pass in with the Client or Request it will automatically turn off redirects.
How to follow redirects with the library
Observing that the method loadFromUrl will make the request and get the response then use loadStr we'll mimic the same but use Guzzle (as it's a dependency of the library).
<?php
// Include the autoloader
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use PHPHtmlParser\Dom;
include_once("vendor/autoload.php");
$client = new Client();
try {
// Showing the allow_redirects for verbosity sake. This is on by default with GuzzleHTTP clients.
$request = $client->request('GET', 'http://theeasyapi.com', ['allow_redirects' => true]);
// This would work exactly the same
//$request = $client->request('GET', 'http://theeasyapi.com');
} catch(GuzzleException $e) {
// Probably do something with $e
var_dump($e->getMessage());
exit;
}
$dom = new Dom();
$domExample = $dom->loadStr($request->getBody()->getContents());
foreach($domExample->find('a') as $link) {
var_dump($link->text);
}
The code above will instantiate a new Guzzle Client, and make a request to the URL allowing redirects. The website used in this example is a site that will 301 redirect from non-secure to secure.
I am using guzzle to send some requests:
$response = $this->client->request(new SomeObject());
using the class below
...
public function request(Request $request)
{
return $this->requestAsync($request)->wait();
}
// Here I'm using Guzzle Async, but I can remove that is needed
public function requestAsync(Request $request)
{
$promise = $this->http->requestAsync($request->getMethod(), $request->getUri());
$promise = $promise->then(
function (ResponseInterface $response) use ($request) {
return $response;
}
);
return $promise;
}
...
I would like to use ReactPHP to send multiple requests at once in a foreach loop:
$requests = [];
foreach ($data as $value) {
$requests[] = $this->client->request(new SomeObject());
}
// pass $requests to ReactPHP here and wait on the response
Any ideas?
First of all, you don't need ReactPHP to use parallel HTTP requests with Guzzle. Guzzle itself provides this feature (if you use cURL handler, which is the default).
For example:
$promises = [];
foreach ($data as $value) {
$promises[] = $guzzleClient->getAsync(/* some URL */);
}
// Combine all promises
$combinedPromise = \GuzzleHttp\Promise\all($promises)
// And wait for them to finish (all requests are executed in parallel)
$responses = $combinedPromise->wait();
If you still want to use Guzzle with ReactPHP event loop, then, unfortunately, there are no straightforward solution. You cat take a look at https://github.com/productsupcom/guzzle-react-bridge (I'm the developer, so feel free to ask questions).
I have run in the situation where i'm handling incoming call using PHP/laravel, so when client calls to the company number the response is this method :
public function respondToUser()
{
$response = new Twiml();
$audio_file_path = trans('ivr_file_paths.welcome');
$response->play($audio_file_path);
$response->redirect('/ivr/call/enqueue', ['method' => 'POST']);
return $response;
}
But what I want to achieve next is to put incoming call in queue and then run the music in background if the operator (one operator /agent only) is busy, if not then connect to him.
this is what it looks like now
public function enqueueCall(Request $request)
{
$please_wait_audio_file = trans('paths.please_wait');
$please_wait_audio_file = trans('ivr_file_paths.please_wait');
$response = new Twiml();
$dial = $response->dial();
$dial->number('+number');
$response->enqueue('support', ['waitUrl' => $please_wait_audio_file]);
Log::info($response);
echo $response;
}
I know there is no queue right now, but this method just ends up the call..
Any suggestions? Thank you very much!
Twilio developer evangelist here.
I recommend you start by looking at the <Enqueue> TwiML verb which queues a caller up, followed by <Queue> which you can use within <Dial> to pop the next user off from the queue and talk to them.
If you need anything more complicated than that, then start reading into TaskRouter.
edit some example code:
Enqueue the caller and dial your agent.
public function enqueueCall(Request $request)
{
// build up the TwiML
$please_wait_audio_file = trans('ivr_file_paths.please_wait');
$response = new Twiml();
$response->enqueue('support', ['waitUrl' => $please_wait_audio_file]);
// make the call to your agent
$client = new Client($yourTwilioAccountSid, $yourTwilioAuthToken);
$call = $client->calls->create(
$yourAgentNumber,
$yourTwilioNumber,
array("url" => "http://example.com/ivr/call/queue")
);
Log::info($response);
echo $response;
}
When the agent connects, dial the queue:
public function dialQueue(Request $request)
{
$response = new Twiml();
$dial = $response->dial();
$dial->queue('support');
echo $response;
}
I don't know if it's the right terms to employ...
I made an API, in which the answer is sent by the die() function, to avoid some more useless calculations and/or functions calls.
example :
if (isset($authorize->refusalReason)) {
die ($this->api_return(true, [
'resultCode' => $authorize->resultCode,
'reason' => $authorize->refusalReason
]
));
}
// api_return method:
protected function api_return($error, $params = []) {
$time = (new DateTime())->format('Y-m-d H:i:s');
$params = (array) $params;
$params = ['error' => $error, 'date_time' => $time] + $params;
return (Response::json($params)->sendHeaders()->getContent());
}
But my website is based on this API, so I made a function to create a Request and return the contents of it, based on its URI, method, params, and headers:
protected function get_route_contents($uri, $type, $params = [], $headers = []) {
$request = Request::create($uri, $type, $params);
if (Auth::user()->check()) {
$request->headers->set('S-token', Auth::user()->get()->Key);
}
foreach ($headers as $key => $header) {
$request->headers->set($key, $header);
}
// things to merge the Inputs into the new request.
$originalInput = Request::input();
Request::replace($request->input());
$response = Route::dispatch($request);
Request::replace($originalInput);
$response = json_decode($response->getContent());
// This header cancels the one there is in api_return. sendHeaders() makes Content-Type: application/json
header('Content-Type: text/html');
return $response;
}
But now when I'm trying to call an API function, The request in the API dies but dies also my current Request.
public function postCard($token) {
$auth = $this->get_route_contents("/api/v2/booking/payment/card/authorize/$token", 'POST', Input::all());
// the code below is not executed since the API request uses die()
if ($auth->error === false) {
return Redirect::route('appts')->with(['success' => trans('messages.booked_ok')]);
}
return Redirect::back()->with(['error' => $auth->reason]);
}
Do you know if I can handle it better than this ? Any suggestion of how I should turn my code into ?
I know I could just use returns, but I was always wondering if there were any other solutions. I mean, I want to be better, so I wouldn't ask this question if I knew for sure that the only way of doing what I want is using returns.
So it seems that you are calling an API endpoint through your code as if it is coming from the browser(client) and I am assuming that your Route:dispatch is not making any external request(like curl etc)
Now There can be various approaches to handle this:
If you function get_route_contents is going to handle all the requests, then you need to remove the die from your endpoints and simply make them return the data(instead of echoing). Your this "handler" will take care of response.
Make your Endpoint function to have an optional parameter(or some property set in the $request variable), which will tell the function that this is an internal request and data should be returned, when the request comes directly from a browser(client) you can do echo
Make an external call your code using curl etc(only do this if there is no other option)
I have REST api on backend created with php (slim php micro framework). So code is:
INDEX.php
$app->post('/coords', 'authenticate', function() use ($app) {
$response = array();
//$jsonData = $app->request->post('podaci');
$jsonData = #file_get_contents('php://input');
$aData = json_decode($jsonData);
$latitude = $aData->location->latitude;
$longitude = $aData->location->longitude;
$podaci = $latitude.' ddd '.$longitude;
global $user_id;
$db = new DbHandler();
// creating new task
$coords_id = $db->createCoords($user_id, $podaci);
if ($coords_id != NULL) {
$response["error"] = false;
$response["message"] = "Coords insert successfully";
echoRespnse(201, $response);
} else {
$response["error"] = true;
$response["message"] = "Coords not inserted";
echoRespnse(200, $response);
}
});
I also have DBhandler file , code (function createCoords):
public function createCoords($user_id, $podaci) {
$stmt = $this->conn->prepare("INSERT INTO coords(podaci) VALUES(?)");
$stmt->bind_param("s", $podaci);
$result = $stmt->execute();
$stmt->close();
}
I try this REST api with sample data with ajax request and work well, but now I need to POST data from phonegap app to server and I write:
// BackgroundGeoLocation is highly configurable.
bgGeo.configure(callbackFn, failureFn, {
url: 'http://agroagro.com/agroMobile/v1/coords', // <-- Android ONLY: your server url to send locations to
params: {
Auth: 'df6017abde2d2re560896b63a0ee1039', // <-- Android ONLY: HTTP POST params sent to your server when persisting locations.
foo: 'bar' // <-- Android ONLY: HTTP POST params sent to your server when persisting locations.
},
desiredAccuracy: 0,
stationaryRadius: 50,
distanceFilter: 50,
notificationTitle: 'Background tracking', // <-- android only, customize the title of the notification
notificationText: 'ENABLED', // <-- android only, customize the text of the notification
activityType: 'AutomotiveNavigation',
debug: true, // <-- enable this hear sounds for background-geolocation life-cycle.
stopOnTerminate: false // <-- enable this to clear background location settings when the app terminates
});
I read here how to make php file to get INPUT data: https://github.com/christocracy/cordova-plugin-background-geolocation/issues/79
as you can see from my index.php code I write everything fine but what can be problem? This just dont work when I test on my android phone.
I do not why you are using 'php://input'); for getting json data.
Its simple. use the following to grab the json data sent from your phone.
$request = $app->request();
$body = $request->getBody();
$input = json_decode($body);
To know more how to handle json data using slim you may try the following website.
http://www.ibm.com/developerworks/library/x-slim-rest/