MySQL Workbenchでデータベースの差分を調べてを更新する方法

こんにちは、ごじぽんです。

今回は MySQL Workbench でデータベースの差分を調べて更新クエリを作成する方法を紹介します。データーベースの差分を埋めたい場合、「CREATE文を出力」→「CREATE文の差分をとる」→「変更クエリの作成」という手順でデータベースを差分を埋めていましたが、もう少し簡単にできる方法を探したところ、MySQL Workbench で簡単に出来たので備忘録込みで書きました。

環境

MySQL Workbench 6.3E(Windows版)

接続設定の作成

前準備として、比較対象の2つのデータベースへの接続設定を作成しておきます。

出力手順

メニューから File -> New Model を選択し、新規モデルの画面を立ち上げます。次に メニューから Model -> Synchronize With Any Source を選択します。 f:id:Derabon:20160330191820p:plain

Introduction

Nextを押して進みましょう。機能説明ですね。2つのデータベースを比較して変更を適用したり、スクリプトの出力ができますという旨のメッセージが書いてあります。

Select Source

比較対象の2つのデータベースの選択画面ですが、Live Database Server を選択し次に進みます。 f:id:Derabon:20160330191905p:plain

Source Database・Target Database

Source Databaseでは 新しいDBの接続 を指定して、Target Database では 古いDBの接続 を指定します。 f:id:Derabon:20160330191933p:plain

Get Source and Target

Nextで進みます。

Select Schemata

対象にするスキーマを選んでOK。 f:id:Derabon:20160330192007p:plain

Fetch Objects

Nextで進みます。

Select Changes to Apply

差分が表示されるので確認できる。見やすくてよい。 f:id:Derabon:20160330192050p:plain

Detected Changes

画面に変更クエリが表示されます。ファイルに保存するボタンやクリップボードにコピーするボタンがあり、画面にもクエリが表示されているので自由に利用できます。 Executeボタン でそのまま実行する事もできます。私の場合はクエリを確認してから、別で実行したいのでクリップボードに保存してキャンセルします。 f:id:Derabon:20160330192111p:plain

Alter Progress

変更クエリが成功したかが分かります。

GUIで分かりやすく差分の確認をしつつ、変更クエリの作成までできるところがとてもよかったです。それと、記事とは関係ありませんが糖質制限により10kg以上減量しました!

.NETでのAPI作成におけるDelegatingHandlerの利用

こんにちは、久しぶりに和朗です。

ASP.NETのControllerの承認フィルターAuthorizeAttributeにはOnAuthorizationメソッドがあり、ここで認証を行うことができますが、 今回は、なんでもできちゃうDelegatingHandlerを利用して認証ロジックの組み込みを行ってみたいと思います。

f:id:teradak:20160328130759p:plain

RequestとResponseの間に入っているので、Controllerに処理を渡さずにResponseすることも可能ですし、Request内容を変更することも可能!

今回利用したもの

  • System.Net.Http.DelegatingHandler
  • System.Security.Principal.GenericIdentity
  • System.Security.Principal.GenericPrincipal
  • System.Web.Http.AuthorizeAttribute

まずは、GenericIdentity

public class CustomeIdentity : System.Security.Principal.GenericIdentity
{
    public CustomeIdentity(string name) : base(name) { }
    /// <summary>
    /// Authorize属性と紐付けるための、プロパティ(ロールA,ロールBなど)
    /// </summary>
    public string[] Roles { set; get; }
}

identityに業務独自のプロパティを設けたい場合に利用。identityは処理のどこからでも参照可能なので便利。

次に、GenericPrincipal

public class CustomePrincipal : System.Security.Principal.GenericPrincipal
{
    public CustomePrincipal(CustomeIdentity identity) : base(identity, identity.Roles) { }
}

ここは、AuthorizeAttributeのroleとの紐付けに利用しました。

そしてメインの、DelegatingHandler

public class CustomeHandler : System.Net.Http.DelegatingHandler
{
    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        CustomeIdentity identity = null;
        {   // 認証に利用できるであろう情報
            var headerAuthorization = request.Headers.Authorization.Parameter;
            var fromIp = ((HttpContextWrapper)request.Properties["MS_HttpContext"]).Request.UserHostAddress;
            var targetUrl = request.RequestUri.ToString();
            var method = request.Method.Method;
            /* 
                * 認証ロジック
                * 省略
                */
            identity = new CustomeIdentity("一意な文字列");
        }

