echo("備忘録");

IT技術やプログラミング関連など、技術系の事を備忘録的にまとめています。

【Xamarin】JXUGC #25 最新情報アップデート&LT 大会に参加しました

概要

昨日(2019/8/31)、マイクロソフト東京本社で開催された、「JXUGC #25 最新情報アップデート&LT 大会」に参加してきました。
その内容になります。

(参考までに)僕のXamarin履歴

  • 2016(4月か10月くらい) : Xamarinイベント&JXUG初参加
    • 3年~3年半のキャリア
  • プライベートでは、Xamarin.Formsをちょいちょい触ってる。
  • C#大好き
  • 仕事では全く縁がない

発表者&内容

下記の通りになります。(詳細は、公式ページ参照。敬称略)

※公式ページから、LT資料を見ることができます。興味があればぜひ。

発表者 内容
@amay077 Xamarin.Forms Hot Reload のススメ
@ailen0ada 1200万ユーザーを抱えるToDoアプリの作り方
@muak_x アプリ「復習帳」開発にまつわるエトセトラ
@himarin269 モバイルのVUI対応とXamarin・Azure・C#
@futa_ttjh Xamarin.Formsで自分用家計簿アプリを作ってみた
@gnk_f327 2年で6個のアプリをリリースした感想 with Xamarin.Forms
@setoazusa Xamarin.Formsでアプリの設定情報を管理するには
@TANY_FMPMD Xamarin.Forms Shellを調べてみた
@hiro128_777 App Center 推奨のOneSignal使ってみた
@omanuke Fabulous is Fabulous
@mishi_cs ARCore で遊ぼう
@nakasho_dev Azure Spatial AnchorsをXamarinで使ってみた
@masatoru AppCenterのAuthとDataを使ってみた

印象に残った内容

※さすがに全部は追いきれないので、概要だけ

HotReloadについて
  • 今、フロントはHotReloadが熱い。
    • Flutterとか、Vue.jsなんかはHotReload対応。
  • でも、XamarinはHotReload未対応。
    • てか、HotReloadっぽい機能は出るんだけど、日の目を見ないままBANしてばかり。
    • マイクロソフト的にはあまり力を入れてない?
  • XAML Hot Reload For Xamarin.FormsがHotReloadを頑張ってくれるみたい。
    • 個人的にデモを見た限り、好感触♪
  • (あめいさんも言ってたけど)正式リリースまで油断は禁物。
    • てか、Xamarinのサービスって、そのパターンが多いよね...
クロスプラットフォーム開発について その1
  • (モバイルに限らず)フロント側は、とにかく進歩が速い!
  • 下記のことが日常茶飯事に起こる
    • 前バージョンで採用した技術が、今はもう古い
    • 前バージョンではいまいちだった技術が、今では超イケてる感じに
  • 大事なのは「前バージョンの技術に固執しないこと」
    • 前回採用した技術でも、今回はお祈りメールを送る事態も十分にある
    • というか、クロスプラットフォームを捨てる(ネイティブ言語開発)ことも大いにある
  • 大事なのは、「状況・環境に応じ、臨機応変に対応すること」
