Java Category/Spring

[Spring MVC] 기본 기능 - HTTP 요청, 요청 파라미터

ReBugs 2024. 2. 21.

이 글은 인프런 김영한님의 Spring 강의를 바탕으로 개인적인 정리를 위해 작성한 글입니다.


HTTP 요청

어노테이션 기반의 스프링 컨트롤러는 다양한 파라미터를 지원한다.

@Slf4j
@RestController
public class RequestHeaderController {
    @RequestMapping("/headers")
    public String headers(HttpServletRequest request,
                          HttpServletResponse response,
                          HttpMethod httpMethod,
                          Locale locale,
                          @RequestHeader MultiValueMap<String, String> headerMap,
                          @RequestHeader("host") String host,
                          @CookieValue(value = "myCookie", required = false) String cookie) {
        
        log.info("request={}", request);
        log.info("response={}", response);
        log.info("httpMethod={}", httpMethod);
        log.info("locale={}", locale);
        log.info("headerMap={}", headerMap);
        log.info("header host={}", host);
        log.info("myCookie={}", cookie);
        return "ok";
    }
}
@CookieValues
@CookieValue(value = "myCookie", required = false) String cookie​

value: 추출하고자 하는 쿠키의 이름을 지정한다.

required: 이 속성은 쿠키의 존재 여부가 필수인지 아닌지를 지정한다. 기본값은 true이며, 이 경우 지정한 이름의 쿠키가 요청에 없으면 예외가 발생한다. false로 설정하면, 지정한 이름의 쿠키가 없어도 예외가 발생하지 않으며, 대신 파라미터 값은 null이 된다.

@RequestHeader("host") String host
클라이언트로부터 받은 요청 중 "host" 헤더의 값을 String 타입의 host 변수에 저장한다.

required: 이 속성은 해당 헤더가 반드시 필요한지를 지정한다. 기본값은 true로, 헤더가 요청에 없을 경우 예외가 발생한다. required 속성을 false로 설정하면, 헤더가 없어도 예외가 발생하지 않고, 파라미터 값은 null 또는 defaultValue로 설정된 값이 된다.

defaultValue: 헤더가 없을 때 사용할 기본값을 지정할 수 있다. required가 false로 설정되어 있고, 요청에 특정 헤더가 없을 경우 defaultValue로 지정된 값이 변수에 할당된다.

HttpMethod: HTTP 메서드를 조회한다. org.springframework.http.HttpMethod

  • Locale: Locale 정보를 조회한다.
  • @RequestHeader MultiValueMap<String, String> headerMap 모든 HTTP 헤더를 MultiValueMap 형식으로 조회한다.
  • @RequestHeader("host") String host 특정 HTTP 헤더를 조회한다.
    속성 필수 값 여부: required -> 기본 값 속성: true
  • @CookieValue(value = "myCookie", required = false) String cookie 특정 쿠키를 조회한다. 
    속성 필수 값 여부: required -> 기본 값: true

 

MultiValueMap

MAP과 유사한데, 하나의 키에 여러 값을 받을 수 있다.
HTTP header, HTTP 쿼리 파라미터와 같이 하나의 키에 여러 값을 받을 때 사용한다.
 -> keyA=value1&keyA=value2

MultiValueMap<String, String> map = new LinkedMultiValueMap();
 map.add("keyA", "value1");
 map.add("keyA", "value2");
 //[value1,value2]
List<String> values = map.get("keyA");​
하나의 키를 입력하면 값이 여러개 이므로 리스트 타입으로 반환된다.

 

참고

@Controller 의 사용 가능한 파라미터 목록은 다음 공식 메뉴얼에서 확인할 수 있다.
https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann- arguments


@Controller 의 사용 가능한 응답 값 목록은 다음 공식 메뉴얼에서 확인할 수 있다.
https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann- return-types

 

HTTP 요청 파라미터

클라이언트에서 서버로 요청 데이터를 전달할 때는 주로 다음 3가지 방법을 사용한다.

  • GET - 쿼리 파라미터
    -/url?username=hello&age=20
    -메시지 바디 없이, URL의 쿼리 파라미터에 데이터를 포함해서 전달
    -예) 검색, 필터, 페이징 등에서 많이 사용하는 방식
  • POST - HTML Form
    -content-type: application/x-www-form-urlencoded
    -메시지 바디에 쿼리 파라미터 형식으로 전달 username=hello&age=20
    -예) 회원 가입, 상품 주문, HTML Form 사용
  • HTTP message body에 데이터를 직접 담아서 요청
    -HTTP API에서 주로 사용, JSON, XML, TEXT
    -데이터 형식은 주로 JSON 사용
    -POST, PUT, PATCH

 

