Back-End/Spring Boot

Spring Boot - RestTemplate를 사용한 Server To Server 연결(1)

러러 2024. 6. 18. 20:56

RestTemplate란

JDK, HttpURLConnection, Apache HttpComponents등과 같은 Http client 라이브러리의 간단한 template method API를  제공하며, Http요청을 동기적으로 처리하는 클라이언트이다.
RestTemplate는 자주 사용되는 Http method templates 외에도, 자주 사용되지 않는 exchange 와 execute 같은 method들도 제공한다.

※ 스프링 3.0부터 지원한다.
spring.io RestTemplate문서

서버의 입장에서 API를 제공하던 지금까지와는 다르게 서버가 하나의 클라이언트가 되어 다른 서버로 API를 요청할 수도 있다.


RestTemplate를 사용해서 간단한 api를 개발해보자.

 

우선 서버를 생성해보자. 간단하게 api/server/hello로 요청이 들어오면 "Hello World"를 돌려주는 API를 작성해보겠다.

 

<server - controller>

// java 17 record 사용

@RestController
@RequestMapping("api/server")
public record ServerApiController() {

  @GetMapping("/hello")
  public String hello() {
    return "Hello World";
  }

}

record를 활용해서 간단한 api하나 생성하였다.

 

 

이제 client용 서버가 필요하다.

우선 api요청을 보낼 RestTemplateService를 만들어보자.

근데 아직 RestTemplate을 사용해서 요청을 보내면 된다는 것만 알 뿐 어떤 함수를 어떻게 사용해야 하는지 모른다.

그래서 공식 문서를 찾아보았다.

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/client/RestTemplate.html

현재 내가 원하는 문자열 데이터를 받고자 하는데 가장 적합한 함수인 것 같다. 

uri와 제네릭으로 선언된 리턴 타입만 명시해주면 GET요청을 보낼수 있다고 한다. overloading된 함수가 3개가 있는데 

3개 중에 URI객체를 넣는 3번 째 함수의 내용을 확인해봤다.

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/client/RestTemplate.html

요청 URI 객체와 returnType에 대한 것을 Class객체로 넣기만 하면 끝인것 같다.

이제 위에서 확인한 내용을 바탕으로 함수를 작성해보자.

 

<client - Service>

// java 17 record사용
@Slf4j
@Service
public record RestTemplateService() {

//  http://localhost:9090/api/server/hello

  public String hello() {
  // 요청 보낼 uri 생성
    URI uri = UriComponentsBuilder
      .fromUriString("http://localhost:10000") // baseUrl
      .path("/api/server/hello") // 경로
      .encode() // 인코딩
      .build()
      .toUri();

    log.info("uri: {}", uri);

    RestTemplate restTemplate = new RestTemplate(); // RestTemplate생성
    
    // get요청으로 Object 객체를 받겠다 라는 의미의 http method get | getter느낌의 get이 아님
    String result = restTemplate.getForObject(uri, String.class); 
    
    log.info("response : {}", result);
    return result;
  }
}

 

Service로직도 구현했으니 Controller에서 사용해보자.

<client - controller>

// java 17 record 사용
RestController
@RequestMapping("/api/client")
public record ApiController(
  RestTemplateService restTemplateService
)
{
  @GetMapping("/hello")
  public UserResponse get() {
    return restTemplateService.hello();
  }
}

 

결과 :

api요청 결과
request uri, reponse 로그

내가 예상한대로 결과가 잘 나왔다.

 

근데 공식문서를 보다 중간에 발견한 함수가 있다.

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/client/RestTemplate.html

내가 요청한 함수를 ResponseEntity로 감싸서 돌려준다고 한다.

당장 써보자!

 

<client - RestTemplateService>

  public ResponseEntity<String> hello() {
    // 요청 보낼 uri 생성
    URI uri = UriComponentsBuilder
      .fromUriString("http://localhost:10000") // baseUrl
      .path("/api/server/hello") // 경로
      .encode() // 인코딩
      .build()
      .toUri();

    log.info("uri: {}", uri);

    RestTemplate restTemplate = new RestTemplate(); // RestTemplate생성

    // get요청으로 Entity 객체를 받겠다 라는 의미의 http method get | getter느낌의 get이 아님
    ResponseEntity<String> result = restTemplate.getForEntity(uri, String.class);

    log.info("response : {}", result);
    log.info("body: {}", result.getBody());
    log.info("status: {}", result.getStatusCode());
    return result;
  }

요청 함수와 리턴 타입만 바꿔주었다.

 

바로 테스트를 해보자. 이전과 동일한 url로 요청을 한 결과

모두 잘 들어있다. 야무지게 사용할 수 있을것 같다.


QueryString과 Header를 담아서 보내보자!

생각해보면 다른 server에 요청을 보낼 때 아무런 인증 없이 보내지는 않을것 같다. queryString으로 데이터를 보내는 것도 확인해볼 필요가 있다.

 

