Help us understand the problem. What is going on with this article?

開発環境のためのCORS有効化(phalconFrameWorkとwebpackとaxios)

More than 1 year has passed since last update.

背景

趣味で作っているwebアプリが、フロントエンドとバックエンドをRESTfulでつないでいる

  • フロントエンド側はvueとwebpackを使っていて、dev環境では、ビルドしなくてもローカルサーバー立ち上がってほっとりローディングですぐに修正を確認できる
  • サーバーサイドでは、dockerを使っているので、dockerコマンド一つでサーバーが立ち上がる。こちらも即座に反映される(当たり前)

これらの特徴から、フロントエンド内での開発PDCA・サーバーサイド内での開発PDCAはだいぶ早いと思っている。

ただ、フロントエンドとサーバーサイドでの疎通確認がボトルネックになると考えていて、チームでの開発では、フロントエンドの開発者とサーバーサイドの開発者は異なることが多い。なので、疎通確認をしたいのに、フロントエンド(またはサーバーサイド)がうまく起動できない(泣)みたいな状況になると思った。PDCA劇遅の予感がする。

現状

フロントエンドとサーバーサイド両方のローカルサーバーを立ち上げてaxiosを使って疎通しようとしても、疎通できない。

セキュリティの観点から、CORSという機能は制限されているためらしい

オリジン間リソース共有 (Cross-Origin Resource Sharing, CORS) は、追加の HTTP ヘッダーを使用して、ユーザーエージェントが現在のサイトとは別のオリジン (ドメイン) のサーバーから選択されたリソースにアクセスする権限を得られるようにする仕組みです。ユーザーエージェントは、現在の文書のオリジンとは異なるドメイン、プロトコル、ポート番号からリソースを要求するとき、オリジン間 HTTP 要求を発行します。

例えば http://domain-a.com から読み込まれた HTML ページが、 <img> src で http://domain-b.com/image.jpg に対して要求を行う場合です。今日のウェブ上では、多くのページが CSS スタイルシートや画像、スクリプトといったリソースを、コンテンツ配信ネットワーク (CDN) などの別のドメインから読み込んでいます。

セキュリティ上の理由から、ブラウザーは、スクリプトによって開始されるオリジン間 HTTP 要求を制限しています。例えば、 XMLHttpRequest や Fetch API は同一オリジンポリシー (same-origin policy)に従います。これは、これらの API を使用するウェブアプリケーションは、 CORS ヘッダーを使用しない限り、アプリケーションが読み込まれたのと同じドメインからしかリソースを HTTP で要求できないことを意味します。

https://developer.mozilla.org/ja/docs/Web/HTTP/HTTP_access_control

なので、phalconフレームワーク側でCORSの制限を緩和する必要がある。あくまで開発環境の時のみ。

仕様の調査

axiosから、クロスドメインなURLを叩くと、勝手にpreflightが飛んでいくようである

スクリーンショット 2018-05-16 22.04.07.png

公式にも書いてあった。今回はJSONでやりとりするため当然のようだ

https://developer.mozilla.org/ja/docs/Web/HTTP/HTTP_access_control#Examples_of_access_control_scenarios

webpackでのproxy設定

簡単な修正方法があった! :surfer:

https://www.yoheim.net/blog.php?q=20170803

webpackの設定ファイルに以下を追記。

    proxyTable: {
      '/api/*': {
        target: 'http://localhost:8111',
        secure: false
      }
    },

僕の場合はこうなった(ほぼテンプレですが)▼

index.js
'use strict'

// see http://vuejs-templates.github.io/webpack for documentation.
const path = require('path')

module.exports = {
  build: {
    env: require('./prod.env'),
    index: path.resolve(__dirname, '../../phalcon/public/index.html'),
    assetsRoot: path.resolve(__dirname, '../../phalcon/public'),
    assetsSubDirectory: 'static',
    // assetsPublicPath: '/',  TODO GithubPagesにあげるために修正
    assetsPublicPath: '',
    productionSourceMap: true,
    // Gzip off by default as many popular static hosts such as
    // Surge or Netlify already gzip all static assets for you.
    // Before setting to `true`, make sure to:
    // npm install --save-dev compression-webpack-plugin
    productionGzip: false,
    productionGzipExtensions: ['js', 'css'],
    // Run the build command with an extra argument to
    // View the bundle analyzer report after build finishes:
    // `npm run build --report`
    // Set to `true` or `false` to always turn it on or off
    bundleAnalyzerReport: process.env.npm_config_report
  },
  dev: {
    env: require('./dev.env'),
    port: 8080,
    autoOpenBrowser: true,
    assetsSubDirectory: 'static',
    assetsPublicPath: '/',
    proxyTable: {
      '/api/*': {
        target: 'http://localhost:8111',
        secure: false
      }
    },
    // CSS Sourcemaps off by default because relative paths are "buggy"
    // with this option, according to the CSS-Loader README
    // (https://github.com/webpack/css-loader#sourcemaps)
    // In our experience, they generally work as expected,
    // just be aware of this issue when enabling this option.
    cssSourceMap: false
  }
}