GET, 쿼리 파라미터 전송

  • http://localhost:8080/request-param?username=hello&age=20

 

POST, HTML Form 전송

  • POST /request-param ...
    content-type: application/x-www-form-urlencoded

    username=hello&age=20

 

GET 쿼리 파리미터 전송 방식이든, POST HTML Form 전송 방식이든 둘다 형식이 같으므로 구분없이 조회할 수 있다.

이것을 간단히 요청 파라미터(request parameter) 조회라 한다.

 

 

@RequestParam(int, String 등 기본 타입)

기본

스프링이 제공하는 @RequestParam 을 사용하면 요청 파라미터를 매우 편리하게 사용할 수 있다.

/**
* @RequestParam 사용
* - 파라미터 이름으로 바인딩
* @ResponseBody 추가
* - View 조회를 무시하고, HTTP message body에 직접 해당 내용 입력
*/
@ResponseBody //View 조회를 무시하고, HTTP message body에 직접 해당 내용 입력
@RequestMapping("/request-param-v2")
public String requestParamV2(@RequestParam("username") String memberName, @RequestParam("age") int memberAge){
    log.info("username={}, age={}", memberName, memberAge);
    return "ok";
}
  • @RequestParam : 파라미터 이름으로 바인딩
  • @ResponseBody : View 조회를 무시하고, HTTP message body에 직접 해당 내용 입력

 

@RequestParam의 name(value) 속성이 파라미터 이름으로 사용

  • @RequestParam("username") String memberName
    -> request.getParameter("username")

 

파라미터 이름 생략

HTTP 파라미터 이름이 변수 이름과 같으면 @RequestParam(name="xx") 생략이 가능하다.

/**
* @RequestParam 사용
* HTTP 파라미터 이름이 변수 이름과 같으면 @RequestParam(name="xx") 생략 가능 */
 @ResponseBody
 @RequestMapping("/request-param-v3")
 public String requestParamV3(
                            @RequestParam String username,
                            @RequestParam int age) {
    log.info("username={}, age={}", username, age);
    return "ok";
}

 

@RequestParam 생략

파라미터의 타입이 기본 타입, HTTP 파라미터 이름이 변수 이름과 같으면 @RequestParam 이 생략 가능하다.

/**
* @RequestParam 사용
* String, int 등의 단순 타입이면 @RequestParam 도 생략 가능
*/
@ResponseBody
@RequestMapping("/request-param-v4")
public String requestParamV4(String username, int age) {
    log.info("username={}, age={}", username, age);
    return "ok";
}

 

@RequestParam 애노테이션을 생략하면 스프링 MVC는 내부에서 required=false 를 적용한다.

 

파라미터 필수 여부 - requestParamRequired

/**
  * @RequestParam.required
* /request-param-required -> username이 없으므로 예외 *
* 주의!
* /request-param-required?username= -> 빈문자로 통과 *
* 주의!
* /request-param-required
* int age -> null을 int에 입력하는 것은 불가능, 따라서 Integer 변경해야 함(또는 defaultValue 사용)
*/
 @ResponseBody
 @RequestMapping("/request-param-required")
 public String requestParamRequired(
        @RequestParam(required = true) String username,
        @RequestParam(required = false) Integer age) {
        
    log.info("username={}, age={}", username, age);
    return "ok";
}

@RequestParam.required -> 파라미터 필수 여부 -> 기본값이 파라미터 필수(true)이다.

/request-param-required 요청
-> username 이 없으므로 400 예외가 발생한다.

 

주의! 
- 파라미터 이름만 사용
/request-param-required?username=
파라미터 이름만 있고 값이 없는 경우 빈문자로 통과

- 기본형(primitive)에 null 입력
/request-param 요청 @RequestParam(required = false) int age
null 을 int 에 입력하는 것은 불가능(500 예외 발생)따라서 null 을 받을 수 있는 Integer 로 변경하거나, 또는 defaultValue 사용

 

기본 값 적용 - requestParamDefault

  • 파라미터에 해당하는 값이 들어오지 않은 경우(null, "")에 requestParamDefault를 쓰면 된다.
/**
 * @RequestParam
 * - defaultValue 사용 *
 * 참고: defaultValue는 빈 문자의 경우에도 적용 * /request-param-default?username=
 */
