カテゴリー: javascript

Kotlin/JSのAWS Lambda関数でJsonを返してみる

f:id:seeds-std:20191010160615p:plain
こんにちは, Web事業部の西村です
私の前回の記事 Promiseを利用して非同期に処理するようにしてみました
今回はこのLambda関数で kotlinx.serialization を用いてJsonを返すようにしてみたいと思います

目次

過去の記事

なぜ kotlinx.serialization?

まず kotlinx.serialization を用いる理由です
ここまでの記事を読んでくださっている方々であれば “わざわざないでしょ” と思っている方もいると思います
その通りで, json()JSON.stringifiy() 関数を用いればJsonを返すことができます
しかし, kotlinx.serialization を用いることにより, Client側でも同じクラスを利用することができ, 開発する際に便利であるといえます
またマルチプラットフォームであるため様々な環境で利用できるというのもメリットです

注意事項

この記事では kotlinx.serialization に関する詳しい解説は行いません
詳しく知りたい方は下記のリポジトリをご覧ください
github.com

開発環境

この記事では下記の環境で開発を行っています

  • AdoptOpenJDK 1.8.0_222-b10
  • IntelliJ IDEA Community 2019.2.2
  • Kotlin 1.3.50

プロジェクト

前回の記事 まで作成したプロジェクトを利用します
プロジェクトの構成は下記のようになっています

KotlinLambda
├─.gradle/
├─.idea/
├─build/
├─gradle/
├─src/
│ └─main/
│   └─kotlin/
│     └─jp.co.seeds_std.lambda.kotlin/
│       └─handler.kt
├─build.gradle.kts
├─compress.zip
├─gradlew
├─gradlew.bat
└─settings.gradle.kts

また, handler.ktは下記のようになっています

package jp.co.seeds_std.lambda.kotlin
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.delay
import kotlinx.coroutines.promise
import kotlin.js.Json
import kotlin.js.json
@JsExport
@JsName("handler")
fun handler(event: Json, context: Json) = GlobalScope.promise {
val tasks = (1..10).map {
async {
delay(1000)
it
}
}
val results = tasks.awaitAll()
return@promise Response(body = "Result: ${results.sum()} - Coroutines")
}
data class Response(
val statusCode: Int = 200,
val body: String = "{}",
val isBase64Encoded: Boolean = false,
val headers: Json = json(),
val multiValueHeaders: Json = json()
)

Jsonを返すようにする

目標

今回は下記のようなJsonを返すようにしてみます

{
"id": 0, // ID
"text": "", // 文字列
"random_numbers": [5, 38, 24] // 適当な数値
}

依存関係を追加する

kotlinx.serialization は外部ライブラリですので依存関係を追加する必要があります
また, 専用のプラグインも必要であるためプラグインの追加も行います

build.gradle.kts / pluginsブロック

plugins {
kotlin("js") version "1.3.50"
kotlin("plugin.serialization") version "1.3.50"
}

依存関係の追加

build.gradle.kts / dependenciesブロック

sourceSets["main"].dependencies {
implementation(kotlin("stdlib-js"))
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-js:1.3.2")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-runtime-js:0.13.0") // 追加
}

リポジトリに jcenter を追加する必要があるので追加します

build.gradle.kts / repositories

repositories {
mavenCentral()
jcenter() // 追加
}

クライアント側に返すクラスを作成する

作成するクラスが今回のJsonに変換するクラスとなるので各種アノテーションも付与します
今回は Responseクラス の下に作成します(本当は別ファイルにするほうがいいです)

handler.kt

@Serializable
data class ResponseBody(
@SerialName("id")
val id: Int = 0,
@SerialName("text")
val text: String = "",
@SerialName("random_numbers")
val randomNumbers: List<Int> = emptyList()
)
  • @Serializable これがついているクラスがシリアライズ/デシリアライズすることができます
  • @SerialName(String) 引数で決められた文字列がkeyとなるようになります, ない場合は変数名が利用されるようになるのでなくても問題ないです

Jsonを返す準備をする

まずは前回までに書いていたコードの一部を削除しスッキリさせます

handler.kt

@JsExport
@JsName("handler")
fun handler(event: Json, context: Json) = GlobalScope.promise {
return@promise Response(body = "")
}

続いて, 先ほど作ったクラスを文字列に変換するために kotlinxのJsonオブジェクト を作成します

handler.kt

@JsExport
@JsName("handler")
fun handler(event: Json, context: Json) = GlobalScope.promise {
val json = kotlinx.serialization.json.Json(JsonConfiguration.Stable) // 追加
return@promise Response(body = "")
}

⚠注意1
今回の記事ではすでにkotlinが実装している Jsonクラス をインポートしているため同じ名前であるkotlinxの Jsonクラス をインポートできません
kotlinのJsonkotlinxのJson とを間違わないよう注意してください

⚠注意2
JsonConfiguration には Stable 以外の設定もありますが, 実験的機能となっていますので利用はお勧めしません

Jsonを返す

まずは先ほど作成した ResponseBodyクラス のオブジェクトを作成します

handler.kt

@JsExport
@JsName("handler")
fun handler(event: Json, context: Json) = GlobalScope.promise {
val json = kotlinx.serialization.json.Json(JsonConfiguration.Stable)
val responseBody = ResponseBody(1, "Lambda Json!!!", List(3) { Random.nextInt(100) }) // 追加
return@promise Response(body = "")
}

最後に stringify関数 を用いてクラスを文字列に変換し レスポンスのbody に設定します

handler.kt

