背景
趣味で作っている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が飛んでいくようである
公式にも書いてあった。今回はJSONでやりとりするため当然のようだ
webpackでのproxy設定
簡単な修正方法があった!
webpackの設定ファイルに以下を追記。
proxyTable: {
'/api/*': {
target: 'http://localhost:8111',
secure: false
}
},
僕の場合はこうなった(ほぼテンプレですが)▼
'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
}
}
結局だめだった。そもそもリクエスト先が変更されていない。。。
愚直にサーバーサイド側でCORSを有効化することにした
phalconでのCORSを有効化(サーバーサイド)
公式で発見。
<?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;
}
}
/**
* 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
しかしこれでは動かなかった 。原因はbeforeHandleRoute
に処理が走っていないためみたいだが、根が深そうなので他の方法を探すことにした。
phalconForumから発見
https://forum.phalconphp.com/discussion/443/enable-cross-origin-resource-sharing
どうやら、プリフライト時と、その後どちらのリクエストでも、Access-Control-Allow-XXXXX
をつける必要があるらしいので以下の様にした。
optionsの範囲とかヘッダーパラメータの付け方とかガバガバなのはスルーしてください
$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;
});
OPTIONSとPOSTが正常系で帰ってる!
完成!!ここまで来るのに4時間はかかった。。。
所感
- CORSのめんどくささを体感した
- proxyServerがなぜ効かないのかは謎。誰か教えて
- ただ一つ不満を言うなら、以下の様にaxios側でURLをdev環境とprod環境で分ける必要があること
- baseのaxiosクラスをつくれば一元管理できるので、なんとか納得できる
return axios.post('http://localhost:8111/session/login', json, {
headers: { 'Content-Type': 'application/json' }
})
return axios.post('/session/login', json, {
headers: { 'Content-Type': 'application/json' }
})