@ResponseBody
@RequestMapping("/request-param-default")
public String requestParamDefault(
        @RequestParam(required = true, defaultValue = "guest") String username,
        @RequestParam(required = false, defaultValue = "-1") int age) {
        
    log.info("username={}, age={}", username, age);
    return "ok";
}

파라미터에 값이 없는 경우 defaultValue 를 사용하면 기본 값을 적용할 수 있다.

이를 적용하면 이미 기본 값이 있기 때문에 required=true 는 의미가 없다.

defaultValue 는 빈 문자의 경우에도 설정한 기본 값이 적용된다.

/request-param-default?username=

즉, 파라미터 값이 null이거나 "" 인 경우에도 기본 값이 적용된다.

 

파라미터를 Map으로 조회하기 - requestParamMap

/**
 * @RequestParam Map, MultiValueMap
 * Map(key=value)
 * 파라미터의 값이 1개가 확실하다면 `Map` 을 사용해도 되지만, 그렇지 않다면 `MultiValueMap` 을 사용하자.
 * MultiValueMap(key=[value1, value2, ...]) ex) (key=userIds, value=[id1, id2])
 */
@ResponseBody
@RequestMapping("/request-param-map")
public String requestParamMap(@RequestParam Map<String, Object> paramMap) {
    log.info("username={}, age={}", paramMap.get("username"), paramMap.get("age"));
    return "ok";
}

 

파라미터를 Map, MultiValueMap으로 조회할 수 있다.

  • @RequestParam Map
    -Map(key=value)
  • @RequestParam MultiValueMap
    -MultiValueMap(key=[value1, value2, ...] ex) (key=userIds, value=[id1, id2])'

 

@ModelAttribute(객체)

HelloData 라는 객체가 아래와 같이 정의되어 있다고 가정하자

import lombok.Data;
@Data
public class HelloData {
    private String username;
    private int age;
}

롬복 @Data : @Getter , @Setter , @ToString , @EqualsAndHashCode , @RequiredArgsConstructor 자동으로 적용해준다.

 

/**
* @ModelAttribute 사용
* 참고: model.addAttribute(helloData) 코드도 함께 자동 적용됨, 뒤에 model을 설명할 때 자세히
설명
*/
@ResponseBody
@RequestMapping("/model-attribute-v1")
public String modelAttributeV1(@ModelAttribute HelloData helloData) {
    log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
    return "ok";
}

이렇게 @ModelAttribute 를 사용하면 HelloData 객체가 생성되고, 요청 파라미터의 값도 모두 들어가진다.

스프링MVC는 @ModelAttribute 가 있으면 다음을 실행한다.

-HelloData 객체를 생성한다.
-요청 파라미터의 이름으로 HelloData 객체의 프로퍼티를 찾는다.
-해당 프로퍼티의 setter를 호출해서 파라미터의 값을 입력(바인딩) 한다.

 

바인딩 오류

age=abc 처럼 숫자가 들어가야 할 곳에 문자를 넣으면 BindException 이 발생한다.

 

@ModelAttribute를 생략할 수도 있다.

/**
 * @ModelAttribute 생략 가능
 * String, int 같은 단순 타입 = @RequestParam
 * argument resolver 로 지정해둔 타입 외 = @ModelAttribute */
@ResponseBody
@RequestMapping("/model-attribute-v2")
public String modelAttributeV2(HelloData helloData) {
    log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
    return "ok";
}
스프링은 해당 생략시 다음과 같은 규칙을 적용한다.

String, int , Integer 같은 단순 타입 = @RequestParam
나머지 = @ModelAttribute (argument resolver 로 지정해둔 타입 외)

 

HTTP message body에 데이터를 직접 담아서 요청

  • HTTP API에서 주로 사용, JSON, XML, TEXT
  • 데이터 형식은 주로 JSON 사용
  • POST, PUT, PATCH

 

요청 파라미터와 다르게, HTTP 메시지 바디를 통해 데이터가 직접 넘어오는 경우는 @RequestParam , @ModelAttribute 를 사용할 수 없다. (물론 HTML Form 형식으로 전달되는 경우는 요청 파라미터로 인정된다.)

 

단순 텍스트

HttpEntity

