How to avoid sending a Guzzle request after instantiation? - php

Context: Laravel 5. Guzzle ~5.2. PHP 5.4. I'm building a class to interact with an external API. I'm providing this class with a Guzzle client using a Service Provider, to avoid instantiating the client within a method.
I want to cache the results. If the user is asking for something that is found in the cache, return it instead of performing a request to said API.
Problem: If I build up a Guzzle client and don't perform a request, the application crashes. Not even a stack trace from PHP. Actually, if I'm using Laravel's artisan serve, a Windows error message shows up saying, PHP CLI has stopped working.
For now, I'm passing the Guzzle client to the method on my class, every single time I call it.
Is there a way to just instantiate the Guzzle client without sending a request? What other route would you choose to achieve this? Is that intended behaviour?

tl;dr RTM
Longer version (from the docs):
Creating Requests
You can create a request without sending it. This is useful for building up requests over time or sending requests in concurrently.
$request = $client->createRequest('GET', 'http://httpbin.org', [
'headers' => ['X-Foo' => 'Bar']
]);
// Modify the request as needed
$request->setHeader('Baz', 'bar');
After creating a request, you can send it with the client’s send() method.
$response = $client->send($request);

from here:
http://docs.guzzlephp.org/en/stable/quickstart.html#making-a-request
use GuzzleHttp\Psr7\Request;
$client = new Client([
// Base URI is used with relative requests
'base_uri' => 'http://httpbin.org',
// You can set any number of default request options.
'timeout' => 2.0,
]);
$request = new Request('PUT', 'http://httpbin.org/put');
$response = $client->send($request, ['timeout' => 2]);

Related

How do I make a guzzle response into a response to a request from a client?

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.

Guzzle returns cURL error 3: <url> malformed

