Using Laravel HTTP Client with concurrent request and Guzzle options - laravel

Using Laravel 9, I'm running a pool of async http queries. However before fetching the body, I need to perform some verifications on the headers (e.g. Content-type and Content-Length)
Here is what I tried:
use GuzzleHttp\RequestOptions;
use Illuminate\Http\Client\Pool;
use Illuminate\Http\Client\Response;
use Illuminate\Support\Facades\Http;
use Psr\Http\Message\ResponseInterface;
$responses = Http::pool(fn(Pool $pool) => [
$pool->withOptions([
RequestOptions::ON_HEADERS => function (ResponseInterface $response) {
if (($length = $response->getHeaderLine('Content-Length')) > 1048576) {
dump('Content-Length: ' . $length);
throw new \Exception('The file is too big');
}
},
])->get('<15MB_FILE_URL>'),
]);
$response = $responses[0];
if ($response instanceof Response) {
dump([
'ok' => $response->ok(),
'successful' => $response->successful(),
'failed' => $response->failed(),
'serverError' => $response->serverError(),
'clientError' => $response->clientError(),
'status' => $response->status(),
'reason' => $response->reason(),
'size' => strlen($response->body()),
]);
}
if ($response instanceof \Throwable) {
$this->warn('error: ' . $response->getMessage());
}
And it produces the following output
"Content-Length: 15882755"
array:8 [
"ok" => true
"successful" => true
"failed" => false
"serverError" => false
"clientError" => false
"status" => 200
"reason" => "OK"
"size" => 0
]
According to the Guzzle documentation:
If an exception is thrown by the callable, then the promise associated with the response will be rejected with a GuzzleHttp\Exception\RequestException that wraps the exception that was thrown.
I was expecting the response to be an Exception, the same way I end up with a GuzzleHttp\Exception\ConnectException when the server is not reachable, though the response seems to be errorless, event if the content is not downloaded.
Is there a better way to check the Content-Length and Content-Type (eventually after a redirection if needed) of a given url to avoid downloading the content whenever the type is not supported or the length is higher than expected in a request concurrency context.
Thanks for helping

Related

How to do concurrent guzzle http post request to rest api in laravel?