@JsExport
@JsName("handler")
fun handler(event: Json, context: Json) = GlobalScope.promise {
val json = kotlinx.serialization.json.Json(JsonConfiguration.Stable)
val responseBody = ResponseBody(1, "Lambda Json!!!", List(3) { Random.nextInt(100) })
return@promise Response(body = json.stringify(ResponseBody.serializer(), responseBody)) // 変更
}
  • ResponseBody.serializer() この関数は @Serializable アノテーションがついているクラスの場合コンパイラが自動的に作成してくれます

動作確認

関数をデプロイし, API GatewayのURLにアクセスし {"id":1,"text":"Lambda Json!!!","random_numbers":[ランダムな数字が3つ]} 表示されれば成功です

最後に

いかがでしたでしょうか
LambdaでJsonを返すのはありがちです
これを利用することによりKotlinで作成するマルチプラットフォームなアプリケーションではかなり強力になりうると考えます
また, ここまで4つの記事に分け Kotlin/JS をもちいて AWS Lambda関数 を作って便利にしてみました
これがベストプラクティスかどうかはプロジェクトによると思いますが、1つの参考になるとうれしい限りです
ここまで読んでいただきありがとうございました


蛇足

JSON.stringify() を使ってJsonを返してみる

JavaScriptにもとから存在する, JSON.stringify を用いてもJsonを返すことができます

handler.js

@JsExport
@JsName("handler")
fun handler(event: Json, context: Json) = GlobalScope.promise {
val responseBody = ResponseBody(1, "Lambda Json!!!", listOf(0, 1, 2))
return@promise Response(body = JSON.stringify(responseBody))
}

実行結果

{"id":1,"text":"Lambda Json!!!","randomNumbers":[0,1,2]}

しかしこれではJsonのKeyは指定できないので変数をすべて private に変更すると結果が変わるので注意が必要です

privateに変更したときの実行結果

{"id_0":1,"text_0":"Lambda Json!!!","randomNumbers_0":[0,1,2]}

こういうケースもあるため kotlinx.serialization を用いることをお勧めします

json() と JSON.stringify() を使ってJsonを返してみる

一番オーソドックスでわかりやすい返し方だと私は思います

handler.js

@JsExport
@JsName("handler")
fun handler(event: Json, context: Json) = GlobalScope.promise {
val responseBody = json(
"id" to 0,
"text" to "Lambda Json!!!",
"random_numbers" to listOf(0, 1, 2)
)
return@promise Response(body = JSON.stringify(responseBody))
}

実行結果

{"id":1,"text":"Lambda Json!!!","random_numbers":[0,1,2]}

蛇足まで読んでいただきありがとうございました

ブラウザ上でJavaScriptを利用したクリップボードへのコピー機能の実装

エンジニアの中氏です。

ブラウザ上でクリップボードへのコピー機能の実装は便利なJSライブラリのzclipを従来利用していましたが、
Flash の機能を利用している為、将来的に Flash が完全廃止になった際に利用出来なくなることが予想されます。

よって、今回はピュアなJavaScriptのみでコピー機能を実装する方法をご紹介します。
具体的には document.execCommand を利用してコピーを行う方法です。
テキストの範囲選択して、document.execCommand('copy') を実行してコピーを行います。

ここでは、画面に表示されているテキストをコピーするという例で下記に関数の例を記載します。

/**
 * 指定した要素のテキストノードのテキストをコピー
 *
 * @param {string|HTMLElement} subject
 */
function copyByTextNode(subject) {
let textNode = $(subject).get(0).childNodes[0];
let range = document.createRange();
range.selectNode(textNode);
// 選択範囲解除
getSelection().removeAllRanges();
// 範囲選択 
getSelection().addRange(range);
// コピー実行
document.execCommand('copy');
// 選択範囲解除
getSelection().removeAllRanges();
}

copyByTextNode([セレクタ])
で使用します。

セレクタを指定するだけで、そのDOMのテキストノードをコピー出来ます。

Kotlin/JSのAWS Lambda関数でPromiseを使うようにする

f:id:seeds-std:20191010160615p:plain
こんにちは, Web事業部の西村です
私の前回の記事 では dyanmic の利用をなくすようにしました
そして今回は, Callbackの呼び出しをやめて, JavaScriptの非同期処理( Promise )になるよう変更したいと思います
また, Promiseスタンダード なものと Coroutines のものの2種類がありますので, この記事ではその両方を紹介したいと思います

目次

過去の記事

なぜ非同期に?

非同期処理することにより複数の処理を同時に実行することができます
例えば, 複数のファイルを受け取り, S3に保存する といったことを考えてみます
非同期処理ではない場合, 1ずつしかファイルのアップロードができません
非同期処理の場合, 複数のファイルを同時にアップロードできるようになるので時間の短縮が図れます

注意事項

この記事では Promise の詳しい解説は行いません
詳しく知りたい方は下記の記事を参考にしてください
qiita.com

開発環境

この記事では下記の環境で開発を行っています

  • AdoptOpenJDK 1.8.0_222-b10
  • IntelliJ IDEA Community 2019.2.2
  • Kotlin 1.3.50

プロジェクト

前回の記事 まで作成したプロジェクトを利用します
プロジェクトの構成は下記のようになっています

KotlinLambda
├─.gradle/
├─.idea/
├─build/
├─gradle/
├─src/
│ └─main/
│   └─kotlin/
│     └─jp.co.seeds_std.lambda.kotlin/
│       └─handler.kt
├─build.gradle.kts
├─compress.zip
├─gradlew
├─gradlew.bat
└─settings.gradle.kts

また, handler.ktは下記のようになっています

