[TIL]22.12.19 3Tier, @controller 와@RestController동작원리, ResponseEntity, Httpstatus

hyewon jeong·2022년 12월 19일
1

TIL

목록 보기
4/138
post-thumbnail

3Tier

https://velog.io/@wonizizi99?tag=3Tier

동작원리 알아보기

@Controller 사용시 500이슈 생김 MVC
.html .thymeleaf 등 회원가입.html등 뷰를 찾는데 뷰가 없기 때문에 에러발생
@ResponseBody 달면 뷰를 찾지 않고 json형식을 찾게 되어 정상적으로 동작함 또는

@Controller 대신
@RestController 바꾸면 정상 작동함

아래의 글 출처:https://mangkyu.tistory.com/49

1. @Controller 이해하기

[ Controller로 View 반환하기 ]
전통적인 Spring MVC의 컨트롤러인 @Controller는 주로 View를 반환하기 위해 사용합니다. 아래와 같은 과정을 통해 Spring MVC Container는 Client의 요청으로부터 View를 반환합니다.

  1. Client는 URI 형식으로 웹 서비스에 요청을 보낸다.
  2. DispatcherServlet이 요청을 위임할 HandlerMapping을 찾는다.
  3. HandlerMapping을 통해 요청을 Controller로 위임한다.
  4. Controller는 요청을 처리한 후에 ViewName을 반환한다.
  5. DispatcherServlet은 ViewResolver를 통해 ViewName에 해당하는 View를 찾아 사용자에게 반환한다.

Controller가 반환환 뷰의 이름으로부터 View를 렌더링하기 위해서는 ViewResolver가 사용되며, ViewResolver 설정에 맞게 View를 찾아 렌더링합니다.



[ Controller로 Data 반환하기 ]

하지만 Spring MVC의 컨트롤러를 사용하면서 Data를 반환해야 하는 경우도 있습니다. 컨트롤러에서는 데이터를 반환하기 위해 @ResponseBody 어노테이션을 활용해주어야 합니다. 이를 통해 Controller도 Json 형태로 데이터를 반환할 수 있습니다.

  1. Client는 URI 형식으로 웹 서비스에 요청을 보낸다.
  2. DispatcherServlet이 요청을 위임할 HandlerMapping을 찾는다.
  3. HandlerMapping을 통해 요청을 Controller로 위임한다.
  4. Controller는 요청을 처리한 후에 객체를 반환한다.
  5. 반환되는 객체는 Json으로 Serialize되어 사용자에게 반환된다.


컨트롤러를 통해 객체를 반환할 때에는 일반적으로 ResponseEntity로 감싸서 반환을 합니다. 그리고 객체를 반환하기 위해서는 viewResolver 대신에 HttpMessageConverter가 동작합니다. HttpMessageConverter에는 여러 Converter가 등록되어 있고, 반환해야 하는 데이터에 따라 사용되는 Converter가 달라집니다. 단순 문자열인 경우에는 StringHttpMessageConverter가 사용되고, 객체인 경우에는 MappingJackson2HttpMessageConverter가 사용되며, 데이터 종류에 따라 서로 다른 MessageConverter가 작동하게 됩니다. Spring은 클라이언트의 HTTP Accept 헤더와 서버의 컨트롤러 반환 타입 정보 둘을 조합해 적합한 HttpMessageConverter를 선택하여 이를 처리합니다. MessageConverter가 동작하는 시점은 HandlerAdapter와 Controller가 요청을 주고 받는 시점이다. 그림의 4번에서는 메세지를 객체로, 6번에서는 객체를 메세지로 변환하는데 메세지 컨버터가 사용된다.

[ @Controller 예제 코드 ]

@Controller
@RequiredArgsConstructor
public class UserController {

private final UserService userService;

@GetMapping(value = "/users")
public @ResponseBody ResponseEntity<User> findUser(@RequestParam("userName") String userName){
    return ResponseEntity.ok(userService.findUser(user));
}

@GetMapping(value = "/users/detailView")
public String detailView(Model model, @RequestParam("userName") String userName){
    User user = userService.findUser(userName);
    model.addAttribute("user", user);
    return "/users/detailView";
}

}

위 예제의 findUser는 User 객체를 ResponseEntity로 감싸서 반환하고 있고, User를 json으로 반환하기 위해 @ResponseBody라는 어노테이션을 붙여주고 있습니다. detailView 함수에서는 View를 전달해주고 있기 때문에 String을 반환값으로 설정해주었습니다. (물론 이렇게 데이터를 반환하는 RestController와 View를 반환하는 Controller를 분리하여 작성하는 것이 좋습니다.)



