ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Rest Template으로 Server(Client) to Server 통신하기
    백엔드 2023. 9. 15. 10:49
    728x90
    반응형
    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

    댓글

Designed by Tistory.