package jp.co.seeds_std.lambda.kotlin
import kotlin.js.Json
import kotlin.js.Promise
import kotlin.js.json
@JsExport
@JsName("handler")
fun handler(event: Json, context: Json, callback: (Error?, Response?) -> Unit) {
val response = Response(body = "Hello from Kotlin Class Lambda!")
callback(null, response)
}
data class Response(
val statusCode: Int = 200,
val body: String = "{}",
val isBase64Encoded: Boolean = false,
val headers: Json = json(),
val multiValueHeaders: Json = json()
)

スタンダードなPromise

まずはkotlin-stdlibに実装されている Promise を利用してみます

関数の書き換え

handler 関数を変更します

handler.kt

@JsExport
@JsName("handler")
fun handler(event: Json, context: Json, callback: (Error?, Response?) -> Unit) {
val response = Response(body = "Hello from Kotlin Class Lambda!")
callback(null, response)
}

@JsExport
@JsName("handler")
fun handler(event: Json, context: Json) = Promise<Response> { executor, reject ->
executor(Response(body = "Hello from Kotlin Async Lambda!"))
}
  • executor は処理成功時に実行する関数で, 引数はジェネリクスで指定したクラス (この記事では Response ) となります
  • reject は処理失敗時に実行する関数で, 引数は Throwable クラスのオブジェクトとなります

動作確認

関数をデプロイし, API GatewayのURLにアクセスし Hello from Kotlin Async Lambda! と表示されれば成功です

もう少し恩恵を受けてみる(スタンダード)

今度はNode.jsの setTimeout を利用して本当に非同期に処理されているかを確認したいと思います
その前に, Kotlin/JSにはNode.jsの setTimeout は定義されていないので追加します
今回追加する場所はコードの最下部に追加します

handler.kt

external fun setTimeout(callback: dynamic, delay: Int, vararg args: Any?): Int
external fun clearTimeout(timeout: Int)
  • external : 外部の関数/変数を利用するために記述します。トランスパイルした場合にそのまま残るようになります
  • callback の部分が dynamic となっていますが, 関数の引数が固定値ではないためやむを得ず dynamic としています

続いて handler 関数を変更します

handler.kt

@JsExport
@JsName("handler")
fun handler(event: Json, context: Json) = Promise<Response> { executor, reject ->
val results = (1..10).map {
Promise<Int> { childExecutor, _ ->
setTimeout({childExecutor(it)}, 1000)
}
}
Promise.all(results.toTypedArray()).then {
executor(Response(body = "Result: ${it.sum()} - Standard"))
}
}

このコードでは 1秒待ってから数字を返す というコードを10回繰り返すものとなっています

動作確認

関数をデプロイし, API GatewayのURLにアクセスし約1秒後に Result: 55 - Standard と表示されれば成功です
※初回の実行では2-3秒ほどかかる場合もあります

通常の処理であれば 1秒待って数字を返す を10回行えば10秒かかるはずですが, 非同期に実行されているため約1秒で完了します

CoroutinesのPromise

標準実装の Promise では見にくく感じませんでしたか?(私は少なからず見にくいなと感じました)
非同期処理を書くのであれば, Node.jsみたいに async で書きたいところです
そこで使うのが kotlinx.coroutines です

依存関係を追加する

kotlinx.coroutines は外部ライブラリなので build.gradle.kts に依存関係を追加する必要があります

build.gradle.kts

implementation(kotlin("stdlib-js"))
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-js:1.3.2") // 追加

コードを書き換える

まずはただの文字列を返すように handelr 関数を変更します

handler.kt

@JsExport
@JsName("handler")
fun handler(event: Json, context: Json) = GlobalScope.promise {
return@promise Response(body = "Hello from Kotlin Coroutines Lambda!")
}

先ほどのコードと比較するとかなり単調なものになりました
※エラーを返す際は throw NullPointerException() のように throw するだけとなります

動作確認

関数をデプロイし, API GatewayのURLにアクセスし Hello from Kotlin Coroutines Lambda! と表示されれば成功です

もう少し恩恵を受けてみる(Coroutines)

先ほどと同じ関数を実装してみます

handler.kt

@JsExport
@JsName("handler")
fun handler(event: Json, context: Json) = GlobalScope.promise {
val tasks = (1..10).map {
async {
delay(1000)
it
}
}
val results = tasks.awaitAll()
return@promise Response(body = "Result: ${results.sum()} - Coroutines")
}

先ほどとは異なり, CoroutineScope 内で実行できるため, async 関数を利用できます

動作確認

関数をデプロイし, API GatewayのURLにアクセスし約1秒後に Result: 55 - Coroutines と表示されれば成功です
※初回の実行では2-3秒ほどかかる場合もあります

最後に

いかがでしたでしょうか
非同期処理が行えるとよりできることの幅が増えるかと思います
また、Coroutinesの Promise と標準実装の Promise は互換性があるため外部ライブラリが非同期処理を行う場合にも使え,
よりKotlinらしい書き方ができるようになっていくと思います
次回は kotlinx.serialization を用いてJsonのレスポンスを行う記事を書きたいと思います
ここまで読んでいただきありございました

Kotlin/JSのAWS Lambda関数でJsonを返してみる

Kotlin/JSのAWS Lambda関数を便利にしてみる

f:id:seeds-std:20191010160615p:plain

こんにちは, Web事業部の西村です
私の前回の記事 ではKotlin/JSを用いてLambda関数を書いてみました
しかし, dynamic を利用していたため使いにくい部分もあったと思います
今回はその点を改良しより使いやすくなるよう変更したいと思います

目次

過去の記事

開発環境

