Laravel - Log Guzzle requests to file - laravel

While working on a project I found third party API's are working from Postman, but doesn't work from Guzzle Client.
Debugging a Guzzle request could be difficult, so is there any way that can log all requests made by Guzzle client can be seen?

TLDR;
There’s an easy way to log all Guzzle requests by passing second parameter to Client and then it will log all the requests. But that’s ugly way if you have many methods using Guzzle Client to send request to third party server. I’ve done it using Laravel’s Service Container.
Long way via Laravel’s Service Container
When I used Guzzle client in my project and used handler to log all requests it looks good. But later on there were many methods in many different classes so I have to write logger logic every where. Then I thought why don’t to leverage Laravel’s Service Container and bind an object once and use it everywhere.
Here’s how I did it. In your AppServiceContainer.php’s boot method we will add all our code. And then in Controllers we will use our Client object.
Add this use statements on top of the AppServiceContainer.php file.
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\MessageFormatter;
use GuzzleHttp\Middleware;
use Illuminate\Support\ServiceProvider;
use Monolog\Handler\RotatingFileHandler;
use Monolog\Logger;
Add below code to your AppServiceContainer.php’s boot method
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
$this->app->bind('GuzzleClient', function () {
$messageFormats = [
'REQUEST: {method} - {uri} - HTTP/{version} - {req_headers} - {req_body}',
'RESPONSE: {code} - {res_body}',
];
$stack = HandlerStack::create();
collect($messageFormats)->each(function ($messageFormat) use ($stack) {
// We'll use unshift instead of push, to add the middleware to the bottom of the stack, not the top
$stack->unshift(
Middleware::log(
with(new Logger('guzzle-log'))->pushHandler(
new RotatingFileHandler(storage_path('logs/guzzle-log.log'))
),
new MessageFormatter($messageFormat)
)
);
});
return function ($config) use ($stack){
return new Client(array_merge($config, ['handler' => $stack]));
};
});
}
Explanation
If you have noticed above code, In first line of boot method we are telling Laravel that we want to register this code as a GuzzleClient in your Service Container.
In last return statement we are returning a function that will accept one argument $config. We used this function as a proxy so that we can pass an argument to it and that can be used in Client Object.
return function ($config) use ($stack){
return new Client(array_merge($config, ['handler' => $stack]));
};
Rest of the code is building Guzzle’s handler object to Log all requests to a file called guzzle-log.log using Logger object of Monolog library. If you have daily logs enabled, a date will be appended to file name like guzzle-log-2019-08-11.log.
Usage
We have binded our object to Service Container, now it’s time to use this container everywhere in our code, and make it looks clean.
For demo purpose I’ve used it directly in routes/web.php file. You can use anywhere.
Route::get('/', function () {
$client = app('GuzzleClient')(['base_uri' => 'http://httpbin.org/']);
$request = $client->get('get',[
'query' => ['foo'=>'bar', 'baz' => 'baz2'] ,
'headers' => [ 'accept' => 'application/json']
]);
$response = json_decode((string) $request->getBody());
return response()->json($response);
});
As you can see I’m making an object $client using app() helper. Also you can pass any valid arguments array that Guzzle client supports as a second parameter. Here I’ve passed base_uri.
Source: http://shyammakwana.me/laravel/laravel-log-guzzle-requests-to-file-using-service-container.html