クロスプラットフォーム開発について その2
  • クロスプラットフォーム開発環境は「銀の弾丸」ではない。
    • もしそうなら、誰もswiftやkotlinに手を出さないよね...
  • クロスプラットフォーム開発環境は万能だけど、細部ではネイティブ開発言語には劣る。
    • 「痒いところ」的な部分は、やはりネイティブ言語にはかなわない。
    • 「時間・リソース分散」を気にしないなら、ネイティブ言語の方が良い。
  • むしろXamarinのウリは、「クライアント&サーバー(クラウド)を一つの言語(C#)で開発できる」ところかな?
    • クラウドはこれがあるから、開発の敷居はそんなに高くない(作法さえ覚えれば、あとはプラットフォーム&インフラ次第)
設定情報(てかトークンとか)の管理
  • 各種トークンの管理、本当に面倒。
    • manifest.xalファイルやInfo.plistファイルに設定する
    • *.csprojの設定を変える
    • 個別に用意して、ビルド時に適用するようにする。(gitの管理からは外す)
  • てか、本当にトークン管理は悩ましい。
    • 現在、Vue.jsのサーバーレスSPAで絶賛悩み中

※あめいさんがこんな記事を書いてくれてます。
Xamarin プロジェクトにおける API key など秘密情報管理のベスト?プラクティス

※本家のStackOverFlowでトークン管理はクライアント?サーバー?みたいなページがあって、参考にしてたのですが...失念。(わかったらリンク載せます)     

App CenterのAuthについて
  • App Centerに「Auth」という認証機能基盤が追加された模様。
  • AWSの「Amplify/Auth」みたいなものかな?
    • 認証関係は本当に厄介なので、こういう基盤が提供されるのはうれしい限り。
良いアウトプットを生む条件

※良いアウトプットは、良い定時退社から生まれる(名言)

良いアウトプットは、良い定時退社から生まれる(大事なことなので(ry))

その他
  • 愛知県民の参加率高スギィ!!!
    • 東京のイベントですよね?
    • 「お前が言うな」と言われたらそれまでですが...

総評

いや、Xamarinイベントは久々の参加だったのですが、参加してよかったです。
やっぱりXamarinはいいですよね。

てか、他の参加者との方とも話していたのですが、ぜひ定期的に開催されると嬉しいですね。
次回も都合が合えば、ぜひ参加したいです。
(10月末までは、2週間に一度のペースで東京に来る予定ですので。)

あと個人的には、おやつタイムがあった&他の参加者とお話しできたのが、すごい良かったなあ、と思いました。

あと、そろそろ私も(Xamarinじゃなくてもよいので)何かのネタで登壇したいなあ、と考えてます。(※)

それでは、今日はこの辺で。

※実は今回Serverless Framework&Azure&Xamarinネタで登壇しようと、管理者の田淵さんとTwitterでやり取りしてたのですが、最終的に「それってXamarinってか、むしろ翌週のAzureイベントで話す内容じゃね?」って結果となり、お蔵入りとなりました(Azureイベントは私用のため不参加)。

【Serverless Framework】Azureにサービスをデプロイする

概要

以前、【Serverless Framework】簡単デプロイとserverless.ymlの記載についてで、Serverless Framework(以下SFW)を使ってAWSにサービスをデプロイする方法を書きました。

今回はその続きとして、最近おざなりになっていたSFWを使用した、Azureへのサービスのデプロイ方法について記載します。

触れないこと

  • SFWの詳細
    • これは「概要」内のAWS版の記事を参照してください。
  • Azureの詳細

インストール&サービス作成

インストール&サービス作成は、AWS版と大差ありません。
インストールして、テンプレートを指定して、サービス作成(create)する...という感じです。

ただAzureでは「serverless-azure-functions」というモジュールが追加で必要のようなので、別途npm installして、「plugins」定義に追加しておきます。

# SFWインストール
> npm install serverless -g  
  
# serverless-azure-functionsのインストール  
> npm install serverless-azure-functions  --save-dev  
  
# サービス作成(templateは他にもいろいろ。--helpで一覧を表示可能)  
> serverless create --template azure-nodejs --name azure-sample

定義の作成

定義の作成ですが、「百聞は一見に如かず」なので、まずはserverless.ymlファイルの一例を。

なお、下記serverless.ymlファイルを含むプロジェクト一式を下記レポジトリで公開していますので、興味があればご一読ください。

service: azure-sample-new-20190825

provider:
  name: azure
  location: japanwest

plugins:
  - serverless-azure-functions

package:
  exclude:
    - README.md
  include:
    - lambda/**

## azure functions dedinition.
## azure functions works at ([endpointurl]/api/[route])
## e.g. https://your-function-app-name.azurewebsites.net/api/hello
## this is able to change at congigration of host.json
## https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-host-json
functions:
  hello:
    handler: lambda/Hello/index.handler
    events:
      - http: true
        x-azure-settings:
          # direction: in
          name: req
          methods: 
            - get
          route: hello
          authLevel : anonymous
      - http: true
        x-azure-settings:
          direction: out
          name: res
  goodbye:
    handler: lambda/Goodbye/index.handler
    events:
      - http: true
        x-azure-settings:
          # direction: in
          name: request
          methods: 
            - get
          route: goodbye
          authLevel : anonymous
      - http: true
        x-azure-settings:
          direction: out
          name: res
  goodbye2:
    handler: lambda/Morning/index.handler
    events:
      - http: true
        x-azure-settings:
          # direction: in
          name: request2
          methods: 
            - post
          route: posthello
          authLevel : anonymous
      - http: true
        x-azure-settings:
          direction: out
          name: response

service

サービス(デプロイするアプリの管理単位。CloudFormationみたいなもの)の名前、という点ではAWSと同じです。

  • Azureの場合、「[サービス名]-rg」というのがリソースグループの名前になります。(AWSと違い、「serverless-deployment-bucket」などを使用して任意の名前を付ける...みたいなことはできないみたい)
  • リソースグループ名は、サブスクリプション内での重複はNGなので、必ず重複がない名前にしてください。

provider

  • name:デプロイ先クラウドの名前。create時に--templateを指定した場合、自動で設定されています。
  • location: デプロイ先のリージョンを指定します。(japaneast, japanwestなど)

plugins

先述の通り、ここには先程インストールした「serverless-azure-functions」を指定します。

include/exclude

AWSと同じく、パッケージファイルに含める/除外するファイルの設定です。

functions

Azure Functionの定義。今回の中心となる部分です。(ただ「定義名」「handler」「[events] - [http]」の部分は、AWSと同じなので省略)

※eventsはhttp(http通信)以外にもいろいろありますが、今回はhttpのみ紹介。(それ以外のイベントはSFW公式サイトを参照)

Azureの場合、下記のルールがあるようです。

  • (イベントの種類以外の)定義は「x-azure-settings」以下に定義
  • リクエスト/レスポンスで個別に定義が必要
  • AWSとは違い、API Management(AWSで言うAPI Gateway)には紐づかない

なお、ここ項目の説明はこちら。

項目名 説明 備考
direction 通信の向き(?)
inはリクエスト、outはレスポンス。
inを定義したらエラーになった。in側は定義不要かも。
name Azure Functionファイル内でのリクエスト、およびレスポンスオブジェクトの名前。
methods リクエストメソッドの種類(get, postなど)を配列形式で in側のみ。
定義とメソッドが1:1ではないのが、AWSとの違い
route リクエストURLに適用される、エンドポイントURLの後に付与される文字 in側のみ。
デフォルトは「定義名(helloなど)」
AuthLevel リクエスト時の認証に適用される認証レベル。 in側のみ。
function, anonymous, adminから選択

デプロイ

デプロイですが、これはAWSと同じく「deploy」コマンドを実施するだけです。

下記の処理が行われた後、デプロイされます。問題がなければ、デプロイ完了するはずです。

  • [関数定義名]-function.jsonの作成
  • host.jsonの作成
  • .serverlessフォルダに、パッケージしたファイル(*.zip)の作成
# デプロイ
> serverless deploy -v  

あとはAzureにアクセスし、リソースグループ&Azure Functionが作成されており、各Azure Functionに「[エンドポイントURL] + [route]の設定値」のURLで正常にレスポンスが返ってくればOKです。

※デフォルトのエンドポイントは、https://[サービス名].azurewebsites.net/api/[routeの設定値] です。(「api」を忘れやすい)

デプロイできない場合

デプロイ時ができない場合、下記事項を確認してください。

「At least one resource deployment operation failed」というエラーが発生する。

この場合「[service]に指定した名前-rg」という名前のリソースグループがすでに存在している可能性が高いです。
コンソール操作や「serverless remove」コマンドで該当のリソースグループを削除してから、再度実施してください。

※deployコマンドがエラーになってもリソースグループ自体は作成されますので、そのリソースグループの消し忘れ...というのが、よくあるケースです

エラーは発生しないが、デプロイ中に「Creating Function App」から一切処理が進まない

この場合、AzureのActivityログに下記「Failed」ログが無いか確認してください。
下記ログがあった場合、過去のCreating Function App処理が何故か生き続けていることが原因である可能性があります。

項目名 内容
Error Code Conflict
Message Cannot modify this web hosting plan because another operation is in progress. (中略) OperationName: Create

対策としては、「service名を変更して、別リソースグループ名でデプロイする」になります。(これ、バグなのかも...)

SFWがAzure対応したのは2017年と比較的新しいからか、一部AWSで対応している機能が機能しなかったりなどありますが(※)、サービスのデプロイは十分可能です。
本格的にサーバーレスアプリを作る場合はもちろん、「WPF、Xamarin.Formsなどでサーバーレスアプリを作りたい!」という場合に、サクッとAzureの環境を作るような場合にも、非常に有用だと思います。

では、今回はこの辺で。

※例えば「package」コマンドや「--package」オプションなどは機能しないようです。(公式サイトにも記載がない)

【AWS】Web ACLを設定する方法&CloudFormationでの注意点

概要

前回前々回と、CloudFrontに関する記事でしたが、今回もCloudFrontです。
今回は前々回に触れられなかった、WAF Web ACL(※)の作成について。

※Web Application Firewall - Web Access Control Listの略。WAFと同じもの...という認識でOK。

やること

  • Web ACLの前提条件作成
  • 条件の作成
  • ルールの作成
  • Web ACLの作成
  • CloudFrontへの適用

Webの前提条件作成

AWS WAF and AWS Shield」から「AWS WAF」を選択し、「Configure Web ACL」を選択します。
※「Concepts overview」では、条件(Condition)・ルール(Rules)・Web ACLについての説明があるので、読んでおくとよいかも。

「Next」ボタンをクリックすると「Name web ACL」画面でWeb ACLの前提条件を設定します。

項目は以下の通り。

項目名 説明
Web ACL name(必須) Web ACL nameの名前
CloudWatch metric name(必須) Web ACLのCloudWatch上でのメトリクス名。(Web ACL Nameを設定すると自動で設定されます。任意設定も可能)
Region(必須) Web ACL nameを設定するWeb ACLのリージョン(下記参照)
AWS resource to associate Web ACLを関連付けするAWSリソース。(後から設定することも可能)

【Regionについて】

  • CloudFrontに関連付けを行う場合、「Global」を選択してください。
  • 個別リージョンは、CloudFront以外のリソース(API Gatewayなど)に関連付けする場合に設定します。

f:id:Makky12:20190812181252p:plain

条件(Condition)の作成

「Create conditions」で、Web ACLに適用する条件(=フィルタ)を設定します。
設定可能なのは、下記の通り。

項目名 説明
Cross-site scripting match conditions XSS攻撃に対応するフィルタ
Geo match conditions アクセスされた地域(国)でのフィルタ
IP match conditions アクセス元のIPアドレスに対応するフィルタ
Size constraint conditions リクエストデータのデータ量でのフィルタ
SQL injection match conditions SQLインジェクション攻撃に対応するフィルタ
String and regex match conditions リクエストデータに含まれる文字列に対応するフィルタ

個々項目の詳細設定については、公式サイトを参考。
(項目名を見ればわかるようなものばかりだけど)

とりあえず、ちょっと分かりにくそうなものだけ抜粋。

項目名 説明
Part of the request to filter on フィルタ対象の項目(ヘッダ、クエリ文字列、など)
Transformation チェック前にフィルタ対象項目の値に実施させる処理。
(「Simplify Command Line」は、コマンドラインと思われる文字列を無効化する処理。OSコマンドインジェクション対応用です)
Comparison operator データ量比較の不等号の設定(=、<=、< など)
Match type 文字列比較のパターン(部分一致、完全一致、など)

f:id:Makky12:20190812181323p:plain

ルール(Rule)の作成

「Create rules」でルールを作成します。
「条件とルールって、何が違うの?」と思うかもしれませんが、Web ACLでの「ルール」は、「1つ以上の条件をまとめて管理する入れもの」となります。

「Create Rule」ボタンをクリック後、ルールの内容、及び「Add Conditions」で関連付けする条件を設定します。

  • 「Add Conditions」は、「Geo Match」及び「IP Address」が「指定した条件と一致」、それ以外は「指定した条件と少なくとも1つが一致」です。
  • 「Rule Type」は、下記の通り。
項目名 説明
Regular Rule 「Add Conditions」で設定した内容を満たしたとき、ルールを適用する
Rate-Based Rule 「Add Conditions」で設定した内容を満たし、かつ5分間のリクエスト回数が「Rate Limit」以上のとき、ルールを適用する

「Add rules to a web ACL」で作成したルールを選んで、「Add Rule to web ACL」をクリックすると、そのルールをWeb ACLへ採用できます(複数選択可能です)。
その後、以下の2項目&その際の動作を設定します。

項目名 説明
If a request matches all of the conditions in a rule 個々のルールについて、設定した条件をすべて満たした場合の動作(上から順に評価)
If a request doesn't match any rules どのルールも、ルールに設定したどの条件も満たさなかった場合(=デフォルトの動作)

最後に「Review And Create」をクリックします。

f:id:Makky12:20190812181348p:plain

Web ACLの作成

最後の「Web ACLの作成」ですが、これは「Review and create」で「Confirm and Create」ボタンをクリックするだけです。
また各項目の「Edit」ボタンをクリックすると、その項目の再設定ができます。

f:id:Makky12:20190812181405p:plain

CloudFrontへの適用

最後にCloudFrontへの適用ですが、これはCloudFrontの[Distribution Settings] - [AWS WAF Web ACL]で、作成したWeb ACLを選択するだけです。

f:id:Makky12:20190812181419p:plain

CloudFromationでWeb ACLを作成する際の注意

ここからは、CloudFormationでWeb ACLを作成する際の注意になります。

※CloudFormationの詳細はここでは説明しないので「CloudFormation?よくわからないし、関係ないな」って人は、スルーでOKです。

WAFリソースの違い

CloudFormationのWAFリソースには、下記2種類があります。

これの違いですが、WAFのリージョンになります。(下表参照)

リソース名 リージョン
AWS::WAF Global(CloudFront)
AWS::WAFRegional Regional WAF Resource(リージョン別WAF)
  • CloudFrontに適用するWAFは「AWS::WAF」で定義しなければなりません。
  • AWS::WAFRegionalで定義すると、デプロイした際のCloudFrontとの紐づけ時に「指定されたリソースにアクセスできない(「アクセス権限がない」だったかも)」というエラーが発生します。
「Geo match conditions」設定時の注意

条件の「Geo match conditions」(=地域(国)でのフィルタ条件)ですが、CloudFromationの場合、下記の事項があるので注意しましょう。

  • AWS::WAFRegionalでのみ設定可能。(AWS::WAFでは設定不可)
    • AWS::WAFで作成すると、ルールとの紐づけ時に「指定されたIDのリソースがありません」というエラーが発生します。
  • 公式の「AWS WAF Regional リソースタイプのリファレンス」ページを表示しても、表示言語を「日本語」にしていると「AWS::WAFRegional::GeoMatchSet」へのリンクが表示されない。
    • 表示言語を「English」にすれば表示されます。
  • 今のところ、CloudFrontに「Geo match conditions」を適用する場合、コンソールで行うしかない?

まとめ

と、またちょっと長くなりましたが、今回はここまで。

最近、Cognito&AWS Amplifyを使った認証コードをガッツリ書いてたので、次のネタはそれにしようかな?

てか、Azureは...

【AWS】API Gatewayのエンドポイントに独自ドメインでアクセスする

概要  

前回CloudFrontを使った独自ドメインによるSPAの公開を記事にしましたが、今回はその第二弾で、API Gateway独自ドメインでアクセス方法に関する記事です。  

触れること

  • CloudFrontとAPI Gatewayの連携方法(てか、これしかない)  

触れないこと  

  • API Gatewayの細かい手順(作成方法・設定・Lambdaなどとの連携など)  
  • 前回触れた手順  

やること一覧  

API Gatewayの各種設定  

API Gatewayの作成や各種などについては、今回は触れません。
※公式サイトなどを参考に作成&デプロイしてください。そこまで難しくはないと思います)

とりあえず、動作確認用のLambda関数の正常動作が「テスト」で確認できればOKです。(「サンプルから作成」でOKです。)

重要なのは、「ステージ」で表示される「エンドポイントURL」で、これをコピーしておいて下さい。   f:id:Makky12:20190802185133p:plain

SSL証明書の作成  

これは前回の記事そのままなので、そちらを参考に独自ドメインの証明書を作成してください。(リージョン:北バージニアじゃないとダメな点に注意!)  

※本当によく忘れるんです、この設定... 

CloudFrontの設定  

で、一番重要なCloudFrontの設定です。
ただこれも前回との共通部分が多いので、違う部分だけ説明します。
(下記に記載していない項目は、前回と同じでOKです。)

項目名 設定値 説明 備考
Original Domain Name API Gatewayの各種設定」でコピーしたエンドポイントURL CloudFrontで制御を行うAWSリソース ※1
Origin Protocol Policy HTTPS Only CloudFrontから各種リソース(今回はAPI Gateway)に接続する際のプロトコル API Gatewayは、HTTPSのみ許可しているので
Origin Response Timeout 4~60のいずれか レスポンスを待つ時間(秒)。これを過ぎたらレスポンスタイムアウトと判断
Origin Keep-alive Timeout 1~60のいずれか API Gatewayとの接続がアイドル状態になったときに待つ待ち時間(秒)
HTTPS Port 任意 API Gatewayへの接続ポート デフォルトは443
Origin Custom Headers 任意 API Gatewayに送信したいヘッダ 任意にヘッダ情報を送信したい場合に設定する。(x-api-keyなど)
Query String Forwarding and Caching 下記説明に従い、適切な値を クエリ文字列の転送とキャッシュ ※2
CNAME API Gateway接続用の独自ドメイン CloudFront→API Gatewayに接続するURLの別名

※1:Original Domain NameにURLをコピペすると、ステージ部分(dev/stgなど)が自動で「Origin Path」にカット&ペーストされます。

※2:文字通り「クエリ文字列の転送とキャッシュ」なのですが、非常に重要な部分です。

項目名 説明 クエリ文字列のオリジン(=API Gateway)への転送
None クエリ文字列はすべて無視し、キャッシュは実行しない 一切転送されない
Forward all, cache based on whitelistl Policy ホワイトリストに指定したパラメータは、値が同値ならキャッシュから使用。違っていれば取得してキャッシュに格納する。
※順番もチェックするので注意。
すべて転送される
Forward all, cache based on all クエリ文字列部分のすべてをチェック。順番、値をすべて含め、1文字でも違ったら値を取得してキャッシュに格納する。
完全一致の場合のみキャッシュから使用
すべて転送される(クエリ文字列として)

つまり「None」だと、クエリパラメータが一切API Gatewayに渡りませんAPI Gatewayへのパラメータ転送はクエリ文字列で行うことも多いと思うので、この点は要注意です。(てか、実際ハマった...)

【参考】

Route53の設定  

最後にRoute53ですが、これは「SSL証明書の作成」同様、前回記事そのままなので、そちらを参考にホストゾーン&レコードセット(エイリアスあり)を設定してください。

あとは、ブラウザのURLなりcURLなりで動作確認してみて、正しいレスポンスが返ってこればOKです。

結構ざっくりな感じになってしまいましたが、今回はここまで。

 

【AWS】CloudFrontを用いてサーバーレスSPAを独自ドメインで公開する

概要

Vue.jsによるサーバーレスSPAをCloudFront(AWSCDNサービス)を用いて、独自ドメインで公開する方法です。

※必要最低限しか書いてないので、後日追加するかもしれません。
※2019/7/29 画像&一部説明を追加しました。

前提条件

  • Vue.jsのサービスをデプロイできる状態にしておく。
  • ドメインの取得を行っておく

※ここではこれらには(あまり)触れません

参考サイト

やること

最低限、下記のことが必要になります(多分)。

※下記事項は、別記事で触れる予定

S3バケットのWebホスティング 

Vue.jsサービスのデプロイ用S3バケットをパブリックに公開して、Webホスティングする必要があります。
手順は下記のとおりです。

  • Vue.jsサービスデプロイ用のS3バケットを作成する
  • [プロパティ]タブ - [Static Website Hosting]で、
    • 「このバケットを使用してウェブサイトをホストする」を選択する。
    • 「インデックスドキュメント」に「index.html」を指定する。
  • [アクセス権限]タブ - [アクセスコントロールリスト]で、
    • 「Everyone」の「オブジェクトの一覧」を許可する。

f:id:Makky12:20190729173019p:plain
f:id:Makky12:20190729173046p:plain
f:id:Makky12:20190729173059p:plain

SSL証明書の発行

独自ドメイン(≒CNAME)で公開するには、先にそのドメインSSL証明書の発行が必要です。
手順は以下の通りです。

  • 「Certificate Manager」にて、「証明書のプロビショニング」の「今すぐ始める」をクリック。
  • 「証明書のリクエスト」で、「パブリック証明書のリクエスト」を選択。
  • ドメイン名の追加」で、ドメイン名を入力。
    • ワイルドカード指定も可能なので、例えば「*.makky12.com」みたいな指定もできる。
  • 「検証方法の選択」では「DNS検証」と「Eメール検証」、いずれかを選択。
    • 画面に記載があるが、「DNS 設定を変更するアクセス許可」があるならDNS検証、ないならEメール認証を選択。
    • AWS的には、権限があるならDNS検証を推奨している。
  • 「証明書」画面で「状況」が「認証完了」になればOK。

※なお、SSL証明書は必ず「リージョン:北バージニア(us-east-1)」で作成しないとCloudFront設定できないので注意!!!

f:id:Makky12:20190729174113p:plain

CloudFrontの設定

で、やってCloudFrontの設定になります。やり方ですが、まず「CloudFront」で「Create Distribution」-「[Web]のGet Started」をクリックします。

設定画面での設定値は、下表を参照。

項目名 設定値 説明 備考
Original Domain Name S3バケットのWebホスティング」でWebホスティングをしたバケット CloudFrontで制御を行うAWSリソース これを選ぶと「Origin ID」が自動で設定される
Viewer Protocol Policy Redirect HTTP to HTTPS ブラウザなどからアクセスされた際に適用するプロトコル 厳密には何でもよいが、セキュリティを考えると、HTTPは避けたほうが。
Object Caching Customoize キャッシュの設定 個別設定を適用するならCustomize
TTL関連の3つ(Maximum, Minimum,Default) すべて0 キャッシュの保存期間(秒) 1以上だと、デプロイしたコンテンツの反映に時間がかかる(らしい)
Alternate Domain Names(CNAMES) 公開するドメイン ここで設定したデストリビューションの別名 証明書で証明を受けないと、設定できない
SSL Certificate 「Custom SSL Certificate」を選択し、「SSL証明書の発行」で作成したSSL証明書を選択 使用するSSL証明書 証明書を「北バージニア」で作成しないと、ここでリストに表示されない。
Default Root Object index.html デフォルトで表示するコンテンツ名 Vue.jsの場合、ルートのindex.htmlを表示する(はず)

で、「Create Distribution」をクリックし、該当のデストリビューションの「Status」が「Deployed」になればOK。
ただし5~15分程度かかるので、気長に待ちましょう。(In Progressの場合、デプロイ処理中です。)

また、以下の設定もデプロイ処理中に行っておきます。

  • 該当のデストリビューションの「ID」をクリック。
  • 「Error Pages」タブの「Create Custom Error Response」をクリック。
  • 「HTTP Error Code」の403、404について、下記設定で「Create」を行う。
項目名 設定値 説明 備考
Customize Error Response Yes レスポンスのカスタマイズを行うか
Response Page Path /index.html エラー発生時に表示するコンテンツ 詳細は下記参照
HTTP Response Code 200 OK 設定したレスポンスに設定するステータスコード

※Vue.jsだと、本来どのURLを参照してもルートのindex.html(/index.html)が表示されないといけませんが、例えば「/hoge」というパスを指定した場合も、
/hoge/index.htmlを参照します。

当然そんなページはないので404 Not Foundが返されますが、その際に代わりに表示するページです。(Vue.jsのRouter側で適切な処理を実施する必要があります)

f:id:Makky12:20190729181635p:plain
f:id:Makky12:20190729181649p:plain
f:id:Makky12:20190729181808p:plain

Route53によるエイリアス設定

最後はRoute53によるエイリアス設定です。
下記手順で行います。

  • Route53の「DNS管理」を選択。
  • 「ホストゾーンの作成」で、サブドメイン名を含まないドメイン名(「makky12.com」など)を入力。
    • 「タイプ」は「パブリックゾーン」
  • 作成したホストゾーンの「ドメイン名」をクリックし、「レコードセットの作成」をクリックする。
  • 「名前」に、サブドメイン名を入力する。(「hogehohe.(makky12.com)」など)
  • エイリアス」を「はい」に設定し、「エイリアス先」に「CloudFrontの設定」で作成したデストリビューションの「Domain Name」を入力する。

f:id:Makky12:20190729182150p:plain

長々と記載してしまいましたが、あとは「S3バケットのWebホスティング」で作成したバケットにVue.jsをデプロイすれば、これで独自ドメインでSPAを起動できるはずです。

【Vue.js】子コンポーネントの埋め込み方法+各種プロパティ設定方法(2021/12/03:一部加筆修正)

経緯

最近、仕事でVue.jsを触っており(AWSによるサーバーレスSPA)、何とか一週間で他のメンバが開発をできる土台(フレームワーク的なもの)をVue.jsで作成する...程度はできるようになりました。

その中で、コンポーネント(*.vue)の扱いでよく使いそうな、子コンポーネントの扱いについて便利そうな事項をまとめました。

【2021/12/03追記】:最初(2019/7/27)に公開した内容を元に、加筆修正を行いました

参考ページ

そもそも、子コンポーネントの埋め込み方は?

単に親コンポーネント内に子コンポーネントを埋め込む場合は、下記でOKです。

/* これが子コンポーネント */
<template>
  <div id="body_header">
    <router-link to="/">page1</router-link>
    <router-link to="/page2">page2</router-link>
  </div>
</template>  
  
<script>
export default {
  name:"body_header",
  components: {},
  data: function () {
    return {}
  },
}
</script>  
  
<style>
#body_header {
  height: 40px;
  background: white;
  /* box-shadow: 0px 3px 3px rgba(0,0,0,0.1); */
  display: flex;
  justify-content: center;
  align-items: center;
  border-bottom: 1px #000000 solid;
}
#body_header a {
  text-decoration: none;
  color: #2c3e50;
  margin: 0 10px;
  padding: 3px 10px;
  background: #5ccebf;
}
</style>  
/* これは親コンポーネント */  
<template>
  <div id="app">  
    <!-- ここで埋め込む -->
    <bodyHeader></bodyHeader>
    <router-view></router-view>
  </div>