この記事では下記の環境で開発を行っています

  • AdoptOpenJDK 1.8.0_222-b10
  • IntelliJ IDEA Community 2019.2.2
  • Kotlin 1.3.50

プロジェクト

前回の記事 で作成したプロジェクトを利用します
プロジェクトの構成は下記のようになっています

KotlinLambda
├─.gradle/
├─.idea/
├─build/
├─gradle/
├─src/
│ └─main/
│   └─kotlin/
│     └─jp.co.seeds_std.lambda.kotlin/
│       └─handler.kt
├─build.gradle.kts
├─compress.zip
├─gradlew
├─gradlew.bat
└─settings.gradle.kts

Jsonを使って便利にしてみる

dynamicをなくす

Kotlin/JSには dynamic に似た Json があります
dynamic はJavaScriptのデータであったり, オブジェクトを格納することができますが, Json はKey-Value形式でのみ格納できるものとなっています
handler関数の引数, eventcontext はどちらもJavaScriptのJsonオブジェクトですのでKotlinの Json に変換できます
また, Callbackの第二引数もJsonオブジェクトを設定するためこちらも変更できます

handler.kt

fun handler(event: dynamic, context: dynamic, callback: (Error?, dynamic) -> Unit)

↑ が ↓ に変更できます

fun handler(event: Json, context: Json, callback: (Error?, Json?) -> Unit)

このJsonオブジェクトへのアクセスはMapのように利用してアクセスすることができます

event["body"] // こちらか
event.get("body") // こちらでデータの取得ができます

Callbackに渡すのJsonを関数で作れるようにする

API Gatewayでは下記のJsonを処理するようになっています *1

{
"isBase64Encoded": true|false,
"statusCode": httpStatusCode,
"headers": { "headerName": "headerValue", ... },
"multiValueHeaders": { "headerName": ["headerValue", "headerValue2", ...], ... },
"body": "..."
}

こちらをもとに引数が5つの関数を作成したいと思います
記述する場所はhandler関数の下になります

handler.kt

fun responseJson(statusCode: Int = 200, body: String = "{}", isBase64Encoded: Boolean = false, headers: Json = json(), multiValueHeaders: Json = json()) = json(
"statusCode" to statusCode,
"body" to body,
"isBase64Encoded" to isBase64Encoded,
"headers" to headers,
"multiValueHeaders" to multiValueHeaders
)
  • json() Jsonを作成する関数です/引数は vararg Pair<String, Any?> となっており json() と書くと空のJsonを作ることができます

Callbackに渡すよう変更する

現在handler関数は下記のようになっていると思います

handler.kt

@JsExport
@JsName("handler")
fun handler(event: Json, context: Json, callback: (Error?, Json?) -> Unit) {
val response: dynamic = object {}
response["statusCode"] = 200
response.body = "Hello from Kotlin Lambda!"
callback(null, response)
}

この関数を先ほど作成した関数を用いた形に変更します

@JsExport
@JsName("handler")
fun handler(event: Json, context: Json, callback: (Error?, Json?) -> Unit) {
val response = responseJson(body = "Hello from Kotlin Json Lambda!")
callback(null, response)
}

関数をデプロイする

前回と同様の手順を用いて関数をデプロイします
画面右側の Gradle タブを開き, kotlin_lambda -> Tasks -> other の順に開き, その中の compress をダブルクリックして実行します
ビルドに成功するとプロジェクトのディレクトリに compress.zip というファイルが更新されます
この生成されたファイルをコンソールからアップロードし, 保存します

動作確認

あらかじめ作成しておいたAPI GatewayのURLにアクセスし Hello from Kotlin Json Lambda! と表示されれば成功です

レスポンスをクラスに変更する

レスポンスはクラスを用いても影響が少ないためクラスに置き換えます
リクエストにクラスを利用しない理由については蛇足をご覧ください

まずは responseJson関数 を変更します

handler.kt

fun responseJson(statusCode: Int = 200, body: String = "{}", isBase64Encoded: Boolean = false, headers: Json = json(), multiValueHeaders: Json = json()) = json(
"statusCode" to statusCode,
"body" to body,
"isBase64Encoded" to isBase64Encoded,
"headers" to headers,
"multiValueHeaders" to multiValueHeaders
)

data class Response(
val statusCode: Int = 200,
val body: String = "{}",
val isBase64Encoded: Boolean = false,
val headers: Json = json(),
val multiValueHeaders: Json = json()
)

続いて, Callbackの引数を変更します

fun handler(event: dynamic, context: dynamic, callback: (Error?, Json?) -> Unit)

fun handler(event: Json, context: Json, callback: (Error?, Response?) -> Unit)

最後にレスポンスを変更します

@JsExport
@JsName("handler")
fun handler(event: Json, context: Json, callback: (Error?, Json?) -> Unit) {
val response = responseJson(body = "Hello from Kotlin Json Lambda!")
callback(null, response)
}

@JsExport
@JsName("handler")
fun handler(event: Json, context: Json, callback: (Error?, Response?) -> Unit) {
val response = Response(body = "Hello from Kotlin Class Lambda!")
callback(null, response)
}

この変更ができたらデプロイし動作を確認します
Hello from Kotlin Class Lambda! と表示されれば成功です

最後に

いかがでしたでしょうか
Kotlinを用いていると dynamic では少し取り扱いにくく感じてしまいます
しかし Json にしてしまうことで,Mapのように利用でき, より使いやすいのではないかと思います
次回は非同期処理にする記事を書きたいと思います
ここまで読んでいただきありございました

Kotlin/JSのAWS Lambda関数でPromiseを使うようにする


蛇足

リクエストのほうもクラス使ったらだめなの?

