Spring/Spring Boot

ResponseEntity란 - 개념, 구조, 사용법, 사용하는 이유

민돌v 2022. 1. 16. 01:32
728x90

 

 

이거 옜날에 적은건데,, 꾸준히 블로그로 유입되시는 분들이 계셔서

부끄러운 마음에 내용을 조금 더 추가해 보았습니다..ㅎㅎ

앞으로도 계속 변경되거나, 알게된 내용 추가할 예정이고, 틀린부분이나 부족한 부분이 있다면 알려주시면 감사하겠습니다~

 


ResponseEntity란

ResponseEntity란, httpentity를 상속받는, 결과 데이터와 HTTP 상태 코드를 직접 제어할 수 있는 클래스이다.

ResponseEntity에는 사용자의  HttpRequest에 대한 응답 데이터가 포함된다.

 

✨ 또한, HTTP 아케텍쳐 형태에 맞게 Response를 보내주는 것에도 의미가 있습니다.

 

에러 코드와 같은 HTTP상태 코드를 전송하고 싶은 데이터와 함께 전송할 수 있기 때문에 좀 더 세밀한 제어가 필요한 경우 사용한다고 합니다.

 

ReponseEntity 구조

ResponseEntity는 HttpEntity를 상속받고 사용자의 응답 데이터가 포함된 클래스이기 때문에

HttpStatus

HttpHeaders

HttpBody

를 포함한다. 

구현된 인터페이스를 살펴보면, 이런식으로 <바디, 헤더, 상태코드> 순의 생성자가 만들어지는 걸 확인하였습니다.

 

HTTP header 와 body의 차이점

  • 짧게 HTTP header 와 body를 살펴보면 (내가 몰라서..ㅠ)

 

http header에는 (요청/응답)에 대한 요구사항이

http body에는 그 내용이 적혀있고,


Response header 에는 웹서버가 웹브라우저에 응답하는 메시지가 들어있고, Reponse body에 데이터 값이 들어가있다고 합니다.

 

* response header form

: 웹브라우저가 요청한 메시지에 대해서 status 즉 성공했는지 여부(202, 400 등), 메시지, 그리고 요청한 응답 값들이 body에 담겨있다.

  • Location : 301, 302 상태코드일 떄만 볼 수 있는 헤더로 서버의 응답이 다른 곳에 있다고 알려주면서 해당 위치(URI)를 지정한다.
  • Server : 웹서버의 종류 ex) nginx
  • Age : max-age 시간내에서 얼마나 흘렀는지 초 단위로 알려주는 값
  • Referrer-policy : 서버 referrer 정책을 알려주는 값 ex) origin, no-referrer, unsafe-url
  • WWW-Authenticate : 사용자 인증이 필요한 자원을 요구할 시, 서버가 제공하는 인증 방식
  • Proxy-Authenticate : 요청한 서버가 프록시 서버인 경우 유저 인증을 위한 값

https://hazel-developer.tistory.com/145

* 응답 상태코드

  • 100 - 109
  • 200 - 206
  • 300 - 305
  • 400 - 415
  • 500 - 505

API 개발 시 올바른 상태코드를 응답하는 것은 매우 중요하다. 예를 들어, 사용자가 요청 파라미터를 잘못 입력한 경우,  잘못된 파라미터로 인해 비즈니스 로직에서 에러가 발생했다고하여, 500 코드를 반환하면 안된다. 사용자가 잘못 입력한 경우이므로, 이 때는 403 코드를 반환해야 한다.

 

 

 결론은 ResponseEntity 클래스를 사용하면, 결과값! 상태코드! 헤더값!을 모두 프론트에 넘겨줄 수 있고, 에러코드 또한 섬세하게 설정해서 보내줄 수 있다는 장점이 있다!

 

 

예시

Controller

@RestController
@RequiredArgsConstructor
public class ResponseEntityController {

    private final ResponseEntityService service;

    @GetMapping("/user/{id}")
    public ResponseEntity<MyDto> findByid(@PathVariable Long id) {
        User user = service.getUser();
        MyDto dto = new MyDto();

        HttpHeaders header = new HttpHeaders();
        header.setContentType(new MediaType("application", "json", Charset.forName("UTF-8")));

        dto.setStatus(StatusEnum.OK);
        dto.setData(user);
        dto.setMessage("메세지메세지!");

        return new ResponseEntity<>(dto, header, HttpStatus.OK);
    }
}

 

 

 


 

ResponseEntity 사용방법

ResponseEntity의 사용방법은 생가보다 간단합니다.

이미 String 내부에서 친절하게 ResponseEntity 객체가 구현이 되어있고, 우리는 그것을 보고, 그대에에에로 사용하면됩니다.

 

