Can't Use on_stats Option Using Laravel HTTP Client - php

Currently, I'm using Laravel HTTP client to make a request to an external URL. Mostly, the package working fine until I try to implement on_stats option from Guzzle.
From the doc, it says we can use Guzzle options using withMethod() method.
Here is my sample code to implement on_stats option using HTTP client.
$response = Http::withOptions([
'debug' => true,
'on_stats' => function(\GuzzleHttp\TransferStats $stats) {
Log::debug($stats->getTransferTime());
}
])
->get('https://laravel.com');
dd($response->status());
The code above will produce an error with the message:
Second array member is not a valid method
However, when I'm using the option within the Guzzle package directly, it works fine.
$client = new \GuzzleHttp\Client;
$response = $client->get('https://laravel.com', [
'on_stats' => function(\GuzzleHttp\TransferStats $stats) {
Log::debug($stats->getTransferTime());
}
]);
dd((string) $response->getStatusCode());
Any idea why this is happening? Is it a bug from the HTTP client wrapper from Laravel?
FYI, I'm using Laravel 8.x.
Thanks.

withOptions uses this code:
return tap($this, function ($request) use ($options) {
return $this->options = array_merge_recursive($this->options, $options);
});
So I'm guessing passing a closure in may not work, since it's not actually an array. From https://laracasts.com/discuss/channels/requests/httpwithtoken-get-total-time-of-request , you can get it from the response instead, so try this:
$client = new \GuzzleHttp\Client;
$response = $client->get('https://laravel.com');
Log::debug($response->transferStats->getTransferTime());

Related

Guzzlehttp 7.3 JSON_UNESCAPED_SLASHES not working (Lumen/Laravel)

I am having an issue with an app created in Lumen and Guzzlehttp requests.
Looks like I cannot pass options like JSON_UNESCAPED_SLASHES whenever I am doing a request:
$response = (new Client())->request($this->typeRequest, $endpoint, $options);
This is hitting my server with escaped slashes ("one\/two") and causing some troubles.
Everything seems to be related with the vendor/guzzlehttp/guzzle/src/Client.php into applyOptions() function which is using jsonEncode and not giving the option to pass anything:
$options['body'] = Utils::jsonEncode($options['json']);
This can be easily fixed just putting the option into jsonEncode:
$options['body'] = Utils::jsonEncode($options['json'], JSON_UNESCAPED_SLASHES);
The issue here is in case I am updating something with composer then will be override.
How can I resolve an issue like this?
You can just pass body directly:
['body'=>json_encode($data, JSON_UNESCAPED_SLASHES)].
use GuzzleHttp\Psr7\Request;
//...
$request = new Request('POST', $endpoint, ['content-type' => 'application/json'], json_encode($data, JSON_UNESCAPED_SLASHES));
$response = (new Client())->send($request);

Intercept GuzzleHTTP Request and Response Bodies

I have a need to override the GuzzleHTTP\Client to log the request and response bodies. I only have ability to change the client in use. Problem I'm having is these are streams and seems reading the contents (at least for response) breaks the request.
Can you think of another way to intercept request/response bodies via extending the Client?
class MyClient extends GuzzleHTTP\Client {
public function send(RequestInterface $request, array $options = []) {
// This doesn't affect original request for some reason
MyLogger::log($request->getBody()->getContents());
$response = parent::send($request, $options);
// This breaks the original request call, I think because stream is read only once
MyLogger::log($response->getBody()->getContents());
return $response;
}
}
The fix here is to rewind the stream after reading the contents of the response
MyLogger::log($response->getBody()->getContents());
$response->getBody()->rewind();
return $response
Guzzle's middleware framework provides end users with a natural method to perform specific actions under specific criteria without resorting to extending GuzzleHttp\Client
An example of this could be:
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\MessageFormatter;
use GuzzleHttp\Middleware;
use Monolog\Logger;
$stack = HandlerStack::create();
$stack->push(
Middleware::log(
new Logger('Logger'),
new MessageFormatter('{req_body} - {res_body}')
)
);
$client = new Client([
'debug' => true,
'handler' => $stack,
]);
$r = $client->get('some_random_uri')->getBody();
Documentation is not the best for GuzzleHttp\Middleware::Log() or for GuzzleHttp\MessageFormatter. A complete list of options is available within the MessageFormatter