注意事項はありますが、クラスを利用しても問題はないです

handler.kt

package jp.co.seeds_std.lambda.kotlin
import kotlin.js.Json
import kotlin.js.json
@JsExport
@JsName("handler")
fun handler(event: Event, context: Json, callback: (Error?, Response?) -> Unit) {
println("body: ${event.body} isBase64Encoded: ${event.isBase64Encoded}")
// event.copy() // Error!
val response = Response(body = "Hello from Kotlin Json Lambda!")
callback(null, response)
}
data class Event(val body: String?, val isBase64Encoded: Boolean)
data class Response(
val statusCode: Int = 200,
val body: String = "{}",
val isBase64Encoded: Boolean = false,
val headers: Json = json(),
val multiValueHeaders: Json = json()
)

この時ログに bodyisBase64Encoded の出力が行われます
その後 copy() を実行しようとした場合, 変数 eventEventクラスではなく Json となっていますので, 関数が見つからず実行時にエラーが出てしまいます
また, 注意点として private な変数はトランスパイルした際に変数名が変更されてしまうためうまく利用できません
そのため, リクエストの eventcontext はJsonで, レスポンスはKotlinのクラスを利用していくことがいいのかなと私は思います
蛇足も読んでいただきありございました

AWS Lambda関数をKotlin/JSで書いてみる

f:id:seeds-std:20190925123639p:plain

WEB事業部の西村です。

唐突ですが皆さんはLambda関数を作成する際にどのような言語を用いていますでしょうか?
用途に合った言語や自分で書きやすいと思う言語などの要因で決めていると思われます
私はKotlinという言語が好きでKotlinで書こうと思ったのですが、Kotlinで書かれている記事はそのほとんどがJavaで実行することを前提とした元なっています
しかし、Javaではコールドスタートした際に実行時間がかかってしまうというデメリットがあります
KotlinはJavaだけでなくJavaScriptでも動作させることができるので今回はNode.jsで実行させる関数の作成を行いたいと思います

目次

この記事の目標

Kotlin/JSを用いたLambda関数の作成とそのレスポンス確認

この記事で取り扱わないこと

この記事では AWS LambdaAmazon API Gateway については詳しく解説いたしません
あらかじめLambda関数とその関数を呼び出すためのAPIの作成を行っておいてください

開発環境

この記事では下記の環境で開発を行っています

  • AdoptOpenJDK 1.8.0_222-b10
  • IntelliJ IDEA Community 2019.2.2
  • Kotlin 1.3.50

プロジェクトの作成

プロジェクトの作成を行います
任意のディレクトリにプロジェクト用のフォルダを作成します(この記事では KotlinLambda としています)
その後そのディレクトリ内に build.gradle.ktssettings.gradle.kts の2つのファイルを作成します

KotlinLambda
├─build.gradle.kts
└─settings.gradle.kts

作成したらIntelliJ IDEAでプロジェクトのフォルダを開きます
続いて、 build.gradle.ktssettings.gradle.kts を開き下記の内容を入力します

build.gradle.kts

plugins {
kotlin("js") version "1.3.50"
}
repositories {
mavenCentral()
}
kotlin {
target {
nodejs()
useCommonJs()
}
sourceSets["main"].dependencies {
implementation(kotlin("stdlib-js"))
}
}

settings.gradle.kts

rootProject.name = "kotlin_lambda"

その後画面右下に出ている Gradle projects need to be importedImport Changes をクリックします
CONFIGURE SUCCESSFUL と出てきたら準備完了です

ハンドラソースコードの追加

次にソースディレクトリを作成します
プロジェクトのディレクトリに src/main/kotlin でディレクトリを作成します
またパッケージ名を追加する場合、パッケージを作成します(この記事では jp.co.seeds_std.lambda.kotlin とします)
作成したディレクトリに handler.kt というファイルを作成し下記の内容を記述します

handler.kt

package jp.co.seeds_std.lambda.kotlin
@JsExport
@JsName("handler")
fun handler(event: dynamic, context: dynamic, callback: (Error?, dynamic) -> Unit) {
val response: dynamic = object {}
response["statusCode"] = 200
response.body = "Hello from Kotlin Lambda!"
callback(null, response)
}
  • @JsExport JavaScriptのexportを行うアノテーションです
  • @JsName(name: String) 記述したクラスや関数をトランスパイルした際 name に指定した名前となるよう変換してくれるようになります
  • dynamic JavaScriptのオブジェクトをそのまま取り扱います(. [] でその中の関数や変数にアクセスできます)

デプロイ

デプロイパッケージの作成を行います
圧縮を自動化するためのタスクを追加します
build.gradle.kts を再度開き下記の内容を追加します

build.gradle.kts