The accepted answer works nicely by using Service Providers. Another option would be to attach GuzzleHttp\Middleware's log to wherever you use Illuminate\Support\Facades\Http. An example which logs the request, the response, and any errors if found would be to use Middleware::log:
<?php
namespace App\Services;
use Illuminate\Support\Facades\Http;
use Monolog\Handler\RotatingFileHandler;
use Monolog\Logger;
use GuzzleHttp\MessageFormatter;
use GuzzleHttp\Middleware;
/**
* Class TestService
* #package App
*/
class TestService
{
private function getAccessToken()
{
try {
$response = Http::asForm()->withMiddleware(Middleware::log(with(new Logger('guzzle-log'))->pushHandler(
new RotatingFileHandler(storage_path('logs/guzzle-log.log'))
), new MessageFormatter(MessageFormatter::DEBUG)))->post("https://test.com/oauth/v2/token", [
'grant_type' => 'client_credentials',
]);
$response->throw();
} catch (\Throwable $th) {
$accessToken = false;
}
return $accessToken;
}
}
This will write log records to logs/guzzle-log-{currentDate}.log file. The format of the log record I used in this example is MessageFormatter::DEBUG which is ">>>>>>>>\n{request}\n<<<<<<<<\n{response}\n--------\n{error}" which nicely outputs the request, response, and any errors. an example of a log file would be:
[2020-08-07T07:13:23.712124+00:00] guzzle-log.INFO: >>>>>>>> POST /oauth/v2/token HTTP/1.1 Content-Length: 29 User-Agent: GuzzleHttp/7 Host: xxxx:4000 Content-Type: application/x-www-form-urlencoded grant_type=client_credentials <<<<<<<< HTTP/1.1 200 OK X-DNS-Prefetch-Control: off X-Frame-Options: SAMEORIGIN Strict-Transport-Security: max-age=15552000; includeSubDomains X-Download-Options: noopen X-Content-Type-Options: nosniff X-XSS-Protection: 1; mode=block Access-Control-Allow-Origin: * Content-Type: application/json; charset=utf-8 Content-Length: 113 ETag: W/"71-DyA+KEnetTKfUlb0lznokGTt0qk" Date: Fri, 07 Aug 2020 07:13:23 GMT Connection: keep-alive {"data":{"token_type":"Bearer","access_token":"XYZ","expires_in":"7776000"}} -------- NULL [] []
Note: The downside to this option is that you will have to attach withMiddleware(Middleware::log(...)) to anywhere you are using Illuminate\Support\Facades\Http.

Related

Laravel Echo + Websockets + Private Channels