</template>  
  
<script>  
// 上記の子コンポーネントを読み込む
import bodyHeader from './components/common/header'
export default {
  name: 'App',
  
  components: {
    // ここで子コンポーネント&HTMLタグ名を登録
    bodyHeader: bodyHeader,
  },
  data: function () {
    return {}
  }
}
</script>  
  
<style>
body {
  margin: 0;
}  
  
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
}
</style>  

【2021/12/03追記】エントリーポイントに定義する方法

上記は各Vue.jsファイルに直接埋め込みましたが、複数のVue.jsファイルでまたがって使用する場合は、エントリポイント(main.jsなど)に定義することもできます。

※親コンポーネントのHTML内での子コンポーネントの埋め込み方法は同じです。

// main.js  
  
// 子コンポーネントの読み込み
import bodyHeader from './components/common/header'  
  
// 1. グローバルコンポーネントとして登録する方法。  
// こうすると、全Vue.jsでbodyHeaderコンポーネントが使えるようになる
Vue.component('bodyHeader', bodyHeader);  
  
// 2. 個別に設定する場合。  
// 個別に設定する場合は、Vue.component()は使用せず、
// new Vue()のcomponentsプロパティに設定する。  
// 下の場合、#fugaに対応したVue.jsでのみbodyHeaderが使える。 
 