import com.google.gson.JsonParser
import java.io.OutputStream
import java.util.zip.ZipOutputStream
import java.util.zip.ZipEntry
plugins { ... }
repositories { ... }
kotlin { ... }
open class CompressTask : DefaultTask() {
lateinit var buildDirectory: File
lateinit var projectName: String
var rootProjectName: String? = null
private val blackLists = setOf("kotlin-test-nodejs-runner", "kotlin-test")
@TaskAction
fun compress() {
val projectPrefix = if(rootProjectName != null && rootProjectName != projectName) "$rootProjectName-" else ""
val zipFile = File(buildDirectory, "../compress.zip")
val outputStream = ZipOutputStream(zipFile.outputStream(), Charsets.UTF_8).apply {
setLevel(9)
}
val jsDir = File(buildDirectory, "js")
val projectDir = File(jsDir, "packages/$projectPrefix$projectName")
val nodeModuleDir = File(jsDir, "node_modules")
addDependencies(outputStream, File(projectDir, "package.json"), nodeModuleDir, mutableSetOf(*blackLists.toTypedArray()))
addZipEntry(outputStream, File(projectDir, "kotlin/$projectPrefix$projectName.js"))
outputStream.close()
}
private fun addZipEntry(zipOutputStream: ZipOutputStream, file: File, addDirectory: String = "") {
val name = if(addDirectory.isEmpty()) file.name else "$addDirectory/${file.name}"
if(file.isDirectory) {
file.listFiles()?.forEach {
addZipEntry(zipOutputStream, it, name)
}
} else {
val zipEntry = ZipEntry(name)
zipOutputStream.addZipEntry(zipEntry) {
it.writeFile(file)
}
}
}
private fun addDependencies(zipOutputStream: ZipOutputStream, packageJsonFile: File, nodeModuleDir: File, addedDependencies: MutableSet<String> = mutableSetOf()) {
val packageJson = JsonParser().parse(packageJsonFile.reader()).asJsonObject
if(!packageJson.has("dependencies")) return
val dependencies = packageJson.getAsJsonObject("dependencies")
dependencies.keySet().forEach {
if(!addedDependencies.contains(it)) {
addedDependencies.add(it)
val dependenciesDir = File(nodeModuleDir, it)
addZipEntry(zipOutputStream, dependenciesDir, "node_modules")
addDependencies(zipOutputStream, File(dependenciesDir, "package.json"), nodeModuleDir, addedDependencies)
}
}
}
private inline fun ZipOutputStream.addZipEntry(entry: ZipEntry, crossinline block: (OutputStream) -> Unit) {
try {
putNextEntry(entry)
block(this)
} catch (e: Exception) {
e.printStackTrace()
} finally {
closeEntry()
}
}
@Suppress("NOTHING_TO_INLINE")
private inline fun OutputStream.writeFile(file: File) {
file.inputStream().use {
it.copyTo(this)
}
}
}
tasks.register("noMainCall") {
doFirst {
kotlin.target.compilations.all { compileKotlinTask.kotlinOptions.main = "noCall" }
}
}
tasks.register<CompressTask>("compress") {
dependsOn("compileKotlinJs", "noMainCall")
this.buildDirectory = rootProject.buildDir
this.projectName = project.name
this.rootProjectName = rootProject.name
}
tasks["compileKotlinJs"].mustRunAfter("noMainCall")

追加後、画面右下に出ている Gradle projects need to be importedImport Changes をクリックします
CONFIGURE SUCCESSFUL と出てきたら画面右側の Gradle タブを開きます
kotlin_lambda -> Tasks -> other の順に開き、その中の compress をダブルクリックして実行します
するとプロジェクトのディレクトリに compress.zip というファイルが生成されます
この生成されたファイルをコンソールからアップロードします
その後の ハンドラkotlin_lambda.{パッケージ名}.handler に変更します (この記事では kotlin_lambda.jp.co.seeds_std.lambda.kotlin.handler となります)
アップロードと変更が完了したら保存を行います

動作確認

あらかじめ作成しておいたAPI GatewayのURLにアクセスし Hello from Kotlin Lambda! と表示されれば成功です

最後に

いかがでしたでしょうか
Javaの時より動作も軽くなったと感じるのではないかと思います
次回はもう少し使いやすくなるような記事も書きたいと思います
ここまで読んでいただきありございました

Kotlin/JSのAWS Lambda関数を便利にしてみる

AWS IoT Enterprise Buttonを使ってSlackに通知を送ってみた

はじめまして!18年度新卒・WEBエンジニアの石田です。

弊社のコーヒーメーカーがリニューアルされ、コーヒーを淹れる機会がすごく多くなりました。
しかし、コーヒーメーカーができてから30分で保温が切れる設定で、
・追加で保温ができない…
・どうせならできたてが飲みたいけど、できたタイミングが分かりにくい…
・かといっていちいちSlackにコーヒーできましたって書くのもめんどくさい!
→自動投稿させたい!
→→ボタンワンクリックで済ませたい!

ということで、会社にAmazon「AWS IoT Enterprise Button」を買ってもらい、折角なのでクリエイターズブログに投稿することにしました!

f:id:seeds-std:20181105211732j:plain
新コーヒーメーカーと「AWS IoT Enterorise Button」

新コーヒーメーカーと「AWS IoT Enterorise Button」

AWS IoT Enterorise Button

ワンプッシュで商品が注文できる!と話題になった Amazon Dash Buttonをベースにした、自由にプログラムを動作させることのできるボタンです。
ボタンのクリックをトリガーに、AWSのサービスに接続できます。すごい。

f:id:seeds-std:20181105212349j:plain
ちっちゃいです

モバイルアプリを使ってWiFiの設定ができ、ステータスも確認可能。

f:id:seeds-std:20181105213458p:plainf:id:seeds-std:20181105221850p:plain
iOSアプリ「AWS IoT 1-Click」

何度か設定してみたのですが、デバイスリージョンはオレゴン一択なのでしょうか。東京リージョンに変更できませんでした。

そこからAWS IoT 1-Clickというサービスを選択し、プロジェクトを作成することでデバイスの動作を定義することができます。

選択できるのは
・SMSの送信
・Eメールの送信
・Lambda関数の選択
Slackに自動投稿したかったのでLambda一択でしたが、SMS・Eメール送信も色々使えそうで良いですね。

あとはLambdaでjavascriptを使ってwebhookにPOSTすれば投稿完了!