I know I'm not the first struggling with this. But after some days and going trough a lot of related questions i somehow feel that my case deserves it's own question :).
I have a working websocket solutions with Laravel Websockets (https://beyondco.de/docs/laravel-websockets/getting-started/introduction) and Laravel Echo for public channels.
My client application is a vue-cli app and connects to the server + broadcast messages on public channels work great. The authorization is handled by Laravel Passport. So through sending a Bearer token in the Authorization header the backend application knows if the user is authenticated.
However I'm struggling to get Private channels to work. Trying to authenticate always gives me this error:
Access to XMLHttpRequest at 'https://my-app.test/broadcasting/auth' from origin 'https://localhost:8080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
I know this CORS error comes on my way when I have a server issues so I tried debugging the request in Insomnia. However when mimicking the request in Insomnia it gives a response 200 and also what would be expected:
I've been reading several guides and stackoverflow questions but I can't find anything similar.
Going back to it might be a CORS issues but I don't think that is the case. My OPTIONS request returns back just fine.
To be complete I also add some code that might help in debugging.
My BroadcastServiceProvider
public function boot()
{
Broadcast::routes(['middleware' => ['auth:api']]);
require base_path('routes/channels.php');
}
My channels.php
use Illuminate\Support\Facades\Broadcast;
Broadcast::channel('App.User.{id}', function ($user, $id) {
return (int) $user->id === (int) $id;
});
The client
this.echoClient = new Echo({
broadcaster: 'pusher',
key: process.env.VUE_APP_WEBSOCKETS_APP_ID,
wsHost: process.env.VUE_APP_WEBSOCKETS_URL,
wssPort: 6001,
// forceTLS: true,
disableStats: true,
authEndpoint: process.env.VUE_APP_SERVER_URL + '/broadcasting/auth',
auth: {
headers: {
Authorization: "Bearer " + this.$store.state.auth.auth_token
}
}
})
// this one works!!
this.echoClient.channel('App.User')
.listen('UpdatePosts', (e) => {
console.log('event received')
console.log(e)
})
// private channels don't work ... :'(
this.echoClient.private('App.User.' + this.user.id)
.listen('UpdatePosts', function(e) {
console.log(e)
})
for anyone struggling with this issue.
For me the solution was to add the api prefix to the broadcast::auth method.
public function boot()
{
Broadcast::routes(['prefix' => 'api', 'middleware' => ['auth:api']]);
require base_path('routes/channels.php');
}
Offcourse you need to correctly set the api prefix on the client:
authEndpoint: process.env.VUE_APP_SERVER_URL + '/api/broadcasting/auth',
I suppose the difference is that when you prefix api Laravel we specifically tell the server to ditch web Middleware.
I still don't really understand why the request was succesfull from Insomnia since there was no x-csrf header set. Insomnia did send a cookie header. Maybe that's the reason why it was working there.
EDIT
Solution provide by #Tippin on laracasts forum.
To add to the answer, it was a CORS issue after all.
https://github.com/fruitcake/laravel-cors
Prefixing the broadcast route with API does not alter middleware at all, so that is not putting it in the api middleware group. What I do think is happening is you may have the cors package installed and in the allowed paths, you have something like api/*, so by simply adding that prefix, you solved your issue. Otherwise, you can add the default broadcast to the whitelist (assuming you use that package for CORS):
/*
* You can enable CORS for 1 or multiple paths.
* Example: ['api/*']
*/
'paths' => ['api/*', 'broadcasting/auth'],
https://github.com/fruitcake/laravel-cors/blob/master/config/cors.php
I've got the same problem
By using fruitcake/laravel-cors, it was solved.
this is my auth option:
auth : {
headers : {
Authorization: "Bearer " + token,
Accept: "application/json",
}
},

Laravel API not accepting JSON request from Postman

Laravel API is not accepting JSON request. If I request as form-data it works but if I post JSON object in postman body then it does not receive the request data.
Route:
$router->group(['prefix' => 'imp'], function () use ($router) {
$router->group(['prefix' => 'lead'], function () use ($router) {
$router->post('/jardy', 'FeedController#jardy');
});
});
Controller:
public function jardy(Request $request)
{
$this->validate($request, [
'api_key' => 'required',
]);
$api_key = $request->input('api_key');
return $api_key;
}
JSON Request:
Form data Request:
Why its not working in case of JSON, content-type is application/json, Accept:*/*???
Comments are not permitted in JSON.
There's a bug in the field Body -> raw -> json
You have to add the header
Accept: application/json
Then laravel parses your RAW json as input vars and they can be accesed with ->input()
see:
using / which is the postman default, will not work..
If you dont want to relay on the header, you could also do $request->json() but i guess, you just want to pass the header.
See the source that handles this:
https://github.com/laravel/framework/blob/7.x/src/Illuminate/Http/Concerns/InteractsWithContentTypes.php#L52
and
https://github.com/laravel/framework/blob/7.x/src/Illuminate/Http/Concerns/InteractsWithContentTypes.php#L32
In my case it was lack of Content-Length header.
Value should be a number of characters of request body.
Content-Type: application/json also should be present.

request() not secure in Laravel with load balancer and TrustedProxies

Why is request()->isSecure() returning false when visiting a HTTPS-url? I am using Laravel Forge and I have a load balancer. Logging the request I get this data:
request()->url(), // "http://xx.xx"
request()->isSecure(), // false
request()->getClientIps(), // XX.XX.XX.XX
In TrustProxies (which is added in the $middleware array in Http/Kernel.php):
protected $proxies = [
'XX.XX.XX.XX', // exactly the same as in the logged data above
];
In my AppServiceProvider's boot() method:
if (env('APP_ENV') == 'production') {
\URL::forceScheme('https');
}
Edit:
I am also using Cloudflare, and I have added all the Cloudflare proxies to $proxies as well. Check the logs, I see these headers in the request:
X-Forwarded-Proto: http
Cf-Visitor: {"scheme":"https"}
Referer: https://xx.xx/someurl
While You are using Cloudflare You should to use "Full SSL/TLS encryption mode".

How to retrieve JSON from a API endpoint using Laravel 5.4?

I have 2 projects made with Laravel 5.4:
Prj-1 it is an Restful API that returns JSON.
Prj-2 it is an website that consumes the above endpoints.
In this way, I have the follow endpoint from Prj-1 :
myAPI.com/city that returns a list of all cities in the database in a JSON format.
Inside Prj-2 I have the follow:
Route:
Route::get('/showCityAPItest','AddressController#getcity');
Controller:
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
...
public function getcity()
{
$request =Request::create('http://myAPI.com/city', 'GET');
$response = Route::dispatch($request);
dd($response);
}
If I use directly the URL (http://myAPI.com/city) in the browser, it works. I can see the JSON as expected.
BUt when I try to retrieve it from the Prj-2 I can't see it in the browser.
Actually I see
404 Not Found
nginx/1.10.1 (Ubuntu)
I was following this post but I dont know what am I doing wrong.
Any help?
Yeah, #Joel Hinz is right. Request will work within same project api end.
You need to use GuzzleHttp.
Step 1. Install guzzle using composer. Windows base operating guzzle command for composer :
composer require guzzlehttp/guzzle
step 2 write following code
public function getcity()
{
$client = new \GuzzleHttp\Client;
$data =$client->request('GET', 'http://myAPI.com/city', [
'headers' => [
'Accept' => 'application/json',
'Content-type' => 'application/json'
]
]);
$x = json_decode($data->getBody()->getContents());
return response()->json(['msg' => $x], 200);
}
The answer to which you are referring is talking about internal routes. You can't use the Request and Route facades for external addresses like that. Instead, use e.g. Guzzle (http://docs.guzzlephp.org/en/latest/) to send your requests from the second site.

Laravel 5 API using json and Guzzle

I'm quite new to Laravel 5 but I'm working on project involing 2 servers (S1 and S2). Each one is running a Laravel 5 REST WebService (API) WS1 and WS2.
The workflow is the following :
WS1 : just get a query like http://s1.ip/api/object/1 where 1 is the id of the object. It just "reroute" the query to WS2.
WS2 : get the same kind of query http://s2.ip/api/object/1 using a personnal access token from Laravel/Passport.
WS2 : Ask the local mysql DB to know if the object with id 1 is 'valid' or not.
WS2 : Create a response to the WS1 Query using json. Something like :
{"id": "2", "valid": true}
WS1 : get the response from WS2 and from it create its own response to the initial GET query.
I test my REST API using Postman.
WS2 is work well when using Postman.
But when I try to query WS1, I never get the json from the response that is send from WS2.
Hope I clear.
Here are some of my source code:
routes\api.php
Route::get('/object/{obj_id?}', 'ObjectController#check')->where('obj_id', '[0-9]+');
app\Http\Controllers\ObjectController.php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Client;
use App\Http\Requests;
use App\Http\Controllers\Controller;
class ObjectController extends Controller
{
public function check($obj_id)
{
$token = 'my personnal access tokens from WS2 Laravel/Passport'
$client = new Client();
$body = $client->request('GET','http://S2.ip/api/object/' . $object_id, [
'Accept' => 'application/json',
'Content-Type' => 'application/json',
'Authorization' => 'bearer ' . $token,
])->getBody();
$contents = (string) $body;
$data = json_decode($contents);
dd($data); // to see what's inside $data
The dd($data) output the following : null
I have try many things but I never manager to get the json.
What am I doing wrong. I would really appreciate some help on this.
Thanks
Edit:
Here is the S2 answer I got from PostMan or Rested:
S2 returns the following when using Postman:
Response - http://s2.ip/api/object/1
200 OK
Headers
Date: Thu, 12 Jan 2017 08:38:31 GMT
Vary: Authorization
Server: Apache/2.4.17 (Win32) OpenSSL/1.0.2d PHP/5.6.21
X-Powered-By: PHP/5.6.21
X-RateLimit-Remaining: 59
Content-Type: application/json
Cache-Control: no-cache
X-RateLimit-Limit: 60
Connection: Keep-Alive
Keep-Alive: timeout=5, max=100
Content-Length: 46
Response body
{
"id": "1",
"authorized": false
}

Resources