I want to make concurrent Guzzle http requests in laravel to rest api i have users in 100k i want to perform billing for users.
Currently my guzzle http is doing synchronous calls to rest api which is taking 6 hours to complete 100k post requests and the requests does not have any call backs they are just post request with users msisdn and unique id in json format.
How to do concurrent 50 requests per second so that billing is performed quickly.
Following is part of my code which i use taken from https://docs.guzzlephp.org/en/stable/quickstart.html#concurrent-requests
$requests = function ($total) {
$url = "url here";
$auth = base64_encode($username . ":" . $password);
for ($i = 0; $i < $total; $i++) {
$msgdata =[
'msisdn'=>$msisdn,
$subscription
=>$subscriptionInfo];
yield new Request('post', $url,
[
'headers' =>
[
'Content-Type' => 'application/json',
'Authorization' => $authorizaton
],
'body' => json_encode($msgdata)
]);
}
$pool = new Pool($client, $requests(50), [
'concurrency' => 5,
'fulfilled' => function (Response $response, $index) {
// this is delivered each successful response
echo $response;
},
'rejected' => function (RequestException $reason, $index) {
// this is delivered each failed request
echo $reason;
},
]);
// Initiate the transfers and create a promise
$promise = $pool->promise();
// Force the pool of requests to complete.
$promise->wait();
i am getting response as
"status":401,"error":"Unauthorized"
But request params are not incorect idk why it is giving response as incorect
finally i found the solution to my problem, the problem was in request header and body parameters.
changed this
yield new Request('post', $url,
[
'headers' =>
[
'Content-Type' => 'application/json',
'Authorization' => $authorizaton
],
'body' => json_encode($msgdata)
]);
to
yield new Request('post', $url,
[
'Content-Type' => 'application/json',
'Authorization' => $authorizaton
],
json_encode($msgdata)
);

Paypal API Subscription Create returns 400 Bad Request response "name", though request is formed as in documentation

I try to implement Paypal subscription service according to: https://developer.paypal.com/docs/api/subscriptions/v1/#subscriptions_create
This is my first try.
In sandbox business account I have created two test subscriptions: monthly and yearly and configured application with their id's.
This is the method:
public function createSubscription($planSlug, $name, $email) {
return $this->makeRequest(
'POST',
'v1/billing/subscriptions',
[],
[
'plan_id' => $this->plans[$planSlug],
'subscriber' => [
'name' => [
'given_name' => $name,
],
'email_address' => $email
],
'application_context'=> [
'brand_name' => config('app.name'),
'shipping_preference' => 'NO_SHIPPING',
'user_action' => 'SUBSCRIBE_NOW',
'return_url' => route('subscribe.approval', ['plan' => $planSlug]),
'cancel_url'=> route('subscribe.cancelled')
],
],
[],
$isJsonRequest = true
);
}
However, when I make a call to API, to create a test subscription, I get weird response that 'name' parameter is formed incorrectly:
php artisan tinker
>>> $paypal = resolve(App\Services\PaypalService::class);
=> App\Services\PaypalService {#3413}
>>> $paypal->createSubscription('monthly', 'Test', 'test#test.com');
GuzzleHttp\Exception\ClientException with message 'Client error: `POST https://api-
m.sandbox.paypal.com/v1/billing/subscriptions` resulted in a `400 Bad Request` response:
{"name":"INVALID_REQUEST","message":"Request is not well-formed, syntactically incorrect, or
violates schema.","debug_id (truncated...)
This is strange, because in Paypal API doc (see above), the 'name' param is described exactly like that!
Do I miss something or it is Paypal API is acting funky?
try this :
try {
// your code here
} catch(\Throwable $th) {
if ($th instanceof ClientException) {
$r = $th->getResponse();
$responseBodyAsString = json_decode($r->getBody()->getContents());
dd($responseBodyAsString);
}
}
I've faced this too before and it was not easy for me to figure out how to show an explicit error message.
'return_url' => route('subscribe.approval', ['plan' => $planSlug]),
'cancel_url'=> route('subscribe.cancelled')
the problem is in this two url, may be you have changed the APP_URL in the .env
APP_URL=http://127.0.0.1:8000
put app url that and try

PayPalRestSDK: Mock Response Instrument_Declined response when capturing order

I've been struggling to mock the 'INSTRUMENT_DECLINED' issue, which according to Paypal is an usual error after attempting to capture an order (https://developer.paypal.com/demo/checkout/#/pattern/server).
Correctly setting the header to mock the response will help me to simulate other errors from the docs https://developer.paypal.com/docs/business/test-and-go-live/simulation-tests/#orders.
I'm using Laravel back end and React front end.
I have already gotten the expected mocked response using axios and calling in the front-end:
return axios
.post(
`https://api-m.sandbox.paypal.com/v2/checkout/orders/${orderID}/capture`,
{},
{
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + accessToken,
"PayPal-Mock-Response":
'{"mock_application_codes":"INSTRUMENT_DECLINED"}',
},
}
)
.then((res) => {
console.log(res);
})
However, I don't want to expose accessToken into the front end, I'd prefer to do everything in the back-end with GuzzleHtttp.
I've already had a positive response - with 'status': 'COMPLETED' without adding PayPal-Mock-Response header like so:
$client = new \GuzzleHttp\Client();
$response = $client->request(
'POST',
'https://api-m.sandbox.paypal.com/v2/checkout/orders/' . $paypalOrderId . '/capture',
[
'headers' => [
'Content-Type' => 'application/json',
'Authorization' => 'Bearer ' . $access_token,
// 'PayPal-Mock-Response' => json_encode(["mock_application_codes" => "INSTRUMENT_DECLINED"]),
// 'PayPal-Mock-Response' => "['mock_application_codes':'INSTRUMENT_DECLINED]",
],
],
);
$data = json_decode($response->getBody(), true);
But when adding the PayPal-Mock-Response header like shown in the commented code from above, both attemps have returned this exception:
"message": "Client error: POST https://api-m.sandbox.paypal.com/v2/checkout/orders/9HG50866FU785784C/capture
resulted in a 404 Not Found response",
"exception": "GuzzleHttp\Exception\ClientException",
"file": "C:\xampp\htdocs\react\React-Laravel\vinos-gdl\vendor\guzzlehttp\guzzle\src\Exception\RequestException.php",
This exception, I'm sure has to do to the way I'm passing the PayPal-Mock-Response header into the Guzzle http call but I can't find the way to do it correctly.
Any help is much appreciated.
UPDATE SOLVED**
Ok the way to go is sending the header like so:
'PayPal-Mock-Response' => json_encode(["mock_application_codes" => "INSTRUMENT_DECLINED"]),
I dunno if I had any typo but I finally got it. I also used INTERNAL_SERVER_ERROR to get a 500 response from server and built a catch for every situation:
} catch (ClientException $e) {
if ($e->hasResponse()) {
return response()->json([
'msg' => 'Client Exception',
'error' => json_decode($e->getResponse()->getBody()),
], 400);
}
return response()->json([
'msg' => 'Client Exception',
'request' => $e->getRequest(),
$e->hasResponse() ? $e->getResponse() : ""
]);
// return response()->json(['msg' => 'Server Error', 'error' => report($e)]);
} catch (BadResponseException $e) {
if ($e->hasResponse()) {
return response()->json([
'msg' => 'Uknown Exception',
'error' => json_decode($e->getResponse()->getBody()),
], 500);
}
return response()->json([
'msg' => 'Uknown Exception',
'request' => $e->getRequest(),
$e->hasResponse() ? $e->getResponse() : ""
]);
}
Thanks for the ones who commented.
"PayPal-Mock-Response":
'{"mock_application_codes":"INSTRUMENT_DECLINED"}',
...
// 'PayPal-Mock-Response' => "['mock_application_codes':'INSTRUMENT_DECLINED]",
There is a difference betwen {} (JSON object) and [] (JSON array).
What you have inside the brackets is not an array, so its syntax is invalid. It doesn't map to anything PayPal expects, hence the 404.