        if (identity == null)
        {   // 認証NG
            return Task<HttpResponseMessage>.Factory.StartNew(() =>
                {
                    return new HttpResponseMessage(HttpStatusCode.BadRequest)
                    {
                        Content = new StringContent("Error encountered while attempting to process authorization token")
                    };
                });
        }
        else
        {   // 認証成功
            var principal = new CustomePrincipal(identity);
            this.SetPrincipal(principal);
        }
            
        return base.SendAsync(request, cancellationToken);
    }

    /// <summary>
    /// http://www.asp.net/web-api/overview/security/authentication-and-authorization-in-aspnet-web-api
    /// </summary>
    /// <param name="p"></param>
    private void SetPrincipal(System.Security.Principal.IPrincipal p)
    {
        // 2つのプロパティにセットする必要があります。
        Thread.CurrentPrincipal = p;
        if (HttpContext.Current != null)
        {
            HttpContext.Current.User = p;
        }
    }
}

作成したハンドラをResisterに設定

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // カスタムハンドラの追加
        config.MessageHandlers.Add(new CustomeHandler());
    }
}

最後に、ApiControllerに利用可能なロールを設定

[System.Web.Http.Authorize(Roles = "ロールA,ロールB")]
public class CustomeController : System.Web.Http.ApiController
{
}

GenericPrincipalのコンストラクタで渡しているロールと紐付けられています。 コントローラやメソッドの属性としてAuthorizeが設定可能です。 コントローラ、メソッド単位でロールを意識することになり、安心感があります。

まとめ

今回の内容により、接続元(接続ユーザ)ごとに利用可能なURLをプログラムレベルで定義することが可能となり コントローラ作成時に常にセキュリティを意識した実装を行うことになります。 もう一つのメリットとして、ログ出力とidentityの連動もメリットだと考えます。 identityを利用することによって接続元の情報にどこからでもアクセス可能となるからです。

AWSの社内勉強会を開きました

こんにちは、AWS担当のwakです。

AWSを弊社で利用し始めて2年ほど経ちますが、未だにEC2やRDSのみしか念頭に置いていないメンバーがいるのも事実です。「AWSにはこんな機能もあるよ!」「これを使うとシステム構築が楽になるかも!」「できることに幅が広がるかも!」という知識の底上げを図るために社内勉強会を実施しました。

EC2 Run Commandは東京リージョンで使えるようになったばかりですが、メンバーからは「こういうことがやりたかった!」と喝采が上がりました。SNS/SQSはうまく活用できればシステム構成を大幅にシンプルかつ強固にできる可能性がありますし、Lambda+API Gatewayは特定の状況ではサーバーを排除することすらできる極めて強力なツールです。今すぐに取り込むことはかなわなくとも、存在を知らなければ採用する・しないという選択肢自体を持ち得ません。

インターネットは「学習の高速道路」だという話がありますが、それならばAWSは高速道路どころかワープポイントのようなものです。当たり前のように乗りこなしていきましょう。

ASP.NET Web APIでログを綺麗に出す

井上です。

Web Request/Responseのログをお手軽に出しましょうというお話です。
ログ出力のサンプルは多々あるのですが、パラメータを全部出していたりとかはあんまりないというところから。

環境

Visual Studio Pro 2013
.NET Framework 4.5.3
ASP.NET Web API

Json.NETをGetする

f:id:ihisa:20160120171820p:plain

ログ出力クラスを作る

https://msdn.microsoft.com/ja-jp/library/system.web.http.filters.actionfilterattribute(v=vs.118).aspxクラスを継承してOnActionExecutedをオーバーライドします。
最初コントローラーに引き渡されるパラメータをリフレクションしまくってクラスプロパティ値出してましたが、JsonConvertでJson形式で出力する良い感じです。

using Newtonsoft.Json;
using System.Web.Http.Filters;

