Laravel 5:API 路由 + 通配符路由导致意外行为

Posted

技术标签:

【中文标题】Laravel 5:API 路由 + 通配符路由导致意外行为【英文标题】:Laravel 5: API routes + wildcard route results in unexpected behavior 【发布时间】:2019-03-06 13:02:20 【问题描述】:

我正在尝试构建一个由 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 能够检测到路由,但它使用了错误的控制器响应。

我该如何解决这个问题?

【问题讨论】:

您能发布您的 OrdersController 代码的一部分吗?因为您能够获得 404,所以我假设您在更改路由后重新启动了 Web 服务。我必须先运行php artisan clear:cachephp artisan route:cache 才能清除。 完成@DanielGale 我还清除了任何类型的缓存,结果没有改变。 您在 Controllers@Index 的响应中调用 Url ` return response()->json([ 'mollie_redirect' => $mollie->getCheckoutUrl() ]);` 它有什么与此有关吗? 这是在 OrdersController 中,而不是在 Controller@index 中。那个基本上就是return view('index') 【参考方案1】:

首先 - 当您删除 api 路由时,POST 方法没有路由(因为您的通配符“catch-all”路由仅适用于 GETHEAD要求)。这就是为什么您会收到 HTTP 404 - 找不到此请求的路由。

如果您按照问题描述添加 api 路由 - 提供的响应似乎是原始树枝视图(可能是布局)。我假设您三重检查了 OrdersController 不会以这种方式响应 - 如果没有,请尝试将 return ''; 添加为控制器的第一行,看看会发生什么。

无论如何 - 它可能与请求类型有关(您将请求标头设置为 application/x-www-form-urlencoded) - RouteServiceProvider 或 api 中间件与它有关。例如,尝试将请求标头设置为application/json,并深入研究 RouteServiceProvider 和 api 中间件。

【讨论】:

我尝试在OrdersController的store方法中添加一个dd('test'),但是一直没有显示出来。【参考方案2】:

我认为这里有两件事:

您没有在此处设置 Content-Typeapplication/json,因此应用“认为”此正常形式已发送 您使用自定义验证类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(或多或少),那么它将返回验证失败的 JSON 响应,否则会进行重定向。

【讨论】:

我实际上只是尝试将Content-Type 设置为application/json,但没有成功。我确保验证不会失败,即使失败了,它也应该将验证错误作为 JSON 对象提供给我,而不是与我正在调用的路由无关的错误响应。【参考方案3】:

与@Marcin Nabialek 所说的类似,这是应该与请求一起发送的标头之一的问题。然而,它不是Content-Type,而是Accept

您必须使用 Accept: application/json 才能接收 API 的 JSON 响应,至少在 Laravel 5.7.6 中是这样的。

【讨论】:

【参考方案4】:

我已经尝试按照上面的建议设置两个标题,但它对我不起作用。相反,我修改了路由正则表达式以匹配不以 api 开头的 url 并且它有效:

Route::view('/any?', 'app')
->where('any', '^(?!api).*');

【讨论】:

这与我的问题无关。

以上是关于Laravel 5:API 路由 + 通配符路由导致意外行为的主要内容,如果未能解决你的问题,请参考以下文章

Laravel 5.6 通配符路由在域中未按预期运行

在 laravel 5 中通配符到错误控制器的路由

带有通配符的 Laravel 路由总是 404

Laravel 5.4:API 路由列表

Laravel 4 通配符路由到不同的控制器

Laravel,路由通配符过滤然后控制器