デザインワン・ジャパン Tech Blog

DesignOne Japan | Activate the World.

CloudFront Functions を使ったリダイレクト処理でクエリパラメータの取得に苦戦した話

まえがき

インフラエンジニアの 冨田(@komitta)です。夏休みの宿題は最終日にやるタイプです。

弊社で運用しているエキテンではEC2環境からコンテナを使った環境への移行を行っています。

今回はEC2サーバーを廃棄するにあたって使用していたリダイレクト処理をCloudFront Functionsに移行したときのお話です。

すんなり終わるかと思っていたら、ハマった箇所があったためその話をしたいと思います。

EC2サーバーで稼働していたリダイレクト処理について

下記要件のリダイレクト処理のためにEC2サーバーで稼働しているApachemod_rewriteモジュールを使用してリダイレクトを行っていました。

アクセスURL リダイレクト先URL
https://example.com/s_12345/?hoge=fuga https://example.com/shop_12345/?hoge=fuga

行っている処理の内容は s_12345 から shop_12345 へのパスの書き換えになります。

この通り複雑な処理ではないため、サーバーを新規で構築するのではなく既に稼働しているCloudFront Functions*1を使えば無駄なリソースを用意することなく対応できると考えました。

※Lambda@Edgeも置き換えの選択肢にありましたが要件がシンプルであるため、より軽量に処理することができるClouFront Functionsを選定しています。

CloudFront Functions でのイベント構造について

CloudFront Functions で受け取るイベント構造*2は以下になります。

{
  "version": "1.0",
  "context": {
    "distributionDomainName": "d111111abcdef8.cloudfront.net",
    "distributionId": "EDFDVBD6EXAMPLE",
    "eventType": "viewer-response",
    "requestId": "EXAMPLEntjQpEXAMPLE_SG5Z-EXAMPLEPmPfEXAMPLEu3EqEXAMPLE=="
  },
  "viewer": {
    "ip": "198.51.100.11"
  },
  "request": {
    "method": "GET",
    "uri": "/media/index.mpd",
    "querystring": {
      "ID": {
        "value": "42"
      },
      "Exp": {
        "value": "1619740800"
      },
      "TTL": {
        "value": "1440"
      },
      "NoValue": {
        "value": ""
      },
      "querymv": {
        "value": "val1",
        "multiValue": [
          {
            "value": "val1"
          },
          {
            "value": "val2,val3"
          }
        ]
      }
    },
    "headers": {
      "host": {
        "value": "video.example.com"
      },
      "user-agent": {
        "value": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:83.0) Gecko/20100101 Firefox/83.0"
      },

~~以下省略~~
}

例えばリクエスURIhttps://example.com/s_12345/?hoge=fuga の場合、以下リクエストイベントを受け取ります。

"request": {
    "method": "GET",
    "uri": "/s_12345",
    "querystring": {
      "hoge": {
        "value": "fuga"
      }
    },
    "headers": {
      "host": {
        "value": "example.com"
      }
   }
}

そのため

function handler(event) {
~~省略~~~
   var domain = event.request.headers.host.value
   var uri = event.request.uri
~~省略~~~
}

のように指定すればそれぞれ example.coms_12345 を取得することが可能です。

しかしクエリパラメータは 上記の通り querystring に格納されますが、格納される値は一意ではないのでホスト名やパスと同じ指定方法をとることができません。

CloudFront Functions でのクエリパラメータの取得方法

悩んだときは先人の力を借りようということで、似たような要件でリダイレクトを行っているページを探したところ、以下ページにquerystringを格納している方法が記載されていました。

github.com

github.com

function objectToQueryString(obj) {
    var str = [];
    for (var param in obj)
        if (obj[param].multiValue)
            str.push(param + "=" + obj[param].multiValue.map((item) => item.value).join(','));
        else if (obj[param].value == '')
            str.push(param);
        else
            str.push(param + "=" + obj[param].value);

    return str.join("&");
}


function handler(event) {
~~省略~~~
 objectToQueryString(event.request.querystring)
~~省略~~~
}

event.request.querystring の値を配列に格納して値を一つづつ & で連携させることでクエリパラメータを表現しています。

こちらの内容を参考に最終的に以下のような形で要件をみたしたリダイレクトを行うことができました。

function objectToQueryString(obj) {
    var str = [];
    for (var param in obj)
        if (obj[param].multiValue)
            str.push(param + "=" + obj[param].multiValue.map((item) => item.value).join(','));
        else if (obj[param].value == '')
            str.push(param);
        else
            str.push(param + "=" + obj[param].value);

    return str.join("&");
}


function handler(event) {
    var path = event.request.uri;
    var domain = request.headers.host.value;
    var beforepath = /s_/;
    var redirectPath = path.replace(beforepath,`shop_`);
    
    if (Object.keys(event.request.querystring).length) 
      var loc = `https://${domain}${redirectPath}?${objectToQueryString(event.request.querystring)}`
    else
      var loc = `https://${domain}${redirectPath}`

    var response = {
        statusCode: 301,
        statusDescription: 'Found',
        headers: {
            'location': { value: loc }
        }
    };
    return response;
}

まとめ

JavaScriptが不慣れだったため、時間がかかりましたが要件を満たすリダイレクト処理をCloudFront Functionsを使って行うことができました。

ただしCloudFront Functionsの最大実行時間が1ms となっているように、あまり複雑なリダイレクト処理は行うべきではないと思います。

これからも要件に応じて最適な構成を選択していければと思います。

おわりに

仲間を募集しております

募集中の職種については以下を御覧ください。

www.wantedly.com