백엔드

Rest Template으로 Server(Client) to Server 통신하기

cottoncover 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