AllIsHackedOff

Just a memo, just a progress

KtorのCORSがchromeのPreflight Requestに対してうまく働かなくてハマった問題

経緯

Kotlinを書いているうち - 適度にシュッとしている点 - Nullableをハンドリングできる点 - Collection系の関数があるところ - IntelliJとの相性抜群 などに惹かれたのでAPIでも書いてみようと軽そうなKtorを触ることに。

発見した問題

ChromeからのCORSのリクエスト、特にPreflight Requestが通らない。 Chromeのインスペクタから Copy as cURLしてやると、下記のリクエストで 403 が返ってくる

curl 'http://localhost:8080/sample' -X OPTIONS -H 'Access-Control-Request-Method: GET' -H 'Origin: http://localhost:8878' -H 'Accept-Encoding: gzip, deflate, br' -H 'Accept-Language: en,ja-JP;q=0.9,ja;q=0.8,en-GB;q=0.7,en-US;q=0.6' -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36' -H 'Accept: */*' -H 'Connection: keep-alive' -H 'Access-Control-Request-Headers: authorization,crossdomain,x-csrf-token' --compressed

解決法

公式ドキュメントを読んでもそれらしき内容はないし、ISSUEもない、ということでなぜ 403 なのかをコードを追って確認することに。 CORS - Servers - Ktor

    private suspend fun ApplicationCall.respondPreflight(origin: String) {
        if (!corsCheckRequestMethod() || !corsCheckRequestHeaders()) {
            respond(HttpStatusCode.Forbidden) // ここで 403が返ってるっぽい
            return
        }

        accessControlAllowOrigin(origin)
        accessControlAllowCredentials()
        response.header(HttpHeaders.AccessControlAllowMethods, methodsListHeaderValue)
        response.header(HttpHeaders.AccessControlAllowHeaders, headersListHeaderValue)
        accessControlMaxAge()
        respond(HttpStatusCode.OK)

    }

ので corsCheckRequestHeader を追うことに

    private fun ApplicationCall.corsCheckRequestHeaders(): Boolean {
        val requestHeaders = request.headers.getAll(HttpHeaders.AccessControlRequestHeaders)?.flatMap { it.split(",") }?.map { it.trim().toLowerCase() } ?: emptyList()

        return !requestHeaders.any { it !in headers }
    }

Access-Control-Request-Headers で指定されたヘッダに許可していないものが入っていないか調べているらしい。Chromeから送られるリクエストは 'Access-Control-Request-Headers: authorization,crossdomain,x-csrf-token' の3つのヘッダーを含んでいるが、これらはKtorのデフォルト設定だと許可されていない

    class Configuration {
        companion object {
            val CorsDefaultMethods = setOf(HttpMethod.Get, HttpMethod.Post, HttpMethod.Head)

            // https://www.w3.org/TR/cors/#simple-header
            val CorsDefaultHeaders: Set<String> = TreeSet(String.CASE_INSENSITIVE_ORDER).apply {
                addAll(listOf(
                        HttpHeaders.CacheControl,
                        HttpHeaders.ContentLanguage,
                        HttpHeaders.ContentType,
                        HttpHeaders.Expires,
                        HttpHeaders.LastModified,
                        HttpHeaders.Pragma
                ))
            }

見た感じデフォルトで許可すべきヘッダはw3cの仕様で決まっている?(ここは追っていない とにかく、許可ヘッダを足す必要があるため、若干ウォークアラウンド感があるが下記のように設定を追加し事なきをえた

           header("CrossDomain")
            header("X-CSRF-Token")

これがブラウザごとに異なる、とかだと結構設定が面倒なので、Ktor側でうまく吸収して欲しい...

KotlinでPrivate Methodをテスト

[2] の方法で実施しようとしたら trySetAccessible が解決できなくて gradle test がエラーになった。 IntelliJでは補完が効いているので謎である。詰まっていても先に進まないので[2]の方法を試したらうまくいった。

    @Test
    fun test6() {
        val tc = TitleConferrer()

        val method = tc::class.memberFunctions.find { it.name == "hoge" }
        val actual = method?.let {
            it.isAccessible = true
            println(it.toString())
            it.call(tc)
        }

find で見つけるのはなんだか効率悪そうなので、抽象化されたメソッドがあるかと思ったが補完候補の中には見当たらなかったので一旦スルー

java.lang.reflect.Methodkotlin.reflect の差異だろうか

References

  1. Kotlinのリフレクション(protected/privateメソッド呼び出し) - White Box技術部
  2. Kotlinでprivateメソッドをテストする

Memo

java.lang.reflect.Methodkotlin.reflect の差異については理解を深めておく 基本的には kotlin.reflect を使えばいいのだろうか

Goで静的ファイルのバイナリ埋め込み

go-bindata が死んでからあんまりやっていなかったけどAWS Lambda上でテンプレートファイルを読んで処理するプログラムを書くことになり、 一時ファイルの置き場やらデプロイやらで面倒なことをしたくなかったのでシングルバイナリにまとめることに

github.com

悪くない。テンプレートはParseするときに普通のstringを受け取れるので、テンプレートファイルを扱うときも適当にかけば動く

main.go
templates/
    admin/
          index.html
import (
    "fmt"
    "html/template"
    "os"

    "github.com/gobuffalo/packr"
)

func main() {
    box := packr.NewBox("./templates")

    s := box.String("admin/index.html")
    t := template.New("admin/index")
    t, err := t.Parse(s)
    if err != nil {
        panic(err)
    }
    err = t.Execute(os.Stdout, map[string]string{
        "Hoge": "Variable hoge",
    })
    if err != nil {
        panic(err)
    }
    fmt.Println("______________")
    fmt.Println(s)
    fmt.Println("--------------")
<html>
    <head>
        <title>admin</title>
    </head>
    <body>
        Variable {{ .Hoge }}
        amdin dayo ---- aaaaaaaaa
    </body>
</html>

GoでSemantic Versionを適当にparseするパッケージ

久々の更新 今回Semantic Versionをparseするパッケージ探していて思ったのですが、awesome-goもアレげだし、普通のgopher勢は 欲しいパッケージをどこで探しているのだろうか? Gopher勢の場合、自前で作っちゃう勢が多そうなイメージがありますがRubyでいうToolbox的なのがあると便利なんだけどなと思ったり。

handling Semantic Versioning in Golang

Goでバージョンを適当に扱う必要がありパッケージを探したところhashcorp製の GitHub - hashicorp/go-version: A Go (golang) library for parsing and verifying versions and version constraints. この子を見つけた。

NewVersion がerrを返すの若干使いづらさを感じるが、別にmap系の関数で移したいわけではないのでよく考えると特に問題はない。 サンプルコード通りだがかきの感じで動く、Versionの昇順がデフォルト sort パッケージに修正が入ってからは Reverse が~とか考えなくて良くなったので非常に良い...

package main

import (
    "fmt"
    "sort"

    "github.com/hashicorp/go-version"
)

func main() {
    versionsRaw := []string{"1.1", "0.7.1", "1.4-beta", "1.4", "2"}
    versions := make([]*version.Version, len(versionsRaw))
    for i, raw := range versionsRaw {
        v, _ := version.NewVersion(raw)
        versions[i] = v
    }

        // この辺、すごい昔にReverseするのがクソめんどくさかった覚えがある 
    sort.Slice(versions, func(i, j int) bool {
        isLess := versions[i].LessThan(versions[j])
        return !isLess
    })

    for _, v := range versions {
        fmt.Println(v.String())
    }
}

GoでDynamoDBのUpdateItemを呼ぶ場合の例

DynamoDBの更新は2種類存在する

  • PutItem

    Creates a new item, or replaces an old item with a new item. If an item that has the same primary key as the new item already exists in the specified table, the new item completely replaces the existing item. You can perform a conditional put operation (add a new item if one with the specified primary key doesn't exist), or replace an existing item if it has certain attribute values. You can return the item's attribute values in the same operation, using the ReturnValues parameter.
    Putはリソースの完全な置き換えを意味する。HTTPのPutと同じ意味合いだ。

  • UpdateItem

    Edits an existing item's attributes, or adds a new item to the table if it does not already exist. You can put, delete, or add attribute values. You can also perform a conditional update on an existing item (insert a new attribute name-value pair if it doesn't exist, or replace an existing name-value pair if it has certain expected attribute values).
    You can also return the item's attribute values in the same UpdateItem operation using the ReturnValues parameter.
    一方でupdateは存在しない要素の追加、条件付きアップデートなどを意味する。 サービス内のデータを「更新」したいときは基本的には差分更新を示すので、こちらの意味合いになる。

参考

DynamoDBのputItemとupdateItemの違い | ハックノート

guregu/dynamodbを使う

気楽にDynamoDBを使おう - Qiitaにあるようにaws-sdk-goを利用するとよくわからない記法でUpdate文を作ることになり「こんなことするために生まれてきたわけではないのに」的な気分に落とし込まれる。guregu氏のライブラリを用いることで直感的にDynamoDBを操作できる。

Updateを実行するためには下記のメソッドでUpdate構造体を生成するのだが、ノリでコーディングしていると引数として何を与えれればいいかワカラナイ…

func (table Table) Update(hashKey string, value interface{}) *Update {

だめな例として

## { "id" : 1, "msg": "Maimi love"} を {"id": 1, "msg": "Kamiko Love"} に書き換えたいとする
## idをHash Key (Primary Key)とする
m.msg = "Kamiko"
u := tbl.Update(strconv.Itoa(m.id, msg) 

### ValidationException: One of the required keys was not given a value

そもそもPrimary KeyがStringじゃなきゃ駄目なのかよ???みたいな疑問が湧いたり等し、ぐぐってexampleを探すも見つからずに時間を浪費する的なことになるので一旦updateItemの公式の記法に当たります。 UpdateItem - Amazon DynamoDB

### Sample Requestより抜粋
{
    "TableName": "Thread",
    "Key": {
        "ForumName": {
            "S": "Amazon DynamoDB"
        },
        "Subject": {
            "S": "Maximum number of items?"
        }
    },
    "UpdateExpression": "set LastPostedBy = :val1",
    "ConditionExpression": "LastPostedBy = :val2",
    "ExpressionAttributeValues": {
        ":val1": {"S": "alice@example.com"},
        ":val2": {"S": "fred@example.com"}
    },
    "ReturnValues": "ALL_NEW"
}      

ということでKeyに対してUpdateしたい値を与えていけば更新することができそうな予感がします。 guregu/dynamoDBにおいてはupdate構造遺体の生成時に1つのキーに対して何かしらの interface{}を割り当てているのでこの記法は使えなそう?

ということで公式の別の例を見ます。

{
    "TableName": "Thread",
    "Key": {
        "ForumName": {
            "S": "Amazon DynamoDB"
        },
        "Subject": {
            "S": "A question about updates"
        }
    },
    "UpdateExpression": "set Replies = Replies + :num",
    "ExpressionAttributeValues": { 
        ":num": {"N": "1"}
    },
    "ReturnValues" : "NONE"
}

UpateExpressionとExpressionAttributeValuesを与えてやることでConditional Updateができるようです。 Guregu/dynamoDBではupdate.go - guregu/dynamo - SourcegraphこのあたりのメソッドでUpdateExpressionを生成していることを鑑みて、よしなに生成されるように書けばよさそうです。

u := tbl.Update("id", m.id)
u.Set("msg", m.msg)
err := u.Run() 

こんな感じに書くことでうまくUpdateできるようです 尚、手元での動作確認には

が便利です。

スマートスピーカーに青空文庫を朗読させるためにATOMのプラグインを作っている話

こんばんは。この記事はGunosy Advent Calendar 2017 - Qiitaの12/22の記事です。 前日の記事はGoogle Home と Android ThingsでLチカする - CrossBridge Labでした。 本末転倒?感のあるタイトルをつけてしまいましたが、 今回はGoogle HomeやらAmazon Echoに青空文庫を朗読させるために余暇で行っている下準備について記載します。

僕の頭のなかではAmazon Echoに青空文庫を朗読しながら眠りにつく毎日を送っているはずだったのですが。 Amazon Echo ... 申込んだのになかなか売ってもらえない....つらい。 実機がない状態でLambdaやPollyを触りつつ、所持しているGoogle Homeで粛々と準備をすすめている次第です。

目次

  • SSML(Speech Synthesis Markup Language)について
  • Amazon Polly
  • SSMLを気軽に作りたい -> Atomプラグイン作成

SSML(Speech Synthesis Markup Language)について

SSMLはSpeech Synthesis Markup Languageの略称です。その名の通り、音声合成マークアップするための言語で、仕様はSpeech Synthesis Markup Language (SSML) Version 1.1に記載されています。 SSMLを記述することにより会話中の

  • 休止(break / 秒数)
  • 強調(emphasis)
  • 声量/ピッチ/強弱/速さ (prosody)

をコントロールすることができます。

Alexaで利用できるタグに関しては下記の公式ドキュメントに非常に丁寧に記載されており、日本語の発話例も提示されています。 Speech Synthesis Markup Language (SSML) Reference | Custom Skills Speechcon Reference (Interjections): Japanese | Custom Skills

12/20に公開された下記の記事に具体例と共にGoogle Assistantの設定まで詳しく書かれており、非常に勉強になりました。 qiita.com

Amazon Polly

Amazon PollyはText-to-Speech用の音声を合成してくれるクラウドサービスです。 https://aws.amazon.com/jp/blogs/news/polly-text-to-speech-in-47-voices-and-24-languages/ AWS Consoleから気軽に試すことができ、無料利用枠も存在しています。

試しに青空文庫から宮沢賢治 狼森と笊森、盗森を取得してGUI上にコピペしてみると f:id:masashisalvador:20171222191624p:plain こんな感じになります。 soundcloud.com 青空文庫の文章を色々投げ込んでみて感じた所感としては、意外と発音は正しい、人名やコンテキストによって読みが変わる文字、ルビがないと人間にも読めないものはやはりうまく発音されません。 (上記の例だと、巨い(おおきい)など) そして何より、素の発音だと抑揚がないため、ぶっちゃけるとあまり面白くありません。

いい朗読をさせるためにはSSMLをうまく書いてやらないといけない。 ということになります。

しかしSSMLはただのXMLです。素のXMLを淡々と打ち込んでいくだけの精神力を、僕は持ち合わせていませんでした。

SSMLを気軽に作りたい -> Atomプラグイン作成

若干調べた感じ、フリーでいい感じにSSMLを編集させてくれるソフトは見当たらないようでした。 SSMLをもっと気軽にかけないと楽しい朗読までたどり着かない...ということで簡単なエディタを作ることにしました。

やりたいことはシンプル。タグを簡単に挿入していきたいのです。 また、使い方が簡単で、プログラマーでなくとも朗読用のSSMLを作れるようにしたい。

ということで、雑ですがイメージとしてはこんな感じです。 f:id:masashisalvador:20171222193402j:plain

しかしエディタを0から作るのはハードルが高い...ということで出来合いのものに乗っかることにします。 業務ではすっかりIntelliJの奴隷と化しているのですが、ここは一つATOM様に助けを求めたいとおもいます。

ATOMプラグイン作成自体は公式ドキュメント atom.io と幾つかの記事を参考にすると簡単に作ることができました。

以下、機能と書いたコードを少しだけ紹介します。

1. 休止の挿入

https://gyazo.com/7c4c3a8a92d6469b964be768d49adfd9

秒数を選択しておけば休止秒数が指定できるように、下記のようなコードを書いています。

  insertBreak() {
    let editor = atom.workspace.getActiveTextEditor();
    // 選択部分の取得
    let selectedText = editor.getSelectedText();
    var breakTag = "";

    if (selectedText.length <= 0) {
      breakTag = '<break strength="strong"/>';
    } else {
      breakTag = '<break time="' + selectedText + 's"/>'
    }

    editor.insertText(breakTag);
  },

2.強調の挿入

選択部分を強調します。選択部の冒頭にsr を付しておくと、強調タグ(emphasisタグ)のlevelが strongreduced になるようにしています。

https://gyazo.com/ad605e72ae354c9f5f7d41d32821452a

  insertEmphasis() {
    let editor = atom.workspace.getActiveTextEditor();
    let selectedText = editor.getSelectedText();
    if (selectedText.length <= 0) {
      return;
    }
  // 中略

    let tag = this.wrapTextWithEmphasisTagWithLevel(selectedText, level);
    editor.insertText(tag);
  },

まとめ

アドベントカレンダーまでには作り終わる!と意気込んでいたのですが、やっていくと色々と考慮しなければならない点が出てきたので未完の状態です。SSMLの各タグで指定できる属性が色々とあるので、属性をどうやってスムースに入力させるか...などを考えているとキリがなくなってきました。

ひとまず自分でひたすら使ってみて、駄目でも先に進むスタイルで年明けあたりに公開したいなと思っています。

それでは皆さんよいクリスマスを!

mattes / migrate のメモ

migrateの使い方

toolchain #golang

how to install? ref : https://github.com/mattes/migrate/tree/master/cli

# with homebrew
brew install migrate
# with curl
curl -L https://github.com/mattes/migrate/releases/download/v3.0.1/migrate.darwin-amd64.tar.gz
tar xvz migrate.darwin-amd64.tar.gz
mv migrate.darwin-amd64.tar.gz /usr/local/migrate

naming rule for migration file ref : https://github.com/mattes/migrate/blob/master/MIGRATIONS.md ここに書いてある形式でファイルを作成するらしい。 なんでupとdownを分けるの?についても

-database        Run migrations against this database (driver://url)
# DSNを指定すればいいっぽい?
# http://go-database-sql.org/accessing.html

up

[👾  @masashi.salvador.mitsuzawa] ~/dev/sample/mattes_migrate
% migrate -database 'mysql://root@tcp(127.0.0.1:3306)/hogehoge' -path ./ up
1/u create_hoge (15.876738ms)
[👾  @masashi.salvador.mitsuzawa] ~/dev/sample/mattes_migrate
% migrate -database 'mysql://root@tcp(127.0.0.1:3306)/hogehoge' -path ./ up
no change

down

[👾  @masashi.salvador.mitsuzawa] ~/dev/sample/mattes_migrate
% migrate -database 'mysql://root@tcp(127.0.0.1:3306)/hogehoge' -path ./ down
no change

良さそう。

実行すると schema_migrations テーブルが作成されます。