public class LoggingAttribute : ActionFilterAttribute
{  
    public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext)
    {
        var log = new
        {
            LogType = "OnActionExecuting",
            Controller = actionContext.ControllerContext.Controller.ToString(),
            Method = actionContext.Request.Method.Method,
            RequestUri = actionContext.Request.RequestUri,
            RequestHeaders = actionContext.Request.Headers,
        };
        Logger.WriteLog(Logger.LogLevel.Info, JsonConvert.SerializeObject(log));
        base.OnActionExecuting(actionContext);
    }

    public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
    {
        // 例外発生時は例外フィルターより先に呼び出されるのでResponse値をチェックしたうえで出力する
        var log = new
        {
            LogType = "OnActionExecuted",
            HTTPStatus = (actionExecutedContext.Response == null) ? "" : actionExecutedContext.Response.StatusCode.ToString(),
            Request = actionExecutedContext.Request.ToString(),
            Parameter = actionExecutedContext.ActionContext.ActionArguments,
            Response = (actionExecutedContext.Response == null || actionExecutedContext.Response.Content == null) ? "" : actionExecutedContext.Response.Content.ReadAsStringAsync().Result,
        };
        Logger.WriteLog(Logger.LogLevel.Info, JsonConvert.SerializeObject(log));
        base.OnActionExecuted(actionExecutedContext);
    }
}

System.Web.Http.Filters.ActionFilterAttributeにはOnActionExecutingOnActionExecutedがありますが、HttpActionExecutedContextにRequestの内容も含まれていますので、OnActionExecutedだけ実装しておけばログ内容としては足りるかと思います。
尚、コード中のLoggerはlog4netを用いたログ出力クラスです。
例フィルターを作る場合はhttps://msdn.microsoft.com/ja-jp/library/system.web.http.filters.exceptionfilterattribute(v=vs.118).aspxクラスを継承してOnExceptionをオーバーライドします。

コントローラーの基底クラスを定義し、フィルターを付ける

基底クラスに以下の通りフィルターを定義しておきます。

[Logging]
public class BaseApiController : ApiController

コントローラーで基底クラスを定義する

    [RoutePrefix("hogehoge")]
    public class HogeHogeController : BaseApiController
    {
        [HttpGet]
        public IHttpActionResult HogeHoGet(string param1, string param2, int param3)
        {
            var result = new
            {
                param1 = "param1は" + param1,
                param2 = "param2は" + param2,
                param3 = "param3は" + param3
            };
            return Ok(result);
        }
    }

ログの確認

http://localhost:61369/hogehoge?param1=sanwa&param2=system&param3=123で呼び出しログを確認します。

OnActionExecutingのログ

{"2016-02-24 13:21:14,195":{"level":"INFO ","message":{"LogType":"OnActionExecuting","Controller":"Ssc.Ttb.Api.Front.Controllers.v1.HogeHogeController","Method":"GET","RequestUri":"http://localhost:61369/hogehoge?param1=sanwa&param2=system&param3=123","RequestHeaders":[{"Key":"Connection","Value":["Keep-Alive"]},{"Key":"Accept","Value":["text/html","application/xhtml+xml","*/*"]},{"Key":"Accept-Encoding","Value":["gzip","deflate"]},{"Key":"Accept-Language","Value":["ja-JP"]},{"Key":"Host","Value":["localhost:61369"]},{"Key":"User-Agent","Value":["Mozilla/5.0","(Windows NT 6.1; WOW64; Trident/7.0; rv:11.0)","like","Gecko"]}]}}}

JSON Viewerで見るとこんな感じです。 f:id:ihisa:20160224174912p:plain

OnActionExecutedのログ

{"2016-02-24 13:21:14,195":{"level":"INFO ","message":{"LogType":"OnActionExecuted","HTTPStatus":"OK","Request":"Method: GET, RequestUri: 'http://localhost:61369/hogehoge?param1=sanwa&param2=system&param3=123', Version: 1.1, Content: System.Web.Http.WebHost.HttpControllerHandler+LazyStreamContent, Headers:\r\n{\r\n Connection: Keep-Alive\r\n Accept: text/html\r\n Accept: application/xhtml+xml\r\n Accept: */*\r\n Accept-Encoding: gzip\r\n Accept-Encoding: deflate\r\n Accept-Language: ja-JP\r\n Host: localhost:61369\r\n User-Agent: Mozilla/5.0\r\n User-Agent: (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0)\r\n User-Agent: like\r\n User-Agent: Gecko\r\n}","Parameter":{"param1":"sanwa","param2":"system","param3":123},"Response":"{\"param1\":\"param1はsanwa\",\"param2\":\"param2はsystem\",\"param3\":\"param3は123\"}"}}}

