개발일기

[Spring, Java] 외부 API 호출

ignuy 2023. 11. 15.

Java 또는 Spring에서 외부 API를 호출하는 방법은 여러 가지이다.

1. HttpURLConnection/URLConnection
2. HttpClient
3. RestTemplate
4. WebClient
5. OpenFeign

빠른 사용/개발을 위해서는 사용하고자 하는 각각의 API vendor가 제공하는 클라이언트 라이브러리 자체를 이용해도 되겠지만, 특정 API에 종속되므로 최대한 지양하고자 한다. 또한, 각각의 기술들이 장단점 및 성격이 다르므로 상황에 맞게 선택하여 사용하여야 한다.

HttpURLConnection/URLConnection

자바에서 제공하는 API 통신을 위한 클래스이다. 순수 자바 코드로 동작하며 URL을 이용하여 외부 API를 통해 데이터를 전송 및 조회할 수 있다.

동기적으로 요청을 보내고, 응답을 받을 때까지 스레드가 대기 상태로 존재한다. 뒤에 소개할 방식에 비해 상대적으로 로우 레벨의 코드에 속하며 기본적인 응답, 요청 기능만을 제공한다.

HttpClient

Http 프로토콜을 쉽게 사용할 수 있는 Apache HTTP 컴포넌트이다.

Apache HttpComponents – Apache HttpComponents

Client Side동작을 구현한 라이브러리로 서버에게 HTTP request를 던지고 response를 받을 수 있다. 서버로부터 데이터를 받아 활용하기 위한 라이브러리란 뜻이다.

HttpURLConnection을 간소화하여 Java를 통해 편리하고 쉽게 HTTP 개발을 할 수 있다는 장점이 있지만 아직 반복적인 코드들을 걷어내지 못하였다.

RestTemplate

스프링에서 제공하는 HTTP통신에 유용하게 사용 가능한 템플릿이다. Spring 3.0 이상에서 지원되고 json, xml response를 모두 받을 수 있다. Rest API 서비스를 요청한 후 응답을 받을 수 있도록 RESTful 형식에 맞추어 설계되어 있다. Blocking 동기 방식을 사용한다.

사용하기 편하고, 직관적이지만 동기적인 HTTP 요청이라는 한계에서는 벗어나지 못했다. 또한, 커넥션 풀을 사용하지 않고 있어 연결할 때마다 포트를 열고 connection을 맺는 오버헤드를 감수해야 한다.

따라서 현재의 Spring framework 5.0 이상부터는 WebFlux 스택과 함께 WebClient라는 새로운 HTTP 클라이언트를 도입하여 기존의 동기식 API를 제공할 뿐만 아니라 효율적인 비차단 및 비동기 접근 방식을 지원하기 때문에 RestTemplate의 사용을 권장하지는 않고 있다.

WebClient

위에 언급한 것처럼 스프링 5부터 도입된 웹 클라이언트 라이브러리이다. 비동기/논블로킹 형식으로 외부 API를 호출한다.

마이크로서비스 패턴: 핵심패턴만 빠르게 이해하기

아래는 RestTemplate을 사용하는 Spring Boot1과 WebClient를 사용하는 Spring Boot2의 성능비교 결과이다. 1000명까지는 비슷하지만 동시사용자가 늘수록 RestTemplate을 사용하는 Spring Boot1은 급격하게 느려지는 것을 볼 수 있다.

대규모 사용자가 있을 때 높은 처리량과 확장성을 지원하는 것 뿐 아니라, 데이터 스트림 또한 효과적으로 처리할 수 있다.

다만, WebFlux 스택이 프로젝트에 강제로 추가되게 된다. 사용자 규모가 크지 않은 서비스가 굳이 WebFlux의 러닝커브를 감당하며 개발이 진행되어야 할지는 여러 가지 판단하에서 결정되어야 할 것이다.

OpenFeign

Netflix에 의해 처음 만들어진 선언적인 웹서비스 클라이언트(Declarative HTTP Client)이다. 현재는 Spring 진영으로 포함되어 Spring 생태계로 들어왔다. “선언적”이라는 의미는 애노테이션의 사용을 의미한다. 인터페이스와 애노테이션 기반의 기술로 작성할 코드가 줄어들고 익숙한 Spring MVC 애노테이션으로 개발이 가능하다. 다른 Spring Cloud 기술들(Eureka, Circuit Breaker, LoadBalancer)과의 통합이 쉽다.

