인프런/스프링 MVC 1편

9)요청매핑

backend dev 2023. 1. 20.
@RestController
public class MappingController {

    private Logger logger = LoggerFactory.getLogger(getClass());

    @RequestMapping("/hello-basic")
    public String helloBasic() {
        logger.info("basic");
        return "ok";
    }
}

@RequestMapping({"/hello-basic","/hello-go"})
public String helloBasic() {
    logger.info("basic");
    return "ok";
}

{ } 안에 url를 여러개 넣어서 매핑할 수 있다.

매핑한 url 그대로 요청을 해야한다!
그래서 method를 매핑해주거나 @GetMapping이런 어노테이션을 써야한다.

@RequestMapping(value = {"/hello-basic", "/hello-go"}, method = RequestMethod.GET)
public String helloBasic() {
    logger.info("basic");
    return "ok";
}

위와같이 @RequestMapping으로 method 매핑가능.

@RestController
public class MappingController {
    private Logger logger = LoggerFactory.getLogger(getClass());

    @RequestMapping(value = "/mapping-get-v1", method = RequestMethod.GET) //@RequestMapping에 method를 매핑하는방식
    public String mappingGetV1() {
        logger.info("mappingGetV1");
        return "ok";
    }
    
    @GetMapping("/mapping-get-v2") // 메소드가 축약되어있는 어노테이션을 사용
    public String helloBasic() {
        logger.info("basic");
        return "ok";
    }
}

 

PathVariable(경로변수) 사용 

//PathVariable사용 가능하다. url주소 자체안에 값을 변수의 값으로 인식하고 사용한다.  (경로변수라고도 한다)
@GetMapping("/mapping/{userId}")
public String mappingPath(
    @PathVariable("userId") String data) { // @Pathvariable("파라미터이름")을 이용해서 매개변수로 꺼낼수있다.
    logger.info("mappingPath : {}", data);
    return "ok";
}

@GetMapping안에 @RequestMapping이 들어있으니 @RequestMapping이라고 부른다. 결국 요청매핑이니까

URL경로의 템플릿화는

"/mapping/{userId}"

처럼 값을 받을수있게끔 한것을 말한다.

 

@PathVariable의 이름(@PathVariable을 받을 변수의 이름)과 파라미터 이름이 같으면 생략가능하다.

@GetMapping("/mapping/{userId}")
public String mappingPath(
    @PathVariable String userId) { //이런식으로 파라미터이름과 Pathvariabe 이름이 같으므로 바로 받을 수 있다.
    logger.info("mappingPath : {}", userId);
    return "ok";
}

변수명에 맞추고 싶다면 위처럼, 받은 파라미터값과 변수명을 다르게하고싶다면 @PathVariable(파라미터명)으로

 

PathVariable 사용 - 다중

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

다음과같이 url경로에 PathVariable을 여러개 사용가능하다.

http://localhost:8080/mapping/users/userA/orders/100 식으로 userA이고 주문번호 100으로 들어왔다.

뭔가 users뒤에 유저아이디 들어가고 orders 뒤에 주문번호가 들어가니까 직관적인거 같다.

잘 들어온다.

 

파라미터 조건 추가 매핑

파라미터 조건을 걸 수 있다.

URL 매핑 + 파라미터 조건 매핑

/**
 * 파라미터로 추가 매핑
 * params="mode",
 * params="!mode"
 * params="mode=debug"
 * params="mode!=debug" (! = )
 * params = {"mode=debug","data=good"}
 */
@GetMapping(value = "/mapping-param", params = "mode=debug")
public String mappingParam() {
    logger.info("mappingParam");
    return "ok";
}

"mode=debug"라고 되어있으면 mode라는 파라미터 이름에 debug라는 값이 담겨있어야되고

그렇지 않으면 오류가 발생한다.  주석을 보면 다양하게 조건을 넣을 수 있다.

 

요즘에는 잘 사용하지않는다.

 

특정 헤더조건 추가 매핑

URL 매핑 + 헤더조건 매핑

/**
 * 특정 헤더로 추가 매핑
 * headers="mode",
 * headers="!mode"
 * headers="mode=debug"
 * headers="mode!=debug" (! = )
 */
@GetMapping(value = "/mapping-header", headers = "mode=debug")
public String mappingHeader() {
    logger.info("mappingHeader");
    return "ok";
}

 

매핑된 헤더를 넣지않고 url호출해보면

404 에러가 발생

헤더가 매핑된대로 키를 mode 값을 debug로 헤더에 넣고 시도하면

성공한다.

주석은 헤더를 매핑하는 다양한 방법을 소개하고있다.

 

MediaType(미디어타입)조건 추가 매핑 - HTTP 요청 Content-Type, consumes

== Content-Type 헤더 기반 추가 매핑 

/**
 * Content-Type 헤더 기반 추가 매핑 Media Type
 * consumes="application/json"
 * consumes="!application/json"
 * consumes="application/*"
 * consumes="*\/*"
 * MediaType.APPLICATION_JSON_VALUE
 */