同じくJSON Viewerで見ます。 f:id:ihisa:20160224175027p:plain

綺麗に出ました。

Slackのリマインダー機能まとめ

こんにちは、家でも会社でも実家でもSlackばかり使っているwakです。会社でSlackのリマインダー機能について聞かれたのでまとめておきます。元ネタは公式です。

f:id:nurenezumi:20160205123231j:plain

とにかく /remind

リマインダーはSlackの標準機能のひとつです。チャット欄(どこのチャンネルでもいいです)に/remindから始まるコマンドを入力すると、Slackが指定した日時にメッセージを送ってきてくれる便利機能です。忘れっぽいあなたには必携と言えるでしょう。また、通知先として自分以外にも他の人を指定できたりします(後述します)。

日時の指定方法にはいくつかのバリエーションがありますが、どの場合でもその場で通知スケジュールが表示されます。

f:id:nurenezumi:20160204162035p:plain

間違えていたら「Cancel」をクリックすればキャンセルできます。

1回だけの通知

1回だけ通知を送る場合、通知先には自分自身、他ユーザー(@yamada@tanakaなど)、パブリックチャンネル(#generalなど)が指定できます。プライベートチャンネルは指定できません。

○○分後、○時間後に通知してほしい

Slackは指定した時間だけ待ってから通知を送ります。

30分後、自分宛に「出かける時間だよ」と通知してほしいとき。

/remind me 出かける準備しろ in 30 min

1時間後、自分宛に「電話かけないと」と通知してほしいとき。

/remind me 早く電話かけろ in 1 hour

2時間後、@yamadaさんに「お腹空いたしそろそろお昼にしません?」と通知してほしいとき。

/remind @yamada ごはんですよ in 2 hour

3時間後、#generalに「経費申請を早くしろ」と通知してほしいとき。

/remind #general 経費! 申請! 経費! 申請! in 3 hour
  • 他ユーザー・パブリックチャンネルを送信先に指定した場合、設定を行った段階で即座に通知が行きます。他人をビックリさせる目的では使えません。
  • 英語の授業ではないので単数・複数形は気にしません。minminuteと書いてもminutesと書いてもいいです。
  • 語順に関しては柔軟です。次のどちらの書式でも受け付けてくれますが、混乱すると面倒なので固定して覚えておいた方がいいです。
/remind me 出かける準備 in 30 min
/remind me in 30 min 出かける準備

指定した時刻に通知してほしい

日付や時刻を絶対指定することもできます。こちらの方がよく使うかもしれません。

今日の23:30に「日付が変わる前にやることがあるよね?」と通知してほしいとき。

/remind me デイリーミッション全部終わった? on 23:30

もちろんその時刻を過ぎていれば明日になります。明日の0:25に何かテレビを見ることを通知してほしいとき。

/remind me てれび on 0:25

日付を指定することもできます。2/10 10:00に#project-neco宛に会議のお知らせを通知してほしいとき。

/remind #project-neco 今日はこれから会議ですよ on 2016-2-10 10:00

12時間制が好きな人はam/pmも指定できるようです。2/15 13:30にチョコレートのセールに行くように通知してほしいとき。

/remind me 売れ残ったチョコを回収しにいこう on 2016-2-15 pm 1:30

時刻については難しいことは考えず、日本時間でそのまま指定してください。またonは省略可能です。

繰り返し通知

「毎日」「毎週○曜日」「毎月○日」「毎年○月×日」の形で繰り返し予定を通知することもできます。こちらは他ユーザーを通知先に指定することはできません。自分自身、パブリックチャンネルのみが送信先にできます。

毎日通知してほしい

毎日15:00にお花に水やりをするように通知してほしいとき。

/remind me お花 on 15:00 every day

毎週○曜日に通知してほしい

毎週月曜日の9:30に自分宛に「先週分の経費を社内システムに登録した?」と通知してほしいとき。

/remind me 経費入れないと on 9:30 every monday

毎月○日に通知してほしい

毎月20日の9時半に請求書を送るように営業各位に通知してほしいとき。

/remind #dept-sales 請求書送るよ~ on 9:30 20th of every month

日付部分には3thのように恥ずかしい英語を書いても通りますが、「20 of every month」ではダメです(強制的に月初になります)。

毎年○月×日に通知してほしい

毎年5月30日の23時半に「もうすぐ妻の誕生日だよ」と通知してほしいとき。

/remind me ハッピーバースデーおーくさーん on 23:30 every 5-30

一覧表示

/remind list

で今設定されているリマインダーを一覧表示することができます。必要のないものは消しておきましょう。

おわりに

以上です。忘れっぽいのはなかなか直りませんが、テクノロジーの力で補完することはできます。使えるものは何でも使って幸せになりましょう。

Twilioを使ってWeb API経由で電話をかける(前編)

こんにちは、AWS担当のwakです。

f:id:nurenezumi:20160127154326j:plain

【インデックス(予定)】

  1. CloudWatchの監視結果をSlackに流す(AWS Lambdaバージョン)
  2. AWS LambdaでWebサイトの死活監視を行ってSlackに結果を流す
  3. DynamoDBにパラメーターを入れてハードコードをなくす
  4. CloudWatchの監視結果をTwilioに流して電話をかける(前編)(今回)
  5. CloudWatchの監視結果をTwilioに流して電話をかける(後編)
  6. SlackからEC2インスタンスを起動・停止するコマンドを作る

APIで電話をかけよう

Twilioというものがあります。これは電話に関する諸々をWeb APIでコントロールできるサービスで、電話を受ける・かける、録音する、SMSを送信するといった面倒そうな処理の面倒をまとめて見てくれる優れものです。

今回はこのサービスの機能のうち、

  • 電話をかける
    • 指定した番号に電話をかけることができます。
  • 日本語読み上げ
    • 指定した文章を、電話口で(そこそこ自然な)日本語で読み上げてくれます。
  • 音声ファイル再生
    • 読み上げの前後に、予め準備した音声ファイル(mp3など)を再生してくれます。

を使って、《CloudWatchでアラートが発生した際、電話でアラートを通知する》仕組みをサーバーなしで構築することにしました。前編ではこの手順のための下準備としてTwilioの使い方を説明します。

概要

  1. まずいつもの通りCloudWatchで監視内容を設定します。
  2. 監視でエラーが発生すると、AWS SNSにアラートが飛ぶようにします。
  3. そのSNSをトリガーとしてAWS Lambdaが起動するようにします。
  4. LambdaはDynamoDBを参照し、エラー通知先の電話番号と通知内容を取得し、Twilio APIを叩いて電話をかけて担当者を叩き起こします。
  5. 担当者は頑張って対応します。

なお、Twilioの全ての機能は有料です。まずクレジットカード(Visa/Masterのみ)を使って事前にいくらか*1料金をチャージしておき、サービスを利用するたびにそこから残高が引き落とされていく仕組みになっています。残高がゼロになるとサービスが利用できなくなりますので適宜追加してください。自動チャージも可能です。

Twilioの準備

アカウント作成

まずはTwilioのトップページより「サインアップ」へ移動してアカウントを作成します。

twilio.kddi-web.com

注意点としては、ここで作ったアカウントは米Twilioのアカウントとは別になるため、サインインする際は https://jp.twilio.com/login/kddi-web (末尾に"kddi-web"がある)を経由する必要があるということです。サインアウトした状態でサービスを使おうとすると https://jp.twilio.com/login にリダイレクトされるのですが、こちらではサインインができません。パスワードのリセットを行おうとしても「このメールアドレスに紐付いたアカウントは存在しない」と言われてしまって混乱することになります。というか、実際に混乱して1時間無駄にしました。

チャージする

アカウントができたら、クレジットカードで適当にチャージします(どうせ使うので)。お試しでもサービスは利用できますが、任意の番号に任意の内容で電話をかけることはできません。

APIクレデンシャルを取得

アカウント取得後、 アカウントページ を見ると、APIを叩くのに必要なキーが表示されています。キーには2種類あり、右側の「テスト」はAPIのテストのために利用できます(これを使ってAPIを叩いても実際に電話をかけるなどの操作は行われません。料金もかかりません)。

f:id:nurenezumi:20160107180340p:plain

発信元電話番号を確認する

電話番号画面を見ると、電話番号が一つ表示されているはずです。この電話番号は自分だけのもので、電話をかける・受けるときに使われます。(有料で)別に新規取得することもできます。最初に確認しておきましょう。

テストしてみる

この情報をもとにエンドポイントにHTTP POSTを送ると電話をかけられます。

ドキュメントの REST API: 通話を開始する を見ると、curlでテスト実行を行うためのサンプルコードが見つかるはずです(「XML」に切り替えるのを忘れないでください)。

f:id:nurenezumi:20160107181519p:plain

既にAPIキーは埋め込まれていますので、{AuthToken}を本物の値に書き換え、Fromを自分の電話番号(上記)に書き換え、さらにToを自分の好きな番号に変更して実行すればいいです。

curl -XPOST https://api.twilio.com/2010-04-01/Accounts/AC92555d**************************/Calls \
    -d "Url=http://demo.twilio.com/docs/voice.xml" \
    -d "To=%2B81311112222" \
    -d "From=%2B8199998888" \
    -u 'AC92555d**************************:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'

発信先電話番号は国番号(日本なら"+81")から指定します。たとえば03-1111-2222にかけるのであれば、先頭のゼロを取り、"+81"を付加して"+81311112222"になります。さらにcurlの仕様により+%2Bエスケープする必要があるので、パラメーターはTo=%2B81311112222と書くことになります。FromはTwilioから発行された電話番号を指定しますが、同様に修正してください。

--data-urlencodeを使えばこの手間は省けますし見やすくなります。

curl -XPOST https://api.twilio.com/2010-04-01/Accounts/AC92555d**************************/Calls \
    --data-urlencode "Url=http://demo.twilio.com/docs/voice.xml" \
    --data-urlencode "To=+81311112222" \
    --data-urlencode "From=+8199998888" \
    -u 'AC92555d**************************:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'

ただのBASIC認証つきのHTTP(S) POSTなので、もちろんPowerShellでも同じように書けます(若干面倒です)。こちらでもエスケープは必要ありません。

$key = "AC92555d**************************"
$secret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
$uri = "https://api.twilio.com/2010-04-01/Accounts/$key/Calls"
$xml = "http://demo.twilio.com/docs/voice.xml"
$parameters = @{Url=$xml; To="+81311112222"; From="+8199998888"; }

# BASIC認証
$auth = "${key}:${secret}"
$base64 = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($auth))
$header = @{Authorization = "Basic $base64"}

