I create the code below to make an async request to get data from an API.
<?php
require 'vendor/autoload.php';
$url = "https://pucminas.instructure.com/api/v1/";
$client = new GuzzleHttp\Client(['base_uri' => $url]);
$headers = [
'Authorization' => "Bearer 11111~dsgffdhstggfjsdhf",
'Accept' => 'application/json',
];
$course_id = "2242";
set_time_limit(300);
$promise = $client->getAsync( 'courses/'.$course_id.'/students/submissions?student_ids[]=all&grouped=true&post_to_sis=false&enrollment_state=active&include[]=user&include[]=assignment&include[]=total_scores&per_page=1000', ['connect_timeout' => 600, 'headers' => $headers]);
$promise
->then(function ($response) {
echo 'Got a response! ' . $response->getStatusCode();
});
?>
<h2>Reports</h2>
I suppose when I load the page it will show the text "Reports" in the page and after getting the content from the API(which needs at least 60 seconds), it will show the message "Got a response", but it won't work that way.
First, the page loads the content from the API and only after that show the text "Reports" and "Got a message" at the same time.
I want the text "Reports" appear instantly when I load the page, and the text "Got a response" only when the data is loaded from the API.
How can I do that?
You can only send a single response from a PHP script i.e. your browser requests a page, the PHP server builds that page and sends it back. The PHP server can't then 'push' an update of the page to your browser as HTTP is stateless.
To achieve the functionality you want you need to write some javascript which will call a PHP script to start the guzzle async promise. This promise would then write the result to a file (or database) when the promise is complete.
You then need a second javascript function which continually checks a second php script. The second PHP script would check the contents of the file (or database entry etc) and return the result.
You might be better to look at something like Axios and lose the PHP altogether
https://www.npmjs.com/package/axios
Related
I have a PHP-based REST API, is that possible to get multiple HTTP responses after I sent the request only once? or is there a way to achieve this goal?
for example, I send an HTTP request for updating a bunch of data, and I need the API response with the status after each process so I can show the % on the page, very similar to the loading bar.
currently, my php response header as below
http_response_code(200);
echo json_encode(
array(
"result" => "Request finished.",
"success" => true,
"progress" => $progress,
)
);
it only shows 100% once all actions are done, and I learned the once code 200 been sent to the client, the HTTP connection has been closed. I also tried return code 100, but not failed.
I am running into a weird issue -> I've got a controller set up to send a POST request to another application using GuzzleHttp - which works fine when this request is started from our VueJs client.
Now I am developing an Artisan Command (per request by the customer) to simplify calling this endpoint like 50+ times (as it generates videos) -> to call this endpoint I am using the following snippet as I am calling an internal controller:
$request = Request::create(
route('videos.new', [], false),
'POST',
[
// Some daata
]
);
$response = app()->handle($request);
$result = json_decode($response->getContent());
But the issue is, now the exact same code in that controller sends a GET request instead of POST to the other application and I cannot figure out why as the method is HARD CODED in there.
I know it's a GET request as I am logging all requests entering the other application at the moment, url etc all looks correct, except that it's a GET request now
Request send with:
$cdnReq = new \GuzzleHttp\Psr7\Request(
'POST',
"/generate/$type?" . http_build_query($query),
[
'Content-Type' => 'application/json'
],
json_encode($input)
);
$this->beforeRequest($cdnReq);
// Step 03: Send request to cdn
Log::debug("Request(" . $cdnReq->getMethod() . ") will be send to: " . $client->getConfig('base_uri') . $cdnReq->getUri(), $input);
$cdnRes = $client->send($cdnReq);
Does anyone have any idea why this is happening?
INFO:
Laravel Version: Laravel/Framework 6.18.32
The issue was related to the URL being called as HTTP and redirected to HTTPS, which changes the POST to GET.
What I want to do (in the controller)
Receive a request, based on which
Send an external HTTP request
Parse the response, based on which
Return a response
I plan to have multiple functions that all follow this pattern. They're called one by one, in succession.
Symfony 4.3 introduces the HttpClient component and the BrowserKit component which at first sight seem to fit the bill. But as it turns out, things get more complicated. The external HTTP requests are sent to a server that tracks sessions with cookies, which shouldn't be anything too unusual. If the request is sent without cookies, the server always responds with the same landing page content. So we need the cookie. No worries, the response to the initial request comes with a "Set-Cookie" header that contains the cookie that is sent in the "Cookie" header in the subsequent requests. The HTTP status of the response with the cookie is "302 Found" which in a normal web environment, using a web browser, redirects to the next page with the same cookie in the request. Replicating this action in my Symfony controller seems to be very tricky. Expanding on step 2. of what I want to do now:
2.a. Send an external HTTP request
2.b. Read the cookie from the 302 response (without automatic redirection)
2.c. Set the "Cookie" header of the subsequent request, the value coming from 2.b.
2.d. Send the request to the new target (set in the "Location" header of the 302 response)
Here's some of the code from the controller:
use Symfony\Component\HttpClient\HttpClient;
use Symfony\Component\HttpClient\Exception\RedirectionException;
use Symfony\Component\HttpFoundation\JsonResponse;
public function init(Request $request): JsonResponse
{
$url = "external url";
$client = HttpClient::create();
try {
$response = $client->request('GET', $url, [
'max_redirects' => -1
]);
} catch (RedirectionException $redirectionException) {
}
return new JsonResponse([], 200);
}
Not setting max_redirects means that the client follows the redirects and ends up returning the 200 response without any cookies. Setting the value to -1 will throw a redirection
exception and again, the 302 response with the cookie seems to be lost.
I've also tried the HttpBrowser component:
$cookieJar = new CookieJar();
$client = new HttpBrowser(HttpClient::create(), null, $cookieJar);
$client->request('GET', $url);
But I get a 200 response without the cookie as it automatically follows the redirects.
I understand that the components mentioned here are new to Symfony 4.3 and "experimental" but I also don't think my task is overly complicated or unusual. But maybe there are other http clients for Symfony that would be better suited for the task?
As it turns out, this task was easier to complete by trying out other HTTP client components for Symfony than the native ones. So I've put Symfony HttpClient and HttpBrowser aside for now. I managed to implement a solution for this problem with guzzlehttp/guzzle version 6.3.3. Here's what it looks like in my controller function:
use GuzzleHttp\Client;
use Symfony\Component\HttpFoundation\Cookie;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
public function init(Request $request): JsonResponse {
$client = new Client([
'base_uri' => 'an external domain',
'cookies' => true
]);
// get the 302 response, without following
$response = $client->get('path',[
'allow_redirects' => false
]);
// get the cookie from the 302 response
$jar = new \GuzzleHttp\Cookie\CookieJar(true,[$response->getHeader('Set-Cookie')]);
// follow the redirect manually, with the cookie from the 302 response
$redirectResponse = $client->request('GET', $response->getHeader('Location')[0], [
'cookies' => $jar
]);
$jsonResponse = new JsonResponse(['message' => 'my message'],200);
$originalCookie = Cookie::fromString($response->getHeader('Set-Cookie')[0]);
$newCookie = Cookie::create(
$originalCookie->getName(),
$originalCookie->getValue(),
$originalCookie->getExpiresTime(),
'/',
'my domain',
false,
true,
$originalCookie->isRaw(),
$originalCookie->getSameSite()
);
$jsonResponse->headers->setCookie($newCookie);
return $jsonResponse;
}
The cookie cannot be taken from the response as such or it will never be sent back because the domain or the path doesn't match. So I create a new cookie which is very similar to the original one, then send it back to the browser. This way the cookie comes back in the subsequent requests and the data content can be sent forward.
Other issues that arise here are the same-origin requirements which have to be taken care of on the external server.
I'm still interested in how to do all this with the native Symfony 4.3 components, or also, if anyone can confirm that it's not possible.
I am trying to fetch geocodes from an api and update in my database against address entries. I am running this through a seed class in laravel.
And I am using Guzzle to make an asynchronous call. I want to run the api calls asynchronously while at the same time I want to read the asynchronous response in the background and update them in the database.
$client = new \GuzzleHttp\Client();
//retrieve the the latitude and longitude from geocode.farm of the given address
$response = $client->get('http:........<url for json request goes here>',['future' => true]);
$response->then(function ($response) {
// in here I read the $response object and get the latitude /longitude to update in the database.
// I tried to echo and print here, it seems the script is not entering here in this callback function
});
I am calling the above line of code in a loop. The above script runs fine when I make a synchronous call, but in asynchronous call I am unable to run it? can you please help me with it.
the script doesn't seem to enter in the callback function
I'm using Guzzle that I installed via composer and failing to do something relatively straightforward.
I might be misunderstanding the documentation but essentially what I'm wanting to do is run a POST request to a server and continue executing code without waiting for a response. Here's what I have :
$client = new \GuzzleHttp\Client(/*baseUrl, and auth credentials here*/);
$client->post('runtime/process-instances', [
'future'=>true,
'json'=> $data // is an array
]);
die("I'm done with the call");
Now lets say the runtime/process-instances runs for about 5mn, I will not get the die message before those 5mn are up... When instead I want it right after the message is sent to the server.
Now I don't have access to the server so I can't have the server respond before running the execution. I just need to ignore the response.
Any help is appreciated.
Things I've tried:
$client->post(/*blabla*/)->then(function ($response) {});
It is not possible in Guzzle to send a request and immediately exit. Asynchronous requests require that you wait for them to complete. If you do not, the request will not get sent.
Also note that you are using post instead of postAsync, the former is a synchronous (blocking) request. To asynchronously send a post request, use the latter. In your code example, by changing post to postAsync the process will exit before the request is complete, but the target will not receive that request.
Have you tried setting a low timeout?