아래에, 클래스 내부 구현된 생성자들을 한번 보았습니다.

친절하게도, 생성자를 아주 자세히 다양하게 작성해 주어서, status값만 넣거나, body만 넣어도 ResponseEntity의 나머지 값이 null로 아주 예쁘게 들어갑니다.

public class ResponseEntity<T> extends HttpEntity<T> {

   private final Object status;

   /**
    * Create a {@code ResponseEntity} with a status code only.
    * @param status the status code
    */
   public ResponseEntity(HttpStatus status) {
      this(null, null, status);
   }

   /**
    * Create a {@code ResponseEntity} with a body and status code.
    * @param body the entity body
    * @param status the status code
    */
   public ResponseEntity(@Nullable T body, HttpStatus status) {
      this(body, null, status);
   }

   /**
    * Create a {@code ResponseEntity} with headers and a status code.
    * @param headers the entity headers
    * @param status the status code
    */
   public ResponseEntity(MultiValueMap<String, String> headers, HttpStatus status) {
      this(null, headers, status);
   }

   /**
    * Create a {@code ResponseEntity} with a body, headers, and a status code.
    * @param body the entity body
    * @param headers the entity headers
    * @param status the status code
    */
   public ResponseEntity(@Nullable T body, @Nullable MultiValueMap<String, String> headers, HttpStatus status) {
      this(body, headers, (Object) status);
   }

   /**
    * Create a {@code ResponseEntity} with a body, headers, and a raw status code.
    * @param body the entity body
    * @param headers the entity headers
    * @param rawStatus the status code value
    * @since 5.3.2
    */
   public ResponseEntity(@Nullable T body, @Nullable MultiValueMap<String, String> headers, int rawStatus) {
      this(body, headers, (Object) rawStatus);
   }

   /**
    * Private constructor.
    */
   private ResponseEntity(@Nullable T body, @Nullable MultiValueMap<String, String> headers, Object status) {
      super(body, headers);
      Assert.notNull(status, "HttpStatus must not be null");
      this.status = status;
   }
 }

 


 

✅생성자를 이용한 ResponseEntity 작성 예시

@PostMapping("/post/like")
public ResponseEntity<SetLikeDto.Response> updateLike(@RequestBody SetLikeDto.Request postLikeDto, @AuthenticationPrincipal UserDetailsImpl userDetails){
    Post post = postService.setPostLike(postLikeDto,userDetails.getUser());

    SetLikeDto.Response response = modelMapper.map(post, SetLikeDto.Response.class);

    return new ResponseEntity<>(HttpStatus.OK);
    return new ResponseEntity<>(response, HttpStatus.ACCEPTED);
    //지금은 header에 빈 값이 들어간, 비어있는 객체 -> 필요하다면 내용을 채워서 이렇게 생성해줄 수 있다.
    return new ResponseEntity<>(response, new HttpHeaders(HttpHeaders.EMPTY), HttpStatus.ACCEPTED);
}

 

✨ 하지만 이것보다 조금 더 가독성이 좋고, 메모리를 덜  사용하는 방법이 있습니다.

  • ResponseEntity는 내부에 정적 팩토리 메소드를 구현해 놓았습니다.
  • 정적 팩토리 메소드를 사용하는게 무조건적으로 좋진 않지만, 미리 구현되어있다면, 사용해보는것도 좋겠죠?

 


 

✅팩토리 메소드를 이용한 가독성이 좋은 ResponseEntity 작성 예시

  •  대에충 아래 내용을 보면, 상태코드를 바탕으로 빌더 패턴을 구현해 놓았습니다.
  • 보기좋게 사용해봅시다!
/**
 * Create a builder with the given status.
 * @param status the response status
 * @return the created builder
 * @since 4.1
 */
public static BodyBuilder status(int status) {
   return new DefaultBuilder(status);
}

/**
 * Create a builder with the status set to {@linkplain HttpStatus#OK OK}.
 * @return the created builder
 * @since 4.1
 */
public static BodyBuilder ok() {
   return status(HttpStatus.OK);
}

/**
 * A shortcut for creating a {@code ResponseEntity} with the given body
 * and the status set to {@linkplain HttpStatus#OK OK}.
 * @param body the body of the response entity (possibly empty)
 * @return the created {@code ResponseEntity}
 * @since 4.1
 */
public static <T> ResponseEntity<T> ok(@Nullable T body) {
   return ok().body(body);
}

/**
 * A shortcut for creating a {@code ResponseEntity} with the given body
 * and the {@linkplain HttpStatus#OK OK} status, or an empty body and a
 * {@linkplain HttpStatus#NOT_FOUND NOT FOUND} status in case of an
 * {@linkplain Optional#empty()} parameter.
 * @return the created {@code ResponseEntity}
 * @since 5.1
 */
