前置き
JavaでWebアプリケーションを作成しています。
システムからメール配信を行うため、SendGridを利用しているのですが、なんらかの原因でメール配信が失敗した場合に、想定していた後処理がコケていたので備忘録として記載します。
すべては前任者の「エラー時にもResponseで結果を返してくるだろう」という思い込みから発生した実装でした。
SendGridを使用してメール配信処理を作成
build.gradleにSendGrid Javaライブラリをプロジェクトにインストールしメール送信メソッドを作成。
SendGridが400番台のエラーを返してくる場合はこちらから送ったリクエストパラメータの不備やリクエスト超過のためリトライは行わず、ログを残してフロント側にエラー情報を返して処理を終了します。
500番台の場合はSendGridサーバにエラーが発生している状態のため、2〜3度リトライを行うように作成していました。
リトライ周りとか省略して(すみません)コードを記載します。
dependencies {
compile ('com.sendgrid:sendgrid-java:4.0.1')
}
import com.sendgrid.*;
import java.io.IOException;
public class Example {
public static void main(String[] args) throws IOException {
SendGrid sg = new SendGrid(System.getenv("SENDGRID_API_KEY"));
try {
Request request = new Request();
request.setMethod(Method.GET);
request.setEndpoint("api_keys");
Response response = sg.api(request);
if (response.getStatusCode() < 300) {
// 200番台は正常送信として処理を続ける
} else if (response.getStatusCode() < 500){
// 400番台エラーはリクエストパラメータが不正と判断して処理終了
// ログを残し、エラーメッセージをフロントへ返却
} else {
// 500番台エラーはSendGridのサーバエラーのため何度かリトライ
// ログは残しておく
}
} catch (IOException ex) {
throw ex;
}
}
}
想定された処理が行われない問題
上記のように返ってきたresponse内のステータスコードを判断してその後の処理を振り分けるように作成していました~~(前任者が)~~。
メール作成時のバリデーションチェック処理に不備があり、メールが送れなかった際に後処理が行われず、想定されたログも残っておらずでこの実装に不備があることが判明します。
実装を確認する
com.sendgrid.SendGridクラスのapi()メソッドからたどって実際にどこでSendGridにリクエスト送信を行っているかを確認します。(3〜4ステップでたどり着けるので中略)
private Response executeApiCall(HttpRequestBase httpPost) throws IOException {
try {
CloseableHttpResponse serverResponse = httpClient.execute(httpPost);
try {
Response response = getResponse(serverResponse);
if(response.getStatusCode() >= 300) {
//throwing IOException here to not break API behavior.
throw new IOException("Request returned status Code "+response.getStatusCode()+"Body:"+response.getBody());
}
return response;
} finally {
serverResponse.close();
}
} catch(ClientProtocolException e) {
throw new IOException(e.getMessage());
}
}
com.sendgrid.ClientクラスにexecuteApiCall()メソッドがあり、この3行目でexecute()していること、そしてなにより6行目のif文で、IOExceptionがthrowされていることがわかります。
ステータスコードが300より大きいときも、responseにその値を詰めて返してくれていいのよ。。。
SebdGridの返却値とその後
Caused by: java.io.IOException: Request returned status Code 400Body:
{
"errors": [
{
"message": "Invalid replyTo email address",
"field": "reply_to",
"help": null
}
]
}
SendGridは上記の様にエラーメッセージを返してくれるので、responseではなくcatchしたIOExceptionから後続処理を書くべきでした。
なお、ここ1〜2年SendGridでは大規模障害が発生していなさそうなことと、メールの使用率を鑑みてリトライ処理は消してさくっとエラーメッセージを表示するようにしました(バリデーションチェックは直したので400番台エラーは発生しなくなるはず!)。
雑感
思い込みと諸々のテストの不備が重なったかなしい事件でしたが、使用ライブラリの返却値はしっかり確認して使おうねと学んだ出来事でした。
※あくまでJavaライブラリの実装なので、他言語のライブラリではエラー時にもresponseに詰めて返してくれるのかもしれません。