new Vue({
  el: '#hoge',
})
  
new Vue({
  el: '#fuga',
  components: { 
    'bodyHeader': bodyHeader,
  }
})

コンポーネントの変数を変えたい

コンポーネントの使い方でよくあるのが、下記の例だと思います。

その場合、子コンポーネントの変数やスタイルシートなどを変更する必要があると思いますが、そのやり方です。

コンポーネントの変数の変更方法

コンポーネントの変数を親コンポーネントから変更する場合、下記でOKです。

/* これが子コンポーネント
(無関係な部分は先述のソースと同じなので省略) 
*/
<template>
  <div id="body_header">
    <router-link to="/page1">{{ $myLink }}</router-link>
  </div>
</template>  
  
<script>
export default {
  name:"body_header",
  components: {},
  
  data: function () {
    return {
      myLink : ''
    }
  },
  
  methods:  {
    setter(linkValue) {
      this.myLink = linkValue;
    }
  }
}
</script>  
/* これは親コンポーネント */  
<template>
  <div id="app">  
    <bodyHeader ref="myBodyHeader"></bodyHeader>
  </div>
</template>  
  
<script>  
// 上記の子コンポーネントを読み込む  
import bodyHeader from './components/common/header'  
export default {
  mounted: function() {
    // 子コンポーネントのpage1へのリンクに「page1へ飛びます」と表示される。
    this.$refs.myBodyHeader.setter('page1へ飛びます');
  }
}
</script>  
  