/**
* HttpEntity: HTTP header, body 정보를 편리하게 조회
* - 메시지 바디 정보를 직접 조회(@RequestParam X, @ModelAttribute X)
* - HttpMessageConverter 사용 -> StringHttpMessageConverter 적용 *
* 응답에서도 HttpEntity 사용 가능
* - 메시지 바디 정보 직접 반환(view 조회X)
* - HttpMessageConverter 사용 -> StringHttpMessageConverter 적용 */
 @PostMapping("/request-body-string-v3")
 public HttpEntity<String> requestBodyStringV3(HttpEntity<String> httpEntity) {
     String messageBody = httpEntity.getBody();
     log.info("messageBody={}", messageBody);
     return new HttpEntity<>("ok");
 }
  • HttpEntity<String> httpEntity: HttpEntity 클래스는 HTTP 요청이나 응답을 표현하며, 여기서는 요청 본문에 담긴 String 타입의 데이터를 받는다.
  • HttpEntity는 요청의 헤더와 본문 정보를 모두 포함하고 있어, 메소드 내에서 요청 본문뿐만 아니라 헤더 정보에도 접근할 수 있다는 장점이 있다.
  • String messageBody = httpEntity.getBody(): getBody() 메소드를 사용해 HTTP 요청 본문의 내용을 String 형태로 추출한다.
  • return new HttpEntity<>("ok"): 처리 결과를 나타내는 "ok" 문자열을 담은 새로운 HttpEntity 객체를 반환한다. 이 객체는 클라이언트에게 응답 본문으로 전송되어, 처리가 성공적으로 이루어졌음을 알린다.

    이 방법은 특히 HTTP API를 구현할 때 클라이언트로부터 전달받은 데이터를 그대로 처리해야 할 경우 유용하다.

 

HttpEntity는 응답에도 사용 가능

  • 메시지 바디 정보 직접 반환
  • 헤더 정보 포함 가능
  • view 조회X

 

HttpEntity 를 상속받은 다음 객체들도 같은 기능을 제공한다.

  • RequestEntity
    -HttpMethod, url 정보가 추가, 요청에서 사용
  • ResponseEntity
    -HTTP 상태 코드 설정 가능, 응답에서 사용
    -return new ResponseEntity<String>("Hello World", responseHeaders, HttpStatus.CREATED)
스프링MVC 내부에서 HTTP 메시지 바디를 읽어서 문자나 객체로 변환해서 전달해주는데, 이때 HTTP 메시지 컨버터(HttpMessageConverter )라는 기능을 사용한다.

 

@RequestBody

/**
 * @RequestBody
 * - 메시지 바디 정보를 직접 조회(@RequestParam X, @ModelAttribute X)
 * - HttpMessageConverter 사용 -> StringHttpMessageConverter 적용 *
 * @ResponseBody
 * - 메시지 바디 정보 직접 반환(view 조회X)
 * - HttpMessageConverter 사용 -> StringHttpMessageConverter 적용
 */
@ResponseBody
@PostMapping("/request-body-string-v4")
public String requestBodyStringV4(@RequestBody String messageBody) {
    log.info("messageBody={}", messageBody);
    return "ok";
}

@RequestBody 를 사용하면 HTTP 메시지 바디 정보를 편리하게 조회할 수 있다.

헤더 정보가 필요하다면 HttpEntity 를 사용하거나 @RequestHeader 를 사용하면 된다.

이렇게 메시지 바디를 직접 조회하는 기능은 요청 파라미터를 조회하는 @RequestParam , @ModelAttribute 와 는 전혀 관계가 없다.

@ResponseBody 를 사용하면 응답 결과를 HTTP 메시지 바디에 직접 담아서 전달할 수 있다.

물론 이 경우에도 view를 사용하지 않는다.

 

@ResponseBody와 @RequestBody

@ResponseBody 어노테이션은 컨트롤러 메소드에서 반환하는 값을 HTTP 응답 본문으로 직접 전달하는 데 사용된다. 이 어노테이션이 붙어 있으면, 메소드의 반환 데이터가 응답 본문에 바로 쓰이게 되며, 스프링의 MessageConverter를 통해 클라이언트가 요구하는 형식(JSON, XML 등)으로 자동으로 변환된다. 이는 주로 RESTful 웹 서비스에서 클라이언트에게 데이터를 직접 반환할 때 활용된다.

@RequestBody 어노테이션은 HTTP 요청 본문에서 데이터를 읽어와 메소드의 파라미터로 바인딩할 때 사용된다. 클라이언트가 보낸 요청 본문의 내용을 자바 객체로 변환하여 컨트롤러 메소드의 인자로 전달하게 된다. 이 과정에서도 스프링의 MessageConverter가 자동으로 해당 작업을 수행한다. @RequestBody는 주로 클라이언트가 서버에 데이터를 전송할 때, 예를 들어 JSON이나 XML 형태의 데이터를 받아 처리할 때 사용된다.

