spring 기초 (5)
HTTP 요청데이터
Client에서 Server로 Data를 전달하는 법
- Client에서 Server로 Data를 전달하는 방법은 Query Parameter, HTTP Form Data, HTTP Request Body 크게 세가지가 있다.
1. GET + Query Parameter(=Query String)
- URL의 쿼리 파라미터를 사용하여 데이터 전달하는 방법
http://localhost:8080/request-params?key1=value1&key2=value2
- HttpServletRequest 사용
@Slf4j
@Controller
public class RequestParamController {
@GetMapping("/request-params")
public void params(
HttpServletRequest request,
HttpServletResponse response
) throws IOException {
String key1Value = request.getParameter("key1");
String key2Value = request.getParameter("key2");
log.info("key1Value={}, key2Value={}", key1Value, key2Value);
response.getWriter().write("success");
}
}
- response.getWriter().write()
- HttpServletResponse를 사용해서 응답값을 직접 다룰 수 있다.
- @Controller 지만 @ResponseBody를 함께 사용한 것과 같다.
Postman 요청
log 출력결과
2. POST + HTML Form(x-www-form-urlencoded)
- HTTP Request Body에 쿼리 파라미터 형태로 전달하는 방법
HTTP Request
POST /form-data
content-type: application/x-www-form-urlencoded
key1=value1&key2=value2
HttpServletRequest 사용
@Slf4j
@Controller
public class RequestBodyController {
@PostMapping("/form-data")
public void requestBody(
HttpServletRequest request,
HttpServletResponse response
) throws IOException {
String key1Value = request.getParameter("key1");
String key2Value = request.getParameter("key2");
log.info("key1Value={}, key2Value={}", key1Value, key2Value);
response.getWriter().write("success");
}
}
Postman 요청
log 출력
+ HttpServletRequest.getParameter(”key”);를 사용하면 Query Parameter, HTML Form Data 두가지 경우 모두 데이터 형식(key=value)이 같기 때문에 해당값에 접근할 수 있다.
3. HTTP Request Body
- 데이터(JSON, TEXT, XML 등)를 직접 HTTP Message Body에 담아서 전달한다.
- 주로 @RestController에서 사용하며, 대부분 JSON 형식으로 데이터를 전달한다.
- POST, PUT, PATCH Method에서 사용한다.
- GET, DELETE Method는 Body에 데이터를 담는것을 권장하지 않는다.
HttpServletRequest 사용
@Getter
@Setter
public class Board {
private String title;
private String content;
}
package com.example.springbasicannotation.controller;
import com.example.springbasicannotation.entity.Board;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.util.StreamUtils;
import org.springframework.web.bind.annotation.PostMapping;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@Slf4j
@Controller
public class RequestBodyController {
// JSON을 객체로 변환해주는 Jackson 라이브러리
private ObjectMapper objectMapper = new ObjectMapper();
@PostMapping("/request-body")
public void requestBody(
HttpServletRequest request,
HttpServletResponse response
) throws IOException {
ServletInputStream inputStream = request.getInputStream();
String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
log.info("messageBody={}", messageBody);
Board board = objectMapper.readValue(messageBody, Board.clss);
log.info("board.getTitle()={}, board.getContent()={}", board.getTitle(), board.getContent());
response.getWriter().write("success");
}
}
Postman 요청
출력결과
+ SON을 Java 객체로 변환하려면 Jackson과 같은 라이브러리를 사용해야 한다.
+Spring Boot는 기본적으로 Jackson 라이브러리의 ObjectMapper를 제공하며, starter-web에 포함되어 있다.
Spring 요청데이터
@RequestParam
- RL에서 파라미터 값과 이름을 함께 전달하는 방식으로 주로 HTTP 통신 Method 중 GET 방식의 통신을 할 때 많이 사용한다.
-@Requestparam을 사용하면 요청 파라미터 값에 아주 쉽고 간편하게 접근(Parameter Binding)할 수 있다.
+ ?뒤에 오는 URL을 Query String, Query Parameter, Request Param이라 한다.
예시코드
@Slf4j
@Controller
public class RequestParamControllerV2 {
@ResponseBody
@GetMapping("/v1/request-param")
public String requestParamV1 (
@RequestParam("name") String userName,
@RequestParam("age") int userAge
) {
// logic
log.info("name={}", userName);
log.info("age={}", userAge);
return "success";
}
}
1. @Controller + @ResponseBody
- View를 찾는 것이 아니라 ResponseBody에 응답을 작성한다(=@RestController)
2. @RequestParam
- 파라미터 이름으로 바인딩한다.
3. @RequestParam(”속성값”)
- 속성값이 파라미터 이름으로 매핑된다.
Postman
출력결과
- 속성값”과 변수명이 같으면 생략이 가능하다. ex) @RequestParam("name") String name
// GET http://localhost:8080/v2/request-param?name=sparta&age=100
@ResponseBody
@GetMapping("/v2/request-param")
public String requestParamV2 (
@RequestParam String name,
@RequestParam int age
) {
// logic
log.info("name={}", name);
log.info("age={}", age);
return "success";
}
Postman
출력결과
@RequestParam 사용법
1. 어노테이션, 속성값 모두 생략
- @RequestParam은 생략이 가능하다.
// GET http://localhost:8080/v3/request-param?name=sparta&age=100
@ResponseBody
@GetMapping("/v3/request-param")
public String requestParamV3 (
String name,
int age
) {
// logic
log.info("name={}", name);
log.info("age={}", age);
return "success";
}
- 생략하면 @RequestParam(required=false) 필수 여부 속성이 default로 설정된다.
- 단, 요청 파라미터와 이름이 완전히 같아야 한다.
- 단순 타입(int, String, Integer 등)이어야 한다.
Postman
출력결과
+ 위의 방식은 권장하지 않습니다. 명시적으로 표시되어있지 않으면 팀의 협의가 있지 않는 경우 다른 개발자들에게 혼동을 주게 됩니다. 최소 @RequestParam String name 속성 값 생략 형태를 쓰면 됩니다.
2. required 속성 설정
- 파라미터의 필수 값을 설정한다.
- API 스펙을 규정할 때 사용한다.
@ResponseBody
@GetMapping("/v4/request-param")
public String requestParam (
@RequestParam(required = true) String name, // 필수
@RequestParam(required = false) int age // 필수가 아님
) {
// logic
log.info("name={}", name);
log.info("age={}", age);
return "success";
}
- @RequestParam을 사용하면 기본 Default값은 True이다.
- True로 설정된 파라미터 값이 요청에 존재하지 않으면 400 BadRequest(클라이언트 측 에러)
Exception이 발생하지 않는 경우
ex) http://localhost:8080/v4/request-param?name=sparta&age=100
Exception이 발생하는 경우
ex) http://localhost:8080/v4/request-param?age=100
ex) http://localhost:8080/v4/request-param
- required = false 설정이 되어있으면 해당 파라미터는 없어도 된다.
주의! http://localhost:8080/v4/request-param?name=sparta 요청한다면?
- 500 Error가 발생한다.
- int Type에는 null을 넣을 수 없다. 0이라도 들어가야 한다.
- 따라서 보통 null을 허용하는 Integer로 사용하거나 default 옵션을 사용한다.
@ResponseBody
@GetMapping("/v4/request-param")
public String requestParam (
@RequestParam(required = true) String name, // 필수
@RequestParam(required = false) Integer age
) {
// logic
log.info("name={}", name);
log.info("age={}", age);
return "success";
}
- 파라미터 Key값만 있고 Value가 없는 경우 http://localhost:8080/request-param?name=
- null과 빈 문자열 “”은 다르다!
- 위 형태는 빈 문자열 “” 로 인식한다, True지만 통과가 되어버린다. 주의해야 한다.
3. default 속성 적용
- 파라미터의 기본 값을 설정한다.
@ResponseBody
@GetMapping("/v5/request-param")
public String requestParam (
@RequestParam(required = true, defaultValue = "sparta") String name,
@RequestParam(required = false, defaultValue = "1") int age
) {
// logic
log.info("name={}", name);
log.info("age={}", age);
return "success"
}
- name Parameter 의 값이 없으면 기본적으로 “sparta”으로 설정한다
ex) http://localhost:8080/v5/request-param?age=100
- age Parameter의 값이 없으면 기본적으로 1 으로 설정한다.
ex) http://localhost:8080/v5/request-param?name=wonuk
ex) http://localhost:8080/v5/request-param
주의! defaultValue 속성을 설정하게 되면 “” 빈 문자열의 경우에도 기본 값이 설정된다.
ex) http://localhost:8080/v5/request-param?name&age
4. Map 사용
- Parameter를 Map형태로 조회가 가능하다.
@ResponseBody
@GetMapping("/v6/request-param")
public String requestParamV6(
@RequestParam Map<String, String> map
) {
// logic
log.info("name={}", map.get("name"));
log.info("age={}", map.get("age"));
return "success";
}
- Map 형태(key=value)로 조회가 가능하다
ex) http://localhost:8080/v6/request-param?name=sparta&age=100
- MultiValueMap 형태(key=[value1, value2])로 조회가 가능하다.
@ResponseBody
@GetMapping("/v6/request-param")
public String requestParamV6(
@RequestParam MultiValueMap<String, String> map
) {
// logic
log.info("name={}", map.get("name"));
log.info("age={}", map.get("age"));
return "success";
}
ex) http://localhost:8080/v6/request-param?name=sparta&name=wonuk&name=tutor&age=100
+ 파라미터 Map의 Value가 1개인 경우에는 Map, 여러 개인 경우 MultiValueMap을 사용한다. 하지만 대부분의 파라미터 값은 한 개만 존재한다.
@ModelAttribute
- 요청 파라미터를 받아 필요한 Object로 바인딩 해준다. 주로 HTML 폼에서 전송된 데이터를 바인딩하고 HTTP Method POST인 경우 사용된다.
1. 기존코드
- @Data는 @Getter, @Setter, @ToString, @EqualsAndHashCode, @RequiredArgsConstructor를 자동으로 설정해주는 역할을 한다. 테스트 용도로만 사용하고 실무에서는 잘 사용하지 않는다.
ex) http://localhost:8080/v1/tutor + x-www-form-urlencoded
@Data
public class Tutor {
private String name;
private int age;
}
@Controller
public class ModelAttributeController {
@ResponseBody
@PostMapping("/v1/tutor")
public String requestParamV1(
@RequestParam String name,
@RequestParam int age
) {
Tutor tutor = new Tutor();
tutor.setName(name);
tutor.setAge(age);
return "tutor name = " + name + " age = " + age;
}
}
- @RequestParam 의 Mapping을 사용하게 되면 위와 같은 객체를 생성하는 코드가 포함된다.
- @ModelAttribute 는 해당 과정을 자동화 한다.
Postman
POST /v1/tutor
content-type: application/x-www-form-urlencoded
name=wonuk&age=100
2. @ModelAttribute 적용
ex) http://localhost:8080/v2/tutor+ x-www-form-urlencoded
@ResponseBody
@PostMapping("/v2/tutor")
public String modelAttributeV2(
@ModelAttribute Tutor tutor
) {
String name = tutor.getName();
int age = tutor.getAge();
return "tutor name = " + name + " age = " + age;
}
Postman
POST /v2/tutor
content-type: application/x-www-form-urlencoded
name=wonuk&age=100
@ModelAttirubte 동작 순서
1. 파라미터에 @ModelAttribute가 있으면 파라미터인 Tutor 객체를 생성한다.
2. 요청 파라미터 이름으로 객체 필드의 Setter를 호출해서 바인딩한다.
- 파라미터 이름이 name 이면 setName(value); 메서드를 호출한다.
- 파라미터 이름과 필드 이름이 반드시 같아야 한다!
@Data의 Setter가 없다면?
@Getter
public class Tutor {
private String name;
private int age;
}
- 객체 필드에 값이 set되지 않는다.
파라미터의 타입이 다른 경우
- 만약 요청 파라미터 age 에 int가 아닌 String 이 전달된다면?
ex) http://localhost:8080/v2/tutor+ x-www-form-urlencoded
POST /v2/tutor
content-type: application/x-www-form-urlencoded
name=wonuk&age=nbcamp
- BindException 발생
- 이런 경우 때문에 Validation(검증)이 필요하다.
3. @ModelAttirubte 생략
- @ModelAttribute와 지난 시간에 배운 @RequestParam은 모두 생략이 가능하다.
ex) http://localhost:8080/v3/tutor+ x-www-form-urlencoded
POST /v3/tutor
content-type: application/x-www-form-urlencoded
name=wonuk&age=100
@ResponseBody
@PostMapping("/v3/tutor")
public String modelAttributeV3(Tutor tutor) {
String name = tutor.getName();
int age = tutor.getAge();
return "tutor name = " + name + " age = " + age;
}
- Spring에서는 @RequestParam이나 @ModelAttribute가 생략되면 String, int, Integer 와 같은 기본 타입은 @RequestParam과 Mapping한다. V4
@ResponseBody
@PostMapping("/v4/tutor")
public String requestParamV2(
String name,
int age
) {
return "tutor name = " + name + " age = " + age;
}
- 나머지 경우들(객체)은 모두 @ModelAttribute 와 Mapping한다. V3
HTTP Message Body(요청)
- @RequestParam, @ModelAttribute는 GET + Query Parameter와, POST HTML Form Data를 바인딩하는 방법.
- HTTP Message Body에 직접적으로 Data가 전달되는 경우 Request Body의 Data를 바인딩하는 방법.
- REST API에서 주로 사용하는 방식이다.
- HTTP Method POST, PUT, PATCH에서 주로 사용한다.
- GET은 Request Body가 존재할 수는 있지만 권장하지 않는다.
- JSON, XML, TEXT 등을 데이터 형식으로 사용한다.
HTTP Message 구조
HTTP Request, Response 예시
- Server에서 Request로 전달받은 Data를 처리하기 위해서 바인딩 해야 한다.
ex) JSON → Object
TEXT
- HTTP Request Body에 Data가 전송되는 경우 HttpMessageConverter를 통해 바인딩된다.
+ 현대에는 Restful API를 주로 사용하고 있어서 대부분의 경우 JSON 형식으로 통신한다.
1. HttpServletRequest 예시
- request.getInputStream();
@Slf4j
@Controller
public class RequestBodyStringController {
@PostMapping("/v1/request-body-text")
public void requestBodyTextV1(
HttpServletRequest request,
HttpServletResponse response
) throws IOException {
ServletInputStream inputStream = request.getInputStream();
String bodyText = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
response.getWriter().write("response = " + bodyText);
}
}
Postman
- Request → Body → raw → Text
- Request Header Content-Type : text/plain
2. I/O 예시
- InputStream(읽기) 파라미터 지원 → HTTP Request Body Data 직접 조회
OutputStream(쓰기) 파라미터 지원 → HTTP Response Body 직접 결과 출력
@PostMapping("/v2/request-body-text")
public void requestBodyTextV2(
InputStream inputStream,
Writer responseWriter
) throws IOException {
String body = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
responseWriter.write("response = " + bodyText);
}
Postman
3. HttpEntity 예시
- HttpMessageConverter 사용 → 추후 설명
- HttpEntity를 사용하면 HttpMessageConverter를 사용한다.
@PostMapping("/v3/request-body-text")
public HttpEntity<String> requestBodyTextV3(HttpEntity<String> httpEntity) {
// HttpMessageConverter가 동작해서 아래 코드가 동작하게됨
String body = httpEntity.getBody();
return new HttpEntity<>("response = " + body); // 매개변수 = Body Message
}
Postman
- Spring의 HttpMessageConverter 덕분에 간편하게 Request Data에 접근할 수 있다.
1. HttpEntity를 사용하면 HttpMessageConverter가 동작하여 자동으로 매핑된다.
2. 요청 뿐만이 아닌 응답까지 HttpEntity 하나만으로 사용이 가능해진다.
+ Converter는 어떤 뭔가를 다른 뭔가로 바꿔주는(Convert) 장치를 말한다.
HttpEntity
- HttpEntity는 HTTP Header, Body 정보를 편리하게 조회할 수 있도록 만들어준다.
HttpEntity 역할
1. Http Request Body Message를 직접 조회한다
2. Request 뿐만 아니라 Response도 사용할 수 있도록 만들어준다.
3. Response Header 또한 사용할 수 있다.
4. Request Parameter를 조회하는 기능들과는 아무 관계가 없다.
5. View를 반환하지 않는다.
HttpEntity를 상속받은 객체
- RequestEntity<>
- HTTP Request Method, URL 정보가 추가 되어있다.
- ResponseEntity<>
- HTTP Response 상태 코드 설정이 가능하다.
코드 예시
@Controller
public class RequestBodyStringController {
@PostMapping("/v4/request-body-text")
public HttpEntity<String> requestBodyTextV4(RequestEntity<String> httpEntity) {
// HttpMessageConverter가 동작해서 아래 코드가 동작하게됨
String body = httpEntity.getBody();
// url, method 사용 가능
return new ResponseEntity<>("response = " + body, HttpStatus.CREATED); // Body Data, 상태코드
}
}
Postman
- 위 방법을 적용해도 불편하다.
- Data를 httpEntity에서 꺼내어 사용해야 한다.
+ Spring은 Http RequestBody Message를 읽어서 String이나 Object로 자동으로 변환해준다. 이때 HttpMessageConverter가 사용된다.
@RequestBody, @ResponseBody
- Spring에서 @RequestBody, @ResponseBody 어노테이션을 사용하면 각각 Request, Response 객체의 Body에 편하게 접근하여 사용할 수 있다.
코드예시
@Controller // @RestController = @Controller + @ResponseBody
public class RequestBodyStringController {
@ResponseBody
@PostMapping("/v5/request-body-text")
public String requestBodyTextV5(
@RequestBody String body,
@RequestHeader HttpHeaders headers
) {
// HttpMessageConverter가 동작해서 아래 코드가 동작하게됨
String bodyMessage = body;
return "request header = " + headers + " response body = " + bodyMessage;
}
}
Postman
@RequestBody
- 요청 메세지 Body Data를 쉽게 조회할 수 있다.
@RequestHeader
- 요청 헤더 정보 조회
@ResponseBody
- 응답 메세지 바디에 값을 쉽게 담아서 전달할 수 있도록 해준다.
- View가 아닌 데이터를 반환한다.
요약
1. 요청 파라미터, HTML Form Data에 접근하는 경우
- @RequestParam, @ModelAttribute 를 사용한다.
2. Http Message Body에 접근하는 경우
- @RequestBody를 사용한다. (JSON, XML, TEXT)
JSON
- Json은 @RestController 에서 가장 많이 사용되는 데이터 형식이다. 현재 대부분의 API는 Request, Response 모두 JSON 형태로 통신한다.
+ Json 형태로 Data를 전송할 때는 Request Header의 content-type이 꼭 application/json 이여야 한다.
1. HttpServletRequest 사용
@Data
public class Tutor {
private String name;
private int age;
}
@RestController
public class JsonController {
private ObjectMapper objectMapper = new ObjectMapper();
@PostMapping("/v1/request-body-json")
public void requestBodyJsonV1(
HttpServletRequest request,
HttpServletResponse response
) throws IOException {
// request body message를 Read
ServletInputStream inputStream = request.getInputStream();
// UTF-8 형식의 String으로 변환한다.
String requestBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
// String requestBody를 ObjectMapper를 사용하여 변환 "{\"name\":\"wonuk\", \"age\":10}"
Tutor tutor = objectMapper.readValue(requestBody, Tutor.class);
// 응답
response.getWriter().write("tutor" + tutor);
}
}
Postman
- Content-Type 헤더 확인 : application/json
- HttpServletRequest를 사용하여 HTTP Message Body 데이터를 Read하여 문자로 변환한다.
- 문자로 만들어진 JSON을 Jackson 라이브러리의 objectMapper를 사용하여 Object로 변환
1. @RequestBody 사용
@RestController
public class JsonController {
private ObjectMapper objectMapper = new ObjectMapper();
@PostMapping("/v2/request-body-json")
public String requesBodytJsonV2(@RequestBody String requestBody) throws IOException {
Tutor tutor = objectMapper.readValue(requestBody, Tutor.class);
return "tutor.getName() = " + tutor.getName() + "tutor.getAge() = " + tutor.getAge();
}
}
Postman
- @RequestBody를 사용하여 HTTP Request Body의 Data에 접근한다.
? ObjectMapper가 계속 반복되는데 @ModelAttribute처럼 객체로 바로 반환은 안되나요?
- Spring은 개발에 필요한 대부분의 기능이 구현되어 있습니다.
3. ObjectMapper 제거
@RestController
public class JsonController {
@PostMapping("/v3/request-body-json")
public String requestBodyJsonV3(@RequestBody Tutor tutor) {
Tutor requestBodyTutor = tutor;
return "tutor = " + requestBodyTutor;
}
}
Postman
위 Controller가 동작하는 이유
@RequestBody
- @RequestBody 어노테이션을 사용하면 Object를 Mapping할 수 있다.
- HttpEntity<>, @RequestBody를 사용하면 HTTPMessageConverter가 Request Body의 Data를 개발자가 원하는 String이나 Object로 변환해준다.
- JSON to Object의 Mapping 또한 가능하다.
- MappingJackson2HttpMessageConverter 의 역할
4. @RequestBody는 생략할 수 없다.
+ @RequstParam, @ModelAttribute는 생략이 가능하다.
@Slf4j
@RestController
public class JsonController {
@PostMapping("/v4/request-body-json")
public String requestBodyJsonV4(Tutor tutor) { // @RequestBody 생략시 @ModelAttribute가 된다.
Tutor requestBodyTutor = tutor;
return "tutor.getName() = " + requestBodyTutor.getName() + " tutor.getAge() = " + requestBodyTutor.getAge();
}
}
Postman
- 생략하면 @ModelAttribute가 된다.
- 요청 파라미터를 처리하도록 설정된다.
- Request Header의 contentType은 꼭 application/json 이여야 한다.
- 위 설정 정보를 기반으로 MessageConverter가 실행된다.
5. HttpEntity 사용
@RestController
public class JsonController {
@PostMapping("/v5/request-body-json")
public String requestBodyJsonV5(
HttpEntity<Tutor> httpEntity
) {
// 값을 꺼내서 사용해야한다!
Tutor tutor = httpEntity.getBody();
return "tutor.getName() = " + tutor.getName() + " tutor.getAge() = " + tutor.getAge();
}
}
Postman
- HttpEntity<Tutor>
- Generic Type으로 Tutor가 지정되어 있기 때문에 해당 Class로 반환된다.
6. @ResponseBody
@Controller
public class JsonController {
@ResponseBody // @RestController = @Controller + @ResponseBody
@PostMapping("/v6/request-body-json")
public Tutor requestJson(@RequestBody Tutor tutor) {
return tutor;
}
}
Postman
- View를 조회하지 않고 Response Body에 Data를 입력해서 직접 반환한다.
- 요청 뿐만이 아니라 응답에도 HttpMessageConverter가 동작한다.
- MappingJackson2HttpMessageConverter 적용
- 응답 객체인 Tutor가 JSON으로 변환되어 반환된다.
- HttpEntity를 사용해도 된다.
요약
1. 요청 데이터는 @RequestBody를 사용해서 바인딩 하면 된다.
2. @RequestBody 는 생략이 불가능하다.
- @ModelAttribute가 적용되기 때문
3. HttpMessageConverter 가 요청 응답 데이터를 모두 변환할 수 있다.
- JSON은 MappingJackson2HttpMessageConverter 를 사용한다.
- Request Header의 Content-Type은 application/json 이어야 한다.
- Header로 어떤 Converter가 동작할지 판별한다.
HTTPMessageConverter
- Spring Framework에서 HTTP 요청과 응답을 변환하는 인터페이스이다.
- 클라이언트와 서버 간에 데이터를 주고받을 때, 요청 데이터를 자바 객체로 변환하거나 자바 객체를 응답 본문으로 변환하는 역할을 수행한다.
+ MappingJackson2HttpMessageConverter는 JSON을 처리하는 대표적인 HTTPMessageConverter의 구현체이다.
HttpMessageConverter의 역할
- 데이터를 Obejct로 변환한다. 대표적으로 JSON을 변환한다.
@RequestBody
- 요청 데이터 + Request Header를 참고하여 Object로 변환한다.
- HTTP Request Body(JSON Data) → Converter(Jackson) → Object
- Reqeust Header → Content-Type : application/json(전달할 데이터 형식)
@ResponseBody
- 응답 데이터 + Accept Header를 참고하여 원하는 데이터 형식으로 변환한다.
- Object → Converter(Jackson) → HTTP Response Body(JSON Data)
- Request Header → Accept : application/json(허용할 데이터 형식)
Spring 응답데이터
Server에서 Client로 Data를 전달하는 방법
- Server에서 Client로 Data를 전달하는 방법은 정적 리소스, View Template, HTTP Message Body 세가지 방법이 있다.
1. 정적리소스
- 정적인 HTML, CSS, JS, Image 등을 변경 없이 그대로 반환한다.
2. View Template
- SSR(Server Side Rendering)을 사용할 때 View가 반환된다.
3. HTTP Message Body
- 응답 데이터를 직접 Message Body에 담아 반환한다.
정적리소스
- 웹 애플리케이션에서 변하지 않는 파일들을 의미한다. 예를 들어, HTML, CSS, JavaScript, 이미지 파일들(JPG, PNG, GIF) 등이 정적 리소스에 해당한다.
Spring Boot의 정적 리소스 경로
- 아래 경로들에 정적 리소스가 존재하면 서버에서 별도의 처리 없이 파일 그대로 반환된다.
1. /static
2. /public
3. /META-INF/resources
4. src/main/resources
- /static
Spring Boot Directory 구조
- src/main/resources/static/hello/world.html 디렉토리 구조라면
- http://localhost:8080/hello/world.html URL로 리소스에 접근이 가능하다.
- /static 대신 /public 혹은 /META-INF/resources 도 사용 가능하다.
View Template
- Spring에서는 Thymeleaf, JSP와 같은 템플릿 엔진을 사용해 View Template을 작성할 수 있고, View Template은 서버에서 데이터를 받아 이를 HTML 구조에 맞게 삽입한 후, 최종적으로 클라이언트에게 전송되는 HTML 문서로 변환하여 사용자에게 동적으로 생성된 웹 페이지를 제공한다.
+ View Template
- View Template은 Model을 참고하여 HTML 등이 동적으로 만들어지고 Client에 응답된다.
- Spring Boot는 기본적으로 View Template 경로(src/main/resources/templates)를 설정한다.
- build.gradle에 Thymeleaf 의존성을 추가하면 ThymeleafViewResolver와 필요한 Spring Bean들이 자동으로 등록된다.
- 기본 설정 예시(아래 내용을 자동으로 Spring Boot가 등록해준다.)
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html // or jsp
1. @Controller의 응답으로 String을 반환하는 경우
- @ResponseBody 가 없으면 View Resolver가 실행되며 View를 찾고 Rendering한다.
@Controller
public class ViewTemplateController {
@RequestMapping("/response-view")
public String responseView(Model model) {
// key, value
model.addAttribute("data", "sparta");
return "thymeleaf-view";
}
}
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Hello</title>
</head>
<body>
<h1>Thymeleaf</h1>
<h2 th:text="${data}"></h2>
</body>
</html>
ex) http://localhost:8080/response-view
- @ResponseBody 가 있으면 HTTP Message Body에 return 문자열 값이 입력된다.
@Controller
public class ViewController {
@ResponseBody // @RestController = @Controller + @ResponseBody
@RequestMapping("/response-body")
public String responseBody() {
return "thymeleaf-view";
}
}
Postman
2. 반환 타입이 void인 경우
- 잘 사용하지 않는다.
- @Controller + (@ResponseBody, HttpServletResponse, OutputStream)과 같은 HTTP Message Body를 처리하는 파라미터가 없으면 RequestMapping URL을 참고하여 View Name으로 사용한다.
@Controller
public class ViewTemplateController {
// thymeleaf-view.html 과 Mapping된다.
@RequestMapping("/thymeleaf-view")
public void responseViewV2(Model model) {
model.addAttribute("data", "sparta");
}
}
- 예시와 같은 경우에는 viewTemplate(viewName)을 RequestMapping URL 주소로 찾는다.
HTTP Message Body(응답)
- REST API를 만드는 경우 Server에서 Client로 HTML을 전달하는 방식이 아닌 HTTP Message Body에 직접 Data를 JSON 형식으로 담아 전달한다.
+ 정적 HTML, View Template 또한 HTTP Message Body에 담겨서 전달된다. 현재 설명하는 Response의 경우는 정적 HTML, View Template을 거치지 않고 직접 HTTP Response Message를 만들어 전달하는 경우를 말하는것.
HTTP Message Body
1. HttpServletResponse 사용
@Controller
public class ResponseBodyController {
@GetMapping("/v1/response-body")
public void responseBodyV1(
HttpServletResponse response
) throws IOException {
response.getWriter().write("data");
}
}
Postman
- Response Body에 data 라는 문자열이 입력되어 응답된다.
- 기존 Servlet을 다룰 때 코드와 형태가 같다.
2. ResponseEntity<> 사용
@GetMapping("/v2/response-body")
public ResponseEntity<String> responseBodyV2() {
return new ResponseEntity<>("data", HttpStatus.OK);
}
Postman
- Response Body에 data라는 문자열과 HttpStatus.OK에 해당하는 상태 코드를 반환한다.
- ResponseEntity는 HttpEntity 를 상속받았다.
- HttpEntity는 HTTP Message의 Header, Body 모두 가지고 있다.
3. @ResponseBody(TEXT, JSON) 사용
@Data
@NoArgsConstructor // 기본 생성자
@AllArgsConstructor // 전체 필드를 인자로 가진 생성자
public class Tutor {
private String name;
private int age;
}
// TEXT 데이터 통신
@ResponseBody
@GetMapping("/v3/response-body-text")
public String responseBodyText() {
return "data"; // HTTP Message Body에 "data"
}
Postman
// JSON 데이터 통신
@ResponseBody
@GetMapping("/v3/response-body-json")
public Tutor responseBodyJson() {
Tutor tutor = new Tutor("wonuk", 100);
return tutor; // HTTP Message Body에 Tutor Object -> JSON
}
Postman
- View를 사용하는 것이 아닌 HTTP Message Converter를 통해 HTTP Message Body를 직접 입력할 수 있다. → ResponseEntity와 같음
- @ResponseStatus 를 사용하여 상태 코드를 지정할 수 있다.
@ResponseStatus(HttpStatus.OK)
@ResponseBody
@GetMapping("/v4/response-body")
public Tutor responseBodyV4() {
Tutor tutor = new Tutor("wonuk", 100);
return tutor;
}
Postman
- 단, 응답 코드를 조건에 따라서 동적으로 변경할 수는 없다. ex) 1번의 경우 OK, 2번의 경우 Created
4. ResponseEntity<Object>(JSON)
@ResponseBody
@GetMapping("/v5/response-body")
public ResponseEntity<Tutor> responseBody() {
Tutor tutor = new Tutor("wonuk", 100);
return new ResponseEntity<>(tutor, HttpStatus.OK);
}
Postman
- ResponseEntity<>두 번째 파라미터에 Enum을 사용하여 상태 코드를 바꿀 수 있다.
- HTTP Message Converter를 통하여 JSON 형태로 변환되어 반환된다.
- 동적으로 응답 코드를 변경할 수 있다.
@ResponseBody
@GetMapping("/v5/response-body")
public ResponseEntity<Tutor> responseBody() {
Tutor tutor = new Tutor("wonuk", 100);
if (조건) {
return new ResponseEntity<>(tutor, HttpStatus.OK);
} else {
return new ResponseEntity<>(tutor, HttpStatus.BAD_REQUEST);
}
}
- HttpEntity를 상속받았다.
정리
Client에서 Server로 Data를 전달하는 세가지 방법
1. GET - Query Param, Query String ex) http://localhost:8080/tutor?name=wonuk&age=100
- 사용하는 어노테이션 : @RequestParam, @ModelAttribute
2. POST - HTML Form(x-www-form-urlencoded)
POST /form-data
content-type: application/x-www-form-urlencoded
key1=value1&key2=value2
- 사용하는 어노테이션 : @RequestParam, @ModelAttribute
3. HTTP Request Body
ex) 데이터(JSON, TEXT, XML 등)를 직접 HTTP Message Body에 담아서 사용한다.
- 사용하는 어노테이션 : @RequestBody
Server(Spring)에서 HTTP 응답을 Client에 전달하는 세가지 방법
1. 정적 리소스
- 정적인 HTML, CSS, JS, Image 등을 변경없이 그대로 반환한다.
2. View Template
- SSR(Server Side Rendering)을 사용할 때 View가 반환된다.
- 사용하는 어노테이션 : @Controller
3. HTTP Message Body
- 응답 데이터를 직접 Message Body에 담아 반환한다.
- 사용하는 어노테이션 : @ResponseBody, ResponseEntity<Object>
요청
- @RequestParam, @ModelAttribute, @RequestBody
응답
- 정적 리소스, View Template(@Controller), @ResponseBody, ResponseEntity<Object>
실습
CRUD실습