@PostMapping(value = "/mapping-consume", consumes = "application/json")
public String mappingConsumes() {
    logger.info("mappingConsumes");
    return "ok";
}

consumes를 이용해서 매핑한다.

타입이 맞지않는다면 415 오류가 발생한다.

postman에서는 body에서 타입을 고르면 알아서 Content-Type을 맞춰준다.

JSON으로 보내니 성공하는걸 볼 수 있다.

 

즉 , http요청할때 Content-Type에 대해 조건을 거는것이다  (consumes)

 

 

MediaType(미디어타입)조건 추가 매핑 - HTTP 요청 Accept, produce

Http 요청의 Accept헤더를 기반으로 매핑한다.

컨트롤러가 생산해내는 미디어타입이 클라이언트가 받을 수 있는 타입인지 확인해야한다.

Accept 헤더는 요청한 클라이언트가 받을 수 있는 Content-Type 을 의미한다.

 

produces에는 컨트롤러가 만들어서 반환하는 미디어타입을 적는다.

/**
 * Accept 헤더 기반 Media Type
 * produces = "text/html"
 * produces = "!text/html" * produces = "text/*"
 * produces = "*\/*"
 */
@PostMapping(value = "/mapping-produce", produces = "text/html")
public String mappingProduces() {
    logger.info("mappingProduces");
    return "ok";
}

produces가 text/html인 상태에서 url호출을 해보았다.

Accpet헤더가 모든 타입을 받는상태라 성공하였다. ( 모든 응답 타입을 받을 수 있다.)

클라이언트가 받을수있는 타입을 application/json으로 바꾸었다.

produces가 text/html이므로 컨트롤러는 text/html타입을 반환할텐데 , 클라이언트는 json형태만 받을 수 있으므로

406오류가 발생하였다.

 

즉 , produces는 http요청헤더의 Accept기반으로 매핑이 된다.

 

Content-Type을 문자열로 적지말고 변수를 사용하자.

@PostMapping(value = "/mapping-consume", consumes = "application/json")
@PostMapping(value = "/mapping-produce", produces = "test/html")

에서 Content-Type을 문자열로 적는거보다는

@PostMapping(value = "/mapping-consume", consumes = MediaType.APPLICATION_JSON_VALUE)
@PostMapping(value = "/mapping-produce", produces = MediaType.TEXT_HTML_VALUE)

 org.springframework.http.MediaType안에 상수로 정해져있는 변수를 이용하는것이 더 좋다.

 

 

 

Produces 추가

    @RequestMapping("/error-page/500")
    public String errorPage500(HttpServletRequest request, HttpServletResponse response) {
        log.info("errorPage 500");
        printErrorInfo(request);
        return "error-page/500";
    }

    @RequestMapping(value = "/error-page/500", produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<Map<String, Object>> errorPage500Api(
        HttpServletRequest request,
        HttpServletResponse response) {

        log.info("API errorPage 500 ");

        Map<String, Object> result = new HashMap<>();

        Exception ex = (Exception) request.getAttribute(ERROR_EXCEPTION);  // ERROR_EXCEPTION = "jakarta.servlet.error.exception";
        //WAS 는 오류 페이지를 단순히 다시 요청(request)만 하는 것이 아니라, 오류 정보를 request 의 attribute 에 추가해서 넘겨준다.
        //그 오류정보중 예외를 꺼내서 객체에 담았다.

        result.put("status", request.getAttribute(ERROR_STATUS_CODE)); //오류정보중 상태코드를 꺼내서 Map에 넣어준다.
        result.put("message", ex.getMessage());

        //아래 코드는 상태코드를 받아오는 코드이다. 이 클래스 맨위에 상수로 지정해놓은값들이 RequestDispatcher안에 동일하게 들어있다.
        // 나중에 사용할때는 이런식으로 가져오면 될듯하다.
        Integer statusCode = (Integer) request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
//         Integer statusCheck = (Integer) request.getAttribute(ERROR_STATUS_CODE);   <--- 결론적으로 이 코드와 같은코드이다.

        return new ResponseEntity<>(result, HttpStatus.valueOf(statusCode));
    }

이렇게 매핑url이 같은 2개의 메소드가 있다.

하나의 메소드는 매핑조건이 없는 일반 메소드이고

하나의 메소드는 매핑조건이 있는 메소드이다.

 

이럴때 http요청 헤더의 Accept가  */* (모든타입받음) 이면 매핑조건이 없는 일반 메소드가 실행되고

 http요청 헤더의 Accept가 application/json 일때만  매핑조건이 있는 메소드가 실행된다.

 

조건매핑이 없는 메소드도 같이 존재할때는 

produces매핑조건이 있는 메소드는 Accept가 해당 Content_Type일때만 동작한다.

 

그렇지않고 매핑조건이 있는 메소드만 있다면

 application/json을 받을 수만있다면 그 메소드가 실행될것이다.


 

댓글