<style scoped>
</style>  

(2021/12/03追記) propsを使う方法

上記では子コンポーネントのsetterを直接呼びましたが、propsを使う方法もあります。(てか、むしろこっちの方が一般的かも...)

propsの使い方ですが、子コンポーネントのscriptタグで、こんな感じで定義します。

<script>    
export default {
  // propsの定義
  props: {
    isHoge: {
      type: Boolean,
      default: false,
      required: true,
      validator: function (value) {
        return typeof value === 'boolean'
      }
    }
  },
  data()  {
    return {
      // 省略
    }
  }
}
</script>  

上のコードで言えば、「isHoge」はpropsの変数名です。
また、各プロパティの意味は以下の通りです。(すべて任意項目です)

key 意味 備考
type propsの型を表す型クラス(のコンストラクタ) 型クラスであることに注意。(プリミティブ型でも「string」「boolean」などはダメ)
default 値が指定されなかった場合の初期値
required 値の指定が必須かどうか
validator 指定された値をチェックするバリデーション関数

で、親コンポーネントでは、以下のように子コンポーネントのタグ内でpropsの値を指定します。

<template>
  <div id="app">  
    <bodyHeader isHoge="true"></bodyHeader>
  </div>
</template> 

参考:https://jp.vuejs.org/v2/api/#props

