[Spring] HTTP 요청 방법과 관련 어노테이션(Annotation)

HTTP 요청 방법 3가지

스프링 HTTP 요청 방법 3가지는 다음과 같이 이뤄져있습니다.

 

GET 방식

메시지 바디 없이, URL의 “쿼리 파라미터”를 사용해 데이터를 전달합니다. ( EX :  검색, 필터, 페이징 등에서 많이 사용 )

쿼리 파라미터는 URL에 다음과 같이 ? 를 시작으로 보낼 수 있습니다.

추가 파라미터는 & 로 구분합니다.

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

 

POST 방식 - HTML Form

 

HTML의 Form을 사용해서 클라이언트에서 서버로 데이터를 전송하는 방식으로 , 주로 회원 가입, 상품 주문 등에서 사용합니다.

컨텐츠 타입은 다음과 같으며 application/x-www-form-urlencoded

메시지 바디에 쿼리 파라미터 형식으로 데이터를 전달합니다.  ( username=hello&age=20 )

쿼리 파라미터 형식으로 전달되는 것이 GET방식의 쿼리 파라미터 형식과 같아 보이는데 GET 요청과 FORM 요청 방식에 아래와 같은 차이점이 있습니다.

 

  • GET과 FORM의 차이 ?

"Content-Type"은 HTTP 메시지 바디의 데이터 형식을 지정하는 헤더에 실리는 정보입니다.

GET 요청 방식은 URL 쿼리 파라미터 형식으로 클라이언트에서 서버로 데이터를 전달할 때는 HTTP 메시지 바디를 사용하지 않기 때문에 Content-Type이 없습니다.

하지만 POST 요청 방식은 HTML Form 형식으로 데이터를 전달하면 HTTP 메시지 바디에 해당 데이터를 포함해서 보내기 때문에 바디에 포함된 데이터가 어떤 형식인지 Content-Type을 꼭 지정해줘야합니다.

이때, 폼으로 데이터를 전송하는 형식application/x-www-form-urlencoded 라고 합니다.

 

 

API 메시지 바디 - 단순 텍스트

 

HTTP message body에 데이터를 직접 담아서 요청합니다.

HTTP API에서 주로 사용하며, JSON, XML, TEXT 형식을 사용하는데 주로 JSON을 사용합니다.

메시지 바디에 데이터를 직접 담아서 요청하기 때문에 POST, PUT, PATCH 메서드에서만 사용이 가능합니다.

 

 

HTTP 요청 관련 어노테이션

@RequestMapping

@RequestMapping( "/test") 일 경우 "/test" URL 호출이 오면 이 메서드가 실행되도록 매핑합니다.

대부분의 속성을 배열[] 로 제공하므로 다중 설정도 가능합니다. {"/test-1", "/test-2"}

 

@RequestMapping(value = "test", method = RequestMethod.GET)

method 속성으로 HTTP 메서드를 지정하지 않으면 HTTP 메서드와 무관하게 호출될 수 있습니다. (모두 허용 GET, HEAD, POST, PUT, PATCH, DELETE)

그래서 위처럼 허용가능한 메서드를 지정할 수 있는데 이렇게 사용하기에는 코드가 길고 적기가 귀찮음이 있습니다;

그래서 Spring은 HTTP 메서드를 축약한 어노테이션을 제공해줍니다.

@GetMapping
@PostMapping
@PutMapping
@DeleteMapping
@PatchMapping

@RequestParam

URI뒤에 붙는 쿼리 파라미터 값을 받아 올 때 사용합니다.

@ResponseBody
@RequestMapping("/test")
public String requestParamTest(
        @RequestParam(required = true) String username,
        @RequestParam(required = false) Integer age) {
    log.info("username={}, age={}", username, age);
    return "ok";
}

http://localhost:8080/test?username=hyun&age=20

test 뒤에 쿼리 파라미터 값을 받아온 것을 볼 수 있습니다.

 

required 옵션

파라미터의 필수 여부로 기본값은 true입니다.

필수 파라미터일 경우 해당 파라미터가 들어오지 않는다면 400 예외가 발생하게 됩니다.

필수 파라미터 아닐 경우에는 생략 가능합니다

만약 파라미터 이름만 있고 값이 없는 경우에는 빈 문자로 인식됩니다. ⇒ http://localhost:8080/test?username= 일 때 username=”” 으로 인식

