Symfony JsonResponse ignoring defined HTTP code and only responses 200 - php

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.

Related

How to stop php script in Laravel when Axios request is cancelled?

I'm making a live search using Laravel, VueJs and axios, so every time the user types a new word, the previous request will be cancelled. My problem is that even when I cancel the previous request using the cancel token (https://github.com/axios/axios#cancellation), the php script is still running.
QUESTION: How can i stop the php script if the axios request has been cancelled?
my Vuejs code
fetchData(query) {
if(cancel != undefined)
cancel(); // cancel the previous request
axios.get("http://sample-link", {
cancelToken: new CancelToken(function executor(c) {
cancel = c;
}),
params: {
query : query
}
}).then(response => {
console.log(response);
}).catch(error => {console.log(error.message)})
}
...
my php code
class SearchController extends Controller {
public function Search(Request $request)
{
$query = $request->input('query');
$accounts = Accounts::search($query, null, true, true)->get();
return response()->json($accounts);
}
...
}
I don't know exactly how the Axios cancellation works, but if the client disconnects the HTTP session (as it should) you could try using the ignore_user_abort setting.
public function Search(Request $request)
{
ignore_user_abort(false);
$query = $request->input('query');
$accounts = Accounts::search($query, null, true, true)->get();
return response()->json($accounts);
}
Other useful functions could be:
connection_status: Check the current status of the connection
connection_aborted: Check if the connection has been canceled
I just want to add that your function does not seem very intensive, so I'm not sure how useful such a check is. As Tauqeer suggested in the comment, the usual approach in those situations is to apply a debounce to your javascript search function, in order to only fire the request when the user finished typing.
An example using lodash:
const search = (query) => {
.. your axios request ...
}
const fetchData = _.debounce(search, 300)
Now you can just call fetchData(query) whenever the user types something and it will only send a request when the user stops typing for 300 milliseconds

Route Middleware in Slim 4 doesn't stop invoking the callable in the route

I'm strugling with authorization middleware in Slim4. Here's my code:
$app = AppFactory::create();
$app->add(new Authentication());
$app->group('/providers', function(RouteCollectorProxy $group){
$group->get('/', 'Project\Controller\ProviderController:get');
})->add(new SuperuserAuthorization());
Authentication middleware checks the user and works fine.
The method get in ProviderController is
public function get(Request $request, Response $response): Response{
$payload = [];
foreach(Provider::all() as $provider){
$payload[] = [
'id' => $provider->id,
'name' => $provider->name,
];
}
$response->getBody()->write(json_encode($payload));
return $response;
}
The SuperuserAuthorization looks like this
class SuperuserAuthorization{
public function __invoke(Request $request, RequestHandler $handler): Response{
$response = $handler->handle($request);
$authorization = explode(" ", $request->getHeader('Authorization')[0]);
$user = User::getUserByApiKey($authorization[1]);
if(! Role::isSuperuser($user)){
return $response->withStatus(403);//Forbidden
}
return $response;
}
}
The thing is that even though the user is not a superuser, the application continues executing. As a result I get json with all the providers and http code 403 :/
Shouldn't route middleware stop the request from getting into the app and just return 403 right away?
I know that I can create new empty response with status 403, so the data won't come out, but the point is that the request should never get beyond this middleware, am I right or did I just misunderstand something hereā€¦
Any help will be appreciated :)
------------- SOLUTION ----------------
Thanks to #Nima I solved it. The updated version of middleware is:
class SuperuserAuthorization{
public function __invoke(Request $request, RequestHandler $handler): Response{
$authorization = explode(" ", $request->getHeader('Authorization')[0]);
$user = User::getUserByApiKey($authorization[1]);
if(! Role::isSuperuser($user)){
$response = new Response();
return $response->withStatus(403);//Forbidden
}
return $handler->handle($request);
}
}
Shouldn't route middleware stop the request from getting into the app and just return 403 right away?
Slim 4 uses PSR-15 compatible middlewares. There is good example of how to implement an authorization middleware in PSR-15 meta document. You need to avoid calling $handler->handle($request) if you don't want the request to be processed any further.
As you can see in the example, if the request is not authorized, a response different from the return value of $handler->handle($request) is returned. This means your point saying:
I know that I can create new empty response with status 403, so the data won't come out, but the point is that the request should never get beyond this middleware
is somehow correct, but you should prevent the request from going further by returning appropriate response before invoking the handler, or throwing an exception and let the error handler handle it.
Here is a simple middleware that randomly authorizes some of requests and throws an exception for others:
$app->group('/protected', function($group){
$group->get('/', function($request, $response){
$response->getBody()->write('Some protected response...');
return $response;
});
})->add(function($request, $handler){
// randomly authorize/reject requests
if(rand() % 2) {
// Instead of throwing an exception, you can return an appropriate response
throw new \Slim\Exception\HttpForbiddenException($request);
}
$response = $handler->handle($request);
$response->getBody()->write('(this request was authorized by the middleware)');
return $response;
});
To see different responses, please visit /protected/ path a few times (remember the middleware acts randomly)

Difficulty in receiving POST parameter in symfony using angular 4

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.

Slim 3 middleware validation

I'm trying to implement json-schema validator from justinrainbow as middleware in Slim 3.
I can't figure out how to get the clients input from GET/POST requests in middleware.
tried like this:
$mw = function ($request, $response, $next) {
$data = $request->getParsedBody();
print_r($data); // prints nothing
$id = $request->getAttribute('loan_id');
print_r($id); // prints nothing
// here I need to validate the user input from GET/POST requests with json-schema library and send the result to controller
$response = $next($request, $response);
return $response;
};
$app->get('/loan/{loan_id}', function (Request $request, Response $response) use ($app, $model) {
$loanId = $request->getAttribute('loan_id'); // here it works
$data = $model->getLoan($loanId);
$newResponse = $response->withJson($data, 201);
return $newResponse;
})->add($mw);
There are 2 possible ways of how I need it. what i'm doing wrong ?
validate it in middleware and send some array/json response to the controller, which i will then get as I understood with $data = $request->getParsedBody();
validate it in middleware but final check will be in controller like this:
$app->get('/loan/{loan_id}', function (Request $request, Response $response) use ($app, $model) {
if($validator->isValid()){
//
}
$loanId = $request->getAttribute('loan_id'); // here it works
$data = $model->getLoan($loanId);
$newResponse = $response->withJson($data, 201);
return $newResponse;
})->add($mw);
Best option for me it do something like here
but I don't understand what should i return in container, and how to pass get/post input to container
Your code in the first point seems alright, you only try to access route parameter from within middleware. At that point the route is not yet resolved and therefore parameters are not parsed from the URL.
This is a known use case and is described in Slim's documentation. Add the following setting to your app configuration to get your code working:
$app = new App([
'settings' => [
// Only set this if you need access to route within middleware
'determineRouteBeforeAppMiddleware' => true
]
]);
In order to understand how middleware works and how to manipulate response object, I suggest you read the User Guide - it's not that long and explains it really well.

Setting status of HTTP request

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)
?>

Categories