イフブロ

イフブロ

インフラエンジニアのブログ

TypeDreamとWordPressを1つのドメインに統合してAWS CloudFrontで配信した。

TL;DR

NoCodeでWebsiteを作れるTypeDreamと、WordPressで作成したサブページを1つのドメインで配信することを目指しました。SEO的な不完全さは感じるものの、問題なく動くところまでいったよ。って記事です。

typedream.com

やったことは

  • AWS Cloud FrontでPathルーティングでTypeDreamとWordPressに振り分け
  • sitemap.xml, robots.txt の統合をCloudFront Lambda@Edge で実現
  • WordPressのURLの末尾スラッシュ(TrailingSlash)の有無をCloudFront Functionsで実現
  • OriginとCDNで重複コンテンツになるのをなんとか回避

です。

概要図

最終的にBehaviorの設定と処理をまとめると以下の図の様になりました。 細かくは後述していきます。

サイトの構成は、

public_domain.com/ がTypeDreamで、 public_domain.com/integrations/ の一部のパスがWordPressです。

cdn routing overview

CloudFrontの設定

Originsの準備

今回登場するオリジンはTypeDreamとWordPressです。 これ以外は使用していません。後述で登場するLambda@Edgeを利用する時は上記いずれかを指定しています。

Originsの設定では特に特殊なことはしていません。 80(HTTP)と443(HTTPS)を許可しています。

OriginのHostnameは最初の構成図にあるように、それぞれHostingされているHost名を指定しています。 ※ もちろんLPトップの public_domain.com では循環参照になるのでオリジンに割り当てられたユニークなホスト名を指定します。

例:

  • Origin TypeDream: typedream_origin_domain.com
  • Origin WP: wordpress_origin_domain.com

cdn-origins

WordPressの準備

一般的なWordPressをどこかに立ち上げればOKです。 ドメインwordpress_origin_domain.com の様なオリジナルなものを割り当ててください。

TypeDreamの準備

TypeDreamでサイトを作ります。 その後、カスタムドメイン(typedream_origin_domain.com)を割り当てます。

カスタムドメインを割り当てないと、後続で出てくるsitemapが作成されませんし、Robots.txtクローラーを拒否する為、コンテンツをインデックスするのが困難になるので 割当は必須になります。

このあとの手順で、TypeDreamのページ間リンクが壊れる、または期待したのと違うURLになるケースが発見されています。文章末尾に対策を書いているのでご参照ください。

Behaviorの準備

cdn-behaviors

Behaviorを準備していきます。 今回の一番のテクニカルなポイントです。

Cloud Frontに到達するリクエストを TypeDreamとWordPressに振り分けるのに必要なパスは以下です。

# for TypeDream
Default (*)

