๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

ํ”„๋กœ์ ํŠธ/Airbnb Clone

[ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ…] RestTemplate ๋กœ๊น… ์ค‘ 404 ์‘๋‹ต์— ๋Œ€ํ•œ FileNotFoundException ํ•ด๊ฒฐ

๊ฒฐ์ œ ์Šน์ธ POST ์š”์ฒญ ์‹œ RestTemplate ๋กœ๊ทธ์—์„œ ๋ฐœ์ƒํ•œ ์˜ค๋ฅ˜

๊ฒฐ์ œ ๊ธฐ๋Šฅ์„ ๊ฐœ๋ฐœํ•˜๋ฉด์„œ, ๊ฒฐ์ œ ์Šน์ธ ์š”์ฒญ์„ ๋ณด๋‚ด๋Š” RestTemplate์— ํƒ€์ž„์•„์›ƒ์„ ์ ์šฉํ•˜๊ณ , ์š”์ฒญ๊ณผ ์‘๋‹ต์— ๋Œ€ํ•œ ๋กœ๊น…์„ ์ถ”๊ฐ€ํ•˜๊ณ ์ž ํ–ˆ์Šต๋‹ˆ๋‹ค.

 

Baeldung์˜ "RestTemplage Logging" ๊ณผ ๋ง๋‚˜๋‹ˆ ๊ฐœ๋ฐœ์ž๋‹˜์˜ ๋กœ๊น… ์„ ์ฐธ๊ณ ํ•ด์„œ RestTemplate ์— ๋กœ๊ทธ๋ฅผ ์ถ”๊ฐ€ ํ–ˆ์ง€๋งŒ ์‘๋‹ต์„ ๋ฐ›์„ ๋•Œ, FileNotFoundException ์ด ๋ฐœ์ƒํ•˜๋ฉฐ ์•„๋ž˜์™€ ๊ฐ™์€ ์Šคํƒ ํŠธ๋ ˆ์ด์Šค๋ฅผ ํ™•์ธํ–ˆ์Šต๋‹ˆ๋‹ค.

java.io.FileNotFoundException: https://api.tosspayments.com/v1/payments/confirm
java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1993)
java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1589)
java.base/sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:224)
org.springframework.http.client.SimpleClientHttpResponse.getBody(SimpleClientHttpResponse.java:89)
org.springframework.http.client.BufferingClientHttpResponseWrapper.getBody(BufferingClientHttpResponseWrapper.java:66)
airdnb.be.config.RestTemplateConfig$LoggingInterceptor.printResponse(RestTemplateConfig.java:65)
airdnb.be.config.RestTemplateConfig$LoggingInterceptor.intercept(RestTemplateConfig.java:51)

 

์Šคํƒ ํŠธ๋ ˆ์ด์Šค์—์„œ HttpURLConnection.java:1993์„ ๋ณด๋ฉด, ์‘๋‹ต ์ฝ”๋“œ๊ฐ€ 404 ๋˜๋Š” 410์ผ ๋•Œ FileNotFoundException์ด ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

if (respCode >= 400) {
    if (respCode == 404 || respCode == 410) {
        throw new FileNotFoundException(url.toString());
    } else {
        throw new java.io.IOException(
        "Server returned HTTP" + " response code: " + respCode + " for URL: " + url.toString());
    }
 }

 

ClientHttpRequestInterceptor์™€ ResponseErrorHandler์˜ ์ˆœ์„œ

๋กœ๊น…์„ ์œ„ํ•ด ClientHttpRequestInterceptor๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด, 404์™€ 410 ์ƒํƒœ ์ฝ”๋“œ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ResponseErrorHandler๋ฅผ ์˜ค๋ฒ„๋ผ์ด๋“œํ•ด๋„ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

์ด์œ ๋Š” ClientHttpRequestInterceptor๊ฐ€ ๋จผ์ € ํ˜ธ์ถœ๋œ ํ›„, ResponseErrorHandler๊ฐ€ ์ž‘๋™ํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

RestTemplate ์˜ ์ธํ„ฐ์…‰ํ„ฐ์™€ ์—๋Ÿฌํ•ธ๋“ค๋Ÿฌ์˜ ์ฒ˜๋ฆฌ ์ˆœ์„œ

 

๋”ฐ๋ผ์„œ, Logging ํ•˜๊ธฐ ์œ„ํ•ด ์ถ”๊ฐ€ํ•œ Interceptor ์—์„œ ๋ฐœ์ƒํ•œ IOException ๋•Œ๋ฌธ์— ResponseErrorHandler ๊ฐ€ ํ˜ธ์ถœ ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

RestTemplate ์˜ ์ „์ฒด ๋™์ž‘ ์ˆœ์„œ๋„ ์ถœ์ฒ˜:

์ถœ์ฒ˜ : https://terasolunaorg.github.io/guideline/5.1.1.RELEASE/en/ArchitectureInDetail/RestClient.html

ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋ ค๋ฉด, ์‘๋‹ต ๋กœ๊น…์„ ์œ„ํ•ด getBody()๋ฅผ ํ˜ธ์ถœํ•˜๊ธฐ ์ „์— getStatusCode()๋ฅผ ํ˜ธ์ถœํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์ฆ‰, ClientHttpResponse ์˜ getStatusCode()๋ฅผ ๋จผ์ € ํ˜ธ์ถœํ•œ ํ›„ getBody()๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.

 