Sending JSON data between two servers with Guzzle

I've edited this question as I realised I was completely on the wrong track, however I still have an issue.
Using Guzzle, how do I send an object in JSON-form from my shop server, which does not use Laravel, to my returns server, which does use Laravel?
I keep receiving the following error:
Client error: `POST https://returns.jdoe.blah.test/createReturn` resulted in a `419 unknown status`.
I think it has something to do with the fact that I don't have a token, but I don't know what to do with it. I know that Laravel uses CSRF tokens, but my shop server does not use that form.
In the shop server, when a user makes an order, it is saved in the object "$order". I added the following code to order_details.php, in an attempt to pass two particular attributes of the order object:
$client = new Client();
$url = "https://returns.jdoe.blah.test/createReturn";
$post_data = array(
'orderId' => $order['aufnr'],
'customerId' => $order['kundennummer']
);
$data = json_encode($post_data);
$request = $client->post($url, array(
'content-type' => 'application/json'
));
$request->setBody($data);
$response = $request->send();
Then in my Laravel project, I have:
web.php
Route::post('/createReturn', 'ProductReturnsController#createReturn');
ProductReturnsController.php
<?php
namespace App\Http\Controllers;
use App\ProductReturn;
use Illuminate\Http\Request;
class ProductReturnsController extends Controller
{
public function createReturn($json)
{
echo "hallo";
/* $jsonDecoded = json_decode($json);
$orderId = $jsonDecoded['orderId'];
echo $orderId;*/
return view('data');
}
}
data.blade.php
<html>
<head>
Test
</head>
<body>
This is a test page.
</body>
</html>
If you need anything else from me to help me solve this, please don't hesitate to ask. Thanks :).
The response to your question is actually on the first page of Guzzle documentation: http://docs.guzzlephp.org/en/stable/
You are doing var_dump($response) which is in fact the response object for the request you made. That object has a method getBody(),
So try doing
dd($response->getBody());
instead.
Try dd($response->getBody()->getContents()) or dd((string) $response->getBody()). Response body is a stream object, so if you want a string you have to do an additional method call.

Send Guzzle's "async" request without calling "wait"

I'm trying to send a request to an endpoint, but I don't want to wait for them to respond, as I don't need the response. So I'm using Guzzle, here's how:
$url = 'http://example.com';
$client = new \Guzzelhttp\Client();
$promise = $client->postAsync($url, [
'headers' => ['Some headers and authorization'],
'query' => [
'params' => 'params',
]
])->then(function ($result) {
// I don't need the result. So I just leave it here.
});
$promise->wait();
A I understood, I have to call the wait method on the client in order to actually send the request. But it's totally negates the request being "async" because if the url was not accessible or the server was down, the application would wait for a timeout or any other errors.
So, the question here is, what does Guzzle mean by "async" when you have to wait for the response anyway? And how can I call a truly async request with PHP?
Thanks
What you can do is:
$url = 'http://example.com';
$client = new \Guzzelhttp\Client();
$promise = $client->postAsync($url, [
'headers' => ['Some headers and authorization'],
'query' => [
'params' => 'params',
]
])->then(function ($result) {
return $result->getStatusCode();
})
->wait();
echo $promise;
You need the wait() to be called as the last line so you get the result which will come from your promise.
In this case it will return just the status code.
Just as mentioned in Github is not able to "fire and forget"so i think what you are trying to achieve, like a complete promise like in Vue or React won't work for you here the way you want it to work.
Another approach and what i do personally is to use a try-catch on guzzle requests, so if there is a guzzle error then you catch it and throw an exception.
Call then() method if you don't want to wait for the result:
$client = new GuzzleClient();
$promise = $client->getAsync($url)
$promise->then();
Empty then() call will make an HTTP request without waiting for result, Very similar to
curl_setopt(CURLOPT_RETURNTRANSFER,false)
use Illuminate\Support\Facades\Http;
...Some Code here
$prom = Http::timeout(1)->async()->post($URL_STRING, $ARRAY_DATA)->wait();
... Some more important code here
return "Request sent"; //OR whatever you need to return
This works for me as I don't need to know the response always.
It still uses wait() but because of the small timeout value it doesn't truly wait for the response.
Hope this helps others.