# for WordPress
/integrations/*
/integrations/wp-admin*
/integrations/*.php

それぞれのパスをそれぞれのOriginを指定します。 今から説明するのは以下の部分です。

cdn-basic-routing

CloudFront -> Typedream

Typedreamはサポートに問い合わせた所、CDNはサポートしていないそうです! ( というか、Typedream自体もCDNを用意しているはずなので普通やらない構成なんでしょうね… 。

ワンドメインで複数オリジンの構成もやりたいし、 TypedreamがLP作成と管理ではめっちゃ楽なので使いたいし…ということでインフラ屋的に検証したくなってみて色々試して進めてきました。 後ほど後述もしましたが、TypedreamのLinkの作り方によってはリンクが切れたり、変なURLになったりするので最後まで記事を読んでくださいね。

Default (*) の経路では以下の様な設定です。

  • http -> https のリダイレクト設定
  • Compress objects automatically を on に
  • Allowed HTTP methods は全てを指定
  • Cache key and origin requests ではQueryStringとCookiesを転送許可

Typedream自体はマネージドなサービスなのでCDN側で制御は何もする必要がありません。 比較的緩く通信を開放しています。厳しくすることでTypeDreamが動かなくなるケースもあると考えたからです。

behavior-default

CloudFront -> WordPress

/integrations/*

この経路の設定はTypedreamとほぼ一緒です。 Allowed HTTP methodはGETとHEADに絞っても良いです。

/integrations/wp-admin*
/integrations/*.php

この経路は基本的な設定は一緒ですが、WordPressの編集画面等が登場するので キャッシュタイムをゼロにしています。

wp-cache-setting

WordPressとCloudFrontの組み合わせは事例が大きくて検索しても困らないですね!

WordPress の設定

CDN経由にするとドメインやHostヘッダーが変わるのでWordPress自体の設定変更も必要です。 WordPressは、設定されているドメイン以外のドメインからアクセスが来たら、設定されているドメインへリダイレクトする機構があります。 それは具体的にはRequest ParameterのHost Headerを見ています。

WordPressをどのサーバーにホスティングしているかによって複数の設定方法が実現可能ですが今回はWordPress自体の設定で実現しています。

wp-config.phpの末尾に以下を追記しました。

if ( defined( 'WP_CLI' ) ) {
    $_SERVER['HTTP_HOST'] = 'localhost';
} else {
    $_SERVER['HTTPS'] = 'on';
    $_ENV['HTTPS'] = 'on';
    $_ENV['HTTP_HOST'] = 'public_domain.com';
    $_ENV['SERVER_NAME'] = 'public_domain.com';
    $_SERVER['HTTP_HOST'] = 'public_domain.com';
    $_SERVER['SERVER_NAME'] = 'public_domain.com';
}

update_option( 'siteurl', 'https://public_domain.com/integrations/' );  ※ WPの設定画面から直接設定(DBに保存)もできます
update_option( 'home', 'https://public_domain.com/integrations/' );   ※ WPの設定画面から直接編集(DBに保存)もできます

この設定はどの経路で来ても、public_domain.comから来たと認識させる設定です。 逆にいうと、自分の会社じゃない所がアクセスを振り分けてきても応答してしまいます。

今回はコンテンツ自体はプライベートなものはないので問題ありませんが、管理画面へのアクセス権限管理や、二段階認証の設定などは行っています。 WordPressは非常にクラッキングが多いのでフィッシングやDoSアタック、乗っ取りなども考えられます。気をつけて対処してください。 対策をするなら、どのHOSTから来たかを判別して、設定を入れる/入れないを判断すればより安全かと思います。

WordPress Trailing Slash

ここまでやった所で、どうやらWordPressのrootである public_domain.com/integrations に末尾スラッシュなしでアクセスすると、WordPressのOriginDomainにリダイレクトされることがわかりました。調べていくとWordPressの手前にあるWebサーバー(NginxかApache)が wordpress_origin_domain.com/integrations のアクセスと解釈して TrailingSlashを付加してwordpress_origin_domain.com/integrations/にリダイレクトしてくれているようでした。 public_domain.comでアクセスしてるのに…

これはホスティングしているWebサーバーの会社によりけりですが、私が利用している環境では .htaccessを利用しても対処できない箇所の様でしたので 諦めてCloudFront Functionsを利用して、CDN側で対処することにしました。 自前のEC2にNginx等で組んでいる場合は、NginxのVirtualServerあたりの設定で回避できると思います。

  1. 以下の様なコードをCloudFront Functionsにアップします。
function handler(event) {
    var request = event.request;
    var uri = request.uri;
    var newuri = uri;
    var response = {};
    var params = '';

    // add Trailing Slash
    if(request.uri !== '/' && !uri.endsWith('/')) {
        if(('querystring' in request) && (request.querystring.length > 0)) {
            params = '?'+request.querystring;
        }

        response = {
          statusCode: 302,
          statusDescription: 'Found',
          headers: { "location": { "value": request.uri + "/" + params } }
        };

        return response;
    }
    return request;
}
  1. /integrations を新規にBehaviroに追加してFunctionsを指定します。

add-trailing-slash

これで対策完了です。直接 wordpress_origin_domain.com/integrationsにアクセスしない限りはこのページは表示されないし、それ以外のサブディレクトリのページは wordpress_origin_domain.com にアクセスしても、WordPresspublic_domain.comにリダイレクトしてくれます。

SEO対策

これで基本的な挙動の実装が終わりました。ここからはSEOに最適化していきます。 最初の構成図のここの部分です。

cdn-seo-optimize

現在の構成ではrobots.txtとsitemap.xmlは以下の構成になっています。

public_domain.com/robots.txt  ... typedream_origin_domain.com/robots.txt を返却します。
public_domain.com/sitemap.xml  ... typedream_origin_domain.com/sitemap.xml を返却します。

これではTypeDreamのコンテンツは返せますが、WordPressのコンテンツは返せません。 また、TypeDreamから返却されているsitemap.xmlの中身は、typedream_origin_domain.comを向いていてこのままでは間違ってOriginをIndexしてしまいそうです。

これらを以下の構成に移行していきます。

seo-optimize-before-after

Behaviorでいう所の以下です。

/robots.txt
/sitemap.xml
/typedream-sitemap.xml
/integrations/*

/robots.txt

public_domain.com/robots.txt

これは自前で作る必要があるので、CloudFront Lambda@Edge を利用します。

以下の様なコードのLambdaをデプロイして、Behavior(/robots.txt)に紐付けます。 Originは必ず指定しなければいけないので何かしら指定します(が、実際にはOriginにはRequestは飛びません)

behavior-robots.txt

exports.handler = async (event, context) => {
    /*
     * Generate HTTP response using 200 status code with a simple body.
     */
    const response = {
        status: '200',
        statusDescription: 'OK',
        body: `User-Agent: *
Allow: /
Sitemap: https://public_domain.com/sitemap.xml`,
    };

    return response;
};