結局だめだった。そもそもリクエスト先が変更されていない。。。
スクリーンショット 2018-05-16 22.39.30.png

スクリーンショット 2018-05-16 22.39.37.png

愚直にサーバーサイド側でCORSを有効化することにした

phalconでのCORSを有効化(サーバーサイド)

公式で発見。

CorsMiddleware.php
<?php

use Phalcon\Events\Event;
use Phalcon\Mvc\Micro;
use Phalcon\Mvc\Micro\MiddlewareInterface;

/**
 * CORSMiddleware
 *
 * CORS checking
 */
class CORSMiddleware implements MiddlewareInterface
{
    /**
     * Before anything happens
     *
     * @param Event $event
     * @param Micro $application
     *
     * @returns bool
     */
    public function beforeHandleRoute(Event $event, Micro $application)
    {
        if ($application->request->getHeader('ORIGIN')) {
            $origin = $application->request->getHeader('ORIGIN');
        } else {
            $origin = '*';
        }

        $application
            ->response
            ->setHeader('Access-Control-Allow-Origin', $origin)
            ->setHeader(
                'Access-Control-Allow-Methods',
                'GET,PUT,POST,DELETE,OPTIONS'
            )
            ->setHeader(
                'Access-Control-Allow-Headers',
                'Origin, X-Requested-With, Content-Range, ' .
                'Content-Disposition, Content-Type, Authorization'
            )
            ->setHeader('Access-Control-Allow-Credentials', 'true');
    }

    /**
     * Calls the middleware
     *
     * @param Micro $application
     *
     * @returns bool
     */
    public function call(Micro $application)
    {
        return true;
    }
}
app.phpまたはmodule.php
/**
 * Create a new Events Manager.
 */
$eventsManager = new Manager();
$application   = new Micro();

$eventsManager->attach('micro', new CorsMiddleware());
$application->before(new CorsMiddleware());

$application->setEventsManager($eventsManager);

https://docs.phalconphp.com/cs/3.1/application-micro#middleware-events-api-cors

しかしこれでは動かなかった :cry: 。原因はbeforeHandleRouteに処理が走っていないためみたいだが、根が深そうなので他の方法を探すことにした。

phalconForumから発見

https://forum.phalconphp.com/discussion/443/enable-cross-origin-resource-sharing

どうやら、プリフライト時と、その後どちらのリクエストでも、Access-Control-Allow-XXXXXをつける必要があるらしいので以下の様にした。

optionsの範囲とかヘッダーパラメータの付け方とかガバガバなのはスルーしてください :smirk:

app.php
$app->options('/(.*)', function () use ($app) {
    $app->response->sendHeaders();
});
$di->setShared('response', function () {
    $response = new Response();
    if (getenv("MODE") == "development") {
        $response->setHeader('Access-Control-Allow-Origin', '*')
            ->setHeader(
                'Access-Control-Allow-Methods',
                'GET,PUT,POST,DELETE,OPTIONS'
            )
            ->setHeader(
                'Access-Control-Allow-Headers',
                'Origin, X-Requested-With, Content-Range, ' .
                'Content-Disposition, Content-Type, Authorization'
            )
            ->setHeader('Access-Control-Allow-Credentials', 'true');
    }
    return $response;
});

スクリーンショット 2018-05-16 22.30.50.png

OPTIONSとPOSTが正常系で帰ってる!

完成!!ここまで来るのに4時間はかかった。。。

所感

  • CORSのめんどくささを体感した
  • proxyServerがなぜ効かないのかは謎。誰か教えて :crying_cat_face:
  • ただ一つ不満を言うなら、以下の様にaxios側でURLをdev環境とprod環境で分ける必要があること
    • baseのaxiosクラスをつくれば一元管理できるので、なんとか納得できる
dev環境
    return axios.post('http://localhost:8111/session/login', json, {
        headers: { 'Content-Type': 'application/json' }
      })
prod環境
    return axios.post('/session/login', json, {
        headers: { 'Content-Type': 'application/json' }
      })
diggy-mo
クソ雑魚エンジニア
https://blog.morifuji-is.ninja/
atma_inc
Change the common sense with algorithm を達成するためのスタートアップ
https://atma.co.jp
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした