- 記事一覧 >
- ブログ記事
LocalStack Kotless(Kotlin) SendGridのオフライン動作環境構築
はじめに
LocalStack、Kotlin のサーバーレスフレームワーク Kotless、メール配信サービスの SendGrid のモック環境を構築しました。
メール配信サービスの SendGrid は、本来クラウドのサービス(SMTP or Web API)ですが、モックを使ってオフラインで動作確認する方法を見つけて使ってみました。
見つけた SendGrid のモックは、以下です。
参考記事:SendGrid 用の Mail モックコンテナを作りました(https://engineer.blog.lancers.jp/docker/sendgrid-maildev/)
ソースコード:https://github.com/yKanazawa/sendgrid-dev
上記参考記事にもありますが、SMTP サーバーも MailDev を利用して、モックで構築しました。
結果、メール送信プログラムのデプロイ先(AWS のモック)、メール配信 API(SendGrid のモック)、SMTP サーバー、全てモックでメール送信プログラムの動作確認ができるようになりました。
全てモックのため、オフラインで動作確認可能です。
↑
AWS - SendGrid - SMTP サーバー - メールボックス の疑似環境全てをオフラインに閉じ込めてテストできます。
これにより、メール送信プログラムの実装をミスっても問題無くなります。
ネットワーク設定などにより、事故が起きる可能性はあります。
本記事情報の誤り、見落とし、考慮不足等により何らかの問題が生じても、一切責任を負いません。
デプロイ先、向き先を
LocalStack → 本物のAWS
sendgrid-dev → 本物のSendGrid
と置き換えれば、動作するはずですが、本物に置き換えての動作確認はしていません。
【検証環境】
VMware Workstation Pro 16
Ubuntu 20.04.2 LTS
Docker 20.10.14
openjdk 17.0.3
Gradle 7.3.3
Kotlin 1.5.31
LocalStack 0.11.2
Ubuntu 20.04.2 LTS
node 14.19.3
npm 6.14.17
maildev 2.0.5
sendgrid-dev 0.9.1
go 1.18.2
MailDev インストール
Docker を使わず、ビルドしていきましたので、ビルドの手順になります。
node,npm インストール
apt でインストールした nodejs v10.19.0
の場合、TextEncoder is not defined
となり、maildev の起動に失敗したため、v14.x
をインストールしました。
# maildev
/usr/local/lib/node_modules/maildev/node_modules/whatwg-url/lib/encoding.js:2
const utf8Encoder = new TextEncoder();
^
ReferenceError: TextEncoder is not defined
# curl -sL https://deb.nodesource.com/setup_14.x -o nodesource_setup.sh
# bash nodesource_setup.sh
# apt update
# apt -y install nodejs
# node -v
v14.19.3
# npm -v
6.14.17
maildev インストール
maildev は、コマンドとして使いたいので、グローバルに npm install
します。
# npm install -g maildev
# maildev -V
2.0.5
特に指定が無い場合、メールデータは、
/tmp/maildev-3621
のように/tmp/
配下になります。maildev-
の後の数字部分は毎回ランダムに決まるようです。
SMTP は 1025、Webは 1080 ポートで待ち受けます。それぞれ、
-s
-w
オプションで変更可能です。
MailDev 動作確認
SMTP サーバー、Web 画面の動作確認を行います。
MailDev を起動します。
# mkdir /home/admin/mail
# maildev --mail-directory /home/admin/mail
なんとなく、メールデータを消えないところに置きたかったので、
--mail-directory
で指定して起動しています。
動作確認用に telnet、nkf をインストールします。
# apt install -y telnet
# apt install -y nkf
nkf は、日本語のメールを送信したいため、インストールしました。
コピペ用に MIME エンコードの値を知りたいためにインストールしただけで、必須ではありません。
以下のように base64 の値をあらかじめ調べておきます。
メール件名:ISO-2022-JP の base64 エンコードヘッダ形式
メール本文:ISO-2022-JP の base64 エンコード
です。
# echo 日本語件名 | nkf -jM
=?ISO-2022-JP?B?GyRCRnxLXDhsN29MPhsoQg==?=
# echo 日本語本文 | nkf -jMB
GyRCRnxLXDhsS1xKOBsoQgo=
調べた値を使って、以下のように
件名:日本語件名
本文:日本語本文
のメールを telnet で送信します。
# telnet localhost 1025
HELO maildev.example.com
MAIL FROM: <from@maildev.example.com>
RCPT TO: <rcpt@maildev.example.com>
data
From: "sender" <sender@maildev.example.com>
To: "rcpt" <rcpt@maildev.example.com>
Subject: =?ISO-2022-JP?B?GyRCRnxLXDhsN29MPhsoQg==?=
MIME-Version: 1.0
Content-Type: text/plain; charset="iso-2022-jp"
Content-Transfer-Encoding: base64
GyRCRnxLXDhsS1xKOBsoQgo=
.
quit
Web 画面(http://localhost:1080/
)へアクセスしてみます。
日本語が文字化けして表示されました。
iconv 組み込みによって、これを解消します。
iconv 組み込み
日本語文字化け解消のため、maildev に iconv を追加します。
# apt -y install build-essential
# cd /usr/lib/node_modules/maildev
# npm install iconv
# apt -y install build-essential
は、gyp ERR! stack Error: not found: make
とエラーになったため、make
を事前にインストールしています。
maildev 再動作確認
CTRL+C で止めて再度起動します。
# maildev --mail-directory /home/admin/mail
Web 画面(http://localhost:1080/
)へアクセスしてみます。
文字化けが解消しました!
sendgrid-dev インストール
SendGrid Web API のモック sendgrid-dev をインストールします。
go インストール
sendgrid-dev は、Go 言語(golang)製のため、go をダウンロードして、インストールします。
# wget https://go.dev/dl/go1.18.2.linux-amd64.tar.gz
# tar zxf go1.18.2.linux-amd64.tar.gz -C /usr/local/
# vi ~/.profile
以下を追記します。
export PATH=$PATH:/usr/local/go/bin
# source ~/.profile
# go version
go version go1.18.2 linux/amd64
直接置いて、パスを通すだけです。最新バージョンは、go公式ページ(https://go.dev/dl/)で確認できます。
sendgrid-dev ビルド
https://github.com/yKanazawa/sendgrid-dev
からソースコードの zip をダウンロードして、go run
でビルド&起動します。
# unzip sendgrid-dev-master.zip
# cd sendgrid-dev-master
# go run main.go
SENDGRID_DEV_API_SERVER :3030
SENDGRID_DEV_API_KEY SG.xxxxx
SENDGRID_DEV_SMTP_SERVER 127.0.0.1:1025
SENDGRID_DEV_SMTP_USERNAME
SENDGRID_DEV_SMTP_PASSWORD
____ __
/ __/___/ / ___
/ _// __/ _ \/ _ \
/___/\__/_//_/\___/ v3.3.10-dev
High performance, minimalist Go web framework
https://echo.labstack.com
____________________________________O/_______
O\
? http server started on [::]:3030
ポート::3030
API キー:SG.xxxxx
SMTP サーバー:127.0.0.1:1025
で起動します。
SMTP サーバーは、MailDev がちょうど、localhost:1025
ポートですので、何も変更する必要は有りませんでした。(デフォルトで、MailDev との連携が想定されているからです。)
変更する場合、環境変数をセットしてから起動します。
sendgrid-dev 動作確認
(maildev が起動しているものとします。)
# apt -y install curl
# curl --request POST \
--url http://localhost:3030/v3/mail/send \
--header 'Authorization: Bearer SG.xxxxx' \
--header 'Content-Type: application/json' \
--data '{"personalizations": [{
"to": [{"email": "to@example.com"}]}],
"from": {"email": "from@example.com"},
"subject": "Test Subject",
"content": [{"type": "text/plain", "value": "Test Content"}]
}'
Web 画面(http://localhost:1080/
)へアクセスしてみます。
メールを受信しています!
Kotless デプロイ
デプロイ先:LocalStack(AWS Lambda のローカル環境版)
デプロイするもの:Kotlin のサーバーレスフレームワーク Kotless プログラム
を準備します。
LocalStack & Kotless の詳しいことは、当ブログ別記事「Kotless と LocalStack で疑似サーバーレス - AWS S3 ダウンロード」に書きましたので、大部分端折って、プログラム作成部分だけ書きます。 LocalStack の準備、JDK、IntelliJ インストールの部分や、設定の意味は、上記記事を見てください。
/home/share/sendgrid
に Kotlin のプロジェクトを新規作成します。
settings.gradle.kts
、gradle.properties
、build.gradle.kts
をそれぞれ以下のようにします。
rootProject.name = "sendgrid"
pluginManagement {
resolutionStrategy {
this.eachPlugin {
if (requested.id.id == "io.kotless") {
useModule("io.kotless:gradle:${this.requested.version}")
}
}
}
repositories {
maven(url = uri("https://packages.jetbrains.team/maven/p/ktls/maven"))
gradlePluginPortal()
mavenCentral()
}
}
kotlin.code.style=official
awsSdkKotlinVersion=0.+
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import io.kotless.plugin.gradle.dsl.kotless
plugins {
//kotlin("jvm") version "1.6.20"
kotlin("jvm") version "1.5.31" apply true
id("io.kotless") version "0.2.0" apply true
}
group = "org.example.sendgrid"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral()
maven(url = uri("https://packages.jetbrains.team/maven/p/ktls/maven"))
}
val awsSdkKotlinVersion: String by project
dependencies {
testImplementation(kotlin("test"))
implementation("io.kotless", "kotless-lang", "0.2.0")
implementation("io.kotless", "kotless-lang-aws", "0.2.0")
implementation("com.sendgrid", "sendgrid-java", "4.9.2")
}
tasks.test {
useJUnitPlatform()
}
tasks.withType<KotlinCompile> {
kotlinOptions.jvmTarget = "1.8"
}
kotless {
config {
aws {
storage {
bucket = "kotless.sendgrid.example.com"
}
profile = "example"
region = "eu-west-1"
}
}
extensions {
local {
port = 8080
//enables AWS emulation (disabled by default)
useAWSEmulation = true
}
}
}
メール送信プログラム src/main/kotlin/org/example/sendgrid/Main.kt
を作成します。
package org.example.sendgrid
import com.sendgrid.Method
import com.sendgrid.Request
import com.sendgrid.SendGrid
import com.sendgrid.helpers.mail.Mail
import com.sendgrid.helpers.mail.objects.Content
import com.sendgrid.helpers.mail.objects.Email
import io.kotless.dsl.lang.http.Get
import org.slf4j.LoggerFactory
import java.io.IOException
private val logger = LoggerFactory.getLogger("sendgrid")
@Get("/")
fun main() = "Hello world!"
@Get("/sendmail")
fun sendmail(mailTo: String = ""): String = sendBySendGrid(mailTo)
fun sendBySendGrid(mailTo: String): String {
logger.info("sendBySendGrid start")
val from = Email("test@example.com")
val subject = "Sending with SendGrid is Fun/SendGridで送信するのは楽しいです"
val to = Email(mailTo)
val content = Content("text/plain", "and easy to do anywhere, even with Kotlin\nKotlinを使用しても、どこでも簡単に実行できます")
val mail = Mail(from, subject, to, content)
val sg = SendGrid("SG.xxxxx", true)
sg.host = "192.168.12.206:3030"
val request = Request()
logger.info("sendBySendGrid request")
try {
request.method = Method.POST
request.endpoint = "mail/send"
request.body = mail.build()
val response = sg.api(request)
logger.info("sendBySendGrid response:${response.statusCode}")
return(response.statusCode.toString())
} catch (ex: IOException) {
logger.info("sendBySendGrid Error")
throw ex
}
}
プログラムは、
Kotlin から SendGrid を利用してメール送信する(https://sendgrid.kke.co.jp/blog/?p=8471)
を参考にしました。
というか、ほぼ、そのままですが、
今回オフラインで動作させたいため、以下の部分が重要になります。
●val sg = SendGrid("SG.xxxxx", true)
SendGrid の最初の引数は、API キーですが、2番目の引数は、今回のようなテスト環境の場合、true
にする必要があります。
これにより、https://
ではなく、http://
でアクセスするようになります。
sendgrid-dev は、http://
のため、true
にする必要があります。
●sg.host = "192.168.12.206:3030"
sendgrid-dev のホスト:ポートにしないと、本物の SendGrid へ行ってしまいます。
本物の SendGrid へ行ってしまったときは、401
が返ります。
LocalStack に Kotless プログラムをデプロイして、リクエストを出してみます。
# cd /home/share/sendgrid
# chmod 755 gradlew
# ./gradlew local
(別端末で)
$ curl http://localhost:8080
Hello world!
@Get("/")
fun main() = "Hello world!"
が動作して、
正常にデプロイされていることが確認できました!
メール送信動作確認
MailDev(SMTP サーバー)
sendgrid-dev(SendGrid モック API)
は起動しているものとします。
http://localhost:8080/sendmail
を起動します。
$ curl http://localhost:8080/sendmail?mailTo=hogehoge@example.com
202
202 が返れば正常です。
Web 画面(http://localhost:1080/
)へアクセスしてみます。
メールを受信しています!
ヨシ!!
エラーまとめ
エラー内容:
# maildev
/usr/local/lib/node_modules/maildev/node_modules/whatwg-url/lib/encoding.js:2
const utf8Encoder = new TextEncoder();
ReferenceError: TextEncoder is not defined
原因:
node のバージョンが低い。
対処内容:
Node v14 にする。
# curl -sL https://deb.nodesource.com/setup_14.x -o nodesource_setup.sh
# bash nodesource_setup.sh
# apt -y install nodejs
# node -v
v14.19.3
エラー内容:
# cd /usr/lib/node_modules/maildev
# npm install iconv
gyp ERR! build error
gyp ERR! stack Error: not found: make
gyp ERR! stack at getNotFoundError (/usr/lib/node_modules/npm/node_modules/which/which.js:13:12)
原因:make
がインストールされていない。
対処内容:
# apt -y install build-essential
エラー内容:
$ curl http://localhost:8080/sendmail?mailTo=hogehoge@example.com
May 29, 2022 7:33:05 PM org.apache.http.impl.execchain.RetryExec execute
INFO: I/O exception (java.net.NoRouteToHostException) caught when processing request to {s}->https://192.168.12.202:3030: No route to host
19:33:05.209 [qtp233996206-16] ERROR i.k.dsl.app.http.RoutesDispatcher - Failed on call of function sendmail
原因:
sendgrid-dev へ https://
でアクセスしている。
対処内容:http://
でアクセスするように修正。(二番目の引数にtrue
をセットすることにより、テストモードにする。)
val sg = SendGrid("SG.xxxxx", true)
sg.host = "192.168.12.206:3030"
その他、宣伝、誹謗中傷等、当方が不適切と判断した書き込みは、理由の如何を問わず、投稿者に断りなく削除します。
書き込み内容について、一切の責任を負いません。
このコメント機能は、予告無く廃止する可能性があります。ご了承ください。
コメントの削除をご依頼の場合はTwitterのDM等でご連絡ください。