ただし、propsは初回レンダリング時は良いんですが、propsを変更してうんぬん...みたいなことをやろうとすると、途端に厄介になります。(ググってみると色々出てきますし、自分もハマりました)

このあたりは、別途独立してブログに出来ればなあ、と思っています。

参考: https://blog.hatena.ne.jp/Makky12/makky12.hatenablog.com/edit?entry=26006613378542201

コンポーネントのスタイルの変更方法

コンポーネントのスタイルを変更する場合は、ちょっと厄介ですが、下記の方法でOKです。

※なお、親コンポーネントのstyleタグにscopeを付けない方法もありますが、本来の使い方ではないですし、Vue.jsのスタイルルールA(必須)に反するので、お勧めしません。

  • 子クラスのスタイルを採用したいHTMLタグに、何かクラスを定義する。
  • 親クラスで、子クラス埋め込みタグの親要素として、何かクラス付きのタグを埋め込む
  • 親クラスのstyle内で、「(子クラス埋め込みタグの親要素クラス) (子クラス埋め込みタグクラス) >>> (子クラスのスタイルを採用したい要素のクラス)」という定義を用意する。
/* これが子コンポーネント
(無関係な部分は先述のソースと同じなので省略) 
*/
<template>
  <div id="body_header">
    <div class="my_item">{{ $myItem }}</div>
  </div>
</template>  
/* これは親コンポーネント */  
<template>
  <div id="app">  
    <div class="parent">
      <bodyHeader class="bh"></bodyHeader>
    </div>
  </div>
</template>  
  
<script>  
// 上記の子コンポーネントを読み込む  
import bodyHeader from './components/common/header'  
export default {
  mounted: function() {
    // 子コンポーネントのpage1へのリンクに「page1へ飛びます」と表示される。
    this.$refs.myBodyHeader.setter('page1へ飛びます');
  }
}
</script>  
  
