I want to chain multiple method calls on slims's $response object, but if i do i get an error ( status 500) and nothing happens.
This might aswell be a lack of basic PHP knowledge, i am not very experienced in PHP and it is my first time working with slim, or any serverside / API framework for that matter.
I have tried flipping arround the order of the calls, and doing them in different lines but to no awail. The long term goel of this, to build an API for an update application. So i will have to handle get requests with multiple parameters, evaluate them and depending on the results, do and return deffirent responses.
// this one fails, i set the status to 900 on purpose just to see what happens
$app->get('/', function (Request $request, Response $response, array $args) {
$response->getBody()->write("Slim main page")->withStatus(900);
return $response;
});
The first example does give me an 500 error on the network tab. This would suggest some type of syntax error i guess? If i alter this route a little bit to look like this :
# this one works fine, except the status code setting gets ignored, but why?
$app->get('/', function (Request $request, Response $response, array $args) {
$response->write("Slim main page")->withStatus(900);
return $response;
});
things alsmost work out, but the status code is not set for some reason.
I would expect the first one, to return the string "slim main page" with the status code 900. Even if i use a non made up status code, this setting gets ignored.
The second code block ist just an alteration for testing purposes.
I am pretty sure this a newby thing but i am really lost here, so any advice, or some fool proof articles / docs besides the slim docs are appreciated.
The write method returns the number of bytes written to the stream (and not the new response object). Try this:
$app->get('/', function (Request $request, Response $response, array $args = []) {
$response->getBody()->write('Slim main page');
$response = $response->withStatus(200);
return $response;
});
Notice 1: Enable the error details on dev: 'displayErrorDetails' => true
Notice 2: The HTTP code 900 is an invalid status code for Slim and will throw the following execption.
Type: InvalidArgumentException
Message: Invalid HTTP status code
File: vendor/slim/slim/Slim/Http/Response.php
Line: 228
Related
I'm using the Laravel Swoole Coroutine go function to do a HTTP request in order to achieve better performance. When I get the response from the external service, a new entry is created in the database with the data from the external service.
I want to be able to return a response from the controller with that new DB record. However, it appears that anything outside the go does not have access to anything that got assigned in the go function. I understand that that happens in a separate thread, but is there a way to implement this so that I can have access to the results inside the go function?
Please note that I have coroutine enabled globally and I only to use function as shown below:
public function store(User $user, Request $request) {
go(function () {
// get data from external API using Laravel HTTP Client
...
$user = User:create($data);
return response($user, 201)->send();
});
}
I have tried using the WaitGroup(), but it complains that the event loop has already been started if I wrap it with the Co\run function.
Update
This seems to relate in some way to the reading of the stream when outputting. The function used by Slim to output the body looks like this, where $body implements StreamInterface and $this->responseChunkSize is 4096:
$amountToRead = $body->getSize();
while ($amountToRead > 0 && !$body->eof()) {
$length = min($this->responseChunkSize, $amountToRead);
$data = $body->read($length);
echo $data;
$amountToRead -= strlen($data);
if (connection_status() !== CONNECTION_NORMAL) {
break;
}
}
It appears the $body->eof() call (which is just a wrapper for PHP's feof() function) is returning true even though the full file has not been read. Not sure why that would be though. I also verified that this does not occur if I just do an fopen() on the file and create a Stream from it, then run the same code. It only happens when the stream is the product of the external REST API call via Guzzle.
Original Post
I have a service built using Slim (v4.4) that calls an external REST API using Guzzle (v6.5.3) that returns a file. This is running in Windows, web server is IIS/FastCGI (I know, unusual). PHP version is 7.3.10. The call from Slim to the external REST API retrieves the file just fine, but when my app calls the service, some files get corrupted, seems some data gets lost based on what I see in the file size. The call from the service to the external REST API is fairly simple:
$file_response = $guzzleClient->request('GET', "{$base_url}/docs/{$file_id}", [
'headers' => [
'Authorization' => "token {$token}"
]
]);
The above call works fine and returns the file correctly, I can either display it to screen or use the 'sink' option in Guzzle to save to a file, it works fine. But when I try to call the service that wraps that call, it fails. I tried a couple things. Firstly, I was just returning the response as is since it conforms to the interface required anyway. My Slim route looks like this:
$app->group('/files', function (Group $group) {
$group->get('/{file_id}', GetFileAction::class);
});
The GetFileAction class has a method like this:
public function __invoke(Request $request, Response $response, $args): Response {
...Guzzle request returning $file_response here...
return $file_response;
}
My app is also using Guzzle to call the service, the call looks like this:
$guzzleClient->request(
'GET',
"{$base_url}/files/{$file_id}",
[
'auth' => [$username, $password],
'sink' => $file_path
]
);
I wondered if returning the Guzzle response in Slim might be causing some unexpected result, so I tried returning this in the service instead:
return $response->withBody(new \Slim\Psr7\Stream($file_response->getBody()->detach()));
Same result. Obviously if somebody who has run into this exact same problem can help out it would be great, but if not some pointers on how I could try to debug the handling of the streams would likely be helpful.
I've confirmed this is linked to a weird issue with the feof() function returning true even though it hasn't read the full file. The solution I came up with involved creating a different Response Emitter than the default Slim 4 one (mostly the same) and overwrite the emitBody function so it does not rely on feof(). I did so like this:
$length = min($this->responseChunkSizeCopy, $amountToRead);
while ($amountToRead > 0 && ($data = $body->read($length)) !== false) {
echo $data;
$amountToRead -= $length;
$length = min($this->responseChunkSizeCopy, $amountToRead);
if (connection_status() !== CONNECTION_NORMAL) {
break;
}
}
So far this has worked well based on my testing. I have no idea why feof() is not working as expected and didn't really find anything that seemed to specifically address it. Maybe it's a Windows specific thing, and since PHP is less common on Windows it's not a common occurrence. But leaving this solution here in case it can help someone.
I'm trying to achieve a similar goal—using Slim to proxy and forward incoming requests to another service via a Guzzle client—and encountered a similar problem when returning the Guzzle response.
In my case the problem was that the other service was incorrectly returning a Transfer-Encoding: chunked header in the response.
Your mileage may vary, but the solution was to replace this with a correct Content-Length header in the returned response:
return $response
->withoutHeader('Transfer-Encoding')
->withHeader('Content-Length', $response->getBody()->getSize());
What is the correct way to stop code execution after sending Response headers, but without using exit()?
I know the script SHOULD return a Response, but how can I force it to be returned from outside of a Controller, for example from a service?
Lets say my service's method does return a Response this way:
return RedirectResponse($url)->send();
But in another place it can return other things. So what I can do is to check what it does actually return:
$result = $myService->doSomething();
if ($result instanceof RedirectResponse) {
return $result;
}
What I want to achieve is to avoid checking result type in every place where I use my service, BUT I would like to see the returned response in Profiler/logs (if I use exit() I can't).
Is there a way to force kernel terminate?
EDIT:
Actually the service is used in event before any controller, so I want to do redirect before any controller execution. So maybe a way to omit controller execution?
A controller is a PHP callable you create that takes information from the HTTP request and creates and returns an HTTP response (as a Symfony Response object).
The only concern for a Controller is to process a request and return a response. Having a Service handle a Response object is probably a bad design choice.
In any case, you could just die/exit your controller and use Kernel events to hook in the Request/Response flow and inspect the returned Response. Probably the terminate event is the right choice http://symfony.com/doc/current/components/http_kernel/introduction.html
Ok, I found a way to do it. All I had to do is to terminate the kernel before exit - it does dispatch all after-response events (like profiling, logging etc.).
$response = new RedirectResponse($url);
$response->send();
$kernel->terminate($request, $response);
exit();
If anyone would found better way do to this, please answer so I can switch the mark.
If the incoming request was an AJAX request, no redirect will be
generated. Instead, an HTTP response with a 422 status code will be
returned to the browser containing a JSON representation of the
validation errors.
This is not working! I am trying to access the route via an ajax request and it redirects back.
If validation passes, your code will keep executing normally. However, if validation fails, an Illuminate\Contracts\Validation\ValidationException will be thrown. This exception is automatically caught and a redirect is generated to the user's previous location. The validation errors are even automatically flashed to the session!
Now I want to know where does laravel catch this exception so that I can modify it?
This is handled inside the FormRequest class:
protected function failedValidation(Validator $validator)
{
throw new HttpResponseException($this->response(
$this->formatErrors($validator)
));
}
You can override this function in your own Request object and handle a failed validation any way you like.
After been researching for a while I will post my results so anyone with this problem saves a lot of time.
#Faiz, you technically shouldn't change a thing if you want to stick to laravel behavior (I'll always try to follow taylor's recommendations). So, to receive a 422 response code status you need to tell phpunit you will send a XMLHttpRequest. That said, this works on laravel 5
$response = $this->call('POST', $url, [], [], [],
['HTTP_X_Requested-With' => 'XMLHttpRequest']);
More information at Github Issues. Besides, if you look at Symfony\Component\HttpFoundation\Request#isXmlHttpRequest you will find that this header is used by "common JavaScript frameworks" and refers to this link
Haven't tested on the browser yet, but I think this should work too.
I have spent the past few days integrating Slim API to handle some PHP web services. The first few services utilized GET which was straight forward and had no problems. However, when trying to integrate some POST methods, I am receiving no response from the service. I have tried even just a simple echo to see if the service is being called. In every case, there is no return. Code below, some of the methods have been removed for clarity.
Any reason the POST method is unresponsive? Thanks! viv
$app->get('/login/:un/:pw/:type','login');
$app->get('/browseMO/:prm1/:prm2', 'browseMedia');
$app->get('/usersReviews/:userID','usersReviews');
$app->get('/pubsReviews/:userID','pubsReviews');
$app->get('/productReviews/:productID','getProductReviews');
$app->get('/productAvg/:productID','averageReviewsByOProduct');
$app->post('/userUpd','updateUserInfo');
$app->run();
function averageReviewsByOProduct($productID){
reviews::getAvgReviewByProduct($productID);
}
function browseMedia($param1, $param2){
browseMediaObjects::getMedia($param1, $param2);
}
function updateUserInfo(){
// $request = Slim::getInstance()->request();
// $body = $request->getBody();
echo "UPDATE CALLED"; // never reached
}
Try creating an anonymous function in the slim post declaration and see if the function gets call. If it doesn't, it's something with slim. If it does, it's something with your code.
$app->post('/userUpd',function() use ($app) {
echo 'Test';
});
If that doesn't work, make sure you are returning your data correctly. For instance the above returns data correctly for an AJAX call that expects a text string response.