I created a download route from which my client successfully downloads the file by adding the following code to /app/Http/routes.php:
$app->get('/dl', function() use ($app){
return response()->download(getcwd() . "/targetfile");
});
but now I'd like to somehow have a callback be executed once the file transfer has been complete for a given instance of the get request. How would I do that?
To be a little more specific I'm trying to delete the file once it has been downloaded but this doesn't work:
$app->get('/dl', function() use ($app){
unlink(getcwd() . "/targetfile");
return response()->download(getcwd() . "/targetfile");
});
Answer to original Question
So in order to have a 'callback' once a response has been sent you use a so-called 'terminable' middleware.
In order for a middleware to be terminable:
1) it has to be global; I.E registered with the middleware function instead of the routeMiddleware
$app->middleware([
App\Http\Middleware\Myterminable::class
]);
2) It has to have the method 'terminate ($request, $response)' this is the method that will be called once the response has been sent.
public function terminate($request, $response)
{
// your code here
}
An alternative
A global middleware seems to be like a waste of computation resources as it's called for every request/response pair so instead I just looked through Lumen's code to find that the download method is actually returning an instance of
Symfony\Component\HttpFoundation\BinaryFileResponse;
On which I called
deleteFileAfterSend(true);
Which served my original goal
return response()->download(getcwd() . "/targetfile")->deleteFileAfterSend(true);
You need to register an after Middleware and apply it to your route.
Documentation about Middleware
Related
I have two POST routes /test and /test_new. The objective is to redirect all incoming requests from /test_new to /test along with the body contents. Hence The following code aims to use a named route for redirection
$app->post('/test', function (Request $request, Response $response, $args) {
$response->getBody()->write($request->getBody()->getContents());
return $response;
})->setName('test');
$app->post('/test_new', function (Request $request, Response $response, $args) use ($app) {
$routeParser = RouteContext::fromRequest($request)->getRouteParser();
// $routeParser = $app->getRouteCollector()->getRouteParser();
return $response->withStatus(307)->withBody($request->getBody())
->withHeader('Location', $routeParser->urlFor('test'));
});
This piece of code fails with the message
/test_new - Uncaught RuntimeException: Cannot create RouteContext before routing has been completed in /home/biswa/test/test-slim/vendor/slim/slim/Slim/Routing/RouteContext.php:40
If I simply replace the $routeParser with the commented line in the second route handler and use the $app for the RouterParser it works fine. But In my actual code I have the handling logic in a class function and have no access to $app in there.
Can someone please help me to sort this out
Missed the loading of RoutingMiddleware as stated here
i've registered a post route and set the "auth" middleware on it,everything is working properly but at the last step when i want to redirect to my registered route after authentication,an error occurred, it seems redirect() helper function uses GET method by default,but my route supports POST method.is there any way to use redirect() with POST method?!!
Route::post('match', 'HomeController#match')->name('match')->middleware('auth');
and inside my LoginController:
if ($is_match==='comes_from_match') {
return redirect()->route('match');
}else{
return redirect()->route('dashboard');
}
this leads to the following error:
"The GET method is not supported for this route. Supported methods: POST."
Try the following:
You can use any instead of post it works for both get and post.
Route::any('match', 'HomeController#match')->name('match')->middleware('auth');
As per latest Laravel documentation:
Sometimes you may need to register a route that responds to multiple HTTP verbs. You may do so using the match method. Or, you may even register a route that responds to all HTTP verbs using the any method:
You can use match or any method:
Route::match(['get', 'post'], '/', function () {
//
});
Route::any('/', function () {
//
});
Reference:
Laravel -> Routing -> Basic Routing
I have a generic route like below -
$app->map(['GET', 'POST'], "/", function (\Slim\Http\Request $request, \Slim\Http\Response $response) use ($app) {
// Some random version 1 processing here ...
}
And I have a v2 version of the same like below -
$app->map(['GET', 'POST'], "/api/v2", function (\Slim\Http\Request $request, \Slim\Http\Response $response) use ($app) {
// Some random version 2 processing here ...
}
I have a problem to solve -
The frontend always hits the v1 of the API (the generic route). I need to internally redirect to v2, based on the parameter appversion's value.
How can I do this in slim framework routing without issuing 302 redirect headers back to the client? Basically, I need to redirect internally to the server itself.
Note: I am aware of Nginx and Apache rewrites. Let us keep them aside and limit the scope to slim framework routing alone.
I'd do it using optional segments in a single route definition.
use Psr\Http\Message\{
ServerRequestInterface as Request,
ResponseInterface as Response
};
$app->map(['GET', 'POST'], '/[{v2:api/v2}]', function (Request $request, Response $response, array $args) {
if (isset($args['v2'])) { // You may also check $request->getAttribute('appversion')
// Version 2 processing here...
// return $response;
}
// Version 1 processing here...
// return $response;
});
What you want to achieve is technically possible, but to me, it seems the reason behind your question is that you want to introduce a new version of your API, yet you want not to (or you can not) update the front end to call the new version, and instead, you want to handle this in the back end.
If you are going to decide which version of your API needs to be called based on appversion but not the endpoint that was hit, then what is the benefit of defining v1 and v2 endpoints?
If someone calls your v1 endpoint, they want your v1 response, and if someone needs your v2 response, they must call your v2 endpoint. If you return the same response for both v1 and v2 endpoints, then you're basically updating your v1 endpoint behavior.
Anyway, you want to dispatch another route in another route callback, and here is a fully working example showing how it's done using a subRequest:
<?php
require 'vendor/autoload.php';
$app = new \Slim\App;
$app->map(['GET', 'POST'], '/v1', function($request, $response) use ($app) {
// Get the appversion query parameter and make the decision based on its value
$appVersion = $request->getParam('appversion');
if($appVersion == '1.0') {
return $app->subRequest($request->getMethod(), '/v2',
http_build_query($request->getQueryParams()),
$request->getHeaders(),
$request->getCookieParams(),
$request->getBody()->getContents()
);
}
return 'API version: v1, appversion: ' . $appVersion;
});
$app->map(['GET', 'POST'], '/v2', function($request, $response) {
return 'API version: v2, request method: ' . $request->getMethod() .', appversion: '. $request->getParam('appversion') . ', body: <pre>' . print_r($request->getParsedBody(), 1);
});
$app->get('/form', function() {
return <<<form
<form method="POST" action="/v1?appversion=1.0">
<input type="text" name="foo" value="bar">
<button type="submit" value="submit">Submit</button>
</form>
form;
});
$app->run();
Now if you try to reach /v1?appversion=1.0 the response from /v2 callback will be returned. Trying to reach /v1 with appversion equal to any other value (for example /v1?appversion=2.0) causes the app to return the v1 response.
The subRequest method is also capable of handling POST requests. Please refer to method documentation in Slim code repository. The example provides a /form URI to demonstrate that.
You'd need to abstract your invocations and create controllers (i.e. your code won't be so slim).
Example:
function doV2() {
// ... do V2 things
}
function doV1() {
// ... do V1 things
}
$app->map(['GET', 'POST'], "/", function (\Slim\Http\Request $request, \Slim\Http\Response $response) use ($app) {
if($app->request()->params('appversion') === 'v2') return doV2();
doV1();
}
$app->map(['GET', 'POST'], "/api/v2", function (\Slim\Http\Request $request, \Slim\Http\Response $response) use ($app) {
doV2();
}
The reason you cannot 'redirect' internally is because your 'controllers' are anonymous closures so you have no way to name/reference/call them. Instead, if you abstract them out to functions (and yes, you'll probably need to pass in $request/$response too) then you have named methods you can invoke for the appropriate routes.
Instead of defining closures or functions, you could also define your controllers and use SlimPHP's Container Resolution within the router - there's a great example in the router docs.
Last up, you could get tricky with middleware to change what happens based on your appversion param depending on the complexity of what you're wanting to achieve.
Brief:
Actually, I'm little bit confused while using Laravel api route file.
Question:
If I need to access the data of my web site in other platform (like android app) that is made using laravel, then should I create a different route in api.php file?
If yes then I will be declaring two routes and controllers for each request, first in web.php and second in api.php. Is it correct?
Basically, I want to ask that how I can make an api, so that I can access the data in website as well as in other platforms?
I was searching for a good tutorial for this, but I didn't got a good one.
Ideally the API routes and Web routes should be completely different but if you want it to be same then instead of defining routes in different file you can add routes only in web.php and add a special parameter from your client and in controller if you are getting the parameter then return the JSON Object or else return the view.
Eg.
web.php
Route::get('getUsers','UserController#getUsers');
UserController.php
...
public function getUsers(Request $request)
{
...
if ($request->has('api')) {
return $users; //API Route (Laravel will by Default return the JSON Response no need to do json_encode)
}
return view('pages.user_list'); //Normal Routes hence returning View
}
...
Requests
Normal Request
<Yourdomain>/getUsers
API Request
<Yourdomain>/getUsers?api=true
I hope that helped...
Write your api routes in api.php and web routes in web.php.
The Api routes always have the name api in the routes thus you can differentiate the routes., I mentioned here because as #Akshay Khale mentioned an example with query parameter.
if you want to use the same controller for both API and Web, Api Requests always have the Header Content-Type : Json and "Accept":"application/json" so in your controller you can do it as below.
public function getUsers(Request $request)
{
...
if ($request->wantsJson()) {
return response()->json($users, 200); //here why we are extending response object because using json() method you can send the status code with the response.
}
return view('pages.user_list'); //Normal Routes hence returning View
}
for the laravel 5.6 and above the above answers won't work for me , so here is my 2 cents.
I have put the routes in web.php and api.php and normal no any magic tricks .
public function getUsers(Request $request)
{
....
if( $request->is('api/*')){
...
return response()->json($user_data, 200);
}
...
return view('users', ['users_data'=>$user_data]);
}
It will return json output for
127.0.0.1:8000/api/users
and normal view in html for
127.0.0.1:8000/users
Im building an API in Laravel 5.3. In my routes/api.php file I have 3 endpoints:
Route::post('/notify/v1/notifications', 'Api\Notify\v1\NotifyApiController#notificationsPost');
Route::post('/notify/v1/alerts', 'Api\Notify\v1\NotifyApiController#alertsPost');
Route::post('/notify/v1/incidents', 'Api\Notify\v1\NotifyApiController#incidentsPost');
Several services will call these routes directly, however, when a request comes in from some services, the input data needs to be processed before it can hit these endpoints.
For example, if a request comes in from JIRA, I need to process the input before it hits these endpoints.
Im thinking that the simplest way to do this would be to have a 4th endpoint like the below:
Route::post('/notify/v1/jira', 'Api\Notify\v1\JiraFormatter#formatJiraPost');
The idea being to hit the /notify/v1/jira endpoint, have the formatJiraPost method process the input and then forward the request to /notify/v1/notifications (/alerts, /incidents) as required.
How can I have the /notify/v1/jira endpoint forward the request to the /notify/v1/notifications endpoint?
Do you see a better way to do this?
Depending how your app will work, you could always have your services pointing to /notify/v1/jira and then do the processing like you suggested.
Another alternative is have the JIRA service pointing at the same routes as all the other services but to use a Before middleware group to pre-process your data. Something like
Route::group(['middleware' => ['verifyService']], function () {
Route::post('/notify/v1/notifications', 'Api\Notify\v1\NotifyApiController#notificationsPost');
Route::post('/notify/v1/alerts', 'Api\Notify\v1\NotifyApiController#alertsPost');
Route::post('/notify/v1/incidents', 'Api\Notify\v1\NotifyApiController#incidentsPost');
});
You could check in your middleware your service.
<?php
namespace App\Http\Middleware;
use Closure;
class verifyService
{
public function handle($request, Closure $next)
{
//You could use switch/break case structure
if (isJIRA($request)) {
//Do some processing, it could be outsourced to another class
//$JiraPost = new formatJiraPost($request);
//Keep the requesting going to the routes with processed data
return $next($request);
}
//You could add extra logic to check for more services.
return $next($request);
}
protected function isJIRA(Request $request){
//Logic to check if it is JIRA.
}
}