<client - RestTemplateService>

  public ResponseEntity<String> hello() {
    // 요청 보낼 uri 생성
    URI uri = UriComponentsBuilder
      .fromUriString("http://localhost:10000") // baseUrl
      .path("/api/server/hello") // 경로
      .queryParam("name", "Uheejoon") // queryString
      .queryParam("age", 25) // queryString
      .encode() // 인코딩
      .build()
      .toUri();

    log.info("uri: {}", uri);

    RestTemplate restTemplate = new RestTemplate(); // RestTemplate생성

    // get요청으로 Entity 객체를 받겠다 라는 의미의 http method get | getter느낌의 get이 아님
    ResponseEntity<String> result = restTemplate.getForEntity(uri, String.class);

    log.info("response : {}", result);
    log.info("body: {}", result.getBody());
    log.info("status: {}", result.getStatusCode());
    return result;
  }

 

요청이 바뀌었으니 받는 곳도 바꿔보자!

<server - controller>

// java 17 record 사용

@RestController
@RequestMapping("api/server")
public record ServerApiController() {

  @GetMapping("/hello")
  public String hello(@RequestParam(name = "name") String name, @RequestParam(name = "age") int age) {
    return name + " " + age;
  }

}

queryString으로 요청 보낸 내용 그대로 잘 나온다.

 

이번에는 헤더에 x-authorization을 보낸다고 가정하고 헤더 요청을 해보자.

지금까지는 URI에 값을 다 담아서 보냈지만 이제부터는 HttpHeaders를 만들어서 보내야한다.

// http headers
HttpHeaders headers = new HttpHeaders();
headers.set("x-authorization", "ffff");
headers.setContentType(MediaType.APPLICATION_JSON);

 

헤더를 문제 없이 잘 생성 했으니 restTemplate에 담아서 보내야하는데

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/client/RestTemplate.html

 

GET요청 함수들 중에 HttpHeaders를 넣을 수 있는 함수가 없다...  다른 함수들을 보던 중

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/client/RestTemplate.html

HttpMethod와 HttpEntity를 받는 exchange라는 함수를 찾았다.

자세한 내용을 드려다보니

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/client/RestTemplate.html#exchange(java.lang.String,org.springframework.http.HttpMethod,org.springframework.http.HttpEntity,org.springframework.core.ParameterizedTypeReference,java.lang.Object...)

 

 

이전과 같이 url에 Http method로 요청을 하는데 새로 추가된 request entity를 요청 보낼 때 추가로 보낸다고 한다.

이 함수를 사용하면 GET요청 보낼 때, 원하는 헤더값을 보낼 수 있을것 같다.

근데 난 HttpEntity가 어떻게 생겼는지 모른다. 다시 말해, 어떻게 생겼는지 열어보아야한다.

 

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/http/HttpEntity.html

 

이제까지 아무생각 없이 사용해왔던 RequestEntity와 ResponseEntity 두 클래스의 부모타입이였다. 

RestTemplate와 함께 어떻게 사용하는지 예시코드까지 함께 들어있다. 이제 저 부분을 토대로 HttpEntity를 만들어 요청을 보낼 수 있을 것 같다.

<client - RestTemplateService>

  public ResponseEntity<String> hello() {
    // 요청 보낼 uri 생성
    URI uri = UriComponentsBuilder
      .fromUriString("http://localhost:10000") // baseUrl
      .path("/api/server/hello") // 경로
      .encode() // 인코딩
      .build()
      .toUri();

    log.info("uri: {}", uri);

	// HttpHeaders
    HttpHeaders headers = new HttpHeaders();
    headers.set("x-authorization", "ffff"); // 헤더 생성
    headers.setContentType(MediaType.APPLICATION_JSON); // contentType은 json

    // HttpEntity 객체 생성
 	// body는 안보내기에 Void삽입
    HttpEntity<Void> entity = new HttpEntity<>(headers);

    RestTemplate restTemplate = new RestTemplate(); // RestTemplate생성

    // exchange에 uri, method, requestEntity, restponseType을 넣어서 보낸다.
    ResponseEntity<String> result = restTemplate.exchange(uri, HttpMethod.GET, entity, String.class);

    log.info("response : {}", result);
    log.info("body: {}", result.getBody());
    log.info("status: {}", result.getStatusCode());
    return result;
  }

 

지금까지 찾아본 내용을 토대로 queryString과 header내용을 담아서 보낼 수 있는 함수를 작성했다.

이제 실행해보자!

 

<server - controller>

  @GetMapping("/hello")
  public String hello(
    @RequestHeader("x-authorization") String authorization,
    @RequestParam(name = "name") String name,
    @RequestParam(name = "age") int age
  ) {
    log.info("authorization = {}", authorization);
    log.info("name = {}", name);
    log.info("age = {}", age);
    return "Hello World";
  }

 

결과: 

 

다행히 잘 나온다.

 

이제까지 공식문서를 찾아보며 RestTemplate와 GET요청을 사용해 server to server연결 하는 법을 알아봤다.

다음 포스트에서는 PathParameter로 보내는 법과 지금까지 한 것을 DTO로 만들어서 유연하게 응답하는 것을 알아보겠다.