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
}
Related
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.
I'm trying to make an https request using the Typhoeus::Request object and i don't get it working.
The code i'm running is something like this:
url = "https://some.server.com/"
req_opts = {
:method => :get,
:headers => {
"Content-Type"=>"application/json",
"Accept"=>"application/json"
},
:params=>{},
:params_encoding=>nil,
:timeout=>0,
:ssl_verifypeer=>true,
:ssl_verifyhost=>2,
:sslcert=>nil,
:sslkey=>nil,
:verbose=>true
}
request = Typhoeus::Request.new(url, req_opts)
response = request.run
The response i'm getting is this:
HTTP/1.1 302 Found
Location: https://some.server.com:443/
Date: Sat, 27 Apr 2019 02:25:05 GMT
Content-Length: 5
Content-Type: text/plain; charset=utf-8
Why is this happening?
Well it's hard to know because your example is not a reachable url. But 2 things I see is that you are not passing an ssl cert or key. But also 302 indicates a redirect. You can try to follow redirection but your first problem is probably you don't need to set SSL options, why are you?
See if you try the following options:
req_opts = {
:method => :get,
:headers => {
"Content-Type"=>"application/json",
"Accept"=>"application/json"
},
:params=>{},
:params_encoding=>nil,
:timeout=>0,
:followlocation => true,
:ssl_verifypeer=>false,
:ssl_verifyhost=>0,
:verbose=>true
}
See the following sections for more info
https://github.com/typhoeus/typhoeus#following-redirections
https://github.com/typhoeus/typhoeus#ssl
I am having difficulty with an API and guzzle. The API requires a json content type, and basic auth. The api example request is
POST /endpoint/v1/create?id=1
[
{
"Name": "Test Room"
}
]
My Code:
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Middleware;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Psr7;
$client = new Client(['base_uri' => env('base_uri')]);
$headers = [
'Authorization' => 'Basic',
'Accept' => 'application/json',
'Content-Type' => 'application/json',
];
$uri='/endpoint/v1/create?id=' . env('id');
$payload = ['Name' => 'Test Name'];
$response = $this->client->request('POST', $uri, ['auth' => ['username', 'password'], 'headers' => $headers, 'json' => $payload]);
All seems good to me. I've used guzzle this way in the past. However, The server responds with a "No data was submitted" Message.
The request:
POST /endpoint/v1/create?id=1 HTTP/1.1
User-Agent: GuzzleHttp/6.3.3 curl/7.58.0 PHP/7.2.5-1+ubuntu18.04.1+deb.sury.org+1
Authorization: Basic {Auth basic string goes here}
Host: {host goes here}
Accept: application/json
Content-Type: application/json
{"Name":"Test Name"}
The Response:
HTTP/1.1 400 Bad Request
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/json; charset=utf-8
Expires: -1
WWW-Authenticate: Basic
Date: Thu, 21 Mar 2019 01:04:21 GMT
Content-Length: 36
{"Message":"No data was submitted."}
EDIT:
I'm able to successfully complete this request in postman. The API responds to [{"Name":"Test Name"}] but not {"Name":"Test Name"}, does anyone know how to replicate that with guzzle?
I was able to solve this problem by wrapping the payload in an array like so:
$payload = [['Name' => 'Test Name']];
I have the following code in my controller:
public function upload(Request $request)
{
$files = $request->file('uploads');
if(!empty($files)) {
foreach($files as $file) {
Storage::put($file-getClientOriginalName(),file_get_contents($file));
}
}
Which is called via an api.php in routes:
Route::post('/upload', [ 'uses' => 'UploadController#upload' ]);
I am using postman to test my application.
Header:
Body:
Raw:
POST /scotic/public/api/upload HTTP/1.1 Host: 127.0.0.1:80
Content-Type: multipart/form-data;
boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW Cache-Control: no-cache
Postman-Token: 0caf7349-5c91-e5f1-766f-72a3f1e33900
------WebKitFormBoundary7MA4YWxkTrZu0gW Content-Disposition: form-data; name="uploads[]"; filename="banana.png" Content-Type:
image/png png data goes here..
------WebKitFormBoundary7MA4YWxkTrZu0gW--
The $files is empty upon uploading the file. What am i doing wrong?
After a bit of digging, I got my uploader working without postman, I noticed that the '--boundary' was missing from the Content-Type in postman. The LHS works, RHS(postman) does not work.
Any ideas?
The issue was that I was explicitly specifying the Content-Type in postman.
According to one of the answers from this post:
There is no need to add a content-type header manually. You are overriding the value set by Postman. Just select form-data in POST request and send your request to see if it works.
I use Laravel 5.1.
A jQuery ajax call is made like that:
$('#export_selected').click(function(){
var checked = $('.select_rows:checked');
var ids = [];
$.each(checked, function(index, value){
ids.push(value.id);
})
$.ajax({
method : "POST",
url : "{{URL::to('/spot/exportTable')}}",
data : {ids:ids}
});
});
And then the php method is defined that way:
public function exportTable(Request $req) {
$spots = array_flatten($req->all());
$res = Spot::whereIn('id', $spots)->get();
Excel::create('Spots', function($excel) use($res) {
$excel->setTitle('Title goes here');
$excel->setCreator('Creator Goes Here')->setCompany('Company Goes Here');
$excel->sheet('Excel sheet', function($sheet) use($res) {
$sheet->setOrientation('landscape');
$sheet->fromArray($res);
});
})->store('csv', storage_path('/exports', true));
$file = base_path() . '/storage/exports/Spots.csv';
$headers = ['Content-Type: application/csv', 'Content Description: File Transfer', 'Cache-Control: must-revalidate, post-check=0, pre-check=0'];
return response()->download($file, 'Spots.csv' , $headers);
}
Chrome developer console prints the results as raw lines.
The file is successfully exported and created in the disk.
The path to file is correct.
But the download is never started.
Echoing the response gives:
HTTP/1.0 200 OK
0: Content-Type: application/csv
Cache-Control: public
Content-Disposition: attachment; filename="Spots.csv"
Date: Mon, 26 Oct 2015 16:08:26 GMT
Last-Modified: Mon, 26 Oct 2015 16:08:26 GMT
1: Content-Description: File Transfer
2: Cache-Control: must-revalidate, post-check=0, pre-check=0
I put the answer here for everybody having the same issue.
#manix figured it out: I'm trying to download via Ajax, which needs to be done in another way, not the way I wrote my code