I tried to set the http status in my custom API when a request is being made.
protected $statusCode = 200;
public function setStatusCode($statusCode) {
$this->statusCode = $statusCode;
return $this;
}
public function respond($data, $headers = []) {
return response()->json($data, $this->getStatusCode(), $headers);
}
public function respondCreated($message) {
return $this->setStatusCode(201)->respond([
'message' => $message
]);
}
$this->respondCreated("Incident was created");
But when I make my server request in POSTMAN, I see status 200 and not 201 as set in the code above and the message is not appearing at all. Do I need to do it differently?
I am using the Laravel framework and implemented the functions by the book "Build APIs you won't hate"
I used the http_response_code() method as suggested and set the code like this:
public function respondCreated($message) {
$this->setStatusCode(201)->respond([
'message' => $message
]);
http_response_code(201);
return $this;
}
When I then return the response code it shows properly, but the POSTMAN Status is still 200?
The helper method by laravel is response() and is described as:
Returning a full Response instance allows you to customize the response's HTTP status code and headers. A Response instance inherits from the Symfony\Component\HttpFoundation\Response class, providing a variety of methods for building HTTP responses:
use Illuminate\Http\Response;
Route::get('home', function () {
return (new Response($content, $status))
->header('Content-Type', $value);
});
For convenience, you may also use the response helper:
Route::get('home', function () {
return response($content, $status)
->header('Content-Type', $value);
});
You can set the HTTP Response Code as stated on PHP documentation.
<?php
// Get the current default response code
var_dump(http_response_code()); // int(200)
// Set our response code
http_response_code(404);
// Get our new response code
var_dump(http_response_code()); // int(404)
?>
Related
Am using laravel with android and whenever a 401 error is triggered in laravel i would like to attach to the 401 error a custom header
WWW-Authenticate: xBasic realm=32334
Whenever a 401 response is returned to android i get
com.android.volley.NoConnectionError: java.io.IOException: No authentication challenges found
So after aresearch i found out the problem is due to the fact that i need to add a header to the response given i laravel
So am using the default passport oauth/token route which routes are set in authservice provider like
public function boot()
{
$this->registerPolicies();
Route::group(['middleware'=>'appconnection'], function(){
Passport::routes();
});
}
As from above ive added an appconnection middleware to passport routes now i want to handle the response to check if 401 is ever returned and add the custom header
so in my middleware am stuck at adding the header
class AppConnectionMiddleware
{
public function handle($request, Closure $next)
{
$returned = $next($request);
//check if $returned has a 401 status response
//am stuck here
}
}
So how do i manipulate the response to include the custom response header
public function handle($request, Closure $next)
{
$response = $next($request);
if ($response->status() == 401) {
$response->header('WWW-Authenticate', 'xBasic realm=32334')
}
return $response;
}
I am using symfony with angular 4. Both are different projects. In symfony I am returning a post variable which is sent via angular. I am just returning the value. Nothing other than that.
But it's returning me blank value. But when I am testing that symfony url using postman it's returning the correct value.
Symfony code
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST');
header('Access-Control-Allow-Headers:Content-Type,Accept');
return new Response("a-".$request->request->get('roll')."-b"); //gives '**a- -b**' in console. but from postman gives `a-2-b`
Angular code
this.http.post("http://localhost:8000/user/login",{roll:2}).subscribe(
(data) => console.log(data)
);
Your postman's POST request is different than the Angular's POST request
Maybe in Angular you have to add the content-type: application/json header.
To debugging I advice you to compare the two request with logging in symfony code
I'm not 100% sure but I believe you have to JSON.stringify your object on the angular side:
this.http.post("http://localhost:8000/user/login",JSON.stringify({roll:2})).subscribe(
(data) => console.log(data)
);
Next, as said in comments above. Symfony handles x-www-form-urlencoded body by default. For JSON requests you will need to parse it yourself.
e.g.:
$data = json_decode($request->getContent(), true));
Now doing this in every controller action may seem tedious and you'll loose the Symfony Parameter bag.
You can solve this by making a Request listener:
e.g.:
class JsonRequestEventListener
{
public function onKernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
if (empty($request->getContent()))
{
return;
}
if (!$this->isJsonRequest($request))
{
return;
}
if (!$this->transformJsonBody($request))
{
$response = Response::create('Unable to parse request.', 400);
$event->setResponse($response);
}
}
private function isJsonRequest(Request $request)
{
return 'json' === $request->getContentType();
}
private function transformJsonBody(Request $request)
{
$content = $request->getContent();
if ($content === '')
{
return true;
}
$data = json_decode($content, true);
if (json_last_error() !== JSON_ERROR_NONE)
{
return false;
}
if ($data === null)
{
return true;
}
$request->request->replace($data);
return true;
}
}
What this listener does is the following:
When a request comes in, it checks if there is any content and if the request is json. Then it tries to json_decode the request and inserts it in the request parameter bag. ($request->request ...)
The next thing you will have to do is register this listener:
e.g.:
jsonrequest.listener:
class: AppBundle\EventListener\JsonRequestEventListener
tags:
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest, priority: 100 }
And from now on, if your request is of type application/json and it contains valid json, you can find it's values in the Request Parameter bag:
public function someAction(Request $request)
{
$request->request->all() //holds the json
}
As I can see from your sample, you set the CORS headers in your controller as well. You could in the same way write a CORS listener to handle this for you.
I'm trying change HTTP code in Response but I always receive 200.
the code below is resumed a lot with both tests:
$app->put('/hdc/v1/pagamento/{pagamento_id}', function($pagamento_id, Request $request) use ($app) {
$pagamento = Pagamento::find($pagamento_id);
return new JsonResponse(["message"=>"error"],400); // returns 200
}
$app->put('/hdc/v1/pagamento/{pagamento_id}', function($pagamento_id, Request $request) use ($app) {
// $pagamento = Pagamento::find($pagamento_id);
return new JsonResponse(["message"=>"error"],400); // returns 400
}
Interacting with the model, JsonResponse is impacted. Why?
Postman screenshot
I think you don't use the good method. See the silex doc :
<?php
$app->put('/hdc/v1/pagamento/{pag_id}', function($pag_id, Request $request) use ($app) {
return $app->json(array('message' => 'error'), 400);
});
Awkward!
My entity file Pagamento had
?>
in the file bottom.
it was sending 200 code header when the Pagamento::find method was called.
I'm using Laravel 5.3 & Passport.
When using Postman to test any route I have set in api.php file it always returns the login page. Here is an example of my testing route:
Route::get('/getKey', function() {
return 'hello';
})->middleware('client_credentials');
Postman params:
Accept application/json
Authorization Bearer <then my key>
I have set middleware to 'auth:api' per another solution I found while searching for the answer.
protected function mapApiRoutes()
{
Route::prefix('api')
->middleware('auth:api')
->namespace($this->namespace)
->group(base_path('routes/api.php'));
}
I've tried just about every solution that has worked for others but still no luck. Any suggestions would be much appreciated.
UPDATE
So I finally got something to work. I created a consumer app and created a few test functions. I was able to consume the api, with verification of token. However, hitting this Route no longer returns my login page, but instead now returns nothing. So its still not working for whatever reason.
Route::get('/user', function (Request $request) {
return $request->user();
})->middleware('client_credentials');
The redirection to the defined login route is occurring in the app\Exceptions\Handler.php class.
protected function unauthenticated($request, AuthenticationException $exception)
{
if ($request->expectsJson()) {
return response()->json(['error' => 'Unauthenticated.'], 401);
}
return redirect()->guest(route('login'));
}
The function tries to detect whether it is being called from an API (it which case it returns a 401 Unauthorized reponse with JSON message) and if not it will redirect to the login page according to the comments it
Converts an authentication exception into an unauthenticated response
To resolve the issue in postman, on the request click on the Headers tab and add:
key: Accept
value: application/json
I'm pretty new to this so am not sure whether this is a header we should be adding when testing all API calls with Postman or just a nusience with how this laravel method is setup.
Anyway this would solve your issue with being redirected to the login page, however it's a sign your underlying authentication isn't working
You need to add Authorization: Bearer YOUR_TOKEN to your every request's header. Also, for latest version Laravel 5.5 or above. You need to add Accept: application/json to request header too.
Add this code on Headers on postman.
key Value
Accept application/json
It is coded to check whether the request comes from Ajax. In that case you will receive the following json if authentication fails:
{
"error": "Unauthenticated."
}
Otherwise it will assume you are using a browser and it will redirect to Html login page for authentication.
You can add the following header to your request to simulate an Ajax request:
X-Requested-With = XMLHttpRequest
From laravel 5.8 till the current 6.0 version there is a a middleware located at the app/http/Middleware which is \App\Http\Middleware\Authenticate, it has a method
redirectTo
with the code
protected function redirectTo($request)
{
if (! $request->expectsJson()) {
return route('login');
}
}
Re-write this to
protected function redirectTo($request)
{
if (! ($request->expectsJson() || collect($request->route()->middleware())->contains('api'))) {
return route('login');
}
}
What this code does is to return a Illuminate\Routing\Redirector instance and sets it as the \Illuminate\Auth\AuthenticationException $redirectTo parameter . which is passed to \App\Exceptions#render by laravel.
At the render function you can write a logic to catch an \Illuminate\Auth\AuthenticationException exception.
Here is my own implementation
/**
* Render an exception into an HTTP response.
*
* #param \Illuminate\Http\Request $request
* #param \Exception $exception
* #return \Illuminate\Http\Response
*/
public function render($request, Exception $exception)
{
/**
* Render an Authentification exception when user trying to viditing a route or
* Perform an action is not properly authenticated
*/
if ($exception instanceof \Illuminate\Auth\AuthenticationException) {
return $this->unauthenticated($request,$exception);
}
}
/**
* Convert an authentication exception into a response.
*
* #param \Illuminate\Http\Request $request
* #param \Illuminate\Auth\AuthenticationException $exception
* #return \Symfony\Component\HttpFoundation\Response
*/
protected function unauthenticated($request, \Illuminate\Auth\AuthenticationException $exception)
{
return $exception->redirectTo()
? redirect()->guest($exception->redirectTo())
: response()->json(['message' => $exception->getMessage()], 401);
}
As noted in many of the answers the reason this happens is indeed the code:
if (! $request->expectsJson()) {
return route('login');
}
in app\Http\Middleware\Authenticate.php
One way to solve this is to wrap your api requests in Middleware that adds 'Accept: application/json' to the header of those requests.
I got this idea from this article: https://medium.com/#martin.riedweg/laravel-5-7-api-authentification-with-laravel-passport-92b909e12528
Laravel version: 9.*
// app/Http/Middleware/Authenticate.php
protected function redirectTo($request)
{
return '/api/unauthenticated'; // You can name whatever route you want
}
// routes/api.php
Route::get('/unauthenticated', function () {
return response()->json(['message' => 'Unauthenticated.'], 403);
});
Hope this helps.
Happy coding!
If you don't want to enforce your api consumer to pass Accept - application/json in all the request headers you can add the following method in App\Exceptions\Handlers.php; to customize error response
//use Illuminate\Auth\AuthenticationException;
protected function unauthenticated($request, AuthenticationException $exception)
{
if ($request->is('api/*')) {
return response()->json(['error' => 'Invalid token', 'status' => false], 401);
}
return redirect()->guest(route('login'));
}
if you are using username instead of email for credential; insert this method at your User model:
function findForPassport($username) {
return $this->where('username', $username)->first();
}
Using Laravel 5.2.
I am getting the following error when navigating to a route.
Argument 1 passed to Illuminate\Session\Middleware\StartSession::addCookieToResponse() must be an instance of Symfony\Component\HttpFoundation\Response, null given, called in /Users/.../vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php on line 72 and defined
What I don't understand is that if the request is made via POST, no ErrorException is thrown and the expected response is received.
I have modified the App\Exceptions\Handler::handler($request, Exception $e) method to the following;
public function render($request, Exception $e) {
// If request is being made via AJAX or wants a JSON response.
if ($request->ajax() || $request->wantsJson()) {
// Check to see if 'getStatusCode()' method exists, otherwise use 500.
$status_code = method_exists($e, 'getStatusCode') ? $e->getStatusCode() : 500;
// Send JSON response.
return json($e->getMessage(), $status_code);
}
return parent::render($request, $e);
}
The json() helper is defined as:
function json($message, $status = 200, $data = null) {
$output = [
'success' => is_between($status, [200, 299]),
'statusCode' => $status,
'timestamp' => time(),
'message' => $message,
];
if(!is_null($data))
$output['data'] = $data;
return \Illuminate\Support\Facades\Response::json($output, $status);
}
and is_between();
function is_between($value, array $range) {
if($value > $range[1])
return false;
if($value < $range[0])
return false;
return true;
}
I have tried reverting App\Exceptions\Handler::handler($request, Exception $e) method back to see if any of that could be causing an issue, but I still get the same problem.
I have also tried rewriting the routes and the controller, with the same end result, works if made via a POST request, otherwise an ErrorException is thrown.
Somehow the framework is not converting the returned string into a response object. The addCookieToResponse method in the Illuminate\Session\Middleware\StartSession class is wanting a Response object as the first param. Make sure that you return one in all of your routes.
Here's a possible quick fix, change it to fit your case.
Before:
Route::get('hi', function() {
return 'hi';
});
After:
Route::get('hi', function() {
return response('hi');
});