2. @RestController 이해하기

[ RestController ]

@RestController는 @Controller에 @ResponseBody가 추가된 것입니다. 당연하게도 RestController의 주용도는 Json 형태로 객체 데이터를 반환하는 것입니다. 최근에 데이터를 응답으로 제공하는 REST API를 개발할 때 주로 사용하며 객체를 ResponseEntity로 감싸서 반환합니다. 이러한 이유로 동작 과정 역시 @Controller에 @ReponseBody를 붙인 것과 완벽히 동일합니다.

  1. Client는 URI 형식으로 웹 서비스에 요청을 보낸다.
  2. DispatcherServlet이 요청을 위임할 HandlerMapping을 찾는다.
  3. HandlerMapping을 통해 요청을 Controller로 위임한다.
  4. Controller는 요청을 처리한 후에 객체를 반환한다.
  5. 반환되는 객체는 Json으로 Serialize되어 사용자에게 반환된다.

[ @RestController 예제 코드 ]

@RestController
@RequestMapping("/user")
@RequiredArgsConstructor
public class UserController {

private final UserService userService;

@GetMapping(value = "/users")
public User findUser(@RequestParam("userName") String userName){
    return userService.findUser(user);
}

@GetMapping(value = "/users")
public ResponseEntity<User> findUserWithResponseEntity(@RequestParam("userName") String userName){
    return ResponseEntity.ok(userService.findUser(user));
  }
}

findUser는 User 객체를 그대로 반환하고 있습니다. 이러한 경우의 문제는 클라이언트가 예상하는 HttpStatus를 설정해줄 수 없다는 것입니다. 예를 들어 어떤 객체의 생성 요청이라면 201 CREATED를 기대할 것이지만 객체를 그대로 반환하면 HttpStatus를 설정해줄 수 없습니다. 그래서 객체를 상황에 맞는 ResponseEntity로 감싸서 반환해주어야 합니다.

출처:https://mangkyu.tistory.com/49

REST는 Representational State Transfer

REST 구성

쉽게 말해 REST API는 다음의 구성으로 이루어져있습니다. 자세한 내용은 밑에서 설명하도록 하겠습니다.

자원(RESOURCE) - URI
행위(Verb) - HTTP METHOD
표현(Representations)

REST API 디자인 가이드

REST API 설계 시 가장 중요한 항목은 다음의 2가지로 요약할 수 있습니다.

첫 번째, URI는 정보의 자원을 표현해야 한다.
두 번째, 자원에 대한 행위는 HTTP Method(GET, POST, PUT, DELETE)로 표현한다.

다른 것은 다 잊어도 위 내용은 꼭 기억하시길 바랍니다.

ResponseEntity ?

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

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

  • ResponseEntity에는 사용자의 HttpRequest에 대한 응답 데이터가 포함된다.
  • 활용 : 에러코드와 같은 HTTP 상태코드를 전송하고 싶은 데이터와 함께 전송 할 수 있기 때문에 좀 더 세밀한 제어가 필요한 경우 사용한다.
    ResponseEntity 클래스를 사용하면, 결과값! 상태코드! 헤더값!을 모두 프론트에 넘겨줄 수 있고, 에러코드 또한 섬세하게 설정해서 보내줄 수 있다는 장점이 있다!
public class HttpEntity<T> {

	private final HttpHeaders headers;

	@Nullable
	private final T body;
}
public class RequestEntity<T> extends HttpEntity<T>

public class ResponseEntity<T> extends HttpEntity<T>

ResponseEntity 구조

ResponseEntity는 httpEntity를 상속받고 사용자의 응답 데이터가 포함된 클래스이기 때문에
HttpStatus
HttpHeaders
HttpBody
를 포함한다.

구현된 인터페이스를 보면 ,< 바디, 헤더,상태코드>순의 생성자가 만들어지는 것을 확인할 수 있다.

HTTP header와 body의 차이점

http header에는 (요청/응답)에 대한 요구사항이
http body에는 그 내용이 적혀 있음

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

상태코드(Status), 헤더(headers), 응답데이터(ResponseData)를 담는 생성자도 존재한다.

public class ResponseEntity<T> extends HttpEntity<T> {
	
	public ResponseEntity(@Nullable T body, @Nullable MultiValueMap<String, String> headers, HttpStatus status) {
		super(body, headers);
		Assert.notNull(status, "HttpStatus must not be null");
		this.status = status;
	}
}

그리고 이제 ResponEntity를 이용해서 클라이언트에게 응답을 보내는 예제를 정리해보자.

import lombok.Data;