public static <T> ResponseEntity<T> of(Optional<T> body) {
   Assert.notNull(body, "Body must not be null");
   return body.map(ResponseEntity::ok).orElseGet(() -> notFound().build());
}

/**
 * Create a new builder with a {@linkplain HttpStatus#CREATED CREATED} status
 * and a location header set to the given URI.
 * @param location the location URI
 * @return the created builder
 * @since 4.1
 */
public static BodyBuilder created(URI location) {
   return status(HttpStatus.CREATED).location(location);
}

 


 

✅상태코드만 반환해 줄 때

@PostMapping("/post/like")
public ResponseEntity<SetLikeDto.Response> updateLike(@RequestBody SetLikeDto.Request postLikeDto, @AuthenticationPrincipal UserDetailsImpl userDetails){
    Post post = postService.setPostLike(postLikeDto,userDetails.getUser());

    SetLikeDto.Response response = modelMapper.map(post, SetLikeDto.Response.class);

    return ResponseEntity.ok().build();
}

 


 

✅Body까지 보내줄 때

//좋아요
@PostMapping("/post/like")
public ResponseEntity<SetLikeDto.Response> updateLike(@RequestBody SetLikeDto.Request postLikeDto, @AuthenticationPrincipal UserDetailsImpl userDetails){
    Post post = postService.setPostLike(postLikeDto,userDetails.getUser());

    SetLikeDto.Response response = modelMapper.map(post, SetLikeDto.Response.class);

    return ResponseEntity.ok(response);
}

 

 

 


 

ResponseEntity 주의할 점

생성까지 간단하게 보았고, 이제 조금 더 생각을 해볼까 합니다.

사실은 사수님한테 혼난 부분입니다..ㅋㅋㅋ

 

자바 제네릭의 특징으로 와일드 카드라는게 았습니다.

ResponseEntity도 제네릭 타입으로, 컴파일 전에 미리 데이터타입을 명시하므로 와일드 카드를 사용할 수 있습니다.

 


와일드 카드란

  • 사실 별게아닙니다, 제네릭 타입으로 데이터타입을 명시하지않고. 런타임까지 유연하게 가져가겠다는 뭐 그런건데,
  • 그냥, 타입을 명시하지않고 되는데로 다 받아줄거다라고 생각하면 됩니다. 아래와 같이 사용됩니다.
@PostMapping("/post/like")
public ResponseEntity<?> updateLike(@RequestBody SetLikeDto.Request postLikeDto, @AuthenticationPrincipal UserDetailsImpl userDetails){
    Post post = postService.setPostLike(postLikeDto,userDetails.getUser());

    SetLikeDto.Response response = modelMapper.map(post, SetLikeDto.Response.class);

    return ResponseEntity.ok(response);
}

 


 

ResponseEntity 가 void를 처리할 때

  • 저는 와일드카드를 보통, 리턴 받는 바디가 없을 때, 즉 delete  처럼 void 를 처리할 때 해주었는데
  • 소트웍스 객체지향 생활체조, 소나린트, 객체지향 등등을 볼 때마다, 와일드카드를 사용하지 말라는 규칙이 나오곤 합니다.
  • 그래서 타입을 형식을 맞춰주기 위해, 와일드 카드를 사용하기 보다, Objects 로 데이터 타입을 명시해주는 것이 가독성 측면과, 혹시 모를 에러 측면, 유지보수 측면에서 더 났다고 합니다.

 

@PostMapping("/post/like")
public ResponseEntity<Objects> updateLike(@RequestBody SetLikeDto.Request postLikeDto, @AuthenticationPrincipal UserDetailsImpl userDetails){
    Post post = postService.setPostLike(postLikeDto,userDetails.getUser());

    SetLikeDto.Response response = modelMapper.map(post, SetLikeDto.Response.class);

    return ResponseEntity.ok(response);
}


//와일드카드 대신 이렇게도 할 수 있지만, 타입을 명시해주는게 더 좋습니다.
@PostMapping("/post/like")
    public ResponseEntity updateLike(@RequestBody SetLikeDto.Request postLikeDto, @AuthenticationPrincipal UserDetailsImpl userDetails){
        Post post = postService.setPostLike(postLikeDto,userDetails.getUser());

        SetLikeDto.Response response = modelMapper.map(post, SetLikeDto.Response.class);

        return ResponseEntity.ok(response);
    }

 

 

 

이상으로 ResponseEntity 간단 가이드 끝입니다!

 

 

 

 

 


참고

반응형