Invoke-WebRequest -Uri $uri -Method POST -Body $parameters -Headers $header

うまく行けば電話がかかってきて「Enjoy!」と言ってくれるはずです。

喋る内容をカスタマイズする

日本語で喋らせる

電話で喋る内容は、上記のパラメーターの中で指定した http://demo.twilio.com/docs/voice.xml の中で指定しています。

<?xml version="1.0" encoding="UTF-8"?>
<Response>
    <Say voice="alice">Thanks for trying our documentation. Enjoy!</Say>
    <Play>http://demo.twilio.com/docs/classic.mp3</Play>
</Response>

これはTwiMLと呼ばれるXMLで、文法は TwiMLTM: Twilio マークアップ言語 で解説されています。これと同じようなXMLファイルを自分で用意してWebサーバーでホストし、そのURLを指定すれば任意の文章を読み上げさせることができます。日本語にも対応していて、単にlanguage属性で言語に日本語を指定すればいいだけです。

<?xml version="1.0" encoding="UTF-8"?>
<Response>
    <Say voice="alice" language="ja-JP" loop="3">大切なサーバーが停止しました。</Say>
    <Play>http://s3-ap-northeast-1.amazonaws.com/YOUR-BUCKET-NAME/very-very-horrible-music.mp3</Play>