exports.handler = function(event, context) {
const slack = (params) => {
let https = require('https');
let host = 'hooks.slack.com';
let data = JSON.stringify({"text": params['message']});
let options = {
hostname: host,
port: 443,
path: params['path'],
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(data)
}
};
let req = https.request(options, (res) => {
console.log('status code : ' + res.statusCode);
res.setEncoding('utf8');
res.on('data', (d) => {
console.log(d);
});
});
req.on('error', (e) => {
console.error(e)
;});
req.write(data);
req.end();
};
var post = function() {
return slack({
path: 'hogehogehogehogehoge',
message: 'コーヒーができました:coffee:'
});
};
setTimeout(post, (60 * 6 * 1000));
};

f:id:seeds-std:20181105221032p:plain
アイコンはデザイナー・コウノ氏が作ってくれました!かわいい

6分で完成するので、その分だけタイムアウトさせるようにしています。
起動時間は5分までだと思ってたからどうしようかな…と思っていたのですが、15分までいけるようになっていたのが驚きでした。

IoT ButtonやLambdaを触ったのははじめてだったので時間がかかりましたが、セットアップからSlackに投稿できるようになるまで1時間ほど。すごく簡単でした。
他にもいろいろな使い方ができそうですね。シーズでは早速、更に2台のIoT Buttonを追加購入しました(笑)
社内でアイデアを募ってどういう使い方をしようかと思案中です!

読み込みのタイミング

こんにちは、永井です。

最近はなかなか多忙な日々を送っており、業務で手一杯になってしまうので
何か+αできるように頑張っていきたいと思います。

さて今回は、業務中に困ったことがあったので、それについて書きたいと思います。

追従メニューが最後まで動きません

javascriptで画面スクロールに追従するメニューを設置したのですが、
意図しない中途半端な位置で追従が止まるという挙動になり困りました。

原因

意図しない誤動作の原因とは、ウィンドウの高さの取得などを行い、
追従のプログラムを組んだのですが、その際に値が正しく取得できていなかったためでした。
なぜ正しく取得できなかったかというと、ページ内のコンテンツに画像が複数設定されており、
その画像の高さが定義されておらず、その影響でウィンドウの高さが正しく取得できなかったようです。

解決策

結果から述べますと、”読み込みのタイミング”を変えることで解決できました。
最初はjQueryのお約束の

$(function(){
ここにプログラムを書き書き...
});

としていたところを

$(window).load(function(){
こっちにプログラムを書き書き...
});

と読み込みのタイミングを変えることで、キチンと最後まで追従するようになりました。

$(function(){})と$(window).load(function(){})の違い

正直なところよくわかっておらず、どちらもそこまで違いがないものと思い込んでおりました。
そこでGoogle先生にご相談したところ、ざっくり述べると、

$(function(){})は、HTMLの構築が完了した時点で処理を実行する。
$(window).load(function(){})は、HTMLの構築だけでなく、画像やFlashなどのデータの読み込みが完了してから処理を実行する。

だそうで、$(window).load(function(){})内ににプログラムを書くことで、コンテンツ内の画像データも含めて
ウィンドウの高さなどを取得することができるようになり、解決につながりました。

注意点

ただし、$(window).load(function(){})には注意すべき点があります。
それは、”画像やFlashなどのデータの読み込みが完了してから”なので、使用箇所、使用機能によっては
ユーザーは反応が遅く感じてしまうことがあると思われるので、
その辺りは、実際にテストを行い考慮していく必要がありそうです。

まとめ

普段は気にせず$(function(){})内に書き込んでいましたが、プログラムによっては
読み込みのタイミングを考慮する必要があるとわかりました。
また、できたから良いのではなく、機能の反応のユーザー体験(ユーザーエクスペリエンス)を
考えるということも大事なのだと思いました。

3.14

HTML5のcanvasを使って絵を書いてみました。

モンテカルロ

&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
&lt;meta charset="UTF-8"&gt;
&lt;title&gt;Canvasでお絵かき&lt;/title&gt;
&lt;script type="text/javascript"&gt;
function fig() {
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
context.lineWidth = 2;
// 正方形
context.beginPath();
context.rect(20,20,500,500);
context.stroke();
// 円
context.beginPath();
context.arc(270,270,250,0,2*Math.PI,true);
context.stroke();
}
function dots() {
var canvas    = document.getElementById('canvas');
var context   = canvas.getContext('2d');
for (i = 0; i &lt; 1000; i++) {
var x = Math.random();
var y = Math.random();
if (Math.pow(2 * x - 1, 2) + Math.pow(2 * y - 1, 2) &lt;= 1) {
context.fillStyle = "rgb(255, 0, 0)";
} else {
context.fillStyle = "rgb(0, 0, 255)";
}
context.beginPath();
context.arc(20 + x * 500, 20 + y * 500, 2, 0, 2*Math.PI, true);
context.fill();
}
}
&lt;/script&gt;
&lt;/head&gt;
&lt;body onLoad="fig()"&gt;
&lt;input type="button" value="点を打つ" onclick="dots();"&gt;&lt;br&gt;
&lt;canvas width="540" height="540" id="canvas"&gt;
&lt;/canvas&gt;
&lt;/body&gt;
&lt;/html&gt;

「点を打つ」ボタンを押すと、dotsメソッドが動いて正方形の中にランダムに点が1000個打たれます。
円の中と外で色分けもしてみました。

ところで、、

3月14日と言えばみなさん何の日かご存知ですよね。
はいそうです。みなさん正解です。円周率の日です。

永遠に続くとかで結婚とかには縁起の良い日みたいですよ!