form_params method get guzzle php

I have an API get list user. postmen
and Headers Content-Type = application/json
- In laravel, I use guzzle to call api
code demo:
$client = new Client();
$headers = ['Content-Type' => 'application/json'];
$body = [
'json' => [
"filter" => "{}",
"skip" => 0,
"limit" => 20,
"sort" => "{\"createAt\": 1}",
"select" => "fullname username",
"populate" => "'right', 'group'",
]
];
\Debugbar::info($body);
$response = $client->get('http://sa-vn.com:2020/api/users/user', [
'form_params' => $body
]);
echo $response->getBody();
But it does not working! please help me
form_params and body both are different params in guzzle. check json
$json = [
"filter" => json_encode((object)[]),
"skip" => 0,
"limit" => 20,
"sort" => json_encode((object)['createAt'=>1]),
"select" => "fullname username",
"populate" => "'right', 'group'"
];
$response = $client->request('get', 'http://sa-vn.com:2020/api/users/user', [
'json' => $json,
]);
If any error occur try without json_encode as well.
$json = [
"filter" => (object)[],
"skip" => 0,
"limit" => 20,
"sort" => (object)['createAt'=>1],
"select" => "fullname username",
"populate" => "'right', 'group'"
];
As per Guzzle doucmentation
form_params
Used to send an application/x-www-form-urlencoded POST request.
json
The json option is used to easily upload JSON encoded data as the body of a request. A Content-Type header of application/json will be added if no Content-Type header is already present on the message.
You are passing json data in postman. So you can use json instead of form_params
Change
$response = $client->get('http://sa-vn.com:2020/api/users/user', [
'form_params' => $body
]);
to
$response = $client->get('http://sa-vn.com:2020/api/users/user', [
'json' => $body
]);

guzzle client throws exception in laravel

I am trying to make a http post request in laravel as below
$client = new Client(['debug'=>true,'exceptions'=>false]);
$res = $client->request('POST', 'http://www.myservice.com/find_provider.php', [
'form_params' => [
'street'=> 'test',
'apt'=> '',
'zip'=> 'test',
'phone'=> 'test',
]
]);
It return empty response. On debugging ,following exception is occurring
curl_setopt_array(): cannot represent a stream of type Output as a STDIO FILE*
I am using latest version of guzzle.
Any idea how to solve it?.
The request() method is returning a GuzzleHttp\Psr7\Response object.
To get the actual data that is returned by your service you should use:
$data = $res->getBody()->getContents();
Now check what you have in $data and if it corresponds to the expected output.
More information on using Guzzle Reponse object here
I had to do this
$data = $res->getBody()->getContents();<br>
but also change<br>
$client = new \GuzzleHttp\Client(['verify' => false, 'debug' => true]);<br>
to<br>
$client = new \GuzzleHttp\Client(['verify' => false]);
Here is what i did for my SMS api
use Illuminate\Support\Facades\Http; // Use this on top
// Sample Code
$response = Http::asForm()
->withToken(env('SMS_AUTH_TOKEN'))
->withOptions([
'debug' => fopen('php://stderr', 'w') // Update This Line
])
->withHeaders([
'Cache-Control' => 'no-cache',
'Content-Type' => 'application/x-www-form-urlencoded',
])
->post($apiUrl,$request->except('_token'));

Resources