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

## 背景
趣味で作っている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](https://qiita-image-store.s3.amazonaws.com/0/158393/b7ca023a-8b28-8a72-c2e3-54883fc06fd4.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
      }
    },
```

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

```js: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](https://qiita-image-store.s3.amazonaws.com/0/158393/686670e2-e02f-14be-1666-e9b156703996.png)

![スクリーンショット 2018-05-16 22.39.37.png](https://qiita-image-store.s3.amazonaws.com/0/158393/c610bb55-9618-d734-7dc0-1be53b5f92b9.png)

愚直にサーバーサイド側でCORSを有効化することにした
## phalconでのCORSを有効化(サーバーサイド)
公式で発見。

```php: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;
    }
}
```

```php: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: 

```php:app.php
$app->options('/(.*)', function () use ($app) {
    $app->response->sendHeaders();
});
```

```php:
$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](https://qiita-image-store.s3.amazonaws.com/0/158393/b4b5b016-0c84-2fe8-6646-9c6991141c72.png)


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

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

## 所感

- CORSのめんどくささを体感した
- proxyServerがなぜ効かないのかは謎。誰か教えて :crying_cat_face: 
- ただ一つ不満を言うなら、以下の様にaxios側でURLをdev環境とprod環境で分ける必要があること
    - baseのaxiosクラスをつくれば一元管理できるので、なんとか納得できる

```js:dev環境
    return axios.post('http://localhost:8111/session/login', json, {
        headers: { 'Content-Type': 'application/json' }
      })
```

```js:prod環境
    return axios.post('/session/login', json, {
        headers: { 'Content-Type': 'application/json' }
      })
```