canvasで書いた上の絵から円周率が求まります(´▽`)

円周率3.14・・・

上の絵で、円の半径をrとして正方形の面積と円の面積の比を計算すると、
比 = 円の面積 / 正方形の面積
  = (π x r x r) / (2r x 2r)
  = π / 4

この比は何を意味するかというと、正方形の中にランダムに点を打って円の中に入る確率に相当します。
だいたい78%くらいですかね。

そうです。赤い点の数を数えて1000で割って4倍すると円周率が求まります。
数えるのは面倒臭いので、プログラムにやらせましょう。
上で書いたJavascriptのdotsメソッドで円の中に入った時に点の数を数えるようにして、最後に計算します。

function dots() {
var canvas    = document.getElementById('canvas');
var context   = canvas.getContext('2d');
for (i = 0; i &lt; 1000; i++) {
var x = Math.random();
var y = Math.random();
→       var in_circle = 0;
if (Math.pow(2 * x - 1, 2) + Math.pow(2 * y - 1, 2) &lt;= 1) {
context.fillStyle = "rgb(255, 0, 0)";
→           incircle += 1;
} else {
context.fillStyle = "rgb(0, 0, 255)";
}
context.beginPath();
context.arc(20 + x * 500, 20 + y * 500, 2, 0, 2*Math.PI, true);
context.fill();
}var pi = 4 * in_circle / 1000;
}

こんな感じです。メチャ簡単!

実行してみましょう!
pi = 3.192
pi = 3.184
pi = 3.116
pi = 3.100
pi = 3.132

精度悪い∑(゚□゚;)

サンプル数を増やしましょう。
だいたい2ケタ増やすと精度が1ケタ良くなるそうです。

javascriptのオブジェクトについて

プログラマのkinuです。普段よく使いますがあまり理解せずに使ってたjavascriptのオブジェクトについて調べました。
javascriptオブジェクト指向プログラムをサポートした言語です。
PHPなどのクラスを実装し、オブジェクトを生成して動作させるクラスベースのオブジェクト指向ではなく、プロトタイプベースのオブジェクト指向です。プロトタイプというのがどういうものを指してるのかまだ理解できてないですが、とりあえずオブジェクトからオブジェクトを生成して動作させるということはなんとなくわかりました。

生成のしくみ

javascript のオブジェクト生成の単純な例です。

[code]
var A = function (x) {
this.x = x;
};
A.prototype.x = 0;
var a = new A(1);
[/code]

[コード1]

http://d.hatena.ne.jp/maeharin/20130215/javascript_prototype_chain
の記事を参考にインスペクタでのぞきながら自分なりに図にまとめました。

figure1

[図1]

四角はオブジェクト、矢印はプロパティです。点線の矢印は構造と直接関係ない補助的なものです。
この図から実際使うオブジェクトのほかにオブジェクトを生成するオブジェクト(コンストラクタ)とベースになるオブジェクト(プロトタイプ)があることがなんとなくわかります。
このままだとわかりづらいので図にまとめると、

figuire2

[図2]

つまり、コンストラクタをnewするとprototypeプロパティにもったオブジェクトをもとにオブジェクトを生成してくれるということです。あとついでにコンストラクタの処理を生成したオブジェクトで実行してくれます。

__proto__ってなに?

プロパティprotoは内部プロパティprototypeの値でプロトタイプをもってます。
[図1]でいうと、aA.prototypeを、A.prototypeObject.prototypeをもっています。
プロパティ(またはメソッド)を使うときにこのprototypeをたどって探索します。これをプロトタイプチェーンといいます。

オブジェクトがオブジェクトを生成する

あらためて[図1]をみると[図2]の形がけっこうみつけられます。
aをみてみるとAA.prototypeをもとにつくられています。AFunctionFunction.prototypeをもとにつくられ、A.prototypeObjectObject.prototypeをもとにつくられてます。
この仕組みでオブジェクトが生成されていることがわかりますね。

Object.create()

コンストラクタをnewしてオブジェクトを生成する方法のほかにもオブジェクトを生成する方法もあります。それはObject.create()です。
以下のようにして使います。

[code]
var a = Object.create(Object.prototype);
a.x = 1;
[/code]

[コード2]

これで[コード1]とほぼ同じオブジェクトが生成されます。
「ほぼ」といっているのはコンストラクタから生成したときに自動に設定されるプロパティconstructorがこれでは設定されません。これはinstanceofで使われているプロパティなので使い方によっては注意が必要です。
実は[コード2]はObject.create()がなくても
[code]
var a = {};
a.x = 1;
[/code]

[コード3]

と書いても同じことができます。あれ、Object.create()いらないんじゃ…と思っちゃいそうですが、既存のオブジェクトからオブジェクトをつくるときに役立ちます。
例えば,

[code]
var a = {};
a.x = 1;
var b = Object.create(a);
b.y = 1;
[/code]

[コード4]

のようにすると既存のオブジェクトを簡単に拡張できます。

まとめ

今回はjavascriptのオブジェクトの生成について調べました。
クラスを作らなくてもオブジェクトが作れるのはプロトタイプベースの利点なのかなあなんてふんわり理解できたような気がします。このあたりは実際使うとときがきて実感するんじゃなかろうか。
またクラスベースっぽいふるまいもできるのでそれぞれの特性を生かして使いわけて実装できるという点はすごくいいなと思いました。その分いろいろと知っておく必要はありますが。
あと、今回プロトタイプチェーンという概念がでてきましたがスコープチェーンというのもあるらしいです。まだまだ知らないことも多いですがこれからも暇をみつけて勉強していきたいです。

参考にしたWebページ

© SEEDS Co.,Ltd.