</Response>

思わず心臓が躍り出すような通告が3回繰り返された後、S3に置いた*2mp3ファイルが再生されます。

TwiMLをS3に配置するときの注意点

TwiMLをS3にホストすればWebサーバーは必要ありませんが、TwilioはデフォルトでこのTwiMLにPOSTでアクセスしに行くため、S3ではエラーになってしまいます*3。パラメーターに Method=GET を追加してこの動作をオーバーライドできます。curlの例であれば--data-urlencode "Method=GET" \を、PowerShellであれば

$xml = "https://s3-ap-northeast-1.amazonaws.com/YOUR-BUCKET-NAME/voice.xml"
$parameters = @{Url=$xml; To="+814155551212"; From="+81012345678"; Method="GET"}

のように書いてください。

Twimletsを使う

いくらサーバーが必要ないとはいえ、あらかじめファイルを作成してアップロードしておくのは面倒なものです。Twilioから提供されているWebサービス「Twimlets」を使うと、上述のTwiMLを動的に生成することができます。

Twilio Labs

パラメーターにXMLの中身を丸ごと渡してあげると、そのままのXMLを出力してくれます。*4

http://twimlets.com/echo?Twiml=%3CResponse%3E%0A%20%20%20%20...

このURLを生成するためのPowerShellのコードを示します。

