본문 바로가기
Spring

[Spring] 🌱ResponseEntity란 / Spring Boot에서 Response하기

by ro117youshin 2022. 9. 4.
728x90


 웹 서비스에서는 많은 정보를 송수신하게 된다. 각각의 다른 웹 서비스들이 대화하려면, 서로 정해진 약속에 맞게 데이터를 가공해서 보내야 한다. 보내는 형식을 우리는 HTTP(HyperText Transport Protocol)이라고 한다. 
Spring에서도 마찬가지로 HTTP에 맞게 데이터를 송수신해야 한다. 요청에 대한 응답을 HTTP형식으로 코드로 직접 작성하는 것은 쉬운 일이 아니다. 이를 만들어주는 ResponseEntity를 통해서, 빠르게 쉽게 규격에 맞는 HTTP응답을 생성해보자.

HTTP란?

HTTPHyperText Transfer Protocol의 약자로, Client와 Server사이에 요청과 응답을 처리하는 규약이다. 해당 규약을 지키게 된다면 살펴보는 것만으로도 어떤 요청을 하는지에 대해서 간략하게 알 수가 있다. HTTP는 요청과 응답 모두 크게 세 가지 요소로 구성된다.

 

먼저, HTTP 요청을 알아보면 Start Line, Headers, Body 세 가지 요소로 구성된다.

  1. Start Line은 method, URL 그리고 version으로 이루어져 있으며, 서버에서 요청을 받아들이는 첫 줄이다.
  2. Headers는 요청에 대한 접속 운영체제, 브라우저, 인증 정보와 같은 부가적인 정보를 담고 있다.
  3. Body는 요청에 관련된 json, html과 같은 구체적인 내용을 포함한다.

HTTP 응답은 다른 요소인 Status Line과 요청도 가지고 있는 Headers, Body 로 구성된다.

  • Status Line은 HTTP버전과 함께 헤딩 요청에 대한 처리의 상태를 나타낸다. 200, 404와 같은 숫자 코드로 동시에 나타낸다.

ResponseEntity 

ResponseBody와 마찬가지로 HTTP 응답을 빠르게 만들어주기 위한 객체이다. @ResponseBody와 달리 Annotation이 아닌 객체로 사용된다. 즉, 응답으로 변환될 정보를 모두 담은 요소들을 객체로 만들어서 반환해준다. 객체의 구성요소에서 HttpMessageConverter는 응답이 되는 본문을 처리해준 뒤에, RESTTemplate에 나머지 구성 요소인 Status를 넘겨주게 된다.

 

Spring Framework에서 제공하는 클래스 중에서 HttpEntity라는 클래스가 존재한다.

이것은 HTTP 요청(Request) 또는 응답(Reponse)에 해당하는 HttpHeader와 HttpBody를 포함하는 클래스이다.

 

public class ResponseEntity<T> extends HttpEntity<T> {
	
    private final Object status;
}

 

먼저, ResponseEntity의 구조를 보게 되면, 위와 같이 Status만 필드 값으로 가지고 있다. ResponseEntity 에서 직접적으로 Status Code 를 지정할 수 있다는 것을 의미한다. 나머지 부분은 HttpEntity 에 구현이 되어있는데, 이는 RequestEntity 와 여러 설정들을 공유하기 때문이다. 다음은 HttpEntity 의 구현 부분을 보도록 하겠다.

 

public class HttpEntity<T> {
    public static final HttpEntity<?> Empty = new HttpEntity<>();
    
    private final HttpHeaders headers;
    
    @Nullable
    private final T body;
}

 

이와 같이 ResponseEntity 는 HttpEntity 를 상속하여 구현이 된다. HttpEntity 에서는 Generic 타입으로 Body 가 될 필드 값을 가질 수가 있다. Generic 타입으로 인하여 바깥에서 Wrapping 될 타입을 지정할 수가 있다. Wrapping 된 객체들은 자동으로 HTTP 규격에서 Body 에 들어갈 수 있도록 변환이 된다. 또한, 필드 타입으로 HttpHeaders 를 가지고 있는데, 이는 ResponseBody 와 다르게 객체 안에서 Header 를 설정해 줄 수 있음을 암시한다.


Constructor 를 활용하여 ResponseEntity 를 사용한 예시는 다음과 같다.

 

