-
Rest Template으로 Server(Client) to Server 통신하기백엔드 2023. 9. 15. 10:49728x90반응형SMALL
Spring Boot 프로젝트를 통해 api를 설계해 Client로부터의 요청에 대한 응답을 내려줄 수 있었습니다.
이번 포스팅에서는 Server에서 다른 Server로 요청을 하고 이 요청에 대한 응답을 받는 방법에 대해 공부해보도록 하겠습니다.
예제 코드
Client ApiController
@RestController @RequestMapping("/api/client") @RequestArgsConstructor public class ApiController { private final RestTemplateService restTemplateService; @GetMapping("/hello") public String getHello(){ return restTemplateService.hello(); } }
Client RestTemplateService
@Service public class RestTemplateService{ // 여기서 다른 서버로 요청을 보냄 public String hello(){ Uri uri = UriComponentBuilder .fromUriString("http://localhost:9090") .path("/api/server/hello") .encode() .build() .toUri(); RestTemplate restTemplate = new RestTemplate(); } }
Service의 hello 메서드 상에서 다른 서버로 요청을 보내고 해당 요청에 대한 응답을 반환해주도록 하겠습니다.
통신할 Server의 uri는 "http://localhost/api/server/hello" 입니다.
UriComponentBuilder를 통해 요청할 서버의 uri를 생성해줍니다. UriComponentBuilder는 queryParam, expand 메서드를 통해 쿼리 파라미터와 패스 파라미터를 uri에 붙여줄 수 있습니다.
통신에 사용할 객체는 RestTemplate입니다.
restTemplate의 메서드 중 xxxforEntity와 같은 경우 반환한 객체가 ResponseEntity에 한 번 감싸진 형태입니다. xxxforObject와 같은 경우 그냥 우리가 지정한 타입으로 받을 수 있습니다. (xxx는 http method 중 하나입니다.)
Server ApiController
@RestController @RequestMapping("/api/server") public class ServerApiController{ @GetMapping("/hello") public String hello(){ return "hello server"; } }
server에서 http://localhost:9090/api/server/hello로 들어오는 요청에 대한 응답을 내려주는 api를 만들어주었습니다.
xxxforObject와 xxxforEntity를 통해 다른 Server로 요청을 보내보도록 하겠습니다.
getforObject
@Service public class RestTemplateService{ // 여기서 다른 서버로 요청을 보냄 public String hello(){ ... RestTemplate restTemplate = new RestTemplate(); String result = restTemplate.getForObject(uri, String.class); return result; } }
서버로의 요청에 대한 응답을 받을 객체의 타입을 String으로 선언을 해줌으로써 응답 객체의 타입이 String이 됩니다.
getForEntity
@Service public class RestTemplateService{ // 여기서 다른 서버로 요청을 보냄 public String hello(){ ... RestTemplate restTemplate = new RestTemplate(); ResponseEntity<String> result = restTemplate.getForEntity(uri, String.class); System.out.println(result.getStatusCode()); System.out.println(result.getBody()); return result.getBody(); } }
ResponseEntity로 한번 감싸진 응답 객체를 받게 되므로 ResponseEntity에 제너릭 타입으로 객체의 타입을 지정해줄 수 있습니다.
ResponseEntity의 경우 응답 받은 객체로부터 여러가지 응답과 관련된 내용들을 확인할 수 있도록 해줍니다.
실제 반환 받은 객체는 getBody() 메서드를 통해 확인할 수 있습니다.
json으로 응답 받을 경우
응답 받을 json 형태
{ "name": "name1", "age": 10 }
해당 객체의 내용을 쿼리파라미터로 받아 객체를 생성해서 반환해주도록 하겠습니다.
Client Response 객체
Client에서 응답으로 받을 객체를 선언해줍니다.
@Data public class UserResponse{ private String name; private int age; }
Client RestTemplateService
@Service public class RestTemplateService{ // 여기서 다른 서버로 요청을 보냄 public UserResponse hello(){ Uri uri = UriComponentBuilder .fromUriString("http://localhost:9090") .path("/api/server/hello") .queryParam("name", "name1") .queryParam("age",10) .encode() .build() .toUri(); RestTemplate restTemplate = new RestTemplate(); ResponseEntity<UserResponse> = restTemplate.getForEntity(uri, UserResponse.class); return result.getBody(); } }
UriComponentBuilder로 uri 생성 시 queryParam이라는 메서드를 통해 쿼리파라미터를 지정해주었습니다.
Server에 UserResponse에 대한 응답을 받을 수 있도록 해줍니다.
Server UserReponse
@Data @NoArgsContructor @AllArgsConstructor public class User{ private String name; private int age; }
Server ApiController
@RestController @RequestMapping("/api/server") public class ServerApiController{ @GetMapping("/hello") public User hello(@QueryParam String name, @QueryParam int age){ User user = new User(); user.setName(name); user.setAge(age); return user; } }
서버에서 쿼리파라미터로 받은 name과 age를 통해 User 객체를 선언하고 반환해줍니다.
클라이언트와 동일한 User객체이므로 클라이언트쪽에서 응답 받은 객체를 잘 전달받는 것을 알 수 있습니다.
지금까지 정리
UriComponentBuilder를 통해 요청 보낼 uri 주소를 정의할 수 있습니다. (쿼리파라미터, 패스파라미터 지정이 가능합니다.)
RestTemplate 객체의 xxxforObject, xxxforEntity를 통해 요청을 보낼 수 있습니다. 이 때 반환 받을 객체의 타입을 지정해주어야 합니다.
xxxForEntity의 경우 ResponseEntity로 객체가 감싸진 상태이기 때문에 응답 관련 내용을 담고 있습니다.
postForEntity
post 요청의 body에 들어갈 객체를 선언해줍니다.
UserRequest
@Data @NoArgsConstructor public class UserRequest{ private String name; private int age; }
client RestTemplateService
@Service public class RestTemplateService{ public UserResponse post(){ Uri uri = UriComponentBuilder .fromUriString("http://localhost:9090") .path("/api/server/user/{userId}/name/{userName}") .encode() .build() .expand(100,"name1") .toUri(); UserRequest req = new UserRequest(); req.setName("name1"); req.setAge(10); RestTemplate restTemplate = new RestTemplate(); ResponseEntity<UserResponse> response = restTemplate.postForEntity(uri, req, UserResponse.class); return response.getBody(); } }
UriComponentBuilder를 통해 path variable을 넣어줄 수 있습니다. 각 path variable을 중괄호로 표시하고 expand 메서드에 차례대로 넣어주면 됩니다.
내가 보내고 싶은 데이터를 body에 넣어서 보내줍니다. 이 데이터는 object이고 objectMapper가 알아서 json으로 바꾼 뒤 restTemplate이 http body에 넣어줍니다.
postForEntity를 통해 post http 요청을 보냅니다. 들어가는 인자는 순서대로 uri, body에 들어갈 객체, 응답 객체 타입이 들어갑니다.
Client ApiController
@RestController @RequestMapping("/api/client") @RequestArgsConstructor public class ApiController { private final RestTemplateService restTemplateService; @GetMapping("/hello") public String getHello(){ return restTemplateService.post(); } }
Server ApiController
@RestController @RequestMapping("/api/server") public class ServerApiController{ @PostMapping("/user/{userId}/name/{userName}") public User hello(@PathVariable int userId, @PathVariable String userName, @RequestBody User user){ return user; } }
위와 같이 postForEntity 메서드를 통해 post http 요청을 보낼 수 있습니다.
Request Body에 들어갈 객체를 정의하고 이를 넣어주면 ObjectMapper를 통해 json으로 변환이 되므로 우리는 객체를 정의해서 넣어주기만 하면 됩니다.
UriComponentBuilder를 통해 pathVariable를 넣어줄 수 있었는데, expand 메서드에 순서대로 값을 넣어줌으로써 생성해줄 수 있었습니다.
RestTemplate의 exchange
보통 서버에 요청을 보낼 경우 header의 값을 조작하는 경우가 많습니다. 이런 경우에 대해서 어떻게 하는지 알아보도록 하겠습니다.
Client ResTemplateService
@Service public class RestTemplateService{ public UserResponse exchange(){ Uri uri = UriComponentBuilder .fromUriString("http://localhost:9090") .path("/api/server/user/{userId}/name/{userName}") .encode() .build() .expand(100,"name1") .toUri(); UserRequest req = new UserRequest(); req.setName("name1"); req.setAge(10); RequestEntity<UserRequest> requestEntity = RequestEntity .post(uri) .contentType(MediaType.APPLICATION_JSON) .header("x-authorization","abcd") .header("custom-header", "efgh") .body(req); RestTemplate restTemplate = new RestTemplate(); ResponseEntity<UserResponse> response = restTemplate.exchange(requestEntity, UserResponse.class); return response.getBody(); } }
exchange의 경우 RequestEntity 객체를 인자로 받아 전송할 수 있습니다.
RequestEntity를 선언한 뒤 uri나 body를 넣어줄 수 있는 것은 물론이고 content type이나 header값들을 조작할 수도 있습니다.
exchange 메서드에 requestEntity와 응답 객체 타입을 지정해주면 됩니다.
Server ApiController
@RestController @RequestMapping("/api/server") public class ServerApiController{ @PostMapping("/user/{userId}/name/{userName}") public User hello(@PathVariable int userId, @PathVariable String userName, @RequestBody User user, @RequestHeader("x-authorization") String authorization @RequestHeader("custom-header") String customHeader ){ return user; } }
server의 api 상에서 @RequestHeader를 통해 header값을 조회해볼 수 있습니다.
형식이 정해진 json을 주고 받을 경우
요청 응답 json 형태
{ "header" : { "responseCode": "", }, "body" : { } }
위와 같이 요청과 응답에서 header와 body를 공통적으로 가지는 json 형태로 데이터를 주고 받을 경우 이 json의 형태를 default로 해서 객체를 주고 받을 수 있습니다.
Req 객체(공통 json 형태)
@Data @AllArgsConstructor @NoArgsConstructor public class Req<T>{ private Header header; private T resBody; @Data @AllArgsConstructor @NoArgsConstructor public static class Header{ private String responseCode; } }
body의 형태는 계속해서 바뀌기 때문에 제너릭 타입으로 선언해줍니다.
Header는 내부 클래스로 정의해줍니다.
위의 Req 객체를 Server와 Client 둘 다 선언해줍니다.
client의 RestTemplateService
@Service public class RestTemplateService{ public UserResponse exchange(){ Uri uri = UriComponentBuilder .fromUriString("http://localhost:9090") .path("/api/server/user/{userId}/name/{userName}") .encode() .build() .expand(100,"name1") .toUri(); UserRequest userRequest = new UserRequest(); userRequest.setName("name1"); userRequest.setAge(100); Req<UserRequest> req = new Req<>(); req.setHeader(new Req.Header()); req.setBody(userRequest); RequestEntity<Req<UserRequest>> requestEntity = RequestEntity .post(uri) .contentType(MediaType.APPLICATION_JSON) .header("x-authorization","abcd") .header("custom-header", "efgh") .body(req); RestTemplate restTemplate = new RestTemplate(); ResponseEntity<Req<UserResponse>> response = restTemplate.exchange(requestEntity, new ParameterizedTypeReference<Req<UserResponse>>(){}); return response.getBody().getResBody(); } }
현재 지정해준 요청 응답 객체인 Req를 활용해 RequestEntity를 구성해줍니다.
반환받을 타입 또한 Req로 한 번 감싼 UserResponse로 지정해줍니다. 제너릭 타입은 클래스를 붙여서 사용할 수 없으므로 ParameterizedTypeReference로 응답 객체 타입을 지정해줍니다.
응답 받은 객체가 Req<UserResponse> 이므로 실제 UserResponse 객체를 얻기 위해서 한 번 더 getResBody를 해주어야 합니다.
Server ApiController
@RestController @RequestMapping("/api/server") public class ServerApiController{ @PostMapping("/user/{userId}/name/{userName}") public Req<User> post(@PathVariable int userId, @PathVariable String userName, @RequestBody Req<User> user, @RequestHeader("x-authorization") String authorization @RequestHeader("custom-header") String customHeader ){ Req<User> response = new Req<>(); response.setHeader(new Req.Header()); response.setBody(user.getResBody()); return response; } }
요청에 담겨있는 body의 타입을 Req로 한 번 감싸주고 응답으로 반환하는 객체 또한 Req로 감싸줌으로써 공통 형태로 데이터를 주고 받을 수 있도록 해줍니다.
배운 점
1. UriComponentBuilder를 통해 요청 보내고 싶은 uri를 생성할 수 있습니다. (쿼리 파라미터, 패스 파라미터 지정 가능)
2. RestTemplate을 통해 다른 서버로의 요청을 보낼 수 있습니다. (xxxForObject, xxxforEntity, exchange 각각의 메서드 특징을 알아두어야 합니다.)
3. RequestEntity는 요청에 대한 header값 등을 조작할 수 있도록 해줍니다. ResponseEntity는 응답과 관련된 내용들을 확인할 수 있습니다.
반응형LIST'백엔드' 카테고리의 다른 글
Spring swagger 적용 방법 (springdoc-openapi) (0) 2023.09.18 Spring Boot 독립된 test DB 구성 (MySQL, H2) (0) 2023.09.15 Spring filter, interceptor (0) 2023.09.14 Spring Validation (0) 2023.09.13 Jpa Embedded (0) 2023.09.06