これは、なにをしたくて書いたもの?
AWS SDK for Java 2.xを使うと、APIの各種レスポンスからSdkHttpResponse
というインターフェースのインスタンスを取得することが
できます。
こちらですね。
SdkHttpResponse (AWS SDK for Java - 2.20.17)
このクラスにはisSuccessful
という成功/失敗を判定できそうなメソッドがあります。
If we get back any 2xx status code, then we know we should treat the service call as successful.
APIの呼び出し結果を扱う時に、エラーハンドリングとしてこの値を見た方がいいのかどうなのかよくわからなかったので、ちょっと
調べてみることにしました。
Amazon S3を例に
Amazon S3を例に、ちょっと見てみましょう。
Amazon S3にアクセスする際には、S3Client
というインターフェース(またはS3AsyncClient
)を使います。
S3Client (AWS SDK for Java - 2.20.17)
バケットを作成するcreateBucket
を見てみると、問題があった時には以下のように例外がスローされるであろうことがわかります。
default CreateBucketResponse createBucket(CreateBucketRequest createBucketRequest) throws BucketAlreadyExistsException, BucketAlreadyOwnedByYouException, AwsServiceException, SdkClientException, S3Exception
一方で、その戻り値であるCreateBucketResponse
の定義を見ると、sdkHttpResponse
というメソッドがあり。
CreateBucketResponse (AWS SDK for Java - 2.20.17)
こちらから先述のSdkHttpResponse
を得ることができ、isSuccessful
というメソッドを備えています。
これを見ていると、スローされる例外とは別にSdkHttpResponse#isSuccessful
の結果も見ないといけないのでは?と思ってしまったのが
今回のエントリーを書いた理由ですね。
ドキュメントはどう言っているか?
ここで、AWS SDK for Java 2.xの例外処理のドキュメントを見てみましょう。
AWS SDK for Java 2.x の例外処理 - AWS SDK for Java 2.x
AwsServiceException
およびそのサブクラスに関する記述を見ると、API呼び出し時にエラーが発生した時はAwsServiceException
の
インスタンス(およびそのサブクラス含む)がスローされることが書かれています。
AWS SDK for Java 2.x の例外処理 / AwsServiceException (およびサブクラス)
たとえば、最初に書いたS3Client#createBucket
メソッドからスローされるとされているBucketAlreadyOwnedByYouException
クラスも、
AwsServiceException
のサブクラスですね。
BucketAlreadyOwnedByYouException (AWS SDK for Java - 2.20.17)
例外の詳細については、AwsErrorDetails
クラスを確認すればよい、となっています。
AwsErrorDetails (AWS SDK for Java - 2.20.17)
こちらのerrorCode
から、各サービスのドキュメントを確認することができます。たとえば、Amazon S3の場合はこちら。
Error Responses / List of Error Codes
AwsErrorDetails
のインスタンスは、AwsServiceException
から取得できます。
AwsServiceException (AWS SDK for Java - 2.20.17)
また、そもそもAPI呼び出しができないような状況の時にはSdkClientException
がスローされる、とも書かれています。
AWS SDK for Java 2.x の例外処理 / SdkClientException
こちらですね。
SdkClientException (AWS SDK for Java - 2.20.17)
どちらにせよ、SdkHttpResponse
インターフェースはまったく登場しません。
このことから、APIの呼び出しに失敗すると例外がスローされるので、API呼び出しの成功確認にSdkHttpResponse#isSuccessful
メソッドを
参照しなくても良さそうな気がしますね。
今回、こちらをもう少し追ってみましょう。LocalStackとAWS SDK for Java 2.xのS3Client
を使って試してみたいと思います。
環境
今回の環境は、こちら。
LocalStack。
$ python3 -V Python 3.10.6 $ localstack --version 1.4.0
起動。
$ localstack start
Javaまわり。
$ java --version openjdk 17.0.6 2023-01-17 OpenJDK Runtime Environment (build 17.0.6+10-Ubuntu-0ubuntu122.04) OpenJDK 64-Bit Server VM (build 17.0.6+10-Ubuntu-0ubuntu122.04, mixed mode, sharing) $ mvn --version Apache Maven 3.9.0 (9b58d2bad23a66be161c4664ef21ce219c2c8584) Maven home: $HOME/.sdkman/candidates/maven/current Java version: 17.0.6, vendor: Private Build, runtime: /usr/lib/jvm/java-17-openjdk-amd64 Default locale: ja_JP, platform encoding: UTF-8 OS name: "linux", version: "5.15.0-67-generic", arch: "amd64", family: "unix"
準備
Maven依存関係などは、こちら。
<properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>bom</artifactId> <version>2.20.17</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>s3</artifactId> </dependency> </dependencies>
サンプルプログラムを書く
今回用意したプログラムは、こちら。
src/main/java/org/littlewings/aws/App.java
package org.littlewings.aws; import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; import software.amazon.awssdk.awscore.defaultsmode.DefaultsMode; import software.amazon.awssdk.awscore.exception.AwsErrorDetails; import software.amazon.awssdk.awscore.exception.AwsServiceException; import software.amazon.awssdk.http.HttpStatusFamily; import software.amazon.awssdk.http.SdkHttpResponse; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.s3.S3Client; import software.amazon.awssdk.services.s3.model.CreateBucketRequest; import software.amazon.awssdk.services.s3.model.CreateBucketResponse; import java.net.URI; public class App { public static void main(String... args) { try (S3Client s3Client = S3Client .builder() .credentialsProvider( StaticCredentialsProvider.create(AwsBasicCredentials.create("access_key_id", "secret_access_key")) ) .region(Region.AP_EAST_1) .defaultsMode(DefaultsMode.AUTO) .endpointOverride(URI.create("http://localhost:4566")) .forcePathStyle(true) .build()) { CreateBucketRequest request = CreateBucketRequest .builder() .bucket("my-bucket") .build(); CreateBucketResponse response = s3Client.createBucket(request); SdkHttpResponse httpResponse = response.sdkHttpResponse(); System.out.printf("isSuccessful = %b%n", httpResponse.isSuccessful()); System.out.printf("statusCode = %d%n", httpResponse.statusCode()); System.out.printf("httpStatusFamily = %s%n", HttpStatusFamily.of(httpResponse.statusCode())); } catch (AwsServiceException e) { AwsErrorDetails errorDetails = e.awsErrorDetails(); System.out.printf("serviceName = %s%n", errorDetails.serviceName()); System.out.printf("errorMessage = %s%n", errorDetails.errorMessage()); System.out.printf("errorCode = %s%n", errorDetails.errorCode()); SdkHttpResponse httpResponse = errorDetails.sdkHttpResponse(); System.out.printf("isSuccessful = %b%n", httpResponse.isSuccessful()); System.out.printf("statusCode = %d%n", httpResponse.statusCode()); System.out.printf("httpStatusFamily = %s%n", HttpStatusFamily.of(httpResponse.statusCode())); e.printStackTrace(); } } }
CreateBucketRequest request =
CreateBucketRequest
.builder()
.bucket("my-bucket")
.build();
CreateBucketResponse response = s3Client.createBucket(request);
あとは、レスポンスに含まれるSdkHttpResponse
の内容を見たり、
SdkHttpResponse httpResponse = response.sdkHttpResponse(); System.out.printf("isSuccessful = %b%n", httpResponse.isSuccessful()); System.out.printf("statusCode = %d%n", httpResponse.statusCode()); System.out.printf("httpStatusFamily = %s%n", HttpStatusFamily.of(httpResponse.statusCode()));
例外ハンドリングを行ったり。
} catch (AwsServiceException e) { AwsErrorDetails errorDetails = e.awsErrorDetails(); System.out.printf("serviceName = %s%n", errorDetails.serviceName()); System.out.printf("errorMessage = %s%n", errorDetails.errorMessage()); System.out.printf("errorCode = %s%n", errorDetails.errorCode()); SdkHttpResponse httpResponse = errorDetails.sdkHttpResponse(); System.out.printf("isSuccessful = %b%n", httpResponse.isSuccessful()); System.out.printf("statusCode = %d%n", httpResponse.statusCode()); System.out.printf("httpStatusFamily = %s%n", HttpStatusFamily.of(httpResponse.statusCode())); e.printStackTrace(); }
スローされる例外はAwsServiceException
クラスのサブクラスなので、こちらで一気に捕捉することにします。
確認してみる
作成したプログラムを動かしてみましょう。
$ mvn compile exec:java -Dexec.mainClass=org.littlewings.aws.App
結果。
isSuccessful = true statusCode = 200 httpStatusFamily = SUCCESSFUL
もう1度実行すると、すでにS3バケットは作成されているので例外がスローされます。
$ mvn compile exec:java -Dexec.mainClass=org.littlewings.aws.App
こんな感じですね。
serviceName = S3 errorMessage = Your previous request to create the named bucket succeeded and you already own it. errorCode = BucketAlreadyOwnedByYou isSuccessful = false statusCode = 409 httpStatusFamily = CLIENT_ERROR software.amazon.awssdk.services.s3.model.BucketAlreadyOwnedByYouException: Your previous request to create the named bucket succeeded and you already own it. (Service: S3, Status Code: 409, Request ID: 44425877V1D0A2F9, Extended Request ID: MzRISOwyjmnup2476E87BD8C5CA707/JypPGXLh0OVFGcJaaO3KW/hRAqKOpIEEp) at software.amazon.awssdk.protocols.xml.internal.unmarshall.AwsXmlPredicatedResponseHandler.handleErrorResponse(AwsXmlPredicatedResponseHandler.java:156) at software.amazon.awssdk.protocols.xml.internal.unmarshall.AwsXmlPredicatedResponseHandler.handleResponse(AwsXmlPredicatedResponseHandler.java:108) at software.amazon.awssdk.protocols.xml.internal.unmarshall.AwsXmlPredicatedResponseHandler.handle(AwsXmlPredicatedResponseHandler.java:85) at software.amazon.awssdk.protocols.xml.internal.unmarshall.AwsXmlPredicatedResponseHandler.handle(AwsXmlPredicatedResponseHandler.java:43) at software.amazon.awssdk.awscore.client.handler.AwsSyncClientHandler$Crc32ValidationResponseHandler.handle(AwsSyncClientHandler.java:95) at software.amazon.awssdk.core.internal.handler.BaseClientHandler.lambda$successTransformationResponseHandler$7(BaseClientHandler.java:270) at software.amazon.awssdk.core.internal.http.pipeline.stages.HandleResponseStage.execute(HandleResponseStage.java:40) at software.amazon.awssdk.core.internal.http.pipeline.stages.HandleResponseStage.execute(HandleResponseStage.java:30) at software.amazon.awssdk.core.internal.http.pipeline.RequestPipelineBuilder$ComposingRequestPipelineStage.execute(RequestPipelineBuilder.java:206) at software.amazon.awssdk.core.internal.http.pipeline.stages.ApiCallAttemptTimeoutTrackingStage.execute(ApiCallAttemptTimeoutTrackingStage.java:73) at software.amazon.awssdk.core.internal.http.pipeline.stages.ApiCallAttemptTimeoutTrackingStage.execute(ApiCallAttemptTimeoutTrackingStage.java:42) at software.amazon.awssdk.core.internal.http.pipeline.stages.TimeoutExceptionHandlingStage.execute(TimeoutExceptionHandlingStage.java:78) at software.amazon.awssdk.core.internal.http.pipeline.stages.TimeoutExceptionHandlingStage.execute(TimeoutExceptionHandlingStage.java:40) at software.amazon.awssdk.core.internal.http.pipeline.stages.ApiCallAttemptMetricCollectionStage.execute(ApiCallAttemptMetricCollectionStage.java:50) at software.amazon.awssdk.core.internal.http.pipeline.stages.ApiCallAttemptMetricCollectionStage.execute(ApiCallAttemptMetricCollectionStage.java:36) at software.amazon.awssdk.core.internal.http.pipeline.stages.RetryableStage.execute(RetryableStage.java:81) at software.amazon.awssdk.core.internal.http.pipeline.stages.RetryableStage.execute(RetryableStage.java:36) at software.amazon.awssdk.core.internal.http.pipeline.RequestPipelineBuilder$ComposingRequestPipelineStage.execute(RequestPipelineBuilder.java:206) at software.amazon.awssdk.core.internal.http.StreamManagingStage.execute(StreamManagingStage.java:56) at software.amazon.awssdk.core.internal.http.StreamManagingStage.execute(StreamManagingStage.java:36) at software.amazon.awssdk.core.internal.http.pipeline.stages.ApiCallTimeoutTrackingStage.executeWithTimer(ApiCallTimeoutTrackingStage.java:80) at software.amazon.awssdk.core.internal.http.pipeline.stages.ApiCallTimeoutTrackingStage.execute(ApiCallTimeoutTrackingStage.java:60) at software.amazon.awssdk.core.internal.http.pipeline.stages.ApiCallTimeoutTrackingStage.execute(ApiCallTimeoutTrackingStage.java:42) at software.amazon.awssdk.core.internal.http.pipeline.stages.ApiCallMetricCollectionStage.execute(ApiCallMetricCollectionStage.java:48) at software.amazon.awssdk.core.internal.http.pipeline.stages.ApiCallMetricCollectionStage.execute(ApiCallMetricCollectionStage.java:31) at software.amazon.awssdk.core.internal.http.pipeline.RequestPipelineBuilder$ComposingRequestPipelineStage.execute(RequestPipelineBuilder.java:206) at software.amazon.awssdk.core.internal.http.pipeline.RequestPipelineBuilder$ComposingRequestPipelineStage.execute(RequestPipelineBuilder.java:206) at software.amazon.awssdk.core.internal.http.pipeline.stages.ExecutionFailureExceptionReportingStage.execute(ExecutionFailureExceptionReportingStage.java:37) at software.amazon.awssdk.core.internal.http.pipeline.stages.ExecutionFailureExceptionReportingStage.execute(ExecutionFailureExceptionReportingStage.java:26) at software.amazon.awssdk.core.internal.http.AmazonSyncHttpClient$RequestExecutionBuilderImpl.execute(AmazonSyncHttpClient.java:193) at software.amazon.awssdk.core.internal.handler.BaseSyncClientHandler.invoke(BaseSyncClientHandler.java:103) at software.amazon.awssdk.core.internal.handler.BaseSyncClientHandler.doExecute(BaseSyncClientHandler.java:171) at software.amazon.awssdk.core.internal.handler.BaseSyncClientHandler.lambda$execute$1(BaseSyncClientHandler.java:82) at software.amazon.awssdk.core.internal.handler.BaseSyncClientHandler.measureApiCallSuccess(BaseSyncClientHandler.java:179) at software.amazon.awssdk.core.internal.handler.BaseSyncClientHandler.execute(BaseSyncClientHandler.java:76) at software.amazon.awssdk.core.client.handler.SdkSyncClientHandler.execute(SdkSyncClientHandler.java:45) at software.amazon.awssdk.awscore.client.handler.AwsSyncClientHandler.execute(AwsSyncClientHandler.java:56) at software.amazon.awssdk.services.s3.DefaultS3Client.createBucket(DefaultS3Client.java:1149) at org.littlewings.aws.App.main(App.java:36) at org.codehaus.mojo.exec.ExecJavaMojo$1.run(ExecJavaMojo.java:279) at java.base/java.lang.Thread.run(Thread.java:833)
今回はBucketAlreadyOwnedByYouException
がスローされました。
software.amazon.awssdk.services.s3.model.BucketAlreadyOwnedByYouException: Your previous request to create the named bucket succeeded and you already own it. (Service: S3, Status Code: 409, Request ID: 44425877V1D0A2F9, Extended Request ID: MzRISOwyjmnup2476E87BD8C5CA707/JypPGXLh0OVFGcJaaO3KW/hRAqKOpIEEp)
例外に含まれているAwsErrorDetails
から得られた情報はこちら。
serviceName = S3 errorMessage = Your previous request to create the named bucket succeeded and you already own it. errorCode = BucketAlreadyOwnedByYou
AwsErrorDetails
から取得できる、SdkHttpResponse
の情報はこちら。
isSuccessful = false statusCode = 409 httpStatusFamily = CLIENT_ERROR
このケースではSdkHttpResponse#isSuccessful
がfalse
になっています。そもそも、HTTPステータスコードが2xxの場合にtrue
になる
プロパティですからね。
If we get back any 2xx status code, then we know we should treat the service call as successful.
というわけで、SdkHttpResponse#isSuccessful
がfalse
になるケースではやはり例外がスローされる気がします。
実装は?
少し、実装を見てみましょう。S3Client
インターフェースの実装クラスを少し見てみます。
$ unzip -p $HOME/.m2/repository/software/amazon/awssdk/s3/2.20.17/s3-2.20.17-sources.jar software/amazon/awssdk/services/s3/DefaultS3Client.java
こんな感じで、エラーコードと例外がマッピングされている箇所がありました。
private AwsS3ProtocolFactory init() { return AwsS3ProtocolFactory .builder() .registerModeledException( ExceptionMetadata.builder().errorCode("NoSuchUpload") .exceptionBuilderSupplier(NoSuchUploadException::builder).build()) .registerModeledException( ExceptionMetadata.builder().errorCode("ObjectAlreadyInActiveTierError") .exceptionBuilderSupplier(ObjectAlreadyInActiveTierErrorException::builder).build()) .registerModeledException( ExceptionMetadata.builder().errorCode("BucketAlreadyExists") .exceptionBuilderSupplier(BucketAlreadyExistsException::builder).build()) .registerModeledException( ExceptionMetadata.builder().errorCode("NoSuchBucket") .exceptionBuilderSupplier(NoSuchBucketException::builder).build()) .registerModeledException( ExceptionMetadata.builder().errorCode("InvalidObjectState") .exceptionBuilderSupplier(InvalidObjectStateException::builder).build()) .registerModeledException( ExceptionMetadata.builder().errorCode("ObjectNotInActiveTierError") .exceptionBuilderSupplier(ObjectNotInActiveTierErrorException::builder).build()) .registerModeledException( ExceptionMetadata.builder().errorCode("BucketAlreadyOwnedByYou") .exceptionBuilderSupplier(BucketAlreadyOwnedByYouException::builder).build()) .registerModeledException( ExceptionMetadata.builder().errorCode("NoSuchKey").exceptionBuilderSupplier(NoSuchKeyException::builder) .build()).clientConfiguration(clientConfiguration) .defaultServiceExceptionSupplier(S3Exception::builder).build(); }
先ほどのサンプルアプリケーションでS3バケットの作成失敗時に、AwsErrorDetails
から得られたエラーコードはBucketAlreadyOwnedByYou
なので、こちらが該当しますね。
.registerModeledException(
ExceptionMetadata.builder().errorCode("BucketAlreadyOwnedByYou")
.exceptionBuilderSupplier(BucketAlreadyOwnedByYouException::builder).build())
マッピングされるエラーコードがない場合は、S3Exception
がスローされるように見えます。
.defaultServiceExceptionSupplier(S3Exception::builder).build();
こう見ると、S3Client#createBucket
のthrows
に書かれている内容がわかってきますね。
default CreateBucketResponse createBucket(CreateBucketRequest createBucketRequest) throws BucketAlreadyExistsException, BucketAlreadyOwnedByYouException, AwsServiceException, SdkClientException, S3Exception
エラーコードがマッピングできた場合はBucketAlreadyExistsException
、BucketAlreadyOwnedByYouException
、それ以外は
S3Exception
、AwsServiceException
、そもそもAPI呼び出しができなかった場合はSdkClientException
という感じですね。
S3AsyncClient
の実装も見てみましたが、同じ感じでした。
$ unzip -p $HOME/.m2/repository/software/amazon/awssdk/s3/2.20.17/s3-2.20.17-sources.jar software/amazon/awssdk/services/s3/DefaultS3AsyncClient.java
こちらですね。
private AwsS3ProtocolFactory init() { return AwsS3ProtocolFactory .builder() .registerModeledException( ExceptionMetadata.builder().errorCode("NoSuchUpload") .exceptionBuilderSupplier(NoSuchUploadException::builder).build()) .registerModeledException( ExceptionMetadata.builder().errorCode("ObjectAlreadyInActiveTierError") .exceptionBuilderSupplier(ObjectAlreadyInActiveTierErrorException::builder).build()) .registerModeledException( ExceptionMetadata.builder().errorCode("BucketAlreadyExists") .exceptionBuilderSupplier(BucketAlreadyExistsException::builder).build()) .registerModeledException( ExceptionMetadata.builder().errorCode("NoSuchBucket") .exceptionBuilderSupplier(NoSuchBucketException::builder).build()) .registerModeledException( ExceptionMetadata.builder().errorCode("InvalidObjectState") .exceptionBuilderSupplier(InvalidObjectStateException::builder).build()) .registerModeledException( ExceptionMetadata.builder().errorCode("ObjectNotInActiveTierError") .exceptionBuilderSupplier(ObjectNotInActiveTierErrorException::builder).build()) .registerModeledException( ExceptionMetadata.builder().errorCode("BucketAlreadyOwnedByYou") .exceptionBuilderSupplier(BucketAlreadyOwnedByYouException::builder).build()) .registerModeledException( ExceptionMetadata.builder().errorCode("NoSuchKey").exceptionBuilderSupplier(NoSuchKeyException::builder) .build()).clientConfiguration(clientConfiguration) .defaultServiceExceptionSupplier(S3Exception::builder).build(); }
ところで、AWS SDK for Java 2.xのGitHubリポジトリを見ても、これらのクラスは存在しません。
GitHub - aws/aws-sdk-java-v2: The official AWS SDK for Java - Version 2
リクエスト、レスポンスまわりのクラスの雰囲気からもなんとなく察しがつきますが、これらのクラスは自動生成な感じがしますね。
@Generated("software.amazon.awssdk:codegen") @SdkInternalApi final class DefaultS3Client implements S3Client { ... @Generated("software.amazon.awssdk:codegen") public final class CreateBucketResponse extends S3Response implements ... @Generated("software.amazon.awssdk:codegen") public final class BucketAlreadyOwnedByYouException extends S3Exception implements ToCopyableBuilder<BucketAlreadyOwnedByYouException.Builder, BucketAlreadyOwnedByYouException> {
ここでは、このあたりは追いませんが。
気にするのは、先ほどのスタックトレースで出現していたこのあたりかなと。
at software.amazon.awssdk.protocols.xml.internal.unmarshall.AwsXmlPredicatedResponseHandler.handleErrorResponse(AwsXmlPredicatedResponseHandler.java:156) at software.amazon.awssdk.protocols.xml.internal.unmarshall.AwsXmlPredicatedResponseHandler.handleResponse(AwsXmlPredicatedResponseHandler.java:108) at software.amazon.awssdk.protocols.xml.internal.unmarshall.AwsXmlPredicatedResponseHandler.handle(AwsXmlPredicatedResponseHandler.java:85) at software.amazon.awssdk.protocols.xml.internal.unmarshall.AwsXmlPredicatedResponseHandler.handle(AwsXmlPredicatedResponseHandler.java:43) at software.amazon.awssdk.awscore.client.handler.AwsSyncClientHandler$Crc32ValidationResponseHandler.handle(AwsSyncClientHandler.java:95) at software.amazon.awssdk.core.internal.handler.BaseClientHandler.lambda$successTransformationResponseHandler$7(BaseClientHandler.java:270
BaseClientHandler
でレスポンスのハンドリングを行っている箇所は、こちら。API呼び出しが成功した場合は、Response
内に
SdkHttpResponse
などが含まれることになります。
Response<OutputT> delegateResponse = responseHandler.handle(response, executionAttributes);
そして、APIの呼び出し結果が成功/失敗による分岐。
if (parsedResponse.isResponseSuccess()) { OutputT response = handleSuccessResponse(parsedResponse); return Response.<OutputT>builder().httpResponse(httpResponse) .response(response) .isSuccess(true) .build(); } else { return Response.<OutputT>builder().httpResponse(httpResponse) .exception(handleErrorResponse(parsedResponse)) .isSuccess(false) .build(); }
handleErrorResponse
メソッドでは、実際にスローされる例外が作成されます。SdkException
の部分ですね。
SdkException exception = errorResponseTransformer.apply(parsedResponse);
exception.fillInStackTrace();
return exception;
エラーコードから、例外に変換しているのは以下の箇所のようです。一致するものがなかった場合は、デフォルトの例外が選択される
ようです。
AwsServiceException.Builder builder = errorRoot
.map(e -> invokeSafely(() -> unmarshallFromErrorCode(response, e, errorCode)))
.orElseGet(this::defaultException);
そして、この例外がスローされることになる、というわけですね。
あとは、先ほどの以下の分岐でtrue
/false
になる情報を見ると良さそうです。
if (parsedResponse.isResponseSuccess()) { OutputT response = handleSuccessResponse(parsedResponse); return Response.<OutputT>builder().httpResponse(httpResponse) .response(response) .isSuccess(true) .build(); } else { return Response.<OutputT>builder().httpResponse(httpResponse) .exception(handleErrorResponse(parsedResponse)) .isSuccess(false) .build(); }
これは、以下の部分ですね。
if (!context.sdkHttpFullResponse().isSuccessful()) { // Request was non-2xx, defer to protocol handler for error root Optional<XmlElement> parsedErrorXml = parsedRootXml.flatMap(errorRootLocationFunction); return context.toBuilder().isResponseSuccess(false).parsedErrorXml(parsedErrorXml.orElse(null)).build(); }
ここでSdkHttpFullReponse
というのは、SdkHttpResponse
インターフェースを拡張したものです。
SdkHttpFullResponse (AWS SDK for Java - 2.20.17)
ということは、SdkHttpFullReponse#isSuccessful
がfalse
の時に例外がスローされることになるので、正常にAPI呼び出しが完了した場合は
SdkHttpResponse#isSuccessful
はtrue
になり、false
になるケースでは例外がスローされることになります。
なので、AWS SDK for Java 2.xの例外処理ではSdkHttpResponse#isSuccessful
の話は出てこない、となりそうです。
AWS SDK for Java 2.x の例外処理 - AWS SDK for Java 2.x
実行パスを全部見たかというとそういうわけでもないのですが、考え方としてはそんなに誤っていないと思うのですが…どうでしょう?
なお、API呼び出しが正常にできた場合にレスポンスのオブジェクトを見ないでいいかというと、レスポンスから得たい情報はAPI次第かと
思います(呼び出し結果の値に関心があるかどうか)。
まとめ
AWS SDK for Java 2.xのSdkHttpResponse#isSuccessful
が気になったので、ちょっと確認してみました。
例外がスローされるかどうかに加えて、SdkHttpResponse#isSuccessful
を見る必要があるかどうかで迷っていたのですが、これはどうやら
スルーして良さそうです。
レスポンスのオブジェクトからは、API呼び出しが正常に終了した場合に欲しい情報を取得するのに使う感じですね。