<style scoped>  
.parent .bh >>> .my_item {
  /* 子コンポーネントのmy_itemクラスの背景色が青色になる */
  background-color: #0000FF;
}
</style>  

【2021/12/03追記】
もちろん、子コンポーネントに直接cssを書いてもOKです。

/* 
  子コンポーネント 
*/
<template>
  <div id="body_header">
    <div class="my_item">{{ $myItem }}</div>
  </div>
</template>  
  
<style scoped>  
.my_item {
  /* もちろん、直書きでもOK */
  background-color: #0000FF;
}
</style>  

(2021/12/03追記) 子コンポーネントcssを動的に変えたい場合

上記とは少し違いますが、場合によっては「親の状態に応じて、子のスタイルを動的に変えたい」というケースもあると思います。

その場合、方法としては、下記のようなものがあります。

  • 親から受け取る変数を「v-if」条件にする
  • class名を変更する
  • cssクラスのプロパティの値のみ変更する

親から受け取る変数を「v-if」条件にする
これは何となくわかりやすと思います。
ただ、HTML部分の分岐が増えてしまうのがちょっと面倒ですね...

/* これが子コンポーネント
(無関係な部分は先述のソースと同じなので省略) 
*/
<template>
  <div id="body_header">
    <div if="bgColor==='red'" class="bg_color_red">{{ $myItem }}</div>
    <div v-else class="bg_color_blue">{{ $myItem }}</div>
  </div>
</template>  
  
<script>  
// 上記の子コンポーネントを読み込む    
export default {
  data() {
    return {
        bgColor: ''
    }
  },  
  
  methods: {
    setBgColor(color) {
      this.bgColor =color;
    }
  }
}
</script>  

class名を変更する
親から取得した変数の値に応じて、class名を変更する方法です。(classはv-bindしておく)

この場合、computed関数「getBgClass」に定義したように、

  • 親から受け取ったクラス名をそのまま返す
  • 親からは状態(state)を受け取り、それに応じたクラス名を返す

などの方法があります。

/* これが子コンポーネント
(無関係な部分は先述のソースと同じなので省略) 
*/
<template>
  <div id="body_header">
    <div :class="getBgClass">abc</div>
  </div>
</template>  
  
<script>  
// 上記の子コンポーネントを読み込む    
export default {
  data(): {
    return {
        bgClass: ''
    }
  },
  
  computed: {
    getBgClass() {
       // this.bgClassに直接クラス名を埋め込む場合
       return this.bgClass;
       
       // this.bgClassにフラグ(0, 1やred, blueなどの値)を埋め込む場合  
       if (this.bgClass==='red') {
         return 'bg_color_red';
       } else if (this.bgClass==='blue') {
         return 'bg_color_blue';
       }  else {
         // その他処理
       }
    },
  },
  
  methods: {
    setBgClass(value) {
       this.bgClass = value;
    }
  }
}
</script>  

cssクラスのプロパティの値のみ変更する

<template>タグ内に<component is="style">タグを記載することで、<template>タグ内にcssを直接記述することが可能になります。

またVue.js 2.xでは、styleタグ内のcssの値は、data()変数やcomputed関数などの値をバインディングすることはできない(はず)ですが、上記のcssならば、それを行うことが可能になりますので、タグ内のclass名を直接変更せずとも、スタイルを動的に変更することができます。

この方法だと、上2つの方法と違い、

  • 条件が増えても、(<template>内の)HTMLタグが増えない
  • class名を直接変更する必要がない(=class定義を分岐数分増やさなくていい)

というメリットがあります。

※Vue.js 3.2から、styleタグ内のcssの値にdata()変数の値をバインディングできるみたいです

/* これが子コンポーネント
(無関係な部分は先述のソースと同じなので省略) 
*/
<template>
  <div>
    <component is="style">
    .bg_class {
      background-color: {{ getBgColor }}
    };
    </component>
    <div id="body_header">
      <div class="bg_class">abc</div>
    </div>
  </div>
</template>  
  
<script>  
// 上記の子コンポーネントを読み込む    
export default {
  data(): {
    return {
        bgColor: ''
    }
  },
  
  computed: {
    getBgColor() {
       if (this.bgColor==='red') {
         return '#FF0000';
       } else if (this.bgColor==='blue') {
         return '#0000FF';
       }  else {
         // その他処理
       }
    },
  },
  
  methods: {
    setBgColor(color) {
       this.bgColor = color;
    }
  }
}
</script>  

ちなみに

もちろん、1つの親コンポーネント内で同じ子コンポーネントを使いまわすことも可能です。

/* これが子コンポーネント
(無関係な部分は先述のソースと同じなので省略) 
*/
<template>
  <div id="body_header">
    <div class="my_item">{{ $myItem }}</div>
  </div>
</template>  
  
<script>
export default {
  name:"body_header",
  components: {},
  
  data: function () {
    return {
      myItem : ''
    }
  },
  
  methods:  {
    setter(itemValue) {
      this.myItem = itemValue;
    }
  }
}
</script>  
  
<style scoped>  
</style>
/* これは親コンポーネント */  
<template>
  <div id="app">  
    <div class="parent">
      <bodyHeader ref="myBodyHeader" class="bh1"></bodyHeader>
    </div>  
    <div class="parent">
      <bodyHeader2 ref="myBodyHeader2" class="bh2"></bodyHeader>
    </div>
  </div>
</template>  
  
<script>  
// 上記の子コンポーネントを読み込む  
import bodyHeader from './components/common/header'  
import bodyHeader2 from './components/common/header'  
  
export default { 
    components: {
      bodyHeader : bodyHeader ,
      bodyHeader2: bodyHeader2
  },
  
  mounted: function() {
    this.$refs.myBodyHeader.setter('bodyHeader1');  
    this.$refs.myBodyHeader2.setter('bodyHeader2')
  }
}
</script>  
  
<style scoped>  
.parent .bh1 >>> .my_item {
  background-color: #0000FF;
}  
  
.parent .bh2 >>> .my_item {
  background-color: #FF0000;
}
</style>  

ちょっと急ぎ足&文字数が多くなりましたが、今回はここまで。