public ResponseEntity<MoveResponseDto> move(@PathVariable String name,
    	@RequestBody MoveDto moveDto) {
    	HttpHeaders headers = new HttpHeaders();
    	headers.set("Game", "Chess");
    
	    String command = makeMoveCmd(moveDto.getSource(), moveDto.getTarget());
	    springChessService.move(name, command, new Commands(command));
    
	    MoveResponseDto moveResponseDto = new MoveResponseDto(springChessService
	        .continuedGameInfo(name), name);

    	return new ResponseEntity<MoveResponseDto>(moveResponseDto, headers, HttpStatus.valueOf(200)); // ResponseEntity를 활용한 응답 생성
}

 

Spring 에서 위와 같이 HTTP 응답으로 반환할 메서드를 만들게 되었다.

이 때, 타입은 ResponseEntity<반환할 타입> 으로 지정한다. Constructor 를 사용시에 Body 부분, Header 그리고 상태로 지정될 Status 를 차례로 입력하여 생성하면 된다. 예시 코드에서는 moveResponseDto라는 객체가 Body 부분에 들어가서 응답으로 전송이 된다.

우리는 HTTP 응답에 필요한 요소들 중 대표적인 StatusHeader , Body 를 지정하여 응답을 만들 수가 있다.

 

Constructor보다는 Builder

이렇게 ResponseEntity 를 사용할 때, Constructor 를 사용하기보다는 Builder 를 활용하는 것을 권장하고 있다. 그 이유는 숫자로 된 상태 코드를 넣을 때, 잘못된 숫자를 넣을 수 있는 실수 때문이다. 따라서, Builder Pattern 를 활용하면 다음과 같이 코드를 변경할 수가 있다.

 

return new ResponseEntity<MoveResponseDto>(moveResponseDto, headers, HttpStatus.valueOf(200));

return ResponseEntity.ok()
      .headers(headers)
      .body(moveResponseDto);

 

이렇게 Builder Pattern 을 활용하면 각 상태에 매칭되는 숫자 코드를 외울 필요 없이 Builder 메소드를 선택하면 된다.

 


 

코드 예시

package com.example.swagger.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.example.swagger.domain.Member;
import com.example.swagger.service.MemberService;

@RestController
@RequestMapping("/members")
public class MemberController {

    @Autowired
    private MemberService memberService;

    public MemberController(MemberService memberService) {
        this.memberService = memberService;
    }
	
    /**
     * 모든 회원 정보를 가져오는 API
     * @return ResponseEntity<List<UserResponse>> 200 OK, 회원 정보 목록
     */
    @GetMapping("")
    public ResponseEntity<List<Member>> getAllMembers() {
        List<Member> memberList = memberService.findAll();
        return ResponseEntity.status(HttpStatus.OK).body(memberList);
    }
	
    /**
     * 회원 정보를 가져오는 API
     * @param memberId 회원의 ID
     * @return ResponseEntity<UserResponse> 200 OK, 회원 정보
     */
    @GetMapping("/{memberId}")
    public ResponseEntity<Member> getMember(@PathVariable("memberId") String memberId) {
        Member member = memberService.findByMemberId(memberId).get();
        return ResponseEntity.status(HttpStatus.NO_CONTENT).body(member);
    }
	
    /**
     * 회원 가입 API
     * @param userRequest 회원 정보
     * @return ResponseEntity<UserResponse> 201 Created, 가입된 회원의 정보
     */
    @PostMapping
    public ResponseEntity<Member> signUp(@RequestBody Member member) {
        Member signUpMember = memberService.save(member);
        return ResponseEntity.status(HttpStatus.CREATED).body(signUpMember);
    }
	
    /**
     * 회원 정보 수정 API
     * @param memberId 회원의 ID
     * @param member 회원 정보
     * @return ResponseEntity<UserResponse> 200 OK, 수정된 회원의 정보
     */
    @PutMapping("/{memberId}")
    public ResponseEntity<Member> update(@RequestBody Member member) {
        Member updateMember = memberService.update(member);
        return ResponseEntity.status(HttpStatus.OK).body(updateMember);
    }

	/**
     * 회원 탈퇴(삭제) API
     * @param memberId 회원의 ID
     * @return ResponseEntity<Object> 204 No Content
     */
    @DeleteMapping("/{memberId}")
    public ResponseEntity<Object> delete(@PathVariable("memberId") String memberId, Member member) {
        memberService.delete(memberId);
        return ResponseEntity.status(HttpStatus.NO_CONTENT).body(member);
    }

}

 

Reference

 

 

'Spring' 카테고리의 다른 글

[Spring] 🌱 AOP(Aspect Oriented Programming)  (0) 2022.07.24
[Spring] 🌱 HelloController  (0) 2022.07.22
[Spring] 🌱 Spring 이란 ( + DI, IoC)  (4) 2022.07.07

댓글