import { CloudFront } from "aws-sdk";
import * as url from "url";
import {
CreateDistributionWithTagsResult,
GetDistributionResult,
UpdateDistributionResult
} from "aws-sdk/clients/cloudfront";
export class CloudFrontClientForMediaPackage {
private cloudFront: CloudFront;
constructor() {
this.cloudFront = new CloudFront({
region: "ap-northeast-1",
apiVersion: '2020-05-31',
});
}
/**
* CloudFront ディストリビューションの情報を取得するために利用する
* @param id CloudFront ディストリビューションの ID
* @return ディストリビューションの情報を取得する
*/
async getDistribution(id: string): Promise<GetDistributionResult> {
const distribution = await this.cloudFront.getDistribution({
Id: id
}).promise()
return distribution;
}
/**
* CloudFront ディストリビューションの設定内容を取得するために利用する
* @param id CloudFront ディストリビューションの ID
* @return ディストリビューションの設定内容を取得する
*/
async getDistributionConfig(id: string): Promise<CloudFront.DistributionConfig> {
const config = await this.cloudFront.getDistributionConfig({
Id: id
}).promise()
return config.DistributionConfig;
}
/**
* CloudFront ディストリビューションを削除する
* @param id 削除したい CloudFront ディストリビューションの ID
*/
async deleteDistribution(id: string) {
const distribution = await this.getDistribution(id);
await this.cloudFront.deleteDistribution({
Id: id, IfMatch: distribution.ETag
}).promise()
}
/**
* CloudFront ディストリビューションを無効化する
* @param id 無効化したい CloudFront ディストリビューションの ID
* @return 無効化した CloudFront ディストリビューションの情報
*/
async disableDistribution(id: string): Promise<UpdateDistributionResult> {
const distribution = await this.getDistribution(id);
const config = distribution.Distribution.DistributionConfig;
config.Enabled = false;
return await this.cloudFront.updateDistribution({
Id: id,
IfMatch: distribution.ETag,
DistributionConfig: config
}).promise();
}
/**
* MediaPackage のエンドポイント用の CloudFront ディストリビューションを作成する
* @param id CloudFront ディストリビューションを判別するための ID
* @param mediaPackageArn MediaPackage チャンネルの ARN
* @param mediaPackageUrl MediaPackage エンドポイントの URL
*/
async createDistributionForMediaPackage(
id: string,
mediaPackageArn: string,
mediaPackageUrl: string
): Promise<CreateDistributionWithTagsResult> {
// 1. url モジュールを用いて URL 文字列をパースする
const mediaPackageEndpoint = url.parse(mediaPackageUrl);
/**
2. MediaPackage のエンドポイント URL から FQDN を取得する。
後述する CloudFront ディストリビューションのオリジンのドメイン名としても利用する
*/
const mediaPackageHostname = mediaPackageEndpoint.hostname;
/**
3. MediaPackage のエンドポイント URL のフォーマットは
https://<AccountID>.mediapackage.<Region>.amazonaws.com/**** となっているので、
FQDN の先頭部分を文字列分割で取り出すとアカウント ID が取得できる
*/
const accountId = mediaPackageHostname.split('.')[0];
// 4. 後述する CloudFront ディストリビューションのオリジン ID として、アカウント ID を利用する
const targetOriginId = `MP-${accountId}`
/**
5. createDistribution ではなく、createDistributionWithTags 関数で、
CloudFront ディストリビューションを作成する。MediaPackage との紐付けにタグを利用するため。
*/
return await this.cloudFront.createDistributionWithTags({
DistributionConfigWithTags: {
Tags: {
Items: [
/**
!!!!!重要!!!!!
6. CloudFront ディストリビューションに紐付けたい
MediaPackage エンドポイントのチャンネル ARN を
mediapackage:cloudfront_assoc で定義する。
mediapackage:cloudfront_assoc を定義することで、
CloudFront ディストリビューションと
MediaPackage チャンネルを紐付けることが可能となる。
*/
{
Key: 'mediapackage:cloudfront_assoc',
Value: mediaPackageArn
},
{
Key: 'Id',
Value: id
},
{
Key: 'Product',
Value: 'product'
},
{
Key: 'Stage',
Value: 'dev'
}
]
},
DistributionConfig: {
CallerReference: new Date().toISOString(),
Comment: `Managed by MediaPackage - ${id}`,
Enabled: true,
/**
7. CloudFront ディストリビューションのオリジンには 2つ設定します。
1つが MediaPackage のエンドポイントに対するものと、
もう 1つが MediaPacakge サービスに対するものです。
基本的には MediaPackage のエンドポイントに対するオリジンを利用します。
例外時に向けるオリジンが MediaPacakge サービスに対するものになります。
*/
Origins: {
Quantity: 2,
Items: [
{
DomainName: mediaPackageHostname,
Id: targetOriginId,
CustomOriginConfig: {
HTTPPort: 80,
HTTPSPort: 443,
OriginProtocolPolicy: 'match-viewer'
}
},
{
DomainName: 'mediapackage.amazonaws.com',
Id: "TEMP_ORIGIN_ID/channel",
CustomOriginConfig: {
HTTPPort: 80,
HTTPSPort: 443,
OriginProtocolPolicy: 'match-viewer'
}
}
]
},
/**
8. CacheBehaviors のいずれにも当てはまらなかった場合の
キャッシュの振る舞いを定義します。
MediaPackage は タイムシフト表示機能を使用する際等で、クエリ文字列に start, m, end を利用しています。
そのため、それらの文字列は WhitelistedNames に含め QueryString には true を指定しておきます。
DefaultCacheBehavior に引っかかる挙動は例外的扱いなので、
使用するオリジンは MediaPackage サービスのものを設定します。
*/
DefaultCacheBehavior: {
ForwardedValues: {
Cookies: {
Forward: 'whitelist',
WhitelistedNames: {
Quantity: 3,
Items: [
'end', 'm', 'start'
]
}
},
QueryString: true,
Headers: {
Quantity: 0
},
QueryStringCacheKeys: {
Quantity: 0
}
},
MinTTL: 6,
TargetOriginId: "TEMP_ORIGIN_ID/channel",
TrustedSigners: {
Enabled: false,
Quantity: 0
},
ViewerProtocolPolicy: 'redirect-to-https',
AllowedMethods: {
Items: [
'GET', 'HEAD'
],
Quantity: 2,
},
MaxTTL: 60
},
/**
9. CloudFront のエラーコード全ての TTL に 1sec を設定します。
MediaPackage のエラーのキャッシュが長時間持続してしまうと、
その間は MediaPackage で正常に配信できているとしても、
復旧できない状態となるからです。
*/
CustomErrorResponses: {
Quantity: 10,
Items: [
{
ErrorCode: 400,
ErrorCachingMinTTL: 1
}, {
ErrorCode: 403,
ErrorCachingMinTTL: 1
}, {
ErrorCode: 404,
ErrorCachingMinTTL: 1
}, {
ErrorCode: 405,
ErrorCachingMinTTL: 1
}, {
ErrorCode: 414,
ErrorCachingMinTTL: 1
}, {
ErrorCode: 416,
ErrorCachingMinTTL: 1
}, {
ErrorCode: 500,
ErrorCachingMinTTL: 1
}, {
ErrorCode: 501,
ErrorCachingMinTTL: 1
}, {
ErrorCode: 502,
ErrorCachingMinTTL: 1
}, {
ErrorCode: 503,
ErrorCachingMinTTL: 1
}
]
},
/**
10. CloudFront ディストリビューションのキャッシュの振る舞いを 2つ定義します。
それぞれの設定内容は基本的に DefaultCacheBehavior で定義したものと同様です。
しかし、利用するオリジンは MediaPackage エンドポイントに向けたものを利用します。
1つは Microsoft Smooth Streaming での配信時に利用する
index.ism に対するもので Smooth Streaming を true に設定しています。
もう 1つは上記 Microsoft Smooth Streaming 以外の
全てに当てはまるストリーミングに適用されるものになります。
*/
CacheBehaviors: {
Quantity: 2,
Items: [{
MinTTL: 6,
PathPattern: 'index.ism/*',
TargetOriginId: targetOriginId,
ViewerProtocolPolicy: 'redirect-to-https',
AllowedMethods: {
Items: [
'GET', 'HEAD'
],
Quantity: 2,
},
ForwardedValues: {
Cookies: {
Forward: 'whitelist',
WhitelistedNames: {
Quantity: 3,
Items: [
'end', 'm', 'start'
]
}
},
QueryString: true,
Headers: {
Quantity: 0
},
QueryStringCacheKeys: {
Quantity: 0
},
},
SmoothStreaming: true
}, {
MinTTL: 6,
PathPattern: '*',
TargetOriginId: targetOriginId,
ViewerProtocolPolicy: 'redirect-to-https',
AllowedMethods: {
Items: [
'GET', 'HEAD'
],
Quantity: 2,
},
ForwardedValues: {
Cookies: {
Forward: 'whitelist',
WhitelistedNames: {
Quantity: 3,
Items: [
'end', 'm', 'start'
]
}
},
QueryString: true,
Headers: {
Quantity: 0
},
QueryStringCacheKeys: {
Quantity: 0
},
}
}]
},
PriceClass: 'PriceClass_All'
}
}
}).promise()
}
}