CORSを許可したLaravel製APIサーバーでput, patch, deleteが出来なくて泣いてたけど、ようやく解決出来た話


経緯

Vue.js×Laravel5.4のSPAを作ろうと思い、APIをサクッとに作って良い気分のままVueの実装に入ったところ、put, patch, deleteでクロスドメイン絡みのエラーが出てしまい詰んでました。。
全部POSTにしようかなと諦めかけた時、同じオフィスにいた神様が答えとなる記事を見つけてくれて解決しました

わりとぶつかりそうな問題なのに日本語記事全然なかったので書いてみました

前提

Laravel5.4

解決策

いきなり解決策いきます。

NGパターン

routes/api.php
Route::put('/test', 'TestController@update');

OKパターン

routes/api.php
Route::match(['options', 'put'], '/test', 'TestController@update');

解説

正直CORS周り詳しくないんで、間違っている部分はご指摘頂けるとありがたいです

CORSにおけるGET、POSTとそれ以外(PUT, PATCH, DELETE)の違い

こちらプリフライトリクエスト を見て頂ければわかる通り、GET、POST以外のリクエストの場合は指定したメソッドの前にOPTIONSメソッドでリクエストが実行されます。

Laravel側が抱えるOPTIONSメソッドまわりの問題

こちらに書いてあるんですが、英語得意じゃないんでもし間違っていたらすいません
一応私なりの解釈を書いていきます。

まず、Laravel側でCORS対策しようとすると、 こちらのようなミドルウェアを仕込むのが一般的だと思います。
このようなミドルウェアを通るGET, POSTのルーティングは問題なく通信出来るのですが、プリフライトリクエストが必要なPUT、PATCH等の場合、一度 OPTIONSメソッド でリクエストが送られます。
この OPTIONSメソッド がLaravelの問題で、 ミドルウェアを通る前にルーティングに入ってしまう為、必要なルーティングに到達出来ない ということみたいです。

ですので、解決策のOKパターンにある通り、PUTやPATCH等で受けるルーティングはOPTIONSでも受け取れるようにしておくことで正常にアクセス出来るようになります。

これ以上深い部分は全然わかってないのでここまでにしておきます

さいごに

Laraelを1年弱触っていて、触れば触るほど好きになっていた中、はじめてイラッとしました。
が、今回少しだけLaravelのかわいい一面が見れて更に好きになりました笑

今月中に5.5が出るみたいなので、出たらすぐに触りたいし、今後もLaravelと仲良くやっていきたいなと思いました

2017-08-18 追記

これで全てうまく行く!と思っていたんですが甘かったです。。。
↓のようなルーティングをしていまして、

routes/api.php
Route::match(['options', 'patch']'/{test_id}', 'TestController@update');
Route::match(['options', 'delete'], '/{test_id}', 'TestController@destroy');

よし、patchで更新しようかなと思って実行したらデータが消えてしまいました。。。

よくよく追ってみたら

  1. options, patchメソッドに対して /{test_id} のルーティングが有効になる
  2. options, deleteメソッドに対して /{test_id} のルーティングが有効になる(ここでoptionsのルーティングが上書きされる)
  3. patchメソッドを実行すると
    1. TestController@destroy が実行される
    2. TestController@update が実行される

となっていました
optionsメソッドのルーティングがdeleteのmatchで上書きされるのはわかるんですが、なにもdestory実行しなくても。。。

根本解決してる時間がなかったので、今回は

app/Http/Controllers/Controller.php
<?php

namespace App\Http\Controllers;

use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;

class Controller extends BaseController
{
    use AuthorizesRequests, DispatchesJobs, ValidatesRequests;

    public function options()
    {
        // クロスドメイン環境からOPTIONSメソッドを許可する為の暫定関数
    }
}

で親側にOPTIONSメソッド用暫定関数を仕込んで、

routes/api.php
Route::options('/{test_id}', 'TestController@options');
Route::patch('/{test_id}', 'TestController@update');
Route::delete('/{test_id}', 'TestController@destroy');

put,patch,deleteを使う時はoptionsメソッド用の暫定関数へのルーティングも追記することで回避しました。。。

optionsメソッドってスリーハンドシェイク的な役割だけだと思ってたのにえらい挙動してくれました。。。

知見お持ちの方いらっしゃいましたら是非コメント頂けますと幸いです

5.5でここがどうなってるのかすごく気になります。