Проблемы с маршрутизацией в CakePHP 4.0.3 'Не удалось найти маршрут, соответствующий X'
Я делаю веб-приложение с внутренним REST API, написанным на CakePHP 4.0.3, и клиентским интерфейсом, написанным на Vue.js 2.6.11, использующим axios 0.19.2 для выполнения запросов.
Проблема в том, что я не могу вызвать метод DELETE ни на одной из моих конечных точек и не могу сказать, в коде ли проблема: во внешнем или внутреннем коде.
В журнале сервера написано:
2020-02-14 10:55:54 Error: [Cake\Routing\Exception\MissingRouteException] A route matching "/meal-plans/14042e24-fa12-49d3-9bbe-91e57847a1c7" could not be found. in /var/www/html/vendor/cakephp/cakephp/src/Routing/RouteCollection.php on line 211
Exception Attributes: array (
'url' => '/meal-plans/14042e24-fa12-49d3-9bbe-91e57847a1c7',
)
Stack Trace:
- /var/www/html/vendor/cakephp/cakephp/src/Routing/Router.php:227
- /var/www/html/vendor/cakephp/cakephp/src/Routing/Middleware/RoutingMiddleware.php:140
- /var/www/html/vendor/cakephp/cakephp/src/Http/Runner.php:73
- /var/www/html/vendor/cakephp/cakephp/src/Http/Runner.php:58
- /var/www/html/vendor/cakephp/cakephp/src/Http/Server.php:90
- /var/www/html/webroot/index.php:40
Request URL: /meal-plans/14042e24-fa12-49d3-9bbe-91e57847a1c7
Referer URL: http://localhost:8080/
Client IP: 172.19.0.1
Понимая, что это проблема маршрутизации, я набираю bin/cake routes
в терминал:
+------------------+-----------------+------------------------------------------------------------------------------------+
| Route name | URI template | Defaults |
+------------------+-----------------+------------------------------------------------------------------------------------+
| mealplans:index | /meal-plans | {"_method":"GET","action":"index","controller":"MealPlans","plugin":null} |
| mealplans:add | /meal-plans | {"_method":"POST","action":"add","controller":"MealPlans","plugin":null} |
| mealplans:view | /meal-plans/:id | {"_method":"GET","action":"view","controller":"MealPlans","plugin":null} |
| mealplans:edit | /meal-plans/:id | {"_method":["PUT","PATCH"],"action":"edit","controller":"MealPlans","plugin":null} |
| mealplans:delete | /meal-plans/:id | {"_method":"DELETE","action":"delete","controller":"MealPlans","plugin":null} |
| meals:index | /meals | {"_method":"GET","action":"index","controller":"Meals","plugin":null} |
| meals:add | /meals | {"_method":"POST","action":"add","controller":"Meals","plugin":null} |
| meals:view | /meals/:id | {"_method":"GET","action":"view","controller":"Meals","plugin":null} |
| meals:edit | /meals/:id | {"_method":["PUT","PATCH"],"action":"edit","controller":"Meals","plugin":null} |
| meals:delete | /meals/:id | {"_method":"DELETE","action":"delete","controller":"Meals","plugin":null} |
+------------------+-----------------+------------------------------------------------------------------------------------+
Но, как видите, там написано, что маршруты загружены.
Я написал тест, чтобы увидеть, может ли он дать мне какие-нибудь подсказки. Тест должен был провалиться
class MealPlansControllerTest extends TestCase
{
use IntegrationTestTrait;
public $fixtures = [
'app.MealPlans',
'app.Meals'
];
public function testDelete()
{
$this->assertCountRecords(1);
$this->delete('/meal-plans/14042e24-fa12-49d3-9bbe-91e57847a1c7');
$this->assertResponseCode(204);
$this->assertCountRecords(0);
}
public function testDeleteInvalid()
{
$this->assertCountRecords(1);
$this->delete('/meal-plans/14042f24-fa12-49d3-9bbe-91e57847a1c7');
$this->assertResponseCode(404);
$this->assertCountRecords(1);
}
private function assertCountRecords($expected)
{
$this->get('/meal-plans');
$this->assertResponseCode(200);
$body = json_decode((string) $this->_response->getBody(), true);
$this->assertArrayHasKey('mealPlans', $body);
$this->assertEquals($expected, count($body['mealPlans']));
}
}
Но оба теста прошли, так что пользы от этого не было.
Так что теперь я застрял. Я понятия не имею, что происходит. Наверное, это что-то простое, но я этого не вижу.
Это оскорбительные маршруты:
/** @var RouteBuilder $routes */
$routes->setRouteClass(DashedRoute::class);
$routes->scope('/', function (RouteBuilder &$builder) {
$builder->setExtensions(['json']);
$builder->resources('MealPlans');
$builder->resources('Meals');
});
Это контроллер, который я пытаюсь использовать:
/**
* Class MealPlansController
* @package App\Controller
*
* @property MealPlansTable $MealPlans
*/
class MealPlansController extends AppController
{
public $modelClass = 'MealPlans';
public function delete($id)
{
try {
$mealPlan = $this->MealPlans->get($id);
} catch (RecordNotFoundException $e) {
$this->response = $this->response->withStatus(404, __d('meal_plans', 'delete_404'));
return $this->render();
}
if ($this->MealPlans->delete($mealPlan)) {
$this->response = $this->response->withStatus(204, __d('meal_plans', 'delete_200'));
} else {
$this->response = $this->response->withStatus(500, __d('meal_plans', 'delete_500'));
}
return $this->render();
}
}
Вызов внешнего интерфейса выглядит так:
deleteMealPlan (mealPlan) {
axios.delete('http://localhost:11000/meal-plans/' + mealPlan.id)
.then((response) => {
console.log(response.statusText);
this.$store.dispatch('planner/syncMealPlans').then(() => this.$forceUpdate());
});
}
И вызываемая конечная точка выглядит как http://localhost:11000/meal-plans/14042e24-fa12-49d3-9bbe-91e57847a1c7
Я могу ПОЛУЧИТЬ указанную выше конечную точку, я еще не тестировал PUT или PATCH.
Единственный другой релевантный фрагмент кода - это моя установка промежуточного программного обеспечения:
$middlewareQueue
->add(new ErrorHandlerMiddleware(Configure::read('Error')))
->add(new AssetMiddleware([
'cacheTime' => Configure::read('Asset.cacheTime'),
]))
->add(new RoutingMiddleware($this))
->add(new BodyParserMiddleware(['json' => true]))
->add(function (ServerRequest $request, Response $response, $next) {
// Allow CORS
return $next($request, $response)
->withHeader('Access-Control-Allow-Origin', '*')
->withHeader('Access-Control-Allow-Headers', '*')
->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE');
});
1 ответ
Это была проблема с моим промежуточным ПО.
Я знал, что веб-браузеры отправляют предполетный запрос, и, включив заголовки CORS, я думал, что прикрылся.
Однако
Я разрешал всем запросам проходить независимо от метода запроса. Это позволит запросам OPTIONS проходить к маршрутизатору.
Когда маршрутизатор пытался проанализировать запрос и получить правильный маршрут, он не смог найти ни одного маршрута с ОПЦИЯМИ, определенными в _method
вариант и вызывает ошибку.
И это объясняет, почему мои тесты проходили успешно. Поскольку запрос OPTIONS никогда не выполнялся, ошибка не возникала.
Таким образом, решение состоит в том, чтобы вернуться раньше, если запрос является запросом OPTIONS.
function (ServerRequest $request, Response $response, $next) {
if ($request->getMethod() === 'OPTIONS') {
return $response
->withHeader('Access-Control-Allow-Origin', '*')
->withHeader('Access-Control-Allow-Headers', '*')
->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE');
} else {
$response = $next($request, $response)
->withHeader('Access-Control-Allow-Origin', '*')
->withHeader('Access-Control-Allow-Headers', '*')
->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE');
return $response;
}
}