Add-Type -AssemblyName System.Web
$raw = '<Response><Say voice="alice" language="ja-JP" loop="3">大切なサーバーが停止しました。</Say><Play>http://s3-ap-northeast-1.amazonaws.com/YOUR-BUCKET-NAME/very-very-horrible-sound.mp3</Play></Response>'
$encoded = [System.Web.HttpUtility]::UrlEncode($raw, [System.Text.Encoding]::UTF8)
$xml = "http://twimlets.com/echo?Twiml=" + $encoded

Linuxであればnkfか何かを使ってください。あらかじめ用意することができる固定メッセージであれば上述のURLからブラウザで作ることもできます。このURLさえ準備できれば、Twilioにリクエストを送る際にUrlオプションにこのURLを渡してあげればいいわけです。なお、curlでリクエストを送る場合は-dではなく--data-urlencodeオプションを使ってください。

Lambda(Node.js)から呼ぶ

ライブラリも提供されているのですが、この程度であれば直接リクエストを送ってしまった方が早いでしょう。次のコードはLambdaで実行すると電話をかけて処理を終了します。

var https = require('https');

exports.handler = function(event, context) {
    var key = "AC92555d**************************"
    var secret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
    var options = {
        hostname: "api.twilio.com",
        port: 443,
        path: "/2010-04-01/Accounts/" + key + "/Calls",
        method: 'POST',
        auth: key + ":" + secret,
        headers : { "Content-Type" : "application/x-www-form-urlencoded" }
    };

    var req = https.request(options, function(res) {
        if (200 <= res.statusCode && res.statusCode <= 204) {
            console.log("OK!")
            context.succeed();
        } else {
            console.log("HTTP status code is NOT 2xx but " + res.statusCode);
            res.setEncoding("utf8");
            res.on('data', function(data) {
                console.error(data);    
                context.fail(res);
            });
        }
    });
    
    req.on('error', function(e) { context.fail(e.message); });

    var rawXml =
        '<?xml version="1.0" encoding="UTF-8"?>' +
        '<Response>' +
        '  <Say voice="alice" language="ja-JP" loop="3">大切なサーバーが停止しました。</Say>' +
        '  <Play>http://s3-ap-northeast-1.amazonaws.com/YOUR-BUCKET-NAME/very-very-horrible-music.mp3</Play>' +
        '</Response>';
    var urlParam = encodeURIComponent('http://twimlets.com/echo?Twiml=' + encodeURIComponent(rawXml));

    req.write("To=+81311112222&");
    req.write("From=+8199998888&");
    req.write("Method=GET&");
    req.write("Url=" + urlParam);
    req.end();
};

注意点としては、

  • HTTP POSTでパラメーターを送信するので、ヘッダにContent-Type : application/x-www-form-urlencodedを指定する
  • XMLの中身はencodeURIComponentでURLエンコードしてTwimletsに渡す
  • そのURL自体をまとめて再度encodeURIComponentでURLエンコードしてTwilioに渡す

