Netlify Functions を使って CORS 制限を回避する

November 16, 2019

概要

webpack-dev-server は、webpack を用いた開発用サーバーを立てるためのモジュールです。 Netlify Functions は、Netlify の提供するアドオンのひとつで、 AWS Lambda を実行基盤にした FaaS(Function as a Service)です。

webpack-dev-server で立てた開発サーバーから Netlify Functions で立てたサーバーを呼び出すと、port が違うため CORS の同一オリジンポリシーの制約に引っかかります。

01

これを回避するには、webpack-dev-server のプロキシ機能を使います。

02

環境

  • webpack-dev-server v3.9.0
  • netlify-lambda v1.6.3

webpack の設定

:webpack.config.js に次の設定を追記します。

module.exports = {
  mode: "development",
  // ... 省略 ...
  devServer: {
    proxy: {
      '/.netlify': {
        target: 'http://localhost:9000',
        pathRewrite: { '^/.netlify/functions': '' }
      }
    }
  }
}:
  • webpack-dev-server に「/.netlify」へのリクエストが来たとき、Netlify Function のサーバー(localhost:9000)へフォワードします。
  • pathRewrite の設定で、「/.netlify/functions」を取り除いた形でのリクエストをバックエンドに投げます。

おまけ:Nuxt.js の場合

Nuxt.js を使って作ったページで、axios を使って Netlify functions へリクエストを送ることを考えます。 Nuxt.js は v2.8.1 で確認しました。

Nuxt.js の設定ファイル nuxt.config.js は次のように設定します。

module.exports = {
  modules: [
    '@nuxtjs/axios',
    '@nuxtjs/proxy'
  ],
  generate: {
    dir: 'dist/client'
  },
  axios: { baseURL: '/.netlify/functions' },
  proxy: {
    '/.netlify': {
      target: 'http://localhost:9000',
      pathRewrite: { '^/.netlify/functions': '' }
    }
  }
}

Nuxt.js でのページと Netlify Functions はこんな感じに作ります。

  • リクエストを送るページ

    <template>
      <main>
        <input v-model="name" placeholder="Name"></input>
        <input v-model="message" maxlength="200" type="textarea"></input>
        <button @click="sendMessage">Send</button>
      </main>
    </template>
    
    <script>
      export default {
        data() {
          return {
            name: '',
            message: ''
          }
        },
        methods: {
          async sendMessage() {
            const params = {
              name: this.name,
              message: this.message
            };
            try {
              const resp = await this.$axios.post('/message', params);
              this.$router.push('/result');
            } catch(e) {
              const message = e || 'Sorry, an error occurred.'
              window.alert(message);
            }
          }
        }
      }
    </script>
  • 結果を表示するページ

    <template>
      <main>
        <span>Thank you!</span>
      </main>
    </template>
    
    <script>
      export default {
        data() {
          return {
          }
        }
      }
    </script>
  • Netlify Functions の Lambda 関数

    'use strict';
    
    const fetch = require('node-fetch');
    export const header = {
      'Access-Control-Allow-Origin': '*',
      'Access-Control-Allow-Credentials': 'true',
      'content-type': 'application/json; charset=utf-8',
    };
    
    exports.handler = async (event, context, callback) => {
      if (event.httpMethod !== 'POST') {
        callback(null, {
          statusCode: 503,
          headers: header,
          body: JSON.stringify({error: 'Method Not Allowed'})
        });
      }
    
      try {
        const data = JSON.parse(event.body);
        const options = {
          method: 'post',
          body: { name: data.name, message: data.message},
          headers: {'Content-Type': 'application/json', 'X-EXAMPLE-Authorization': 'XXXXX'}
        };
        const resp = await fetch('https://exmaple.com/api/v1/message', options);
        callback(null, {
          statusCode: 200,
          headers: header,
          body: JSON.stringify(resp)
        });
      } catch(err) {
        callback(null, {
          statusCode: 500,
          headers: header,
          body: JSON.stringify(err)
        });
      }
    };

参考