여기서 age의 타입을 int가 아닌 Interger를 사용했는데 빈 값일 경우 null이 되어야하는데 int는 null을 받을 수 없기 때문에

null을 받을수 있는 Integer타입을 사용하였습니다.

 

defaultValue 옵션

값이 없을 경우 기본 값을 적용할 수 있습니다.

defaultValue를 사용하면 이미 기본 값이 있기 때문에 required는 의미가 없습니다.

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

@ResponseBody
@GetMapping("/test")
public String requestParamTest(
        @RequestParam(defaultValue = "guest") String username,
        @RequestParam(defaultValue = "-1") int age) {
    log.info("username={}, age={}", username, age);
    return "ok";
}

@PathVariable

REST API 호출 시 주로 많이 사용하는 방법으로 URI에서 식별자의 값을 받아 올 때 사용합니다.

http://localhost:8080/test/users/{userId}

만약, @PathVariable 의 이름과 파라미터 이름이 같으면 생략할 수 있습니다.

@ResponseBody
@GetMapping("/test/users/{userId}")
public String mappingPath(@PathVariable String userId) {
 log.info("userId={}, orderId={}, userId);
 return "ok";
}

http://localhost:8080/mapping/users/hyun 으로 위 처럼 요청 했을 시 결과는 userId = hyun 가 됩니다.

 

하나의 식별자뿐 아니라 여러 식별자도 받아 올 수 있습니다.

@ResponseBody
@GetMapping("/test/users/{userId}/orders/{orderId}")
public String mappingPath(@PathVariable String userId, @PathVariable Long orderId) {
 log.info("userId={}, orderId={}", userId, orderId);
 return "ok";
}

http://localhost:8080/mapping/users/hyun/orders/10 으로 위 처럼 요청 했을 시 결과는 userId = hyun, orderId = 10 가 됩니다.


@ModelAttribute

개발을 하면 요청 파라미터를 받아서 필요한 객체를 만들고 그 객체에 값을 넣어주어야 할때가 있습니다.

스프링에서는 위 과정을 자동화해주는 @ModelAttribute 기능을 제공합니다.

 

아래는 테스트를 위해 직접 만든 객체입니다.

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

 

ModelAttribute를 사용하지 않을 경우
@ResponseBody
@GetMapping("/model-attribute")
public String modelAttribute(@RequestParam String username, @RequestParam int age) {
    HelloData helloData = new HelloData();
    helloData.setUsername(username);
    helloData.setAge(age);

		log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
		log.info("helloData={}", helloData);

    return "ok";
}

 

ModelAttribute를 사용하는 경우
@ResponseBody
@GetMapping("/model-attribute")
public String modelAttribute(@ModelAttribute HelloData helloData) {
    
    log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
    log.info("helloData={}", helloData);

    return "ok";

}

위 코드를 실행하고 쿼리에 객체의 값을 지정하여 요청하면, 자동으로 HelloData 객체가 생성되고, 요청 파라미터 값이 들어가게 됩니다.

 

@ModelAttribute의 작동 과정

스프링MVC는 @ModelAttribute 가 있으면 다음을 실행하게 됩니다.

  1. HelloData 객체를 생성한다.
  2. 요청 파라미터의 이름으로 HelloData 객체의 프로퍼티를 찾는다. 그리고 해당 프로퍼티의 setter를 호출해서 파라미터의 값을 입력(바인딩) 한다.
    예) 파라미터 이름이 username 이면 setUsername() 메서드를 찾아서 호출하면서 값을 입력한다.
  3. username 프로퍼티의 값을 변경하면 setUsername() 이 호출되고, 조회하면 getUsername() 이 호출된다.

프로퍼티란?

프로퍼티 객체에 getUsername() , setUsername() 메서드가 있으면, 이 객체는 username 이라는 프로퍼티를 가지고 있다고 할 수 있다.

 

@ModelAttribute , @RequestParam과 생략할 시

스프링은 @ModelAttribute , @RequestParam 과 같은 해당 애노테이션을 생략시 다음과 같은 규칙을 적용하게 됩니다.

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

 

메서드 위에 @ModelAttribute 어노테이션 사용 시

컨트롤러의 다른 요청 핸들러 메소드를 호출하기 전에 항상 메소드명 위에  @ModelAttribute가 붙은 메소드를 먼저 호출한합니다.