といったあたりです。

まとめ

bashPowerShellのコンソールと、電話という子供の頃から慣れ親しんできたデバイスとが繋がるのはなんだか不思議な感じがします。次回はこちらを使い、CloudWatch+Lambdaから呼び出して実際に電話をかけることにします。

*1:2,000円以上、1,000円単位になります

*2:Twilioのサーバーが取得できるように外部からアクセス可能でないといけません

*3:S3はHTTP POSTを受け取ると無条件でエラーを返す仕様となっています

*4:メッセージ内容だけを渡すバージョンもあるのですが、language属性の指定ができないため日本語では使えませんでした

Google Chromeアプリ[Postman]の使い方メモ

井上です。WEB API作成時にとても便利なPostman。その使い方についてです。

Postmanとは

Google Chromeのブラウザ上からWEB APIを呼び出すことができるアプリです。
公開APIを用いたシステムを開発の際に実際に呼び出して確認したり、自社で提供するWEB APIを作成した際にドライバ替わりに使ったりできるので開発者にとって大変利便性の高いアプリとなっています。

Postman

f:id:ihisa:20160125192828p:plain

実際に公開APIを呼んでみる!

郵便番号検索APIに記載のリクエスト例をPostmanで呼んでみます。
f:id:ihisa:20160127182527p:plain

美しく結果が返ってきました。楽ちん!
APIと言ってますがつまりはHTTPリクエストなので、いつもご覧のページを呼んでみると楽しいかもしれません。

Postmanを便利に使う!

URL入れてパラメータセットして呼ぶだけでも十分便利なのですが、せっかくなのでもっと活用しましょう。特にコレクションとテスト機能は便利ですので是非使いましょう。

コレクションの活用

よく使うものをコレクションに登録する

SendしまくってるとHistoryがどんどんたまっていきます。いつも使うものはブックマークできたらいいですよね。

Historyで履歴を選択すると[Add to collection]と表示されますので押下します。
f:id:ihisa:20160126090841p:plain

登録したいフォルダを作成/選択します。
f:id:ihisa:20160125203743p:plain

登録されます。
f:id:ihisa:20160126172218p:plain

説明文を追加する

urlだけだと目的が分からなくなりがちなので、説明を入れましょう。
対象のコレクションにカーソルをあてると[・・・]アイコンが出ますので選択→editです。
f:id:ihisa:20160126171806p:plain

Request Descriptionに説明文を入力します。 f:id:ihisa:20160126171923p:plain

コレクションから対象を選ぶとurlの下に先ほど入力した説明文が表示されています。
f:id:ihisa:20160126172052p:plain

登録したコレクションを更新する

対象のコレクションを選択・更新した後、右側フロッピー的なアイコンを押すと更新できます。
f:id:ihisa:20160126090752p:plain

コレクションをダウンロード / アップロード

メンバーが作ったコレクションを共有したい場合に使えます。

ダウンロードはダウンロードしたいコレクションの[・・・]アイコン押下→[Download]、もしくはその上の矢印アイコン押下で[Run]ボタンから右4つめのアイコンでダウンロードできます。
f:id:ihisa:20160126090652p:plain

アップロードは画面上の[Import]から実行します。ダウンロードしたファイルをアップすればコレクションに追加されます。
f:id:ihisa:20160126090916p:plain

テスト

期待値チェック

いろいろ期待値をチェックできます f:id:ihisa:20160126091030p:plain [Tests]タブを選ぶとSNIPETTSが右側に出てきますので、選択することで左のテキストエリアに設定されます。自身でテストコードを書くこともできます。
詳しくはWriting tests | Postman Learning Center

設定した後[Send]しますと、[Tests]タブに結果がバッチリ入っています。素晴らしい! f:id:ihisa:20160126091310p:plain

一気にテスト実行

対象のコレクションを選ぶと[Run]ボタンが出てきますのでここから実行します。 f:id:ihisa:20160125204936p:plain

f:id:ihisa:20160125205007p:plain

Postmanを使って効率いい開発ライフを送りましょう。