Using PHP 8.1, Laravel 9, Guzzle 7.4.2.
I have this snippet in a service singleton registered in AppServiceProvider:
$middleware = new AccessTokenGuzzleMiddleware(
client: new Client([
'base_uri' => config('spica.url') . config('spica.space_api.path'),
'verify' => config('spica.verify_ssl'),
]),
cache: $this->app->make('cache')->store(),
tokenUrl: config('spica.url') . 'auth',
clientId: config('spica.space_api.client_id'),
clientSecret: config('spica.space_api.key'),
);
$stack = HandlerStack::create();
$stack->push($middleware);
$spaceClient = new Client([
'headers' => [
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'handler' => $stack,
'base_uri' => config('spica.url') . config('spica.space_api.path'),
'verify' => config('spica.verify_ssl'),
]);
I can't test myself, because I have no access to base_uri, and it will only be tested once on development server, but I am curious if the Client inherits anything from the handler stack, and how does it do so if there are multiple middlewares stacked in handler?
Asking because all the examples in Guzzle docs create the final client with just $client = new Client(['handler' => $stack]);.
As you can see I currently repeat base_uri and verify lines.
Would the following work the same?
$middleware = new AccessTokenGuzzleMiddleware(
client: new Client([
'headers' => [
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'base_uri' => config('spica.url') . config('spica.space_api.path'),
'verify' => config('spica.verify_ssl'),
]),
cache: $this->app->make('cache')->store(),
tokenUrl: config('spica.url') . 'auth',
clientId: config('spica.space_api.client_id'),
clientSecret: config('spica.space_api.key'),
);
$stack = HandlerStack::create();
$stack->push($middleware);
$spaceClient = new Client(['handler' => $stack]);
For completeness, here's the AccessTokenGuzzleMiddleware class:
<?php
declare(strict_types=1);
namespace App\Support;
use GuzzleHttp\Client;
use Psr\Http\Message\RequestInterface;
use Illuminate\Contracts\Cache\Repository as CacheContract;
/**
* This is a Guzzle middleware (see more at https://docs.guzzlephp.org/en/stable/handlers-and-middleware.html).
* It adds "Authorization: Bearer" header to requests.
*/
class AccessTokenGuzzleMiddleware
{
public function __construct(
private Client $client,
private CacheContract $cache,
private string $tokenUrl,
private string $clientId,
private string $clientSecret
) {
}
public function __invoke(callable $handler)
{
return function (RequestInterface $request, array $options) use ($handler) {
$request = $request->withHeader('Authorization', 'Bearer ' . $this->getAccessToken());
return $handler($request, $options);
};
}
/**
* Defines cache key for access token.
*
* #return string
*/
private function cacheKey(): string
{
return self::class . $this->tokenUrl . $this->clientId . $this->clientSecret;
}
/**
* Get access token from cache or external API.
*
* #return string
*/
private function getAccessToken(): string
{
$cacheKey = $this->cacheKey();
if ($this->cache->has($cacheKey)) {
return $this->cache->get($cacheKey);
}
$tokenData = $this->retrieveClientAccessTokenData();
$this->cache->set(
key: $cacheKey,
value: $tokenData->access_token,
ttl: $tokenData->expires_in - 10
);
return $tokenData->access_token;
}
/**
* This is effectively the client (this service, not on behalf of a user)
* authenticating itself.
*/
private function retrieveClientAccessTokenData(): object
{
$authResponse = $this->client->post($this->tokenUrl, [
'json' => [
'apiKey' => $this->clientSecret,
'client_id' => $this->clientSecret,
],
]);
assertResponseOk($authResponse);
return json_decode($authResponse->getBody()->getContents());
}
}
EDIT 1
Until I get a better answer, I went with the following:
$this->app->singleton(KeycloakService::class, function (Application $app) {
// Log all Guzzle HTTP requests and responses.
// https://github.com/gmponos/guzzle-log-middleware#advanced-initialization
$middleware = new OAuthGuzzleMiddleware(
client: new Client(['verify' => config('keycloak.verify_ssl')]),
cache: $this->app->make('cache')->store(),
tokenUrl: config('keycloak.url') . 'auth/realms/' . config('keycloak.realm') . '/protocol/openid-connect/token',
clientId: config('keycloak.client_id'),
clientSecret: config('keycloak.client_secret'),
);
$stack = HandlerStack::create();
$stack->push($middleware);
$oAuthKeycloakClient = new Client([
'headers' => [
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'handler' => $stack,
'base_uri' => config('keycloak.url') . 'auth/admin/realms/' . config('keycloak.realm') . '/',
'verify' => config('keycloak.verify_ssl'),
]);
return new KeycloakService(
client: $oAuthKeycloakClient,
groupHandler: new GroupHandler(),
userHandler: new UserHandler(),
);
});
$this->app->singleton(SpicaService::class, function (Application $app) {
// Time API
$timeClient = new Client([
'base_uri' => config('spica.url') . config('spica.time_api.path'),
'headers' => [
'Content-Type' => 'application/x-www-form-urlencoded',
'Accept' => 'application/json',
'Authorization' => config('spica.time_api.auth_token'),
],
'verify' => config('spica.verify_ssl'),
]);
// Space API
$middleware = new AccessTokenGuzzleMiddleware(
client: new Client(['verify' => config('spica.verify_ssl')]),
cache: $this->app->make('cache')->store(),
tokenUrl: config('spica.url') . 'auth',
clientId: config('spica.space_api.client_id'),
clientSecret: config('spica.space_api.key'),
);
$stack = HandlerStack::create();
$stack->push($middleware);
$spaceClient = new Client([
'headers' => [
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'handler' => $stack,
'base_uri' => config('spica.url') . config('spica.space_api.path'),
'verify' => config('spica.verify_ssl'),
]);
// Create service
return new SpicaService(
timeClient: $timeClient,
spaceClient: $spaceClient,
employeeHandler: new EmployeeHandler(),
);
});
That's not really how Guzzle works. So there's three concepts in Guzzle that you're trying to mix up, Client, Middleware and Handler which I'll try to explain.
With the Client you build the request and prepare it for the middleware/handler, this is where you define what the request is.
The Handler executes the request, note: when you create a Handlerstack::create() the default handler will be the CurlHandler, you can overwrite the handler here as a parameter
The Middleware is used to modify the request or response after the request is executed. But the request will eventually be passed to the handler to execute it.
What you can do is provide the client as a service in the AppServiceProvider
$this->app->bind(Client::class, function () {
return new Client([
'headers' => [
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'base_uri' => config('spica.url') . config('spica.space_api.path'),
'verify' => config('spica.verify_ssl'),
]);
});
That way everywhere the Client is injected in the constructor it will inject that pre-configured client
Edit:
Just realised that would miss the handler in some cases, you could provide it as a singleton and overwrite the client when you've authenticated
Edit with contextual binding:
$this->app->when(KeycloakService::class)
->needs(Client::class)
->give(function () {
return new Client();
});
$this->app->when(KeycloakService::class)
->needs(Client::class)
->give(function () {
return new Client();
});
$this->app->when(AccessTokenGuzzleMiddleware::class)
->needs(Client::class)
->give(function () {
return new Client([
'headers' => [
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'base_uri' => config('spica.url') . config('spica.space_api.path'),
'verify' => config('spica.verify_ssl'),
]);
});
I have a method and variable which i want to be used on multiple controllers. I'm not too sure whether to use traits or services as both of them to my understanding serve almost the same purpose?
private $app_id;
private $archiving;
public function setArchieve()
{
$this->app_id = config('archiving.id');
$this->archiving = Application::findOrFail($this->app_id);
}
public function guzzleClient() {
$headers = [
'Content-Type' => 'application/json',
'X-Requested-With' => 'XMLHttpRequest',
'Authorization' => 'Bearer ' . $this->archiving->token,
];
$client = new \GuzzleHttp\Client([
'headers' => $headers,
'http_errors' => false,
'base_uri' => $this->archiving->url . '/api/',
]);
return $client;
}
I have:
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use GuzzleHttp\Client;
class GetBlahCommand extends Command {
protected $name = 'blah:blah';
protected $description = "name";
public function handle()
{
$client = new Client();
$res = $client->request('GET', 'https://someapi.com', [
'api_key' => ['privatekey']
]);
echo $res->getStatusCode();
}
}
But the param api_key isn't being passed along.
How can I get this to work?
I have adjusted my code, but now I am getting NULL returned:
$ndbnos = [
'ndbno' => '01009'
];
$client = new Client(['base_uri' => 'https://soemapi.com']);
$res = $client->request('GET', '/', [
'query' => array_merge([
'api_key' => 'somekey'
], $ndbnos)
])->getBody();
$res = json_decode($res);
var_dump($res);
I figured it out:
public function handle()
{
$ndbnos = [
'ndbno' => '01009'
];
$client = new Client(['base_uri' => 'https://someapi.com']);
$res = $client->request('GET', '', [
'query' => array_merge([
'api_key' => 'somekey',
'format' => 'json'
], $ndbnos)
]);
print_r(json_decode($res->getBody()));
}
You can be do it by the following way:
public function handle()
{
$client = new Client(['base_uri' => 'https://someapi.com/']);
$res = $client->request('GET', '/', [
'headers' => [
'api_key' => 'YOUR_KEY'
]
]);
}
I thought it's a header parameter. If it's a form input you can do it by the following way:
public function handle()
{
$client = new Client(['base_uri' => 'https://someapi.com/']);
$res = $client->request('GET', '/', [
'query' => [
'api_key' => 'YOUR_KEY'
]
]);
}
I try in postman like this :
I fill just input password. Then I click button update request
The view like this :
This is header :
This is body. I select raw and input data like this :
Then I click button send and it can get the response
But when I try use guzzle 6 like this :
public function testApi()
{
$requestContent = [
'headers' => [
'Accept' => 'application/json',
'Content-Type' => 'application/json'
],
'json' => [
'auth' => ['', 'b0a619c152031d6ec735dabd2c6a7bf3f5faaedd'],
'ids' => ["1"]
]
];
try {
$client = new GuzzleHttpClient();
$apiRequest = $client->request('POST', 'https://myshop/api/order', $requestContent);
$response = json_decode($apiRequest->getBody());
dd($response);
} catch (RequestException $re) {
// For handling exception.
}
}
The result is empty
How can I solve this problem?
See in Postman, you correctly specify the field Authorization in the "headers" tab. So it sould be the same when you use Guzzle, put it in the headers:
public function testApi()
{
$requestContent = [
'auth' => ['username', 'password']
'headers' => [
'Accept' => 'application/json',
'Content-Type' => 'application/json',
],
'json' => [
'ids' => ["1"]
]
];
try {
$client = new GuzzleHttpClient;
$apiRequest = $client->request('POST', 'https://myshop/api/order', $requestContent);
$response = json_decode($apiRequest->getBody());
dd($response);
} catch (RequestException $re) {
// For handling exception.
}
}
i trying to post data as Async with using Guzzle 6(latest ver)
$client = new Client();
$request = $client->postAsync($url, [
'json' => [
'company_name' => 'update Name'
],
]);
but i am not getting any request form Guzzle like post request on terminal
Because it's a promise, you need to put then
and the promise will not called unless you put $promise->wait()
This is a simple post request using postAsync based on your question:
$client = new Client();
$promise = $client->postAsync($url, [
'json' => [
'company_name' => 'update Name'
],
])->then(
function (ResponseInterface $res){
$response = json_decode($res->getBody()->getContents());
return $response;
},
function (RequestException $e) {
$response = [];
$response->data = $e->getMessage();
return $response;
}
);
$response = $promise->wait();
echo json_encode($response);
Have you tried to send the request?
http://guzzle.readthedocs.org/en/latest/index.html?highlight=async
$client = new Client();
$request = new Request('POST', $url, [
"json" => [
'company_name' => 'update Name']
]);
$promise = $client->sendAsync($request)->then(function ($response) {
echo 'I completed! ' . $response->getBody();
});
$promise->wait();
Guzzle 6 has very little practical examples/documentation for developers to refer. I am sharing an example on how to use postAsync and Pool object. This allows concurrent async requests using guzzle 6. ( I was not able to find a direct example and spent 2 days to get working code )
function postInBulk($inputs)
{
$client = new Client([
'base_uri' => 'https://a.b.com'
]);
$headers = [
'Authorization' => 'Bearer token_from_directus_user'
];
$requests = function ($a) use ($client, $headers) {
for ($i = 0; $i < count($a); $i++) {
yield function() use ($client, $headers) {
return $client->postAsync('https://a.com/project/items/collection', [
'headers' => $headers,
'json' => [
"snippet" => "snippet",
"rank" => "1",
"status" => "published"
]
]);
};
}
};
$pool = new Pool($client, $requests($inputs),[
'concurrency' => 5,
'fulfilled' => function (Response $response, $index) {
// this is delivered each successful response
},
'rejected' => function (RequestException $reason, $index) {
// this is delivered each failed request
},
]);
$pool->promise()->wait();
}
most likely you'll need to call wait();
$request->wait();