@Data
public class Message{
 private StatusEnum status;
 private String message;
 private Object data;

 
 public Message(){
 	this.status = StatusEnum.BAD_REQUEST;
    this.data = null;
    this.message = null;
  }
}

Message라는 클래스를 만들어 상태코드, 메세지, 데이터를 담을 필드를 추가한다.

public enum StatusEnum{
	OK(200,"OK"),
   BAD_REQUEST(400,"BAD_REQUEST"),
   NOT_FOUND(404,"NOT FOUND"),
   INTERNAL_SERVER_ERRPR(500,"INTERNAL_SERVER_ERROR");
   
   int statusCode;
   String code;
   
   StatusEnum(int statusCode, String code){
   	this.statusCode = statusCode;
       this.code = code;
   }
}

그리고 상태코드로 보낼 몇가지의 예시만 적어놓은 enum을 만들었다.

@RestController
public class UserController{
	Private UserDaoService userDaoService;
   
   public UserController(UserDaoService userDaoService){
   	this.userDaoService = userDaoService;
   }
   
   @GetMapping(value = "/user/{id}")
   public ResponseEntity<Message> findById(@PathVariable int id){
   	User user = userDaoService.findOne(id);
       Message message = new Message();
       HttpHeaders headers = new HttpHeaders();
       headers.setContentType(new MediaType("application", "json",Charset.forName("UTP-8")));
       
       message.setStatus(StatusEnum.OK);
       message.setMessage("성공코드");
       message.setData(user);
       
       return new ResponseEntity<>(message, heade
}

그리고 위와 같이 Controller를 하나 만든 후 , id를 통해서 User를 가져오고 Message클래스를 통해서 StatusCode, ResponseMessage,ResponseData를 담아서 클라이어트에게 응답을 보내는 코드임

22.12.20

🔺 위와 같이 코드 구현시 에러 발생하여 수정 해 봄

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/user")
public class UserController {

    private final UserService userService;
    private final UserRepository userRepository;
    private org.springframework.http.ResponseEntity<Message> ResponseEntity;

    //상태코드 구현 추후 구현 안 될시 지울 예정???????????????????
    @GetMapping(value = "/{id}")
    public ResponseEntity<Message>findById(@PathVariable long id) {
        Optional<User> user = userRepository.findById(id);
        Message message = new Message();
        
        //이 부분 수정함
      HttpHeaders headers = new HttpHeaders();
        Charset utf8 = Charset.forName("UTF-8");
        MediaType mediaType = new MediaType("application", "json", utf8);
        headers.setContentType(mediaType);

        message.setStatus(StatusEnum.OK);
        message.setMessage("회원가입 성공");
        message.setData(user);

        return ResponseEntity;
    }

구현 후 에러 없어졌고 run했을시 문제가 되지 않았지만 아직 테스트 하기 전이라 한 번더 봐야 한다.

❔❓ headers.setContentType (new MediaType("application", "json",Charset.forName("UTP-8"))); 의미는?? 공부중

HTTP 헤더 확인
질문: 웹 문서의 HTTP 헤더에서 문자 인코딩 정보를 확인하려면 어떻게 합니까?

웹에서 제공되는 문서의 문자 인코딩(charset)을 명확히 지정하는 것이 중요합니다. 그렇지 않을 경우, 수신인이 문서를 제대로 해석하지 못할 수 있습니다. 예를 들어, 웹 브라우저가 판독 가능한 텍스트 대신 임의의 문자를 표시할 수 있습니다. 웹 문서의 문자 인코딩을 지정하는 한 가지 방법은 Content-Type 헤더의 charset 매개변수에 이 정보를 넣는 것입니다.

특히, HTTP 헤더에 선언된 인코딩이 HTML 및 CSS 파일의 모든 문서내 인코딩 선언을 덮어쓰는 것이 중요합니다.

참고 ResponseEntity
참고 ResponseEntity이용법


Authorization 헤더는 인증 토큰(JWT든, Bearer 토큰이든)을 서버로 보낼 때 사용하는 헤더입니다. API 요청같은 것을 할 때 토큰이 없으면 거절당하기 때문에 이 때, Authorization을 사용하면 됩니다.

Authorization: Bearer XXXXXXXXXXXXX
보통 Basic이나 Bearer같은 토큰의 종류를 먼저 알리고 그 다음에 실제 토큰 문자를 적어 보냅니다.

profile
개발자꿈나무

1개의 댓글

comment-user-thumbnail
2022년 12월 20일

우와 첫캡쳐화면 직접 제작하신 건가요?? 저도 프로그램 알려주세요 ㅎㅎ
혜원님 힘드셨을텐데 벨로그에 글 많이많이 남겨주세요~~

답글 달기