질문, 피드백 등 모든 댓글 환영합니다.
스프링에서 HTTP 요청을 처리하고 응답하는 방법을 정리하겠습니다.
Sevlet 같이 낮은 레벨에서 HTTP 요청을 다루는 방법보단 실제로 스프링(스프링부트)를 사용하여 개발할 때 요청을 처리하는 방법을 정리하겠습니다.
HTTP는 HyperText Transfer Protocol로 데이터를 전송하는 방식 중 하나입니다.
HTTP 요청은 크게 Start Line, Header, Message Body로 구분 할 수 있습니다.
Start-Line : HTTP 요청 메서드, 요청 경로, HTTP 버전 등을 포함하며 서버가 수행해야 할 동작을 지정
Header : - HTTP 전송에 필요한 부가 정보를 포함. ex) 메시지 바디의 내용, 크기, 압축, 인증, 브라우저 정보, 캐시 관리 정보 등
Message Body : Byte의 형태로 표현할 수 있는 모든 데이터
HTTP 요청과 응답은 대부분 컨트롤러를 통해 처리합니다.
컨트롤러 관련 어노테이션
@Controller : 해당 클래스를 스프링빈으로 등록하고 컨트롤러 계층으로 인식 (컴포넌트 스캔의 대상)
@RequestMaaping : 요청 URL을 매핑, 등록된 URL이 호출되면 해당 필드의 메서드 실행
@GetMapping, PostMapping, PutMapping, PatchMapping, DeleteMapping, : 각 HTTP 메서드 요청을 처리
@RequestParam : HTTP 요청 파라미터 매핑
@ModelAttribute : HTTP 요청 파라미터를 객체로 변환
@RequestBody : HTTP 요청 메시지 바디를 매핑
@ResponseBody : HTTP Message Body에 직접 입력 가능
스프링은 HTTP 요청에 대한 응답으로 브라우저에 정적인 html, css, js 등을 제공할 수 있습니다.
스프링 부트는 /static
, /public
, /resources
, /META-INF/resources
경로에 있는 정적 리소스를 참고할 수 있으며 기본적으론 src/main/resources/static
을 제공합니다.
src/main/resources/static/spring/basic.html
경로의 정적 리소스를
http://..../spring/basic.html
로 브라우저에서 접근 시 해당 리소스에 접근할 수 있습니다.
스프링은 뷰 템플릿을 거쳐 동적인 리소스를 제공합니다. (자바 템블릿 엔진으로 jsp, thymeleaf 등이 존재. 이 블로그에선 Thymeleaf를 예시로 사용)
주로 동적으로 html을 생성하여 사용하지만 뷰 템플릿으로 생성 가능한 모든 것을 응답 결과로 제공할 수 있습니다.
타임리프를 사용하는 경우 classpath가 기본으로 아래의 경로로 설정 되어있습니다.
prefix=classpath:/templates/
suffix=.html
스프링부트의 기본 뷰 템플릿 경로는 src/main/resources/templates
이며
src/main/resources/templates/spring/basic.html
을 응답 결과로 제공하기 위해선 컨트롤러를 사용 해야합니다.
ModelAndView 반환
@Controller
public class ExController {
@RequestMapping("/home")
public ModelAndView home() {
return new ModelAndView("spring/basic");
}
}
"/home"
경로로 요청이 발생하면 ModelAndView
를 반환하여 spring/basic.html
을 렌더링.
@RequestMapping("/home")
public ModelAndView home(Object object) {
ModelAndView modelAndView = new ModelAndView("spring/basic");
modelAndView.addObject("object", object); // 임의의 객체
return modelAndView;
}
addObject
를 이용하여 Model
에 데이터를 저장
하지만 이러한 방식은 개발자가 ModelAndView
를 직접 반환하기 때문에 불편합니다. 스프링은 HandlerAdaptor
를 통해 ModelAndView
를 직접 반환하지 않고 뷰의 논리이름을 반환할 수 있도록 지원합니다. 때문에 실제 개발 시 아래의 방식을 주로 사용합니다.
String 반환, HTTP 메서드 매핑 어노테이션 분리
@GetMapping("/home")
public String home() {
return "spring/basic";
}
@PostMapping("/home")
public String postHome(Model model, Object object) {
model.addAttribute("object", object); // 임의의 객체
return "spring/basic";
}
Get
과 Post
요청을 위와 같이 분리하여 처리
디스패처 서블릿으로부터 Model
을 파라미터로 받아 데이터를 저장, view
논리이름 반환 (만약 Model
을 사용할 필요가 없다면 생략 가능)
이 경우 spring/basic.html
을 전송
ex) request/hello
-> templates/request/hello.html
하지만 이러한 방식은 가독성이 떨어질 뿐만 아니라 실제로 리소스의 경로가 요청 경로와 일치하는 경우도 거의 없기에 잘 사용하지는 않습니다.
HTTP Form 요청이나 쿼리 파라미터 요청은 형식이 같으므로 같은 방식으로 처리 가능합니다.
ex) name=userA&age=20
차이가 있다면 쿼리파라미터의 경우 요청 경로에 데이터가 포함되므로 요청 메서드에 무관하게 사용 가능하지만 form은 Get 메서드 요청이 불가능 합니다.
@RequestParam
HTTP 요청 파라미터를 단순 타입으로 바인딩 할 경우 @RequestParam
으로 처리 가능
ex) /home?name=userA
로 요청 시
@GetMapping("/home")
public String postHome(@RequestParam("name") String name) {
System.out.println(name); // 결과 : "userA"
return "spring/basic";
}
HTTP 요청 파라미터 이름과 변수 이름이 같으면 @RequestParam
의 value
("name") 생략 가능
String, Integet, int 등의 단순 타입일 경우 @RequestParam
도 생략 가능
요청이 /home?name=
형식으로 온다면 String의 경우 null
이 아닌 ""
이 넘어옴
숫자 타입의 경우 Integer 형식이라면 null을 받을 수 있지만 int는 받지 못하므로 예외 발생
required
속성 true로 설정 시 예외 발생
defaultValue
속성으로 기본값 설정 가능 (기본값이 있으므로 required는 의미 없음)
@ModelAttribute
만약 요청 파라미터를 기반으로 객체를 생성해야 한다면 @ModelAttribute
사용할 수 있습니다.
public class User {
String name;
int age;
}
/home?name=userA&age=20
요청 시
@PostMapping("/home")
public String postHome(@ModelAttribute("user") User user) {
return "spring/basic";
}
@ModelAttribute
는 파라미터를 객체로 변환시키는 기능 뿐만 아니라 그 객체를 모델에 저장하는 기능을 포함하고 있습니다. ( model.addAtrbute(user)
)
@ModelAttribute
는 기본적으로 기본 생성자를 통해 객체를 생성합니다. single unique constructor
가 있을 경우엔 해당 생성자를 호출하지만 만약 생성자가 여러개거나 없다면 기본생성자를 호출합니다.
생성자가 정의되있다면 해당 생성자를 통해 값을 바인딩하지만 기본생성자의 경우 값을 바인딩 할 수 없기 때문에 프로퍼티 접근법(setter)를 통해 값을 바인딩합니다.
@ModelAttribute
의 value
("user")는 파라미터 이름과 변수 이름이 같을 경우 생략 가능
객체일 경우 @ModelAttribute
생략 가능
어노테이션 생략 시 기본 타입은 @RequestParam
으로 매핑, 객체 타입은 @ModelAttribute
로 매핑
현재 대부분의 API 통신에 JSON을 사용합니다. HttpServletRequest
, HttpEntity
등으로 직접 message body를 읽어 json으로 변환할 수도 있지만 스프링은 편리하게 json을 처리할 수 있는 기능을 제공합니다.
@RequestBody
@RequestBody
는 HTTP 요청의 메시지 바디를 매핑하는 어노테이션으로 기본적으론 String 타입으로 매핑하지만 객체 타입으로도 매핑이 가능합니다.
@ResponseBody
@PostMapping("/homeApi")
public String postHome(@RequestBody User user) {
return "message body 직접 입력";
}
@RequestBody
를 사용하면 마치 @ModelAttribute
를 사용한 것 처럼 JSON 데이터를 분석하여 객체로 매핑
@RequestBody
는 생략 불가능 (기본 타입일 경우 @RequestParam
, 객체 타입일 경우 @ModelAttribute
적용)
@RequestBody
은 HTTP 요청의 content-type
이 application/json
인 경우에만 동작합니다.
@PostMapping(value = "/homeApi", consumes = "application/json)
@PostMapping(value = "/homeApi", consumes = MediaType.APPLICATION_JSON_VALUE)
를 이용하여 검증할 수 있고 맞지 않는다면 415 상태코드 반환합니다.
@ResponseBody
@ResponseBody
를 통해 HTTP Message Body에 직접 데이터를 입력할 수 있습니다.
String 타입을 반환하여 JSON 형식으로 데이터를 입력하거나 HttpEntity
를 상속받은 ResponseEntity
를 이용하여 JSON 형식으로 반환할 수도 있지만 여러 번거로움이 존재합니다.
스프링은 반환된 객체를 JSON으로 변환하여 응답 결과를 전송할 수 있는 기능을 제공합니다.
@ResponseBody
@PostMapping("/homeApi")
public User postHome(@RequestBody User user) {
return user;
}
반환된 JSON 형태
{
"name":"user"
"age":20
}
이렇게 HTTP 요청을 처리하기 위해 컨트롤러를 사용하다보면 여러가지 궁금증이 생길 수 있습니다.
스프링이 제공하는 @RequestParam
, @RequestBody
, @ModelAttribute
등이 어떤 원리로 데이터를 변환하고 처리하는지, 또한 컨트롤러가 매개변수로 받는 Model
, HttpServletRequest
, 객체 등은 분명 어디선가 생성하여 넘겨주는 것일텐데 우리는 그러한 기능을 구현하거나 세부적인 사항을 지정해준 적이 없습니다.
스프링 MVC는 이런 기능을 HttpMessageConverter
, HandlerMethodArgumentResolver
등으로 구현해 놓았습니다.
그 내용은 다음 블로그에 이어서 작성하겠습니다.