質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.64%

CakePHPでPreflight RequestでMissingControllerExceptionになる

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 1,821

HALBY

score 14

前提・実現したいこと

CakePHPでREST APIを作り、AngularからHttp.getでデータを取得するプログラムを作っています

発生している問題・エラーメッセージ

AuthorizationヘッダをくっつけてGETリクエストすると404 Not Foundになります

エラーメッセージ
2017-06-22 11:22:05 Error: [Cake\Routing\Exception\MissingControllerException] Controller class Users.json could not be found.
Exception Attributes: array (
  'class' => 'Users.json',
  'plugin' => false,
  'prefix' => false,
  '_ext' => false,
)
Request URL: /users.json
Referer URL: http://localhost:4200/account

該当のソースコード

一部抜粋

// Routes.php
Router::scope('/', function ($routes) {
    $routes->extensions(['json']);
    $routes->resources('Users');
});

// AppController
class AppController extends Controller
{
    public function initialize()
    {
        parent::initialize();

        $this->loadComponent('RequestHandler');
        $this->loadComponent('Flash');

        // CORS
        $this->response->cors($this->request)
            ->allowOrigin(['*'])
            ->allowMethods(['GET', 'POST', 'OPTIONS'])
            ->allowHeaders(['X-CSRF-Token, Authorization'])
            ->allowCredentials()
            ->exposeHeaders(['Link'])
            ->maxAge(300)
            ->build();
    }
}

// UsersController
class UsersController extends AppController
{
    public function index()
    {
        $users = $this->paginate($this->Users);

        $this->set(compact('users'));
        $this->set('_serialize', 'users');
    }
}
    private getServerData(user: firebase.User) {
        user.getToken(true).then((token) => {
            let headers = new Headers({
                'Authorization': 'Bearer ' + token,
            });
            let options = new RequestOptions({
                headers: headers
            });
            this.http.get(
                'http://localhost:8765/users.json',
                options
            ).subscribe((res) => {
                console.log(res);
            });
        });
    }

試したこと

以下の例では200が返ってきて成功します
・Authorizationヘッダなし
・Chromeを--disable-web-securityで起動しCORSを無効化した状態でリクエスト
・当該URLをアドレスバーで直接叩く
・アドレスから.jsonを取り払ってリクエスト

404 Not Foundになるのは
・localhost:8765/users""".json"""に
・Authorization: Bearer hogehogeをつけて
・プリフライトリクエスト(OPTIONSメソッド)が送信される状態
でリクエストしたときになります

補足情報(言語/FW/ツール等のバージョンなど)

Angular 4
CakePHP 3.4

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

0

resources()が対応するメソッドに、 OPTIONS が含まれていないかもしれません
https://book.cakephp.org/3.0/en/development/routing.html#creating-restful-routes

★リクエストが成功する時

  1. /users.json というパターンに対応するRoutingを探す
  2. routesで「GET users.json は users/index」として「UsersController::index」と解釈
  3. 実行する

★リクエストが失敗する時

  1. /users.json というパターンに対応するRoutingを探す
  2. OPTIONSメソッドに対応する /users.jsonが 存在しない
  3. fallbackとして「Cakeの元々の規則に沿ったRouting」を探す
  4. /:controller/:action としての解釈なので、 第一階層 = users.json までを「コントローラー名」として解釈する
  5. 結果、「対応するコントローラーがない」エラーになる

のかな・・?と思いました。
※たぶん、 routes.phpの中にて $routes->fallbacks()を読んでいますよね‥?

対応策としては、

  1. Routingの設定 or Middleware layerで「OPTIONSでのリクエストを拾えるようにする」
  2. 「OPTIONSでのリクエストの時に、結果を返せるようにする」

というものになるでしょうか。