...?

@Slf4j
class LoggingInterceptor implements ClientHttpRequestInterceptor {

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) {
        ...
        printResponse(UUID 6์ž๋ฆฌ, ClientHttpResponse);
        ...
    }

    private void printRequest(String sessionNumber, HttpRequest req, byte[] body) {
        ...
    }

    private void printResponse(String sessionNumber, ClientHttpResponse response) throws IOException {
>>>     HttpStatusCode code = response.getStatusCode();
>>>     String body = new BufferedReader(
                new InputStreamReader(response.getBody(), StandardCharsets.UTF_8))
            .lines()
            .collect(Collectors.joining("\n"));

        log.info("[{}] Status:{}, Headers:{}, Body:{}",
                sessionNumber, code, response.getHeaders(), body);
    }
}

 

getStatusCode()์™€ getBody() ํ˜ธ์ถœ ์ˆœ์„œ์˜ ์˜๋ฏธ

 

- RestTemplate ResponseErrorHandler cannot handle 404 FileNotFound

- RestTemplate response.getBody throws exception on 4** and 5** errors for put and post request but works fine for get requests

 

* getBody() ๋Š” post, put ์š”์ฒญ ์‹œ ๋ฐœ์ƒํ•˜๋Š” 400,500๋ฒˆ๋Œ€ ์‘๋‹ต์—์„œ๋งŒ ์—๋Ÿฌ๋ฅผ ๋ฐœ์ƒํ•œ๋‹ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ์ด์— ๋Œ€ํ•œ ์ด์œ  ์—ญ์‹œ ์œ„์˜ 2๊ฐœ์˜ ๋งํฌ ์ค‘ 2๋ฒˆ์งธ ๋“ค์–ด๊ฐ€๋ฉด ํ™•์ธ์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

 

ํ•ด๋‹น ๋งํฌ์—์„œ๋Š” getStatusCode() ๋ฅผ ํ˜ธ์ถœํ•œ ์ดํ›„์— getBody() ๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด errorStream ์ด null ์ด ์•„๋‹ˆ๋ผ๊ณ  ์–˜๊ธฐํ•ฉ๋‹ˆ๋‹ค.

Why does calling response.getBody() not throw an IOException after you called response.getStatusCode()?
It is because getStatusCode calls getInputStream internally.
Thus, errorStream will be not null when getBody is called.

 

* errorStream ์€ getBody() ๊ตฌํ˜„๋ถ€์— ์žˆ์Šต๋‹ˆ๋‹ค.

@Override
public InputStream getBody() throws IOException {
    InputStream errorStream = this.connection.getErrorStream();
    this.responseStream = (errorStream != null ? errorStream : this.connection.getInputStream());
    return this.responseStream;
}

 

 

Spring์—์„œ๋Š” getStatusCode()๊ฐ€ ๋‚ด๋ถ€์ ์œผ๋กœ getInputStream()์„ ํ˜ธ์ถœํ•˜์—ฌ, ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•œ ๊ฒฝ์šฐ errorStream์ด ์„ค์ •๋˜๋„๋ก ์„ค๊ณ„๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

์˜ค๋ฅ˜ ์‘๋‹ต(4xx, 5xx)์ด ๋ฐœ์ƒํ•˜๋ฉด HttpURLConnection์—์„œ getInputStream()์ด IOException์„ ๋ฐœ์ƒ์‹œํ‚ค๋ฉด์„œ errorStream์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.

์ด๋กœ ์ธํ•ด ์ดํ›„ getBody()๋ฅผ ํ˜ธ์ถœํ•  ๋•Œ๋Š” errorStream์ด null์ด ์•„๋‹ˆ๊ฒŒ ๋˜์–ด, ์—๋Ÿฌ ์‘๋‹ต ๋ณธ๋ฌธ์„ ์ฝ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋ž˜์„œ, getBody() ๋ฅผ ๋จผ์ € ํ˜ธ์ถœํ•˜๋ฉด getInputStream ์—์„œ IOException ์ธ FileNotFoundException ์ด ๋ฐœ์ƒํ•˜๋Š” ๊ฒƒ์ด์—ˆ์Šต๋‹ˆ๋‹ค.

 

 

---

์˜ค๋ฅ˜๋ฅผ ํ•ด๊ฒฐํ•˜๋Š” ๊ณผ์ •์—์„œ ๊ทธ ์ด์ƒ์˜ ๋ฐฐ์›€์„ ์–ป๊ฒŒ ๋จ์„ ๋‹ค์‹œ ํ•œ๋ฒˆ ๋Š๋‚๋‹ˆ๋‹ค.
์ด์ƒ์œผ๋กœ RestTemplate์— ๋กœ๊ทธ๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด์„œ ๋งˆ์ฃผํ–ˆ๋˜ ์˜ค๋ฅ˜์— ๋Œ€ํ•œ ๊ธ€์„ ๋งˆ์น˜๊ฒ ์Šต๋‹ˆ๋‹ค.