문제 상황
- Flex Appliance API에 대해
RestTemplate
으로 요청 시 예외 응답의 경우 다음과 같이 Flex API에서 내려주는 포멧이 유실되는 문제가 발생함.- 특히, 인증관련 API 호출 시 임의로 잘못된 토큰을 토대로 요청했거나 요청 body에 잘못된 값을 기반으로 요청했을 경우 아래 디버깅 화면 처럼 no body로 나타남.
- 다른 케이스에 대해선 해당 문제가 발생하지 않음. (400 에러)
원인 분석
- 현재 Flex Appliance 서버의 앞단엔 Nginx가 붙어있는데, 예외 응답의 경우 Nginx에서 gzip 처리로 내려주는 상황에 대해
SimpleClientHttpRequestFactory
가 해제하지 못함. - 또한, 기본 클라이언트의 내부를 살펴보면
java.net.HttpRetryException
으로 예외를 던지기도 하며,org.springframework.web.client.DefaultResponseErrorHandler#getResponseBody
모든 예외를 무시하고 응답 본문이 있더라도 빈 바이트 배열을 반환함.
관련 링크
- Spring RestTemplate's 401 Response redacted (contain's [no body]) even though it has a body | StackOverFlow
- Why is the response Redacted for 401 POST and PUT request, but not GET? | Spring Project > spring-boot, GitHub Issue
관련 코드 수정
AS-IS
- Spring Boot RestTemplate 기본 클라이언트인
SimpleClientHttpRequestFactory
를 사용. - TLS 관련 부가 설정이 적용됨.
@Slf4j
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate() {
try {
TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
}
};
RestTemplate restTemplate = new RestTemplate();
SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
HostnameVerifier allHostsValid = (hostname, session) -> true;
HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
// **이 부분에 주목**
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setConnectTimeout(3 * TimeInMillis.SECOND);
requestFactory.setReadTimeout(5 * TimeInMillis.SECOND);
restTemplate.setRequestFactory(requestFactory);
return restTemplate;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
TO-BE
- Apache HTTP Component 기반
HttpComponentsClientHttpRequestFactory
로 변경build.gradle
내 해당 의존성 추가
- 기존 TLS 관련 설정에서 모든 인증서를 신뢰하도록 설정함.
- 변경된 클라이언트의 경우 해당 설정에 대해 JVM 레벨에서 JVM truststore 관련 설정이 필요한데, Real 환경이 특수한 환경(서버간 통신만을 수행 및 인프라 레벨 보안처리가 되어 있는 상황)이라는 점과 추후 인프라 레벨의 변경점을 대비하여 관리 포인트를 줄이는 방향으로 생각하여 Application 레벨에선 별도의 설정을 하지 않도록 처리함.
@Slf4j
@Configuration
public class RestTemplateConfig {
@Bean
public HttpClient httpClient() throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException {
// 모든 인증서를 신뢰하도록 설정한다
TrustStrategy acceptingTrustStrategy = (cert, authType) -> true;
SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy).build();
// Https 인증 요청시 호스트네임 유효성 검사를 진행하지 않게 한다.
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("https", sslsf)
.register("http", new PlainConnectionSocketFactory()).build();
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
httpClientBuilder.setConnectionManager(connectionManager);
return httpClientBuilder.build();
}
// 변경된 클라이언트 (Apache HTTP Component)
@Bean
public HttpComponentsClientHttpRequestFactory factory(HttpClient httpClient) {
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
factory.setConnectTimeout(3000);
factory.setHttpClient(httpClient);
return factory;
}
@Bean
public RestTemplate restTemplate(HttpComponentsClientHttpRequestFactory factory) {
return new RestTemplate(factory);
}
}
이후 결과는 아래와 같음.
두 클라이언트를 비교하자면?
주요 차이 상세 설명
- 네트워크 처리 기반
SimpleClientHttpRequestFactory
는 Java SE 표준인HttpURLConnection
기반
→ 가볍고 의존성 없음, 하지만 기능이 제한됨HttpComponentsClientHttpRequestFactory
는 Apache HttpClient 기반
→ 매우 안정적이고 기능 확장성이 크며 대규모 서비스에 적합
- GZIP 압축 응답 처리
SimpleClientHttpRequestFactory
는 GZIP 응답을 직접 처리하지 않음
→ClientHttpRequestInterceptor
또는 스트림 래핑 필요HttpComponentsClientHttpRequestFactory
는Content-Encoding: gzip
자동 처리
→ 코드 없이 압축 응답 정상 파싱 가능
- SSL 커스터마이징
SimpleClientHttpRequestFactory
:HttpsURLConnection.setSSLSocketFactory(...)
등으로 설정 필요 → 전역 설정이라 위험HttpComponentsClientHttpRequestFactory
:TrustStrategy
,NoopHostnameVerifier
등 객체 단위로 안전하게 커스터마이징 가능
- 커넥션 관리
SimpleClientHttpRequestFactory
: 매 요청마다 새로운 TCP 연결HttpComponentsClientHttpRequestFactory
:PoolingHttpClientConnectionManager
사용 시 Keep-Alive 및 커넥션 재사용 가능 → 성능 개선, 고부하 처리 유리
- 응답 재사용 / 예외 응답 처리
SimpleClientHttpRequestFactory
: 예외 발생 시 body 스트림이 닫혀서 getResponseBodyAsString() 호출 시 null 또는 빈 문자열HttpComponentsClientHttpRequestFactory
: 내부적으로 응답 body를 byte[]로 캐싱해둠 → 재사용 가능
결론
SimpleClientHttpRequestFactory
는 가볍고 단순한 환경에만 적합.- 실전에서는 대부분
HttpComponentsClientHttpRequestFactory
를 사용해야 함. - Spring Boot에서도 기본 설정을 커스터마이징해서 Apache HttpClient 기반으로 전환하는 것이 모범 사례로 알려짐.
'트러블 슈팅 > BE' 카테고리의 다른 글
Redis 분산락 컴포넌트 리팩토링 적용기 (2) | 2025.01.04 |
---|