I'm working on a php application using Slim framework. My application homepage is making about 20 REST API calls, which is slowing down the page load.
I read that I can use Http Clients like Guzzle to call these API's asynchronously but I couldn't find any article that tells how to use Guzzle with Slim.
Can someone tell how to use Guzzle with Slim.
Or is there any other solution that can speed up the page load?
N.B: I'm a novice in PHP
To use Guzzle with Slim, you need to
Install it by running composer
$ composer require guzzlehttp/guzzle:~6.0
Guzzle installation
Guzzle Quickstart
Create dependency registration, for example
<?php
use GuzzleHttp\Client;
$container = $app->getContainer();
$container['httpClient'] = function ($cntr) {
return new Client();
};
and put it somewhere where it will be executed when index.php the main bootstrap file is loaded.
Then in your code, you can get guzzle instance from container
$guzzle = $container->httpClient;
For example if you have following route
$app->get('/example', App\Controllers\Example::class);
And controller Example as follow
<?php
namespace App\Controllers;
use GuzzleHttp\ClientInterface;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
class Example
{
private $httpClient;
public function __construct(ClientInterface $httpClient)
{
$this->httpClient = $httpClient;
}
public function __invoke(Request $request, Response $response, array $args)
{
//call api, etc..etc
$apiResponse = $this->httpClient->get('http://api.blabla.org/get');
//do something with api response
return $response;
}
}
To inject guzzle instance to Example controller, you create its dependency registration
use App\Controllers\Example;
$container[Example::class] = function ($cntr) {
return new Example($cntr->httpClient);
}
To speed up your page load, if you are API developer then start from there. If you are not API developer and have no control, try to think if you can reduce number of API calls by removing non essential ones. Or as last resort, cache API call response to storage that is faster for your application to retrieve later.
For example using redis.
You calculate hash of API url call including its querystring and use hash as key to access cached API call response.
Related
I have an application running on webserver A. I have a second application running on webserver B. Both webservers require a login. What I need to do is have a request to webserver A pass through to webserver B and return a file to the client without having the client login to Webserver B. (In other words, webserver B will be invisible to the client and I will take care of the auth credentials with my request to B from A). The code below is built on a laravel framework, but I don't believe the answer needs to be laravel specific.
The code works but it is only returning the HEAD information of the file to the calling client. Not the file itself.
Any help will be greatly appreciated!
Controller:
public function getAudioFile(Request $request)
{
//This is the id we are looking to pull
$uid = $request->uniqueid;
$audioServices = new AudioServices();
return $audioServices->getWavFile($uid);
}
Service:
public function getWavFile(String $uniqueId)
{
$client = new GuzzleHttp\Client(['verify' => false]);
return $client->request('GET', $this->connectString.$uniqueId, ['auth' => ['username', 'password']]);
}
As mentioned by bishop you can use sink option from Guzzle to stream the response of a Guzzle request.
You can pass that stream to a response from your controller. I'm not sure if Laravel has built-in stream support, but the underlying symfony httpfoundation components do. An example of it's usage can be found in this tutorial.
If you prefer not to use the sink option from Guzzle you can also use the response itself as that implements PSR-7 stream objects.
I am using Slim on a REST API server. Some of the endpoints need to blindly be proxied to to another server, and I am using Guzzle for this part. It works most of the time to just use the Slim request as the Guzzle request (with some minor modification such as the host, etc).
<?php
use Psr\Http\Message\ServerRequestInterface as SlimRequest;
use Psr\Http\Message\ResponseInterface as SlimResponse;
use GuzzleHttp\Psr7\Request as GuzzleRequest;
use GuzzleHttp\Psr7\Response as GuzzleRequest;
$app->post('/bla/bla/bla', function (SlimRequest $slimRequest, SlimResponse $slimResponse) {
$slimRequest = $slimRequest->withUri($slimRequest->getUri()->withHost('https://example.com'));
$guzzleResponse=$this->httpClient->send($slimRequest);
});
One of my endpoints uses multipart content, and the files nor the POST content is being sent. As an alternative, I've tried the following but without success.
<?php
use Psr\Http\Message\ServerRequestInterface as SlimRequest;
use Psr\Http\Message\ResponseInterface as SlimResponse;
use GuzzleHttp\Psr7\Request as GuzzleRequest;
use GuzzleHttp\Psr7\Response as GuzzleRequest;
$app->post('/bla/bla/bla', function (SlimRequest $slimRequest, SlimResponse $slimResponse) {
$headers = array_intersect_key($slimRequest->getHeaders(), array_flip(["HTTP_CONNECTION", "CONTENT_LENGTH", "HTTP_ACCEPT", "HTTP_ACCEPT_ENCODING", "HTTP_ACCEPT_LANGUAGE", "CONTENT_TYPE"]));
$guzzleRequest = new \GuzzleHttp\Psr7\Request($slimRequest->getMethod(), $slimRequest->getUri()->getPath(), $headers, $slimRequest->getBody());
$guzzleResponse=$this->httpClient->send($guzzleRequest);
});
If necessary, I will resort to manually creating the multipart form, however, I expect there is a better way to do so since both are PSR-7 complient.
How should this best be accomplished?
PSR-7 Request objects are IMMUTABLE. That is, you cannot alter the values. Setting something new will return a new instance.
https://www.php-fig.org/psr/psr-7/
So, just change
$slimRequest->withUri($slimRequest->getUri()->withHost('https://example.com'));
to
$slimRequest = $slimRequest->withUri($slimRequest->getUri()->withHost('https://example.com'));
Also, $slimRequest->getUri()->withHost('https://example.com') returns a Request object too. What you need here is:
$slimRequest->getUri()->withHost('https://example.com')->getHost()
which will give you your string.
I would like to remove a specific cookie in a Guzzle response object.
My application uses Slim framework and I make calls to an API with Guzzle. Both Slim and Guzzle implement the Request and Response Interface (Psr7) so I can easily return a Guzzle response in a Slim controller like this :
class APIController {
public function call($request, $response) {
// Do stuff with $request (check body and params, change url, etc)
$client = new \GuzzleHttp\Client();
$response = $client->send($request, []);
return $response;
}
}
Everything works fine but the API returns a cookie I want to remove. I can remove the whole header with :
$response = $response->withoutHeader('Set-Cookie');
Is there a native way in Guzzle to remove a specific cookie by name instead of removing the whole header ?
I am trying to add user-specific Metadata to every API response using Dingo Api. I assumede this would be done in an AddMetadata middleware:
<?php
namespace App\Http\Middleware\Api;
use Closure;
use Dingo\Api\Http\Request;
class AddMetadata {
public function handle(Request $request, Closure $next)
{
$response = $next($request);
/*
* Dingo API response has the ability to modify metadata responses
*/
if ($response instanceof \Dingo\Api\Http\Response) {
$oldMeta = $response->getMeta();
$meta = array_merge($oldMeta, $request->user()->metadata());
$response->setMeta($meta);
}
return $response;
}
}
What I find is the Response at this point is no longer a Dingo API response, therefore I am unable to add metadata. I tried using the Dingo\Api\Http\Response::makeFromExisting() method to create a new response from the old request, I've also tried instantiating a new response but it appears that the Dingo Api response is processed before getting to the middleware.
What would be the most efficient way of adding the user-specific metadata to the response? Ideally I don't want to be adding it to every API endpoint individually.
I'm using Goutte to make a webscraper.
For development, I've saved a .html document I'd like to traverse (so i'm not constantly making requests to the website). Here's what I have so far:
use Goutte\Client;
$client = new Client();
$html=file_get_contents('test.html');
$crawler = $client->request(null,null,[],[],[],$html);
Which based of what I know should call request in Symfony\Component\BrowserKit, and pass in the raw body data. Here's the error message I'm getting:
PHP Fatal error: Uncaught exception 'GuzzleHttp\Exception\ConnectException' with message 'cURL error 7: Failed to connect to localhost port 80: Connection refused (see http://curl.haxx.se/libcurl/c/libcurl-errors.html)' in C:\Users\Ally\Sites\scrape\vendor\guzzlehttp\guzzle\src\Handler\CurlFactory.
If I were to just use DomCrawler, it's non-trivial to create a crawler using a string. (see: http://symfony.com/doc/current/components/dom_crawler.html). I'm just unsure about how to do the equivalent with Goutte.
Thanks in advance.
Tools you decided to use make real http connections and are not suitable for what you want to do. At least out of the box.
Option 1: Implement your own BrowserKit Client
All goutte does is it extends BrowserKit's Client. It implements http requests with Guzzle.
All you need to do to implement your own client, is to extend the Symfony\Component\BrowserKit\Client and provide the doRequest() method:
use Symfony\Component\BrowserKit\Client;
use Symfony\Component\BrowserKit\Request;
use Symfony\Component\BrowserKit\Response;
class FilesystemClient extends Client
{
/**
* #param object $request An origin request instance
*
* #return object An origin response instance
*/
protected function doRequest($request)
{
$file = $this->getFilePath($request->getUri());
if (!file_exists($file)) {
return new Response('Page not found', 404, []);
}
$content = file_get_contents($file);
return new Response($content, 200, []);
}
private function getFilePath($uri)
{
// convert an uri to a file path to your saved response
// could be something like this:
return preg_replace('#[^a-zA-Z_\-\.]#', '_', $uri).'.html';
}
}
$client = new FilesystemClient();
$client->request('GET', '/test');
Client's request() needs to accept real URIs, therefore you need to implement your own logic to convert it to a filesystem location.
Have a look at Goutte's Client for insipration.
Option 2: Implement a custom Guzzle handler
Since Goutte uses Guzzle, you could provide your own Guzzle handler that would load responses from files, instead of making real http requests. Have a look at the handlers and middleware doc.
If you're just after caching responses so you make less http requests, Guzzle provides support for this already.
Option 3: Use DomCrawler directly
new Crawler(file_get_contents('test.html'))
The only drawback is you'll loose some of convenience methods of the BrowserKit client, like click() or selectLink().