I want to try out the guzzle library and am following through their quickstart tutorial to make http requests to an api.
Yet it doesn't seem to work, because I get the following error:
cURL error 3: <url> malformed
Since I have never worked with cURL before, I don't even know how to respond to that error message. Here is my code with the request I am making:
$client = new Client();
$client->get('/', ['verify' => true]);
$response = $client->get('https://api.github.com/');
dd($response);
I am using the Laravel 5 framework and calling the index method in my HomeController. Also am using WAMP.
I would appreciate any help and suggestion, because I would like to try Guzzle out.
Here is a picture of the Error Message I get:
In case you came here because you googled "Guzzle returns cURL error 3: malformed" check the client parameter. In some version it's base_uri and other base_url
$client = new Client([
'base_uri' => 'http://localhost:8000', // <-- base_uri instead of base_url
]);
If you want to disable verification (don't do this!):
$response = $client->get('https://api.github.com/', ['verify' => false]);
Rather than disabling verification entirely, this can likely be fixed by providing proper CA bundle file. See verify in Guzzle documentation.
$client->setDefaultOption(
'verify',
'C:\Program Files (x86)\Git\bin\curl-ca-bundle.crt'
);
You should not have this call:
$client->get('/', ['verify' => true]);
That is what is throwing the error. The third line is okay.
The error is means what it says. The URL / is obviously not valid. When you instantiate the client, use the base_uri option, or specify a full URL in the call to get().
In my case, I mistakenly named my environment file as "env"
Make sure it exists at root path and named ".env"

Laravel 5 REST client CRUD

Is there a way in Laravel 5 to do a CRUD using REST? I have a REST API already using CodeIgniter and I want my Laravel application to communicate with it.
So let's say I have this url to get all gender: http://api.local/api/api_gender/gender
In my controller, it seems I can do something like this:
$results = json_decode(file_get_contents('http://api.local/api/api_gender/gender/'));
But I dont know if this is the right way to do it.
Now, how could I do it if I want to add a new gender in Laravel? In CI, I could simply use Phil's rest library and just use $this->rest->put('http://api.local/api/api_gender/gender/', $parameters)
I would use Guzzle, a very popular, flexible HTTP client for PHP. As far as I know, it is one of the most used packages to make HTTP requests in PHP.
$client = new GuzzleHttp\Client();
$client->put('http://api.local/api/api_gender/gender/', ['json' => ['foo' => 'bar']]);
// or
$client->put('http://api.local/api/api_gender/gender/', ['query' => ['foo' => 'bar']]);

Can Slim PHP make request to other services?

I'm currently building an AngularJS application with a PHP backend. The routing is done using Slim PHP and I've found an AngularJs module to do token-based authentication. In the module example for the backend they use Laravel and a client called GuzzleHttp\Client(). Now, I'm not sure what GuzzleHttp do that Slim PHP don't (if any) but I'm trying to follow along their example but I don't want to install 2 frameworks that could essentially do the same thing.
So I have my routing done so that when a request is made to the backend (auth/google) it'll do this:
public function google()
{
$app = \Slim\Slim::getInstance();
$request = $app->request()->getBody();
$body = json_decode($request);
$accessTokenUrl = 'https://accounts.google.com/o/oauth2/token';
$peopleApiUrl = 'https://www.googleapis.com/plus/v1/people/me/openIdConnect';
$params = array(
'code' => $body->code,
'client_id' => $body->clientId,
'redirect_uri' => $body->redirectUri,
'grant_type' => 'authorization_code',
'client_secret' => GOOGLE_SECRET
);
$client = new GuzzleHttp\Client();
// Step 1. Exchange authorization code for access token.
$accessTokenResponse = $client->post($accessTokenUrl, ['body' => $params]);
$accessToken = $accessTokenResponse->json()['access_token'];
$headers = array('Authorization' => 'Bearer ' . $accessToken);
// Step 2. Retrieve profile information about the current user.
$profileResponse = $client->get($peopleApiUrl, ['headers' => $headers]);
$profile = $profileResponse->json();
// Step 3a. If user is already signed in then link accounts.
if (Request::header('Authorization'))
{
$user = User::where('google', '=', $profile['sub']);
if ($user->first())
{
return Response::json(array('message' => 'There is already a Google account that belongs to you'), 409);
}
$token = explode(' ', Request::header('Authorization'))[1];
$payloadObject = JWT::decode($token, Config::get('secrets.TOKEN_SECRET'));
$payload = json_decode(json_encode($payloadObject), true);
$user = User::find($payload['sub']);
$user->google = $profile['sub'];
$user->displayName = $user->displayName || $profile['name'];
$user->save();
return Response::json(array('token' => $this->createToken($user)));
}
// Step 3b. Create a new user account or return an existing one.
else
{
$user = User::where('google', '=', $profile['sub']);
if ($user->first())
{
return Response::json(array('token' => $this->createToken($user->first())));
}
$user = new User;
$user->google = $profile['sub'];
$user->displayName = $profile['name'];
$user->save();
return Response::json(array('token' => $this->createToken($user)));
}
}
Now this won't work because I don't have GuzzleHttp installed but my question is: can I do this in Slim PHP or do I need GuzzleHttp to complement it?
Guzzle is a code based HTTP client package/framework which also contains DOM crawling functionality not a micro-framework, thus it is not analogous to Slim.
From their Readme:
Guzzle is a PHP HTTP client that makes it easy to send HTTP requests and trivial to integrate with web services.
Slim does not provide this functionality directly because it doesnt fall under what Slim is meant to do which is transform HTTP requests into HTTP responses (and the core things that need to happen in between).
Since your example is in Guzzle and it implements what you are trying to do i would probably use Guzzle. However, you could do the same types of thing (ie. interact with an external web service) using cURL, ext/http, or another HTTP client package. There are several.
Slim is a micro-framework that provides basically client requests and responses, coming from the application. It means that Slim responds to request, it doesn't make requests to external HTTP, for instance. The idea of Slim is to provide routes, do stuff when it comes and responds to the client.
If you need to use external calls, you'll need to use any HTTP Client, that'll provide the "ability" to make requests and threat with the response. You can use curl natively (all the others are only "interfaces" to curl) or a lib.
While Slim and Guzzle have a lot in common ie they both deal with psr-7 requests and responses
There is one key difference
Slim Deals with processing requests and sending responses
Guzzle Deals with sending requests and processing responses
as such they are not interchangeable and deal with opposite ends of the communication pipeline
so if your processing requests that someone has sent to you you need slim or something similar
if you are sending requests to someone else then you need guzzle or something similar

How Follow the Don't Repeat Yourself Principle When Consuming My Own Laravel API?

I'm developing a Laravel 4 app that will make the same CRUD operations on my data set available through a JSON REST API and a Web UI. It seems that to prevent breaking the DRY principle that my UI should consume my own API by routing all requests from the UI back to the API. I'm unsure though about the best approach to making this work. Presumably I would have separate UI and API controllers and somehow route the requests through. Or should I be looking at a different approach altogether?
I'm actually tinkering with the same idea and it's pretty neat. With Laravel you do have the ability to make internal requests (some might refer to this as HMVC, but I won't). Here's the basics of an internal request.
$request = Request::create('/api/users/1', 'GET');
$response = Route::dispatch($request);
$response will now contain the returned response of the API. Typically this will be returned a JSON encoded string which is great for clients, but not that great for an internal API request. You'll have to extend a few things here but basically the idea is to return the actual object back through for the internal call, and for external requests return the formatted JSON response. You can make use of things like $response->getOriginalContent() here for this kind of thing.
What you should look at doing is constructing some sort of internal Dispatcher that allows you to dispatch API requests and return the original object. The dispatcher should also handle malformed requests or bad responses and throw exceptions to match.
The idea itself is solid. But planning an API is hard work. I'd recommend you write up a good list of all your expected endpoints and draft a couple of API versions then select the best one.
NOTE: As vcardillo pointed out below, route filters are not called with these methods.
I am currently doing the same thing, and Jason's answer got me going in a great direction. Looking at the Symfony\Component\HttpFoundation\Request documentation, I figured out how to POST, as well as everything else I'd need to do. Assuming you're using a form, here is some code that could help you:
GET:
$request = Request::create('/api/users/1', 'GET');
$response = Route::dispatch($request);
POST:
$request = Request::create('/api/users/1', 'POST', Input::get());
$response = Route::dispatch($request);
POST w/ cookies
$request = Request::create('/api/users/1', 'POST', Input::get(), Cookie::get('name'));
$response = Route::dispatch($request);
POST w/ files
$request = Request::create('/api/users/1', 'POST', Input::get(), null, Input::file('file'));
$response = Route::dispatch($request);
I hope this helps someone else. If you aren't using a form, or you are but not using Laravel's Input / Cookie facade, replace the Input / Cookie facades with your own content.
Taylor Otwell suggested using app()->handle() rather than Route::dispatch() to achieve a clean request.
For Route::dispatch($request) I noticed if the endpoint of your non-GET request (parameters on the HTTP request body) uses a dependency injected \Illuminate\Http\Request or \Illuminate\Foundation\Http\FormRequest extending instance, state of the parameters, cookies, files, etc. are from the original HTTP request. i.e., for your application's controller action method.
If parameter names and post method type for your app controller and API controller are the same, you won't notice the difference since the original parameter values are passed on. But when you're manually assembling the 3rd parameter of Request::create(), Route::dispatch() will result in it being ignored.
app()->handle() fixes that context problem in the Laravel request lifecycle.
Caveat: app()->handle() affects Illuminate\Support\Facades\Request, refreshing it with this new request instance. As a knock-on effect, calls like Request::isXmlHttpRequest() or redirect()->back() invoked after app()->handle() will cause unpredictable behaviour. I'd suggest tracking the context of your original request and instead use redirect()->to(route('...')) so you strictly control flow and state of your app.
Given all these corner cases, it may be best to just do a manual curl using a Guzzle HTTP client.
If you are looking for using passport login api internally, then you need to add the parameters to original request:
protected function manualLogin(Request $request)
{
$email = $request->input('email');
$password = $request->input('password');
$request->request->add([
'username' => $email,
'password' => $password,
'grant_type' => 'password',
'client_id' => $clientID,
'client_secret' => $clientSecret,
'scope' => '*']);
$newRequest = Request::create('/oauth/token', 'post');
return Route::dispatch($newRequest)->getContent();
}
If you're consuming your own API, use app()->handle() instead of Route::dispatch() as Derek MacDonald has suggested.
app()->handle() creates a fresh request, while Route::dispatch() runs the route within the stack, effectively ignoring parameters that are part of the request that you're sending.
Edit: Just a heads-up. Taylor Otwell advises against using sub-requests to make internal API calls, as they mess the current route. You can an HTTP API client like Guzzle instead to make the API calls.
You can use Optimus API consumer, the API is clean and simple, example making an internal request:
$response = app()->make('apiconsumer')->post('/oauth/token', $data);
In it's core, it uses Illuminate\Routing\Router and Illuminate\Http\Request to make the call
// create the request
$this->request->create($uri, $method, $data, [], [], $server, $content);
// get the response
$response = $this->router->prepareResponse($request, $this->app->handle($request));

Categories