HTTP Client

February 02, 2021

HTTP Request

자바에서 HTTP 요청을 날리는 방법은 여러 가지가 존재한다. 대표적으로 java.net 패키지에 존재하는 URLConnection을 직접 사용하거나, 그 구현체인 HttpURLConnection을 사용할 수 있다.

public static JSONObject get(String uri){
	try{
		URL url = new URL(uri);
		HttpURLConnection conn = (HttpURLConnection) url.openConnection();

		conn.setRequestMethod("GET");
		conn.setRequestProperty("X-Auth-Token", "temp");

		BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
		String inputLine;
		StringBuilder response = new StringBuilder();
		while ((inputLine = in.readLine()) != null) {
			response.append(inputLine);
		}
		in.close();

		return new JSONObject(response.toString());
	}
	catch(Exception e){
		e.printStackTrace();
	}

	return null;
}

물론 동작은 되겠지만 이런 코드는 생산성이 떨이질 수 밖에 없다. 예외 발생 시 처리는 물론이고 직접 도메인 클래스와 연관 시켜 변환 작업을 해줘야 하는 등 부가적인 작업이 필요하다.

RestTemplate

스프링 3.0에서부터는 이런 HTTP Client 코드를 기본적으로 제공하고 있다. RestTemplate 객체를 직접 생성하거나, RestTemplateBuilder를 주입 받아 사용할 수 있다.

  • delete(...)
  • exchange(...)
  • execute(...)
  • getForEntity(...)
  • headForHeaders(...)
  • optionsForAllow(...)
  • patchForObject(...)
  • postForEntity(...)
  • postForLocation(...)
  • postForObject(...)
  • put(...)

각 메소드는 URL String 또는 java.net.URI, Map<String, String>에 담겨있는 URL 필드를 처리할 수 있다.

@Data
class Post {
    private String userId;
    private String id;
    private String title;
    private String body;
}

반환되는 JSON이 위 클래스 필드로 정의될 때, 간단히 처리 가능하다.

RestTemplate restTemplate = new RestTemplate();

Post post = restTemplate.getForObject(
		"https://jsonplaceholder.typicode.com/posts/{id}",
		Post.class,
		1);

Traverson

Traverson은 스프링 데이터 HATEOAS에서 제공이 된다. 스프링에서 하이퍼미디어 기반 API를 사용할 수 있다.

Traverson traverson = new Traverson(URI.create("http://localhost/api"), MediaTypes.HAL_JSON);
ParameterizedTypeReference<Resource<Ingredient>> ingredientType = new ParameterizedTypeReference<Resource<Ingredient>>() {};

Resource<Ingredient> ingredientRes = traverson
					.follow("ingredients")
					.toObject(ingredientType);

Collection<Ingredient> ingredients = ingredientRes.getContent();

follow() 메소드를 통해 리소스 링크 관계 이름이 ingredients인 리소스로 이동 가능하다. 그리고 toObject() 메소드를 통해 리소스를 가져온다.

String ingredientsUrl = traverson
					.follow("ingredients")
					.asLink()
					.getHref();

// ingredientsUrl 기반 Rest 호출

asLink() 메서드를 통해 링크를 요청하고 getHref()를 통해 URL 값을 가져올 수 있다.

WebClient

위의 예제들은 Blocking I/O 기반으로 synchronous하게 동작한다. Blocking으로 동작하는 경우 외부 시스템으로 부터 응답을 받을 때까지 스레드가 대기하는 것이다.

하지만 스프링 5.0에서 부터 지원하는 Spring webflux에서는 Non-Blocking I/O 기반 non-synchronous API인 WebClient를 제공하고 있다. 추세가 비동기 기반으로 바뀌고 있기 때문에 RestTemplate API 문서에서도 WebClient를 사용할 수 있는 상황이라면 이를 사용할 것을 적극 권고하고 있다. webflux 기반 어플리케이션이 아니더라도, webflux 의존성을 추가하여 해당 기능을 사용할 수 있다.

WebClient webClient = webClientBuilder.build();
Mono<Post> webClientSample = webClient.get()
		.uri("https://jsonplaceholder.typicode.com/posts/{id}", 1)
		.retrieve()
		.bodyToMono(Post.class);

webClientSample.subscribe(result -> {
	log.info(result.getUserId());
	log.info(result.getId());
	log.info(result.getTitle());
	log.info(result.getBody());
});

위 예제 같은 경우에는 단순히 응답 결과를 로깅하고 있어 완벽하게 비동기로 처리할 수 있어 Spring MVC 구조에서도 성능상 이점을 가져갈 수 있다. 그런데 하지만 응답 결과를 사용해 다시 클라이언트에 전달해야하는 경우에는 결국 Blocking 될 수 밖에 없다는 한계가 있는 것 같다.


참고

  • Craig Walls, Spring in Action 5/E, 심재철, 제이펍

songmk 🙁