간단히 말해, @ResponseBody는 서버에서 클라이언트로 데이터를 보낼 때 사용되며, @RequestBody는 클라이언트에서 서버로 데이터를 전송할 때 사용된다.

 

요청 파라미터 vs HTTP 메시지 바디

요청 파라미터를 조회하는 기능: @RequestParam , @ModelAttribute
HTTP 메시지 바디를 직접 조회하는 기능: @RequestBody

 

 

JSON

아래의 내용들은 content-typeapplication/json 일때 해당하는 내용들이다.

@RequestBody 문자 변환

/**
* @RequestBody
* HttpMessageConverter 사용 -> StringHttpMessageConverter 적용 *
* @ResponseBody
* - 모든 메서드에 @ResponseBody 적용
* - 메시지 바디 정보 직접 반환(view 조회X)
* - HttpMessageConverter 사용 -> StringHttpMessageConverter 적용 */
 @ResponseBody
 @PostMapping("/request-body-json-v2")
 public String requestBodyJsonV2(@RequestBody String messageBody) throws IOException {
     HelloData data = objectMapper.readValue(messageBody, HelloData.class);
     log.info("username={}, age={}", data.getUsername(), data.getAge());
     return "ok";
}
ObjectMapper

Jackson 라이브러리의 핵심 클래스로, 자바 객체와 JSON 간의 변환을 담당한다.
JSON 문자열을 자바 객체로 변환하거나(역직렬화), 자바 객체를 JSON 문자열로 변환하는(직렬화) 작업을 수행한다.
ObjectMapper 는 높은 유연성과 다양한 설정 옵션을 제공하여 복잡한 JSON 구조와 자바 객체 간의 매핑을 가능하게 한다.

 

@RequestBody 객체 변환

/**
* @RequestBody 생략 불가능(@ModelAttribute 가 적용되어 버림)
* HttpMessageConverter 사용 -> MappingJackson2HttpMessageConverter (content-type:application/json)
*/
 @ResponseBody
 @PostMapping("/request-body-json-v3")
 public String requestBodyJsonV3(@RequestBody HelloData data) {
     log.info("username={}, age={}", data.getUsername(), data.getAge());
     return "ok";
}

HttpEntity , @RequestBody 를 사용하면 HTTP 메시지 컨버터가 HTTP 메시지 바디의 내용을 우리가 원하는 문자나 객체 등으로 변환해준다.
HTTP
메시지 컨버터는 문자 뿐만 아니라 JSON도 객체로 변환해준다.

 

HttpEntity

@ResponseBody
@PostMapping("/request-body-json-v4")
public String requestBodyJsonV4(HttpEntity<HelloData> httpEntity) { //HttpEntity를 사용해도 됨
    HelloData data = httpEntity.getBody();
    log.info("username={}, age={}", data.getUsername(), data.getAge());
    return "ok";
}

 

리턴을 객체로

/**
* @RequestBody 생략 불가능(@ModelAttribute 가 적용되어 버림)
* HttpMessageConverter 사용 -> MappingJackson2HttpMessageConverter (content-type:application/json)
*
* @ResponseBody 적용
* - 메시지 바디 정보 직접 반환(view 조회X)
* - HttpMessageConverter 사용 -> MappingJackson2HttpMessageConverter 적용(Accept:application/json)
*/
@ResponseBody
@PostMapping("/request-body-json-v5")
public HelloData requestBodyJsonV5(@RequestBody HelloData data) {
    log.info("username={}, age={}", data.getUsername(), data.getAge());
    return data;
 }

 

응답의 경우에도 @ResponseBody 를 사용하면 해당 객체를 HTTP 메시지 바디에 직접 넣어줄 수 있다.

물론 이 경우에도 HttpEntity 를 사용해도 된다.

 

@RequestBody 요청

  • JSON 요청 -> HTTP 메시지 컨버터 -> 객체

 

@ResponseBody 응답

  • 객체 -> HTTP 메시지 컨버터 -> JSON 응답

'Java Category > Spring' 카테고리의 다른 글

[Spring] 어노테이션 정리  (0) 2024.02.23
[Spring MVC] 기본 기능 - HTTP 응답  (0) 2024.02.22
[Spring MVC] 기본 기능 - 요청 매핑  (0) 2024.02.20
[Spring] Logging  (0) 2024.02.19
[Spring MVC] 스프링 MVC - 구조 이해  (1) 2024.02.18

댓글