주로 요청 핸들러 메소드를 호출하기 전모델 객체를 만들기 위해 사용합니다.

    @ModelAttribute("regions")
    public Map<String, String> regions() {
        Map<String, String> regions = new LinkedHashMap<>();
        regions.put("SEOUL", "서울");
        regions.put("BUSAN", "부산");
        regions.put("JEJU", "제주");
        return regions;
    }

옵션으로 뷰 템플릿으로 넘기는 데이터의 이름을 위처럼 지정이 가능합니다.


@RequestHeader

@Slf4j
@RestController
public class RequestHeaderController {

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

- @RequestHeader MultiValueMap<String, String> headerMap : 모든 HTTP 헤더를 MultiValueMap 형식으로 조회

- @RequestHeader("host") String host : 특정 HTTP 헤더를 조회 속성 - 필수 값 여부: required , 기본 값 속성: defaultValue

- @CookieValue(value = "myCookie", required = false) String cookie : 특정 쿠키를 조회 속성 - 필수 값 여부: required , 기본 값: defaultValu

 

MultiValueMap?

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

2023-03-27 23:40:28.364  INFO 1919828 --- [nio-8080-exec-7] h.s.b.request.RequestHeaderController    : response=org.apache.catalina.connector.ResponseFacade@4b31a9a0
2023-03-27 23:40:28.364  INFO 1919828 --- [nio-8080-exec-7] h.s.b.request.RequestHeaderController    : httpMethod=GET
2023-03-27 23:40:28.364  INFO 1919828 --- [nio-8080-exec-7] h.s.b.request.RequestHeaderController    : locale=ko_KR
2023-03-27 23:40:28.364  INFO 1919828 --- [nio-8080-exec-7] h.s.b.request.RequestHeaderController    : headerMap={content-type=[application/json], user-agent=[PostmanRuntime/7.31.3], accept=[*/*], cache-control=[no-cache], postman-token=[d875e541-f2c0-4143-92c7-0dcd8ba920ae], host=[localhost:8080], accept-encoding=[gzip, deflate, br], connection=[keep-alive], content-length=[5]}
2023-03-27 23:40:28.365  INFO 1919828 --- [nio-8080-exec-7] h.s.b.request.RequestHeaderController    : header host=localhost:8080
2023-03-27 23:40:28.365  INFO 1919828 --- [nio-8080-exec-7] h.s.b.request.RequestHeaderController    : myCookie=null

 


@RequestBody

HTTP 메시지 바디 정보를 편리하게 조회할 수 있습니다.

 

단순 문자 반환 - (반환 타입 : String)
@ResponseBody
@PostMapping("/request-body-string")
public String requestBodyString(@RequestBody String messageBody) {
    log.info("messageBody = {}", messageBody);
    return "ok";
}

postman으로 body의 내용을 위처럼 적어 요청시 나온 결과는 다음과 같습니다.

 

JSON 반환 - (반환 타입 : 객체타입)

@RequestBody HelloData data 처럼 직접 만든 객체를 지정하여 반환할 수도 있는데,

객체 타입을 반환하게 되면 HTTP 메시지 컨버터가 HTTP 메시지 바디의 내용을 JSON으로 반환해 줍니다.

@ResponseBody
    @PostMapping("/request-body-json-v3")
    public HelloData requestBodyJsonV3(@RequestBody HelloData helloData) {
        log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
        return helloData;
    }

 

postman으로 객체의 값을 JSON으로 지정하여 요청시 나온 결과는 다음과 같습니다. 

 

 

@RequestBody 생략시 주의 점

@RequestBody는 생략할 수 있는데 생략하게 된다면 다음과 같은 문제점이 생기므로 주의해야합니다.

이 경우 HelloData에 @RequestBody 를 생략하면 @ModelAttribute가 자동으로 적용되어 HTTP 메시지 바디가 아니라 요청 파라미터를 처리하게 됩니다.

 


HTTP 응답 방법과 관련 어노테이션 보기

 

[Spring] HTTP 응답 방법과 관련 어노테이션(Annotation)

HTTP 응답 방법 3가지 스프링에서 응답 데이터를 만드는 방법은 크게 3가지가 있습니다. 정적 리소스 스프링 부트는 클래스 패스의 아래 디렉토리에 있는 정적 리소스를 제공합니다. /static , /public

hstory0208.tistory.com