Laravel 5: API 路由 + 通配符路由导致意外行为
Laravel 5: API routes + wildcard route results in unexpected behavior
我正在尝试构建一个由 Laravel API 支持的 React 应用程序,因此基本上使用通配符路由进行客户端路由,然后简单地使用一个 API 路由组来处理数据。
这是我的 routes/web.php
文件:
<?php
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/
Route::get('/payment/redirect/{orderId}', ['as' => 'mollie.redirect', 'uses' => 'Controller@index']);
Route::get('/{any}', ['as' => 'index', 'uses' => 'Controller@index'])->where('any', '.*');
这是我的 routes/api.php
文件:
<?php
use Illuminate\Http\Request;
/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/
Route::post('/orders', ['as' => 'orders.store', 'uses' => 'OrdersController@store']);
Route::post('/payment/webhook', ['as' => 'mollie.webhook', 'uses' => 'OrdersController@webhook']);
这导致:
但是,每当我尝试在 POST api/orders
发出请求时,这就是我在 Postman 中得到的结果:
Controller@index
应该回应,而不是 OrdersController@store
,这应该是 JSON 回应。
这是我的 OrdersController
代码:
<?php
namespace Http\Controllers;
use Customer;
use Http\Requests\OrderCreateRequest;
use Order;
use Product;
use Services\CountryDetector;
use Services\LanguageService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Route;
class OrdersController extends Controller
{
const ERROR_PRODUCT_COUNTRY_UNAVAIALBLE = 'errors.products.country_unavailable';
public function store(OrderCreateRequest $request, LanguageService $language, Order $orders, Customer $customers, Product $products)
{
$customer = $customers->firstOrCreate(['email' => $request->input('customer.email')], [
'name' => $request->input('customer.fullname'),
'email' => $request->input('customer.email'),
'phone' => $request->input('customer.phone'),
'country' => $language->getCurrentCountry(),
'company_name' => $request->input('customer.company_name'),
'city' => $request->input('customer.city'),
'optin_newsletter' => $request->input('customer.newsletter')
]);
$product = $products->find($request->input('cart.product_id'));
$pricing = $product->getCountryPrice($language->getCurrentCountry());
if (! $pricing)
{
return response()->json([
'error' => trans(self::ERROR_PRODUCT_COUNTRY_UNAVAILABLE, ['productName' => $product->name])
], 422);
}
$order = $orders->create([
'customer_id' => $customer->id,
'product_id' => $product->id,
'product_flavor' => $request->input('cart.flavor'),
'amount' => $pricing->amount,
'vat_amount' => $pricing->vat_amount,
'currency' => $pricing->currency,
'carehome_selection' => $request->input('carehome.custom'),
'carehome_name' => $request->input('carehome.name'),
'carehome_type' => $request->input('carehome.type'),
'carehome_address' => $request->input('carehome.address'),
'carehome_city' => $request->input('carehome.city'),
'carehome_notes' => $request->input('carehome.notes'),
'custom_message' => $request->input('gifting_options.message'),
'is_anonymous' => $request->input('gifting_options.anonymous'),
'wants_certificate' => $request->input('gifting_options.certificate'),
'status' => Order::STATUS_PENDING,
'type' => $request->input('payment_type')
]);
$mollie = $order->getOrCreateMollie();
return response()->json([
'mollie_redirect' => $mollie->getCheckoutUrl()
]);
}
}
此外,如果我尝试暂时删除 API 路由,但仍然尝试访问它们,我会很奇怪地得到 404,这意味着 Laravel 能够检测到路由,但是它使用了错误的控制器响应。
我该如何解决这个问题?
首先 - 当您删除 api 路由时,POST
方法没有路由 (因为您的通配符 "catch-all"路由仅适用于 GET
或 HEAD
请求)。这就是您收到 HTTP 404 的原因 - 找不到此请求的路由。
如果您按照问题所述添加 api 路由 - 提供的响应似乎是原始树枝视图(可能是布局)。我假设您已三重检查您的 OrdersController 不会以这种方式响应 - 如果没有,请尝试添加 return '{}';
作为控制器的第一行,看看会发生什么。
无论如何 - 它可能与请求类型有关(您将请求 header 设置为 application/x-www-form-urlencoded
) - RouteServiceProvider 或者 api
中间件可能有一些事情要做用它。例如,尝试将请求 header 设置为 application/json
,并深入研究 RouteServiceProvider 和 api 中间件。
我认为这是两件事的结合:
- 你这里没有设置
Content-Type
到application/json
所以app"thinks"发送这个正常的表格
- 您使用自定义验证 class
OrderCreateRequest
并且可能验证失败。这就是为什么如果将 dd('test');
放入控制器方法中,它根本不会执行
万一验证失败验证器抛出 ValidationException
异常和实现,在这种情况下发生的情况如下所示:
protected function convertValidationExceptionToResponse(ValidationException $e, $request)
{
if ($e->response) {
return $e->response;
}
return $request->expectsJson()
? $this->invalidJson($request, $e)
: $this->invalid($request, $e);
}
因此,如果它是 AJAX 请求或 Content-Type 设置为 application/json
(或多或少),那么它将 return JSON 响应验证失败否则进行重定向。
与@Marcin Nabialek 所说的类似,headers 中的一个问题应该随请求一起发送。然而,它不是 Content-Type
,而是 Accept
。
您必须使用 Accept: application/json
才能接收 API 的 JSON 响应,至少这是它在 Laravel 5.7.6 中的行为方式。
我试过按照上面的建议设置两个 headers,但它对我不起作用。相反,我修改了路由正则表达式以匹配不以 api
开头的网址并且它有效:
Route::view('/{any?}', 'app')
->where('any', '^(?!api).*');
我正在尝试构建一个由 Laravel API 支持的 React 应用程序,因此基本上使用通配符路由进行客户端路由,然后简单地使用一个 API 路由组来处理数据。
这是我的 routes/web.php
文件:
<?php
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/
Route::get('/payment/redirect/{orderId}', ['as' => 'mollie.redirect', 'uses' => 'Controller@index']);
Route::get('/{any}', ['as' => 'index', 'uses' => 'Controller@index'])->where('any', '.*');
这是我的 routes/api.php
文件:
<?php
use Illuminate\Http\Request;
/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/
Route::post('/orders', ['as' => 'orders.store', 'uses' => 'OrdersController@store']);
Route::post('/payment/webhook', ['as' => 'mollie.webhook', 'uses' => 'OrdersController@webhook']);
这导致:
但是,每当我尝试在 POST api/orders
发出请求时,这就是我在 Postman 中得到的结果:
Controller@index
应该回应,而不是 OrdersController@store
,这应该是 JSON 回应。
这是我的 OrdersController
代码:
<?php
namespace Http\Controllers;
use Customer;
use Http\Requests\OrderCreateRequest;
use Order;
use Product;
use Services\CountryDetector;
use Services\LanguageService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Route;
class OrdersController extends Controller
{
const ERROR_PRODUCT_COUNTRY_UNAVAIALBLE = 'errors.products.country_unavailable';
public function store(OrderCreateRequest $request, LanguageService $language, Order $orders, Customer $customers, Product $products)
{
$customer = $customers->firstOrCreate(['email' => $request->input('customer.email')], [
'name' => $request->input('customer.fullname'),
'email' => $request->input('customer.email'),
'phone' => $request->input('customer.phone'),
'country' => $language->getCurrentCountry(),
'company_name' => $request->input('customer.company_name'),
'city' => $request->input('customer.city'),
'optin_newsletter' => $request->input('customer.newsletter')
]);
$product = $products->find($request->input('cart.product_id'));
$pricing = $product->getCountryPrice($language->getCurrentCountry());
if (! $pricing)
{
return response()->json([
'error' => trans(self::ERROR_PRODUCT_COUNTRY_UNAVAILABLE, ['productName' => $product->name])
], 422);
}
$order = $orders->create([
'customer_id' => $customer->id,
'product_id' => $product->id,
'product_flavor' => $request->input('cart.flavor'),
'amount' => $pricing->amount,
'vat_amount' => $pricing->vat_amount,
'currency' => $pricing->currency,
'carehome_selection' => $request->input('carehome.custom'),
'carehome_name' => $request->input('carehome.name'),
'carehome_type' => $request->input('carehome.type'),
'carehome_address' => $request->input('carehome.address'),
'carehome_city' => $request->input('carehome.city'),
'carehome_notes' => $request->input('carehome.notes'),
'custom_message' => $request->input('gifting_options.message'),
'is_anonymous' => $request->input('gifting_options.anonymous'),
'wants_certificate' => $request->input('gifting_options.certificate'),
'status' => Order::STATUS_PENDING,
'type' => $request->input('payment_type')
]);
$mollie = $order->getOrCreateMollie();
return response()->json([
'mollie_redirect' => $mollie->getCheckoutUrl()
]);
}
}
此外,如果我尝试暂时删除 API 路由,但仍然尝试访问它们,我会很奇怪地得到 404,这意味着 Laravel 能够检测到路由,但是它使用了错误的控制器响应。
我该如何解决这个问题?
首先 - 当您删除 api 路由时,POST
方法没有路由 (因为您的通配符 "catch-all"路由仅适用于 GET
或 HEAD
请求)。这就是您收到 HTTP 404 的原因 - 找不到此请求的路由。
如果您按照问题所述添加 api 路由 - 提供的响应似乎是原始树枝视图(可能是布局)。我假设您已三重检查您的 OrdersController 不会以这种方式响应 - 如果没有,请尝试添加 return '{}';
作为控制器的第一行,看看会发生什么。
无论如何 - 它可能与请求类型有关(您将请求 header 设置为 application/x-www-form-urlencoded
) - RouteServiceProvider 或者 api
中间件可能有一些事情要做用它。例如,尝试将请求 header 设置为 application/json
,并深入研究 RouteServiceProvider 和 api 中间件。
我认为这是两件事的结合:
- 你这里没有设置
Content-Type
到application/json
所以app"thinks"发送这个正常的表格 - 您使用自定义验证 class
OrderCreateRequest
并且可能验证失败。这就是为什么如果将dd('test');
放入控制器方法中,它根本不会执行
万一验证失败验证器抛出 ValidationException
异常和实现,在这种情况下发生的情况如下所示:
protected function convertValidationExceptionToResponse(ValidationException $e, $request)
{
if ($e->response) {
return $e->response;
}
return $request->expectsJson()
? $this->invalidJson($request, $e)
: $this->invalid($request, $e);
}
因此,如果它是 AJAX 请求或 Content-Type 设置为 application/json
(或多或少),那么它将 return JSON 响应验证失败否则进行重定向。
与@Marcin Nabialek 所说的类似,headers 中的一个问题应该随请求一起发送。然而,它不是 Content-Type
,而是 Accept
。
您必须使用 Accept: application/json
才能接收 API 的 JSON 响应,至少这是它在 Laravel 5.7.6 中的行为方式。
我试过按照上面的建议设置两个 headers,但它对我不起作用。相反,我修改了路由正则表达式以匹配不以 api
开头的网址并且它有效:
Route::view('/{any?}', 'app')
->where('any', '^(?!api).*');