[Spring 6.2] HTTP Interface

Spring 6 이 release 되면서 새롭게 HTTP Interface 라는 방식의 HTTP request/response 를 주고 받을 수 있는 방법이 생겼다.

마이너 버전이 새로 release 되면서 약간 변경된 점들도 있어서 6.2 기준으로 사용법에 대해 정리해보았다.

 

HTTP Interface 가 무엇인지 정확히 이해하기 위해선 팩토리 메서드 패턴, 프록시 패턴에 대한 이해를 우선 하고 있어야 한다.

사용 방법 자체는 매우 간단하다. 예시를 통해 알아보자.

 

예시로 https://news.naver.com/section/105 url 을 사용해 보자.

 

JPA 레포지토리 만들듯이 interface 를 구현해주어야 한다. HTTP Interface 라는 말에서 알 수 있듯이, Interface 를 구현한 뒤 spring 에서 제공해주는 proxyFactory class 를 이용해 구현체를 생성해 api 요청을 해줄 수 있다.

 

사용법

1. interface 구현

@HttpExchange("/section")
public interface RestHttpInterface {
	@GetExchange("/105")
	List<News> getNews();

	@PostExchange
	News createNews(@RequestBody News news);

	@PutMapping("/{id}")
	News changeNews(@PathVariable int id, @RequestBody News news);

	@DeleteMapping("/{id}")
	void deleteNews(@PathVariable int id);
}

 

예시의 경우 getNews 만 동작한다. 다른 HTTP METHOD 는 이렇게 사용할 수 있다 라는 참고용이다.

 

2. interface 구현체 생성 및 실제 요청

 

2-1. 구현체를 생성해 실제 요청을 보내기 위해선 어떤 HTTP Client 를 사용할지 정해야 한다. (RestTemplate, WebClient, RestClient)

2-2. 생성한 HTTP Client 인스턴스에 맞는 adapter 객체를 생성한다. adapter 를 통해 HttpServiceProxyFactory 객체를 생성한다.

2-3. HttpServiceProxyFactory 를 사용해 위에 구현한 interface 의 구현체를 생성한다.

2-4. 사용한다.

 

adapter 의 경우 HTTP Interface 가 나오며 새로 생겼고, 각 HTTP Client 에 맞는 adapter 가 존재한다. 기호에 맞는 HTTP Client 를 사용할 수 있게 하기 위해 만들어 준 것으로 보인다.

 

아래는 RestTemplate 를 사용한 예시이다.

// 2-1
RestTemplate restTemplate = new RestTemplate();
restTemplate.setUriTemplateHandler(new DefaultUriBuilderFactory("https://news.naver.com"));

// 2-2
RestTemplateAdapter adapter = RestTemplateAdapter.create(restTemplate);
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();

// 2-3
RestHttpInterface restHttpInterface = factory.createClient(RestHttpInterface.class);

// 2-4
List<News> news = restHttpInterface.getNews();

System.out.println(news);

 

이렇게 하면 매우 손쉽게 http 요청/응답을 받아올 수 있다.

단순하지만 많이 반복되는 경우에 사용하면 편할 것 같다.

 

많이 사용한다면 위 로직 자체를 bean 으로 만들어 더욱 손쉽게 사용하는 방법도 있어보인다.

 

RestClent

// 2-1
RestClient restClient = RestClient.builder().baseUrl("http://localhost:8080").build();

// 2-2
RestClientAdapter adapter = RestClientAdapter.create(restClient);
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();

// 2-3
RestHttpInterface restHttpInterface = factory.createClient(RestHttpInterface.class);

// 2-4
List<News> news = restHttpInterface.getNews();

System.out.println(news);

 

WebClient

// 2-1
WebClient webClient = WebClient.builder().baseUrl("http://localhost:8080").build();

// 2-2
WebClientAdapter adapter = WebClientAdapter.create(webClient);
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();

// 2-3
RestHttpInterface restHttpInterface = factory.createClient(RestHttpInterface.class);

// 2-4
List<News> news = restHttpInterface.getNews();

System.out.println(news);

 

WebClient 의 경우 응답값을 Mono, Just 로 받을 수 있다.

 

추가 참고

custom argument resolver

HTTP Interface 는 RequestEntity 를 파라미터로 사용할 수 없다. 그래서 파라미터로 보다 복잡한 값을 커스텀으로 넣고 싶을 경우가 있을 때 사용할 수 있는 방법이다.

 

쿼리 값을 커스텀으로 넣고싶다고 가정하자.

public class NewsQuery {
	private String category;
    	private String title;
    	private LocalDateTime startDate;
}

 

 

interface NewsQueryInterface {
	@GetExchange();
    	List<News> getNews(NewsQuery newsQuery);
}

인터페이스 파라미터에 커스텀 쿼리 class 를 넣어준다.

 

static class NewsQueryArgumentResolver implements HttpServiceArgumentResolver {
	@Override
	public boolean resolve(Object argument, MethodParameter parameter, HttpRequestValues.Builder requestValues) {
		if (parameter.getParameterType().equals(NewsQuery.class)) {
			NewsQuery newsQuery = (NewsQuery) argument;
			requestValues.addRequestParameter("category", newsQuery.getCategory());
			requestValues.addRequestParameter("title", newsQuery.getTitle());
			requestValues.addRequestParameter("startDate", newsQuery.getStartDate());
			return true;
		}
		return false;
	}
}

reslover 를 구현한다.

 

이 reslover 를 이제 HttpServiceProxyFactory 를 생성할 때 추가해 주면 된다.

// 2-1
RestClient restClient = RestClient.builder().baseUrl("http://localhost:8080").build();

// 2-2
RestClientAdapter adapter = RestClientAdapter.create(restClient);
HttpServiceProxyFactory factory = HttpServiceProxyFactory
.builderFor(adapter)
.customArgumentResolver(new NewsQueryArgumentResolver())
.build();

// 2-3
RestHttpInterface restHttpInterface = factory.createClient(RestHttpInterface.class);

// 2-4
List<News> news = restHttpInterface.getNews();

System.out.println(news);

 

Error Handling

에러 핸들링은 클라이언트 생성할 때 설정해주면 된다.

RestClient restClient = RestClient.builder()
		.defaultStatusHandler(HttpStatusCode::isError, (request, response) -> ...)
		.build();

 

 

더 구체적으로 알아보고 싶다면 공식문서 정독을 추천한다.

사용 가능한 응답값과 파라미터 값, 에러 핸들링 등등에 대한 구체적인 설명이 있다.

https://docs.spring.io/spring-framework/reference/integration/rest-clients.html#rest-http-interface