Doing HTTP requests FROM Laravel to an external API

What I want is get an object from an API with a HTTP (eg, jQuery's AJAX) request to an external api. How do I start? I did research on Mr Google but I can't find anything helping.
Im starting to wonder is this is even possible?
In this post Laravel 4 make post request from controller to external url with data it looks like it can be done. But there's no example nor any source where to find some documentation.
Please help me out?
Based upon an answer of a similar question here:
https://stackoverflow.com/a/22695523/1412268
Take a look at Guzzle
$client = new GuzzleHttp\Client();
$res = $client->get('https://api.github.com/user', ['auth' => ['user', 'pass']]);
echo $res->getStatusCode(); // 200
echo $res->getBody(); // { "type": "User", ....
We can use package Guzzle in Laravel, it is a PHP HTTP client to send HTTP requests.
You can install Guzzle through composer
composer require guzzlehttp/guzzle:~6.0
Or you can specify Guzzle as a dependency in your project's existing composer.json
{
"require": {
"guzzlehttp/guzzle": "~6.0"
}
}
Example code in laravel 5 using Guzzle as shown below,
use GuzzleHttp\Client;
class yourController extends Controller {
public function saveApiData()
{
$client = new Client();
$res = $client->request('POST', 'https://url_to_the_api', [
'form_params' => [
'client_id' => 'test_id',
'secret' => 'test_secret',
]
]);
echo $res->getStatusCode();
// 200
echo $res->getHeader('content-type');
// 'application/json; charset=utf8'
echo $res->getBody();
// {"type":"User"...'
}
You just want to call an external URL and use the results? PHP does this out of the box, if we're talking about a simple GET request to something serving JSON:
$json = json_decode(file_get_contents('http://host.com/api/stuff/1'), true);
If you want to do a post request, it's a little harder but there's loads of examples how to do this with curl.
So I guess the question is; what exactly do you want?
As of Laravel v7.X, the framework now comes with a minimal API wrapped around the Guzzle HTTP client. It provides an easy way to make get, post, put, patch, and delete requests using the HTTP Client:
use Illuminate\Support\Facades\Http;
$response = Http::get('http://test.com');
$response = Http::post('http://test.com');
$response = Http::put('http://test.com');
$response = Http::patch('http://test.com');
$response = Http::delete('http://test.com');
You can manage responses using the set of methods provided by the Illuminate\Http\Client\Response instance returned.
$response->body() : string;
$response->json() : array;
$response->status() : int;
$response->ok() : bool;
$response->successful() : bool;
$response->serverError() : bool;
$response->clientError() : bool;
$response->header($header) : string;
$response->headers() : array;
Please note that you will, of course, need to install Guzzle like so:
composer require guzzlehttp/guzzle
There are a lot more helpful features built-in and you can find out more about these set of the feature here: https://laravel.com/docs/7.x/http-client
This is definitely now the easiest way to make external API calls within Laravel.
Updated on March 21 2019
Add GuzzleHttp package using composer require guzzlehttp/guzzle:~6.3.3
Or you can specify Guzzle as a dependency in your project's composer.json
{
"require": {
"guzzlehttp/guzzle": "~6.3.3"
}
}
Include below line in the top of the class where you are calling the API
use GuzzleHttp\Client;
Add below code for making the request
$client = new Client();
$res = $client->request('POST', 'http://www.exmple.com/mydetails', [
'form_params' => [
'name' => 'george',
]
]);
if ($res->getStatusCode() == 200) { // 200 OK
$response_data = $res->getBody()->getContents();
}
Definitively, for any PHP project, you may want to use GuzzleHTTP for sending requests.
Guzzle has very nice documentation you can check here.
I just want to say that, you probably want to centralize the usage of the Client class of Guzzle in any component of your Laravel project (for example a trait) instead of being creating Client instances on several controllers and components of Laravel (as many articles and replies suggest).
I created a trait you can try to use, which allows you to send requests from any component of your Laravel project, just using it and calling to makeRequest.
namespace App\Traits;
use GuzzleHttp\Client;
trait ConsumesExternalServices
{
/**
* Send a request to any service
* #return string
*/
public function makeRequest($method, $requestUrl, $queryParams = [], $formParams = [], $headers = [], $hasFile = false)
{
$client = new Client([
'base_uri' => $this->baseUri,
]);
$bodyType = 'form_params';
if ($hasFile) {
$bodyType = 'multipart';
$multipart = [];
foreach ($formParams as $name => $contents) {
$multipart[] = [
'name' => $name,
'contents' => $contents
];
}
}
$response = $client->request($method, $requestUrl, [
'query' => $queryParams,
$bodyType => $hasFile ? $multipart : $formParams,
'headers' => $headers,
]);
$response = $response->getBody()->getContents();
return $response;
}
}
Notice this trait can even handle files sending.
If you want more details about this trait and some other stuff to integrate this trait to Laravel, check this article. Additionally, if interested in this topic or need major assistance, you can take my course which guides you in the whole process.
I hope it helps all of you.
Best wishes :)
Basic Solution for Laravel 8 is
use Illuminate\Support\Facades\Http;
$response = Http::get('http://example.com');
I had conflict between "GuzzleHTTP sending requests" and "Illuminate\Http\Request;" don't ask me why... [it's here to be searchable]
So looking for 1sec i found in Laravel 8 Doc...
**Guzzle is inside the Laravel 8 Http Request !**
https://laravel.com/docs/8.x/http-client#making-requests
as you can see
https://laravel.com/docs/8.x/http-client#introduction
Laravel provides an expressive, minimal API around the Guzzle HTTP
client, allowing you to quickly make outgoing HTTP requests to
communicate with other web applications. Laravel's wrapper around
Guzzle is focused on its most common use cases and a wonderful
developer experience.
It worked for me very well, have fun and if helpful point up!
I also created trait similar to #JuanDMeGonthat's that u can use anywhere in your project.Please check this out
trait ApiRequests
{
public function get($url, $data = null)
{
try {
$response = Http::get($this->base_url . $url, $data);
} catch (\Exception $e) {
info($e->getMessage());
abort(503);
}
if ( $response->status() == 401) {
throw new AuthenticationException();
} else if (! $response->successful()) {
abort(503);
}
return $response->json();
}
public function post($url, $data = [])
{
$token = session()->get('token');
try {
$response = Http::acceptJson()->withToken($token)->post($this->base_url . $url, $data);
} catch (\Exception $e) {
abort(503);
}
if ($response->status() == 401 && !request()->routeIs('login')) {
throw new AuthenticationException();
}
return $response;
}
}
class Controller extends BaseController
{
protected $base_url;
use AuthorizesRequests, DispatchesJobs, ValidatesRequests, ApiRequests;
public function __construct()
{
$this->base_url = env("BASE_URL","http://192.168.xxxxxxx");
View::share('base_url', $this->base_url);
}
}
You can use Httpful :
Website : http://phphttpclient.com/
Github : https://github.com/nategood/httpful
Here is the simple call for laravel 9.4
Route::get('/currency', function () {
$response = Http::withHeaders([
'x-api-key' => 'prtl6749387986743898559646983194',
])->get('https://partners.api.skyscanner.net/apiservices/v3/culture/currencies');
return response()->json(['status'=> true,'data'=> json_decode($response->body()), 'Message'=>"Currency retrieved successfully"], 200);
});
Don't forget to import
use Illuminate\Support\Facades\Http;

Categories