다만, HTTP 2에 대해 지원을 하지 않고 공식적으로 Reactive 모델을 지원하지 않는다. 또한, 별도의 테스트 도구를 제공하지 않아 설정 파일의 작성이 필요하다.

HttpURLConnection을 이용한 방법

private final String URL = "www.~~~~.com";

private JSONObject getJsonObject(String param) throws IOException  {
    StringBuilder urlBuilder = new StringBuilder(URL);
    urlBuilder.append(param);

    BufferedReader in = null;
    HttpURLConnection conn = null;
    JSONObject obj = null;
    try {
        // reqParams.put("body_contents1", body_contents1); // body에 들어갈 내용을 담는다.
        URL url = new URL(urlBuilder.toString()); // 호출할 외부 API 를 입력한다.

        conn = (HttpURLConnection) url.openConnection();
        conn.setRequestMethod("GET");
        conn.setRequestProperty("Content-Type", "application/json; utf-8");
        conn.setDoOutput(true);

        // Response
        in = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
        StringBuilder sb = new StringBuilder();
        String line;

        while ((line = in.readLine()) != null) {
            sb.append(line);
        }

        obj = new JSONObject(sb.toString()); // json으로 변경 (역직렬화)
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        Objects.requireNonNull(in).close();
        Objects.requireNonNull(conn).disconnect();
    }

    return obj;
}

코드 플로우

  1. URL 객체 만들기
  2. URL에서 URLConnection 객체 얻기
  3. URL 연결 구성
  4. 헤더 필드 읽기
  5. 입력 스트림 가져오기 및 데이터 읽기
  6. 출력 스트림 가져오기 및 데이터 쓰기
  7. 연결 닫기

URLConnection interface 구성

  • setConnectTimeout(int timeout) : 연결 타임아웃 설정(단위 : 밀리초)
  • setReadTimeout(int timeout) : 읽기 타임아웃 설정(단위 : 밀리초)
  • setDefaultUseCaches (boolean default) : 기본적으로 캐시를 사용하는지 여부 설정(기본값 true)
  • setUseCaches(boolean useCaches) : 연결이 캐시를 사용하는지 여부를 설정 (기본값 true)
  • setDoInput (boolean doInput) : URLConnection을 서버에서 콘텐츠를 읽는 데 사용할 수 있는지 여부(기본값은 true).
  • setDoOutput (boolean doOutput) : URLConnection이 서버에 데이터를 보내는 데 사용할 수 있는지 여부(기본값은 false).
  • setIfModifiedSince (long time) : 주로 HTTP 프로토콜에 대해 클라이언트가 검색한 콘텐츠의 마지막 수정 시간을 설정 → 예를 들어, 서버가 지정된 시간 이후에 콘텐츠가 변경되지 않았음을 발견하면 콘텐츠를 가져오지 않고 상태 코드 304(Not Modified)을 반환한다. 클라이언트는 지정된 시간보다 최근에 수정된 경우 새로운 콘텐츠를 받게 된다.
  • setAllowUserInteraction (boolean allow) : 사용자 상호 작용을 활성화 또는 비활성화 → 예를 들어 필요한 경우 인증 대화 상자를 표시합니다(기본값은 false).
  • setDefaultAllowUserInteraction (boolean default) : 이후의 모든 URLConnection객체에 대한 사용자 상호 작용의 기본값을 설정
  • setRequestProperty (String key, String value) : key=value 쌍으로 지정된 일반 요청 속성을 설정 → 키가 있는 속성이 이미 있는 경우 이전 값을 새 값으로 덮어씁니다.

HttpURLConnection을 위한 interface

  • setRequestMethod (String method) : HTTP 메소드 GET, POST, HEAD, OPTIONS, PUT, DELETE, TRACE 중 하나인 URL 요청에 대한 메소드를 설정 → default method는 GET입니다.
  • setChunkedStreamingMode (int chunkLength) : 콘텐츠 길이를 미리 알 수 없는 경우 내부 버퍼링 없이 HTTP 요청 본문을 스트리밍
  • setFixedLengthStreamingMode (long contentLength) : 콘텐츠 길이를 미리 알고 있는 경우 내부 버퍼링 없이 HTTP 요청 본문을 스트리밍
  • setFollowRedirects (boolean follow) : HTTP 리디렉션 뒤에 이 클래스의 향후 개체가 자동으로 따라야 하는지 여부를 설정(기본값은 true).
  • setInstanceFollowRedirects (boolean follow) : HTTP 리디렉션 다음에 이 HttpURLConnection클래스의 인스턴스가 자동으로 따라와야 하는지 여부를 설정(기본값은 true).

댓글