/sitemap.xml

こちらも自前で作っていきます。CloudFront Lambda@Edge を利用します。

以下の様なコードのLambdaをデプロイして、Behavior(/sitemap.xml)に紐付けます。 Originは必ず指定しなければいけないので何かしら指定します(が、実際にはOriginにはRequestは飛びません)

'use strict';

exports.handler = (event, context, callback) => {
  const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.google.com/schemas/sitemap/0.84">
  <sitemap>
    <loc>https://public_domain.com/typedream-sitemap.xml</loc>
  </sitemap>
  <sitemap>
    <loc>https://public_domain.com/integrations/sitemap.xml</loc>
  </sitemap>
</sitemapindex>`
    console.log(sitemap);
    const response = {
        status: '200',
        statusDescription: 'OK',
        body: Buffer.from(sitemap, 'utf-8').toString()
    };
    callback(null, response);
};

/typedream-sitemap.xml

/sitemap.xml から参照するsitemapの内の1つのtypedreamのsitemapは、残念ながらレスポンスデータの中に入っているURLが typedream_origin_domain.com になっています。それはそうです。TypeDream側は*1typedream_origin_domain.comのサイトだと認識しているので。

これは、Behaviorに/typedream-sitemap.xmlを指定して、Lambdaを呼び出します。 Lambdaの中で typedream_origin_domain.com/sitemap.xmlをFetchしてBodyの中身を書き換えて、ClientにReponseさせることで回避します。

typedream用のsitemapを用意しているんだなーと思うと、実はOriginのSitemapを流用しているという形です。

/typedream-sitemap.xmlというBehaviorを設定して、同じく以下のようなLambdaと紐付けます。

'use strict';

const https = require('https');

exports.handler = (event, context, callback) => {
    const request = event.Records[0].cf.request;
    const typedream_sitemap_url = 'https://typedream_origin_domain.com/sitemap.xml';

    // Fetch sitemap of typedream 
    https.get(typedream_sitemap_url, (res) => {
        let body = '';
        res.setEncoding('utf8');
        res.on('data', (chunk) => {
            body += chunk;
        });

        res.on('end', (res) => {
            let sitemap = body.replace(/typedream_origin_domain.com/g,'public_domain.com');
            console.log(sitemap);
            const response = {
                status: '200',
                statusDescription: 'OK',
                body: Buffer.from(sitemap, 'utf-8').toString()
            };
            callback(null, response);
        });
    }).on('error', (e) => {
        console.log(e.message); //エラー時
    });
};

/integrations/sitemap.xml

これはWordPressGoogle XML Sitemaps の様なPluginで作成したものをルーティングで参照させているだけです。Behaviorの追加をしなくても、既存の /integrations/ のルーティングで参照することができます。

サマリー

これでOriginの指定と、SEO対策の実施が完了しました。 今後もしオリジンが増えても * Originの追加 * Behaviorの追加 * /sitemap.xml に NewOrigin の sitemapへの参照を追加

で増やしていくことができますね。

ハマり

TypeDreamのページ間リンクが壊れる、または期待したのと違うURLになるケース

TypeDreamは今回の構成をする場合にリンクが壊れるケースを発見しています。 対応策を書いていくのでご参照ください。

問題

TypeDreamのリンク設定で、Link to Pageを使うと壊れます。笑 そのままPublishしてWebページでアクセスすると、Linkが切れているか、又は以下の様な期待しないURLに改変されてしまいます。

期待 https://public_domain.com/hogehoge

結果 https://public_domain.com/domain/typedream_origin_domain.com/hogehoge

調査したり問い合わせしたけど、CDNでは解決できなかったのでワークアラウンドで回避しています。

  • Linkを貼る時にLink to Pageを使わずに、同じPATHを指定して、External Linkで指定する。
    link-to-page-direct

まとめ

TypeDream自体がまだまだマイナーだし、マイナーの手前にCDNを置いて、マイナーなことしている自覚がある作業でしたが そうゆう作業こそ、ネットの海に流すべきかなーということで久しぶりに筆を取りました。

どなたか見えない方のお役に立てたら幸いです。

*1:TypeDreamには事前にCustomDomainを指定しています。その為にTypeDreamは自分自身はtypedream_origin_domain.comと認識しています