...てかはてなブログ、Vue.jsに対応してくれないかなあ。(HTML/CSS/javascriptうまく表示してくれるとか)

【Serverless Framework】簡単デプロイとserverless.ymlの記載について その2

※2019/7/1 デプロイ先バケット名を任意の名前にする方法を追記しました。

概要

前回Serverless Frameworkについて書きましたが、書ききれなかった点があったので、今回はその点についての内容になります。

serverless.ymlで使用できる変数について

Serverless.ymlでは、下記の変数を使用できます。

  • 変数は${self;;xxx}のように、「${}」形式で参照します。
  • 「custom」項目内に変数名と値のペアを設定することで、独自の変数&値を設定することができます。
  • 外部ファイルに定義した変数を使用することもできます。(下記ソース参照)
  • 下記変数は、カンマの後に値を指定することで、初期値を設定可能。
記載方法 説明 備考
${self:xxx} そのserverless.ymlファイル自身の項目(xxx)を参照する
${env:YYY} 使用しているPCの環境変数(YYY)を参照する
${opt:zzz} deployコマンド実施時に、引数(zzz)に指定された値を参照する
${file(aaa):bbb} 外部ファイル(aaa)に定義した変数(bbb)を参照する bbbは外部ファイルの変数名。省略した場合、外部ファイルの変数すべてが読み込まれる。
${cf:リージョン名.ccc} 別のCloudFormationの「出力」cccに設定されている値を参照する 「出力」にはServerless.ymlの「Outputs」で登録可能。
リージョン名は省略可能。その場合、デフォルトのリージョン名が適用される。
service:
  name: makky12-service
  
# 独自の変数を定義する場合、こんな感じでcustom内に定義する。
custom:
  author: makky12

  # 外部ファイルの変数を参照する例。  
  # exportは外部ファイルの内容をすべて読み込み、export_partnerは外部ファイルの  
  # 特定の変数(partner)の値のみを読み込む。  
  # またどちらも実際に読み込むファイルは、ルート直下の「--env xxx」で  
  # 指定されたxxxフォルダ内のserverless.ymlファイル。  
  # 例えば、 deploy --env dev なら、ルート直下の「dev」フォルダ内の  
  # serverless.ymlファイルを参照する。
  export: ${(./${opt:env, 'dev'}/serverless.yml)}  
  export_partner: ${(./${opt:env, 'dev'}/serverless.yml):partner}  
  
provider:  
  # 環境変数を使用する例。  
  # 例えば事前にser MY_REGION=ap-northeast-1としていた場合、  
  # regionにはap-northeast-1が設定される。  
  region: ${env:MY_REGION}  
  
  apiName: HogehogeGateway  
  
  # 例えば、事前にOutputsに下記定義をしたserverless.ymlをデプロイした場合...
  # resources:
  #   Outputs:
  #     Value:
  #       "Ref": myRestApi
  #     Export:
  #       name: myRestApiId  
  #  
  # restApiIdには、スタック名が「myStack」というCloudFormationの、  
  # 事前にmyRestApiで定義したAPI GatewayのRestAPI IDが代入される。
  apiGateway:  
    restApiId: ${cf:myStack.myRestApiId}  
  
# serverless.ymlの変数を使用する方法
functions:
  myFunction:  
    # この場合、参照先serverless.ymlで下記定義をしていた場合、  
    # partner: abc  
    # thanksto: xyz
    #   
    # 実際のdescriptionの出力内容は以下の通りになる。  
    # author=makky12_partner=abc_thanksto=xyz
    description: author=${self:custom.author}_partner=${self:custom.export_partner}_thanksto=${self:custom.export.thanksto}
  myFunction2:
    # もちろん、serverless framework既定の項目の値を参照することも可能。
    description: ApiGateway=${self.provider.apiName}

deploymentBucket記載時の注意

前回「deploymentBucketには、パッケージ化したファイルが格納されるバケット情報を指定する」と記載しましたが、これには一つ問題があり、

name(バケット名)を定義すると、その名前のバケットが存在しない場合、エラーになる

という現象があります。(nameを指定しない場合、バケットがデフォルトの名前で新規作成されます。またnameのバケットが既に存在する場合は問題ありません)

これだとバケット名を任意の名前で新規作成できないのですが、下記方法で回避できます。

  • npm経由で「serverless-deployment-bucket」モジュールをインストールする
  • serverless.ymlの「plugins」に、このモジュールを追加する。(配列で定義する)
  • deploymentBucket.nameに、デプロイ先バケットの名前を設定する。
  • serverless-deployment-bucketの設定を「custom」に定義する。(下表参照。なおすべて任意項目で、値はすべてbooleanで指定。)
項目名 説明 初期値
versioning デプロイ先バケットで、バージョニングを可能にする false
enabled serverless-deployment-bucketを有効にするか true
policy バケットのポリシーをJSONで出力するか(?) なし(設定しない)
> npm install serverless-deployment-bucket --save-dev
# オプション項目を設定する場合。(基本、デフォルトで問題ないと思う)
custom:  
  enabled: true  
  versioning : true  
  policy: true  
    
plugins:
  - serverless-deployment-bucket  
  
provider:  
  deploymentBucket:  
    name: self-custom-bucket-name  

【参考】:serverless-deployment-bucket

デプロイのやり方

といっても、デプロイ自体はとても簡単で、サービスのルートフォルダ(serverless.ymlがあるフォルダ)で、「sls deploy」コマンドを実施するだけです。
ただ、いくつか便利なオプションがあります。

オプション名 説明 備考
--packege(-P) <格納先フォルダのパス> 関連ファイルのパッケージのみ行い、デプロイはしない フォルダのパスを省略した場合、serverlessというフォルダを作成し、そこにパッケージ化したファイルを格納する
--verbose(-v) デプロイ状況をコンソール上で確認できる CloudFormationのログに近い。よく使うと思う。
--function(-f) <functions:内の定義名> 定義名のLambda関数のみデプロイし、全体のデプロイは行わない 一回デプロイ後、Lambda関数のみ修正した場合に便利。通常デプロイに比べて、短時間で済む

もちろん、まだまだフォローしきれてない部分もありますので、そこは適宜公式サイトで確認すると、いろいろ理解が深まると思います。

それでは、今回はこの辺で。