今手元でコードを読んだり検証したりができていないので、↑の内容はもしかしたら齟齬がある・・かもしれないのですが、
こちらの回答などは「たぶん大丈夫そう?」と思ったので、よろしければお試しください。
https://stackoverflow.com/questions/39365229/cakephp-3-rest-api-cors-request-and-options-method 

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/06/23 10:21

    回答ありがとうございます!
    StackOverflowのその回答のコードは前に試してみたのですが、同じエラーを吐いてしまいます
    beforeFilterにたどり着く前に例外を投げてしまうようです

    キャンセル

  • 2017/06/23 10:27 編集

    Stack Traceはこんな感じです
    #0 ~(略)~/vendor/cakephp/cakephp/src/Http/ControllerFactory.php(65): Cake\Http\ControllerFactory->missingController(Object(Cake\Http\ServerRequest))
    #1 ~(略)~/vendor/cakephp/cakephp/src/Http/ActionDispatcher.php(90): Cake\Http\ControllerFactory->create(Object(Cake\Http\ServerRequest), Object(Cake\Http\Response))
    #2 ~(略)~/vendor/cakephp/cakephp/src/Http/BaseApplication.php(78): Cake\Http\ActionDispatcher->dispatch(Object(Cake\Http\ServerRequest), Object(Cake\Http\Response))
    #3 ~(略)~/vendor/cakephp/cakephp/src/Http/Runner.php(65): Cake\Http\BaseApplication->__invoke(Object(Cake\Http\ServerRequest), Object(Cake\Http\Response), Object(Cake\Http\Runner))
    #4 ~(略)~/vendor/cakephp/cakephp/src/Routing/Middleware/RoutingMiddleware.php(59): Cake\Http\Runner->__invoke(Object(Cake\Http\ServerRequest), Object(Cake\Http\Response))
    #5 ~(略)~/vendor/cakephp/cakephp/src/Http/Runner.php(65): Cake\Routing\Middleware\RoutingMiddleware->__invoke(Object(Cake\Http\ServerRequest), Object(Cake\Http\Response), Object(Cake\Http\Runner))
    #6 ~(略)~/vendor/cakephp/cakephp/src/Routing/Middleware/AssetMiddleware.php(93): Cake\Http\Runner->__invoke(Object(Cake\Http\ServerRequest), Object(Cake\Http\Response))
    #7 ~(略)~/vendor/cakephp/cakephp/src/Http/Runner.php(65): Cake\Routing\Middleware\AssetMiddleware->__invoke(Object(Cake\Http\ServerRequest), Object(Cake\Http\Response), Object(Cake\Http\Runner))
    #8 ~(略)~/vendor/cakephp/cakephp/src/Error/Middleware/ErrorHandlerMiddleware.php(92): Cake\Http\Runner->__invoke(Object(Cake\Http\ServerRequest), Object(Cake\Http\Response))
    #9 ~(略)~/vendor/cakephp/cakephp/src/Http/Runner.php(65): Cake\Error\Middleware\ErrorHandlerMiddleware->__invoke(Object(Cake\Http\ServerRequest), Object(Cake\Http\Response), Object(Cake\Http\Runner))
    #10 ~(略)~/vendor/cakephp/debug_kit/src/Middleware/DebugKitMiddleware.php(52): Cake\Http\Runner->__invoke(Object(Cake\Http\ServerRequest), Object(Cake\Http\Response))
    #11 ~(略)~/vendor/cakephp/cakephp/src/Http/Runner.php(65): DebugKit\Middleware\DebugKitMiddleware->__invoke(Object(Cake\Http\ServerRequest), Object(Cake\Http\Response), Object(Cake\Http\Runner))
    #12 ~(略)~/vendor/cakephp/cakephp/src/Http/Runner.php(51): Cake\Http\Runner->__invoke(Object(Cake\Http\ServerRequest), Object(Cake\Http\Response))
    #13 ~(略)~/vendor/cakephp/cakephp/src/Http/Server.php(80): Cake\Http\Runner->run(Object(Cake\Http\MiddlewareQueue), Object(Cake\Http\ServerRequest), Object(Cake\Http\Response))
    #14 ~(略)~/webroot/index.php(37): Cake\Http\Server->run()
    #15 {main}

    キャンセル

  • 2017/06/23 12:19

    やはり、 `「OPTIONSでのリクエストを拾えるようにする」` の辺りの問題かと思いますね。
    「OPTIONSで来た時に、対応するコントローラーが見つからない」ので
    「コントローラーを起動できない」 = 「beforeFIlterより前に」になるかと思います。

    ### ざっくばらんにやるなら
    1. OPTIONSリクエストを受け取れるようにする
    ```php
    // routes.phpの修正
    Router::scope('/', function ($routes) {
    $routes->extensions(['json']);
    $routes->resources('Users', [
    'map' => [
    'options' => ['action' => 'options', 'method' => 'OPTIONS', 'path' => ''],
    ]
    ]);
    });
    ```
    2. OPTIONSを捌けるようにメソッドを生やす
    ```php
    // AppController
    public function options()
    {
    if ($this->request->is('options')) {
    return $this->response;
    }
    }
    ```

    こうすることで、
    1. `OPTIONS users.json`のリクエストも捌いて、コントローラーを起動できるようになり
    2. 対応アクションとして `AppController::options()`が呼び出されるようになる

    ので、件のエラーは解消するかと思います。
    ※ CORS用の出力については、こちらの内容も参照してみてください。
    https://book.cakephp.org/3.0/ja/controllers/request-response.html#cors

    ### 丁寧目にやるなら
    https://github.com/ozee31/cakephp-cors を利用します。
    このプラグインの場合、「Middlewareレイヤーを利用する」という方法を採用しています
    (Middlewareについては [コチラ](https://book.cakephp.org/3.0/ja/controllers/middleware.html)

    ---
    ちなみに、私の関わっているPJでは「(自前で実装した)MiddlewareでCORSを捌く」方法を利用していて、処理も簡潔になるため、このやり方の方がオススメです^^

    キャンセル

  • 2017/06/23 13:05

    このプラグインで一発で解決しました!ありがとうございます!

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.64%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる