[Spring] 요청 매핑, 요청 데이터 조회

olsohee·2023년 5월 2일
0

Spring

목록 보기
5/12
post-custom-banner

1. 요청 매핑

@RestController

  • @Controller는 반환 값이 String이면 뷰 이름으로 인식된다. 따라서 해당 이름의 뷰를 찾고 뷰가 랜더링 된다.

  • 반면 @RestController는 반환 값으로 뷰를 찾는 것이 아니라, 반환 값을 HTTP 메시지 바디에 바로 입력한다.

@RequestMapping

  • 예를 들어 @RequestMapping("/hello-basic")으로 매핑하는 경우, 해당 URL을 호출하면 URL 매핑한 메소드가 실행된다.

  • URL 다중 설정도 가능하다.
    ex, @RequestMapping({"/hello-basic", "/hello-go"})

  • method 속성을 지정할 수 있으며, 축약 애노테이션으로 @GetMapping, @PostMapping, @PutMapping 등이 있다.
    ex, @RequestMapping(value = "/hello-basic", method = RequestMethod.GET)
    ex, @GetMapping("/hello-basic")

    method 속성을 지정하지 않으면 HTTP 메소드와 무관하게 호출된다. 즉 GET, POST, PUT 등 모두 허용한다.

다음 두가지 요청은 다른 URL이지만, 스프링은 같은 요청으로 매핑한다.

  • 매핑: /hello-basic
  • URL 요청: /hello-basic, /hello-basic/

@PathVariable

  • @RequestMapping은 URL 경로를 템플릿화 할 수 있는데, @PathVariable을 사용하면 매칭 되는 부분을 편리하게 조회할 수 있다.
@GetMapping("/mapping/{userId}")
public String mappingPath(@PathVariable("userId") String data) {}
  • @PathVariable의 이름과 파라미터 이름이 같으면 생략할 수 있다.
@GetMapping("/mapping/{userId}")
public String mappingPath(@PathVariable String userId) {}
  • 다음과 같이 다중 사용도 가능하다.
@GetMapping("/mapping/users/{userId}/orders/{orderId}")
public String mappingPath(@PathVariable String userId, @PathVariable Long orderId) {}

조건 매핑

특정 파라미터 조건 매핑

@GetMapping(value = "/mapping-param", param = "mode=debug")
  • 위 예제의 경우 http://localhost:8080/mapping-param?mode=debug와 같이mode=debug라는 파라미터가 있는 경우에만 호출된다. 만약 http://localhost:8080/mapping-param로 호출하면 매핑되지 않는다.

특정 헤더 조건 매핑

@GetMapping(value = "/mapping-header", headers = "mode=debug")
  • 위 예제의 경우 요청 헤더 값으로 mode=debug를 넣어서 요청하는 경우에만 호출된다.

미디어 타입 조건 매핑 (Content-Type)

@PostMapping(value = "/mapping-consume", consumes = "application/json")
  • 위 예제의 경우 요청 헤더의 Content-Type이 application/json인 경우에만 호출된다.

미디어 타입 조건 매핑 (Accept)

@PostMapping(value = "/mapping-produce", produces = "text/html")
  • 위 예제의 경우 요청 헤더의 Accept가 text/html인 경우에만 호출된다.

2. 헤더 정보 조회

@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) {
    ...                      
	}
}
  • HttpServletRequest, HttpServletResponse
    HttpServletRequest, HttpServletResponse 객체를 통해 HTTP 요청 메시지와 응답 메시지를 쉽게 조회할 수 있다.

  • HttpMethod
    HTTP 메소드를 조회한다.

  • Locale
    Locale 정보를 조회한다.

  • @RequestHeader MultiValueMap<String, String> headerMap
    모든 HTTP 헤더를 MutiValueMap 형식으로 조회한다.

    MutiValueMap

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

  • @RequestHeader("host") String host
    특정 HTTP 헤더를 조회한다.

  • @CookieValue(value = "myCookie", required = false) String cookie
    특정 쿠키를 조회한다.


3. 요청 데이터 조회

HTTP 요청 메시지를 통해 클라이언트에서 서버로 데이터를 전달하는 방식으로 다음 3가지 방식이 있다.

  1. GET 방식의 쿼리 파라미터
    • 메시지 바디 없이, URL의 쿼리 파라미터에 데이터를 포함해서 전달한다.
      ex, /url?username=hello&age=20
    • 검색, 필터, 페이징 등에서 주로 사용한다.
  2. POST 방식의 HTML Form
    • 메시지 바디에 쿼리 파라미터 형식으로 전달한다.
      ex, username=hello&age=20
    • 회원 가입, 상품 주문, HTML Form 등에서 주로 사용한다.
  3. HTTP message body에 데이터를 직접 담아서 요청
    • HTTP API에서 주로 사용한다.
    • 데이터 형식은 JSON, TEXT, XML 등이 있고, JSON을 주로 사용한다.

3-1. 파라미터 (쿼리 파라미터, HTML Form)

request.getParameter()

@RequestMapping("/request-param-v1")
public void requestParamV1(HttpServletRequest request, HttpServletResponse response) throws IOException {
    
	String username = request.getParameter("username");
	int age = Integer.parseInt(request.getParameter("age"));
        
	response.getWriter().write("ok");
}

@RequestParam

  • @RequestParam는 파라미터 이름으로 바인딩한다.
@ResponseBody
@RequestMapping("/request-param-v2")
public String requestParamV2(@RequestParam("username") String username,
          					 @RequsetParam("age") int age) {
    
	return "ok";
}
  • HTTP 파라미터 이름이 변수 이름과 같으면 파라미터 이름을 생략할 수 있다.
@ResponseBody
@RequestMapping("/request-param-v3")
public String requestParamV3(@RequestParam String username,
          					 @RequsetParam int age) {
    
	return "ok";
}
  • String, int, Integer 등 단순 타입이면 @RequestParam도 생략할 수 있다.
@ResponseBody
@RequestMapping("/request-param-v4") 
public String requestParamV4(String username, int age) {
    
	return "ok";
}

required

  • required 속성은 파라미터 필수 여부를 뜻하며, 기본값이 true이다.

  • 만약 @RequestParam(required = false) int age이고 파라미터 값이 없는 경우, int형 변수에 null이 입력될 수 없기 때문에 예외가 발생한다. 따라서 이 경우 int형이 아닌 Integer형으로 변경해야 한다.

  • 파라미터 이름만 있고 값이 없는 상태로 URL 요청을 하면, 이때는 null 값이 아닌 공백 문자가 입력된다. 예를 들어 http://localhost:8080/request-param-required/username=인 경우, username에는 공백 문자가 입력된다.

defaultValue

  • 파라미터 값이 없는 경우 defaultValue를 통해 기본 값을 적용할 수 있다.
@ResponseBody
@RequestMapping("/request-param-default") 
public String requestParamDefault(
    				@RequestParam(defaultValue = "guest") String username,
          		    @RequsetParam(defaultVale = -1) Integer age) {
    
	return "ok";
}
  • required 속성과 다르게 defaultValue 속성은 빈문자가 들어오면 빈문자가 아닌 값이 없다고 판단하여 defaultValue 값으로 매핑한다. 따라서 만약 http://localhost:8080/request-param-required/username=인 경우 username은 guest로, age는 -1로 매핑된다.

파라미터를 Map이나 MultiValueMap으로 조회

@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";     
}

@ModelAttribute

  • 요청 파라미터를 받아서 필요한 객체를 만들고 그 객체에 값을 넣어야 하는 상황에서, @ModelAttribute를 사용하면 이러한 과정을 자동화해준다.
@ResponseBody
@RequestMapping("/model-attribute-v1") 
public String modelAttributeV1(@ModelAttribute HelloData helloData) {
	
    ...
    
    return "ok";
}
  • 스프링 MVC는 @ModelAttribute가 있으면 다음을 실행한다.

    • HelloData 객체를 생성한다.

    • 요청 파라미터 이름(ex, username, age)으로 객체의 setter 메소드를 찾아 호출하여 파라미터의 값을 바인딩한다.
      ex, 파라미터로 username=lee&age=26 데이터가 들어오면, HelloData의 setUsername()setAge() 메소드를 찾아서 lee와 26이라는 데이터를 바인딩해준다.

    • 해당 객체를 Model에 자동으로 넣어준다.
      (ex, model.addAttribute("helloData", helloData); 생략 가능)

  • @ModelAttribute에 이름을 지정하면 해당 이름으로 모델에 등록되고, 이름을 지정하지 않으면 클래스 이름의 첫 문자를 소문자로 바꾼 이름으로 모델에 등록된다.
    ex, @ModelAttribute("data") HelloData helloData -> data
    ex, @ModelAttribute HelloData helloData -> helloData

  • @ModelAttribute는 생략할 수 있다.

@ResponseBody
@RequestMapping("/model-attribute-v2") 
public String modelAttributeV2(HelloData helloData) {
	
    log.info("username={}, age={}", helloData.getUserName(), helloData.getAge());
    return "ok";
}

@ModelAttribute, @RequestParam 생략

@ModelAttribute, @RequestParam 둘 다 생략할 수 있는데, 스프링은 어떻게 구분해서 처리할까?
변수가 String, int, Integer 같은 단순 타입이면 @RequestParam, 나머지 형이면(argument resolver로 지정해둔 타입 제외) @ModelAttribute로 처리한다.

3-2. HTTP message body

InputStream

  • InputStream: HTTP 요청 메시지 바디의 내용을 직접 조회할 수 있다.

  • OutputStream: HTTP 응답 메시지 바디에 직접 결과를 출력할 수 있다.

텍스트 형식

@Slf4j
@Controller
public class RequestBodyStringController {

	@PostMapping("/request-body-string-v1")
    public void requestBodyString(HttpServletRequest request, HttpServletResponse response) throws IOException {
    
    	ServletInputStream inputStream = request.getInputStream();
        String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
        
        log.info("messageBody={}", messageBody);
        response.getWriter().write("ok");
    }
    
	@PostMapping("/request-body-string-v2")
    public void requestBodyStringV2(InputStream inputStream, Writer responseWriter) {
    
        String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
        
        log.info("messageBody={}", messageBody);
        responseWriter.write("ok");
    }
}

JSON 형식

@Slf4j
@Controller
public class RequestBodysonController {

	private ObjectMapper objectMapper = new ObjectMapper();
    
    @PostMapping("/request-body-json-v1")
    public void requestBodyJsonV1(HttpServletRequest request, HttpServletResponse response) throws IOException {
    	//HTTP 메시지 바디에서 데이터를 읽어와서 문자로 변환
    	ServletInputStream inputStream = request.getInputStream();
        String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
        
        //문자를 객체로 변환
        HelloData data = objectMapper.readValue(messageBody, HelloData.class);
        
        log.info("meaasgeBody={}", messageBody);
        log.info("username={}, age=[]", data.getUsername(), data.getAge());   
        response.getWriter().write("ok");
    }
}

HttpEntity

  • HttpEntity를 통해 HTTP 헤더바디 정보를 편리하게 조회할 수 있다.
    ex, httpEntity.getHeaders(), httpEntity.getBody()

  • HttpEntity는 응답에도 사용할 수 있다. 뷰를 조회하지 않고 메시지 바디 정보를 직접 반환한다.

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

    • RequestEntity: HTTP 헤더, 바디 정보 외에도 HttpMethod, url 정보를 조회할 수 있다.

    • ResponseEntity: HTTP 상태 코드를 설정할 수 있다.
      ex, return new ResponseEntity<String>("ok", HttpStatus.CREATED);

텍스트 형식

@PostMapping("/request-body-string-v3")
public HttpEntity<String> requestBodyStringV3(HttpEntity<String> httpEntity) {
    
	String messageBody = httpEntity.getBody();
        
	log.info("messaeBody={}", messageBody);
	return new HttpEntity<>("ok");
}

JSON 형식

@ResponseBody
@PostMapping("/request-body-json-v4")
public String requestBodyJsonV4(HttpEntity<HelloData> httpEntity) {

	HelloData data = httpEntity.getBody();
	log.info("username={}, age=[]", data.getUsername(), data.getAge());
	return "ok";
}

@RequestBody

  • @RequestBody를 통해 HTTP 메시지 바디 정보를 편리하게 조회할 수 있다.
    만약 헤더 정보가 필요하다면, HttpEntity 또는 @RequestHeader를 사용하면 된다.

  • cf, @ResponseBody: 리턴 값으로 뷰를 조회하지 않고, 리턴 값을 HTTP 메시지 바디에 직접 담아서 전달한다.

텍스트 형식

@ResponseBody
@PostMapping("/request-body-string-v4")
public String requestBodyStringV4(@RequestBody String messageBody) {
    
	log.info("messaeBody={}", messageBody);
	return "ok";
}

JSON 형식

@PostMapping("/request-body-json-v2")
public void requestBodyJsonV2(@RequestBody String messageBody) throws IOException {

		//문자를 객체로 변환
        HelloData data = objectMapper.readValue(messageBody, HelloData.class);
        
        log.info("meaasgeBody={}", messageBody);
        log.info("username={}, age=[]", data.getUsername(), data.getAge()); 
        response.getWriter().write("ok");
    }
}
@ResponseBody
@PostMapping("/request-body-json-v3")
public void requestBodyJsonV3(@RequestBody HelloData data) { //JSON -> 메시지 컨버터 -> 객체

	log.info("username={}, age=[]", data.getUsername(), data.getAge());
	return data; //객체 -> 메시지 컨버터 -> JSON
}
  • @RequestBody, HttpEntity를 사용하면 HTTP 메시지 컨버터가 HTTP 메시지 바디의 내용을 우리가 원하는 문자나 객체 형태로 변환해준다.

  • 이때 @RequestBody 애노테이션은 생략할 수 없다. @RequestBody 애노테이션을 생략하면, 스프링은 @ModelAttribute 또는 @RequestParam으로 인식한다.

profile
공부한 것들을 기록합니다.
post-custom-banner

0개의 댓글