Spring MVC - HTTP 요청 매핑

YUNU·2023년 9월 24일
0

스프링

목록 보기
28/33
post-thumbnail

🪴 Spring MVC


🌱 HTTP 요청 매핑

🟦 요청 매핑

🔷 @RestController & @RequestMapping()

  • @Controller
    반환값이 String인 경우에 뷰 이름으로 인식
    -> 해당하는 이름의 뷰를 찾고 뷰가 렌더링 됨

  • @RestController
    HTTP 메시지 바디에 바로 입력

  • @RequestMapping("URL")
    해당하는 URL 호출이 오면 해당 메서드가 실행되도록 매핑
    URL에 다중 설정 가능 ex) {"/member-join", "/member-list"}

  • URL 끝에 / 오는 경우
    매핑 URL : /member-list 일 때
    SpringBoot 3.0 미만 - /member-list, /member-list/ 같은 URL요청으로 매핑
    SpringBoot 3.0 이상 - /member-list, /member-list/ 다른 URL요청으로 매핑


🔷 HTTP 메서드 매핑

  • HTTP 메서드

    • GET, HEAD, POST, PUT, PATCH, DELETE
  • HTTP 메서드 매핑
    ex)

    @RequestMapping(value="URL", method = RequestMethod.'메서드')
     //...
  • HTTP 메서드 매핑 축약

    • @GetMapping, @PostMapping, @PutMapping, @DeleteMapping, @PatchMapping
      ex)
    @PostMapping(value="URL")
     //...
  • PathVariable 사용 (리소스 경로에 식별자 넣는 스타일)

    ex)

    @GetMapping("URL/{Id}")
    public String UsePathVariable(@PathVariable("Id") String Id) {
    	//...
    }
      
    @PathVariable("Id") String Id 와 같이 변수명이 같은 경우(PathVariable 이름과 파라미터명이 같은 경우)
    -> @PathVariable Id 로 축약해서 사용 가능

🔷 HTTP 헤더 매핑

  • 미디어 타입 조건 매핑 - HTTP 요청 Content-Type, consume
    HTTP 요청의 Content-Type 헤더를 기반, 미디어 타입으로 매핑
    맞지 않는 경우 HTTP 415 상태코드(Unsupported Media Type) 반환
    // Controller가 cosumes 할 수 있는 Content-Type이 "무엇"이라는 의미

    • consumes="application/json"
    • consumes="!application/json"
    • consumes="application/*"
    • consumes="\/"
    • MediaType.APPLICATION_JSON_VALUE
    @PostMapping(value = "/URL", consumes="미디어 타입")
  • 미디어 타입 조건 매핑 - HTTP 요청 Accept, produce
    HTTP 요청의 Accept 헤더를 기반, 미디어 타입으로 매핑
    맞지 않는 경우 HTTP 406 상태코드(Not Acceptable) 반환
    // Controller가 Content-Type이 "무엇"인 content를 produce한다

    • produces = "text/html"
    • produces = "!text/html"
    • produces = "text/*"
    • produces = "\/"
    @PostMapping(value = "/URL", produces="미디어 타입")

🟦 HTTP Request - 헤더 조회


@Slf4j
@RestController
public class RequestHeaderController {
	
    @RequestMapping("/URL")
    public String inquiryHeaders(HttpServletRequest request,
    							 HttpServletResponse response,
                                 HttpMethod httpMethod,
                                 Lacale locale,
                                 // 모든 HTTP 헤더를 MultiValueMap 형식으로 조회
                                 @RequestHeader MultiValueMap<String, String> headerMap, 
                                 // 특정 HTTP 헤더 조회
                                 @RequestHeader("host") String host, 
                                 // 특정 쿠키 조회
                                 // required : 파라미터 필수 여부, 기본값=true(필수)
                                 @CookieValue(value="Cookie", 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("Cookie={}", cookie);
        
        return "하이";
    }
}
  • HttpServletRequest & HttpServletResponse

    • WAS가 웹브라우저로부터 Servlet 요청을 받을 때

      1. 요청 받을 때 전달 받은 정보 -> HttpServletRequest 객체 생성하여 저장
      2. 웹브라우저에 답할 응답을 위한 HttpServletResponse 객체 생성(비어 있음)
      3. 생성된 HttpServletRequest와 HttpServletResponse를 Servlet에 전달
    • HttpServletRequest
      Http프로토콜의 request정보를 Servlet에 전달하기 위해 사용
      Header정보, Parameter, Cookie, URI, URL 등의 정보를 읽어 들이는 메소드를 포함한 클래스
      Body의 Stream을 읽어들이는 메소드 가짐

    • HttpServletResponse
      Servlet은 HttpResponse 객체를 이용하여 Content-Type, 응답코드, 응답 메시지 등을 담아서 전송

  • HttpMethod
    HTTP 메서드 조회

  • Locale
    Locale 정보 조회( 언어, 지역 설정, 출력 형식 등을 정의하는 문자열 [ex] ko_KR.UTF-8 )

  • @Slf4j
    로그 선언해주는 애노테이션, 개발자는 편리하게 log라고 사용하면 됨


🟦 HTTP 요청 파라미터

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

  • HTTP 요청 데이터 조회

    • Get-쿼리 파라미터

    • POST-HTML Form

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

🔷 Query Parameter

  • Get - Query Parameter
    /url?username=YUNU&gender=M
    메시지 바디 없이 URL의 쿼리 파라미터에 데이터를 포함해서 전달
    ex) 검색, 필터, 페이징

🔷 HTML Form

  • POST - HTML Form
    content-type: applivation/x-www-form-urlencoded
    메시지 바디에 쿼리 파라미터 형식으로 전달 username=YUNU&gender=M
    ex) 회원가입, 상품주문, HTML Form 사용

GET 쿼리 파라미터 전송 방식과 POST HTML From 전송 방식은 형식이 같으므로 구분없이 조회 가능
➡️ 요청 파라미터(request parameter) 조회라고 함

🔹 요청 파라미터 - @RequestParam 사용

  • @RequestParam : 파라미터 이름으로 바인딩
    @RequestParam("name(value)")
    name(value) 속성이 파라미터 이름으로 사용
    ex) @RequestParam("memberId") -> request.getParameter("memberId")
  • @ResponseBody : View 조회 무시, HTTP message body에 직접 해당 내용 입력
@ReponseBody
@RequestMapping("/URL")
public Stroing requestParam(
		@RequestParam("memberId") String memberId, // 파라미터명과 변수명이 같은 경우 @RequestParam String memberId로 축약 가능
        @RequestParam("gender") String memberGender) {
        
        //...
}
  • 주의점
    1. 파라미터 이름만 있고 값이 없는 경우
      /url?memberId=
      NULL이 아닌 ""(빈문자)로 통과함
    2. 기본형(primitive)에 null 입력
      @RequestParam(required=false) int age
      null을 int에 입력하는 것 불가능(500 예외 발생)
      int -> Integer로 변경 or defaultValue 사용
  • defaultValue
    @RequestParam(defaultValue = "YUNU") String memberId
    @RequestParam(defaultValue = "20") int age

🔹 요청 파라미터 - @ModelAttribute 사용

실제 개발에선 요청 파라미터를 받아서 필요한 객체를 만들고 그 객체에 값을 넣어주어야 함

이 과정을 스프링은 자동화해주는 @ModelAttribue 기능을 제공

//요청 파라미터 바인딩 받을 객체
//@Data -> @Getter, @Setter, @ToString, @EqualsAndHashCode, @RequiredArgsConstructor 를 자동 적용
@Data
public class MemberInfo {
	private String name;
    private int age;
}

@ResponseBody
@RequestMapping("/URL")
public String UseModelAttribute(@ModelAttribute MemberInfo memberInfo) {
	//...
}

스프링 MVC는 @ModelAttribute가 있으면
1. 바인딩 받을 객체를 생성(위에서 MemberInfo 객체)
2. 요청 파라미터의 이름으로 MemberInfo 객체의 프로퍼티를 찾음
3. 해당 프로퍼티의 setter를 호출해서 파라미터의 값을 입력(바인딩)
ex) 파라미터의 이름이 name이면 setName() 메서드를 호출하여 값 입력

- 프로퍼티
객체에 getter, setter 메서드가 있으면, 이 객체는 프로퍼티를 가짐
객체에 getName(), setName() 메서드가 있으면, 이 객체는 name이라는 프로퍼티를 가짐
name 프로퍼티의 값을 변경 -> setName() 호출
name 프로퍼티의 값을 조회 -> getName() 호출

🟦 HTTP 요청 메시지

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

  • HTTP 요청 데이터 조회

    • Get-쿼리 파라미터

    • POST-HTML Form

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

🔷 HTTP message body

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

요청 파라미터와 달리 HTTP 메시지 바디를 통해 데이터가 직접 넘어오는 경우
➡️ @RequestParam, @ModelAttribute 사용 불가능(HTMP Form 형식은 예외)

🔹 단순 텍스트

  • InputStream(Reader) : HTTP 요청 메시지 바디의 내용을 직접 조회

  • OutputStream(Writer) : HTTP 응답 메시지의 바디에 직접 결과 출력

    @PostMapping("/URL")
    public void requestBody(InputStream inputStream, Writer reponseWriter) throws IOExeption {
    	String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8); //inputStream에서 데이터를 읽어와서 UTF-8 인코딩을 사용하여 문자열로 변환한 다음, 그 문자열을 messageBody 변수에 저장 
      //...
      responseWriter.write("응답 메시지");
  • HttpEntity : HTTP header, HTTP body 정보를 편리하게 조회

    • 요청
      메시지 바디 정보를 직접 조회함
      HttpMessageConverter를 사용하여 StringHttpMessageConverter를 적용
      (요청 파라미터를 조회하는 기능과는 관계 없음)

    • 응답
      메시지 바디 정보를 직접 반환(view 조회 X), 헤더 정보 포함 가능
      HttpMessageConverter를 사용하여 StringHttpMessageConverter를 적용

    • HttpEntity를 상속받은 객체
      RequestEntity
      : 요청에서 사용, HttpMethod, url 정보 추가
      ResponseEntity
      : 응답에서 사용, HTTP 상태 코드 설정 가능

    @PostMapping("/URL")
    public HttpEntity<String> requestBody(HttpEntity<String> httpEntity) {
    	String messageBody = httpEntity.getBody();
      //...
      
      return new HttpEntity<>("응답 메시지"); // 제네릭타입 HttpEntity 객체 반환 -> String 타입
    }
  • @RequestBody
    메시지 바디 정보를 직접 조회
    HttpMessageConverter를 사용하여 StringHttpMessageConverter를 적용
    (요청 파라미터를 조회하는 기능과는 관계 없음)
    헤더 정보가 필요한 경우 HttpEntity or @RequestHeader 사용
  • @ResponseBody
    메시지 바디 정보를 직접 반환(view 사용 X)
    -> 응답 결과를 HTTP 메시지 바디에 직접 담아서 전달
    HttpMessageConverter를 사용하여 StringHttpMessageConverter를 적용
    @ResponseBody
    @PostMapping("/URL")
    public String requestBody(@RequestBody String messageBody) {
    		  log.info("messageBody={}",messageBody);
        //...
        return "응답 메시지";
    }
  • 요청 파라미터 vs HTTP 메시지 바디
    요청 파라미터 조회 기능 -> @RequestParam, @ModelAttribute
    HTTP 메시지 바디 직접 조회 기능 -> @RequestBody

🔹 JSON

HTTP API에서 주로 사용하는 데이터 형식

  • @RequestBody 문자 변환

    1. @RequestBody를 사용하여 HTTP 메시지에서 데이터를 꺼내고 messageBody에 저장
    2. 문자로 된 JSON 데이터인 messageBody를 objectMapper를 통해 자바 객체로 변환

    문자로 변환하고 다시 JSON으로 변환하는 과정은 불편
    @ModelAttribute처럼 한 번에 객체로 변환하기 위해 @RequestBody의 HttpMessageConverter 사용

  • @RequestBody 객체 변환
    HttpMessageConverter 사용
    -> MappingJackson2HttpMessageConverter (content-type: application/json)

@ResponseBody
@PostMapping("/URL")
public String requestBody(@RequestBody MemberInfo memberInfo) { // 요청파라미터 ModelAttribute에서 사용했던 MemberInfo
	//...
	log.info("name={}, age={}", memberInfo.getName(), memberInfo.getAge());
    return "응답 메시지";
}
  • @RequestBody 객체 파라미터
    위의 코드에서 @RequestBody MemberInfo memberInfo
    @RequestBody에 직접 만든 객체를 지정할 수 있음

    HttpEntity나 @RequestBody를 사용하면 HTTP 메시지 컨버터가 HTTP 메시지 바디의 내용을 우리가 원하는 문자나 객체 등으로 변환해줌

  • @RequestBody는 생략하면 안됨
    스프링은 @ModelAttribute, @RequestParam 애노테이션 생략 시에
    String, int, Integer와 같은 단순 타입 -> @RequsetParam 적용
    나머지 -> @ModelAttribute 적용함

    @RequestBody 생략하면 @ModelAttribute가 적용됨
    ➡️ HTTP 메시지 바디가 아닌 요청 파라미터를 처리함

    - 주의
    JSON 처리를 위한 HTTP 메시지 컨버터 실행을 위해서
    HTTP 요청시에 content-type이 application/json인지 꼭 확인

🟦 HTTP Response

스프링(서버)에서 응답 데이터를 만드는 방법

  • 정적 리소스
    웹 브라우저에 정적인 HTML, css, js를 제공할 때
    해당 파일을 변경 없이 그대로 서비스하는 것

  • 뷰 템플릿
    웹 브라우저에 동적인 HTML 제공할 때

  • HTTP 메시지
    HTTP API를 제공하는 경우에는 HTML이 아닌 데이터를 전달해야 함
    ➡️ HTTP 메시지 바디에 JSON 같은 형식으로 데이터 실어 보냄

🔷 정적 리소스

스프링 부트는 ClassPath의 다음 디렉토리에 있는 정적 리소스를 제공
/static, /public, /resources, /META-INF/resources

src/main/resources는 리소스를 보관하는 곳 + 클래스패스의 시작 경로
➡️ 정적 리소스 경로 src/main/resources/static...

ex)
src/main/resources/static/start/join.html
-> 웹 브라우저에서 http://localhost:8080/start/join.html과 같이 실행됨

🔷 뷰 템플릿

뷰 템플릿을 거쳐서 HTML 생성, 뷰가 응답을 만들어 전달
HTML을 동적으로 생성하는 용도 + 뷰 템플릿이 만들 수 있는 것이라면 생성 가능

  • 스프링 부트가 제공하는 기본 뷰 템플릿 경로
    src/main/resources/templates
    ex)src/main/resources/templates/start/join.html

  • 뷰 템플릿 호출하는 컨트롤러

    @Controller
    public class CallViewTemplateController {
    	@RequestMapping("/URL")
      public String responseView(Model model) { // model : 데이터를 view로 전달하는 객체
      	model.addAttribute("key", "data"); // "data"를 "key"라는 키와 함께 모델에 저장
          return "start/join"; // src/main/resources/templates/start/join.html
  • String 반환하는 경우

    • @ResponseBody가 없는 경우
      return 값(start/join)으로 뷰 리졸버 실행되어 뷰를 찾아 렌더링
    • @ResponseBody가 있는 경우
      뷰 리졸버 실행 X, HTTP 메시지 바디에 직접 리턴값(start/join) 문자 입력
  • HTTP 메시지
    @ResponseBody, HttpEntity를 사용하면 뷰 템플릿 사용 X
    HTTP 메시지 바디에 직접 응답 데이터 출력

🔷 HTTP API, 메시지 바디에 직접 입력

HTTP API를 제공하는 경우 HTML이 아닌 데이터를 전달해야 함
➡️ HTTP 메시지 바디에 JSON같은 형식으로 데이터를 실어 보냄

정적 리소스나 뷰 템플릿을 거치지 않고 직접 HTTP 응답 메시지를 전달하는 경우

  • RestController
    해당 컨트롤러에 모두 @ResponseBody가 적용됨
    ➡️ 뷰 템플릿을 사용하는 것이 아니라 HTTP 메시지 바디에 직접 데이터 입력
    RestAPI를 만들 때 사용하는 컨트롤러

    @ResponseBody도 클래스 레벨에 두면 전체 메서드에 적용
    @RestController 애노테이션 안에 @ResponseBody 적용되어 있음

  • ResponseEntity
    HttpEntity 상속 받음 + HTTP 응답 코드 설정 가능
    HttpEntity는 HTTP 메시지의 헤더, 바디 정보를 가지고 있음

    @RestController
    public class ResponseBodyController {
    
      @GetMapping("/URL")
      public ResponseEntity<String> responseBody() {
    	    return new ResponseEntity<>("응답 메시지", HttpStatus.OK);
      }
      
      @GetMapping("/URL")
      public ResponseEntity<MemberInfo> responseBodyJSON() {
      	MemberInfo memberInfo = new MemberInfo();
          memberInfo.setName("YUNU");
          memberInfo.setAge("100");
          return new ResponseEntity<>(memberInfo, HttpSatus.OK);
      }
    }

🟦 HTTP Message Converter

Stream 읽어서 직접 변환하는 것은 불편함
➡️ Message Converter 사용

🔷 스프링 MVC가 HTTP 메시지 컨버터 적용하는 경우

  • HTTP 요청에서 @RequestBody, HttpEntity(RequestEntity)
  • HTTP 응답에서 @ResponseBody, HttpEntity(ResponseEntity)

🔷 HTTP 메시지 컨버터는 HTTP 요청, HTTP 응답에 모두 사용

  • 응답에 있는 메시지 바디를 읽어 객체로 변환, Controller의 파라미터로 전달
  • Controller의 return 값을 HTTP 응답 메시지에 넣음

🔷 스프링부트는 다양한 메시지 컨버터를 제공

대상 클래스 타입과 미디어 타입(요청 : Content type/응답 : Accept type)을 체크하여 사용 여부 결정

  • HTTP 요청 데이터 읽기
    Controller에서 @RequestBody, HttpEntity 파라미터 사용

    메시지 컨버터가 canRead()를 호출(대상 클래스타입 지원? Content 미디어타입 지원? 체크)
    ➡️ canRead() 조건 만족하면 객체 생성, 반환
    ➡️ canRead() 조건 만족하지 못하면 다음 순번 메시지 컨버터로 넘어가서 다시 체크

  • HTTP 응답 데이터 생성
    Controller에서 @ResponseBody, HttpEntity로 값이 반환

    메시지 컨버터가 canWrite()를 호출(대상 클래스타입 지원? Accept 미디어타입 지원? 체크)
    ➡️ canWrite() 조건 만족하면 HTTP 응답 메시지 바디에 데이터 생성
    ➡️ canWrite() 조건 만족하지 못하면 다음 순번 메시지 컨버터로 넘어감


  • 스프링부트 기본 메시지 컨버터

    • (순번0) ByteArrayHttpMessageConverter - byte[] 데이터 처리
      클래스타입: byte[] , 미디어타입: */*

    • (순번1) StringHttpMessageConverter - String 문자로 데이터 처리
      클래스타입: String , 미디어타입: */*

    • (순번2) MappingJackson2HttpMessageConverter - application/json
      클래스타입: 객체 or HashMap , 미디어타입 application/json


🟦 Request Mapping Handler Adapter 구조

HTTP 메시지 컨버터는 핸들러 어댑터인 RequestMappingHandlerAdapter에서 사용

🔷 RequestMappingHandlerAdapter 동작 방식

  1. Controller의 파라미터, 애노테이션 정보를 기반으로 전달 데이터 생성

    • 애노테이션 기반의 컨트롤러는 다음과 같은 다양한 파라미터 사용 가능
    HttpServletRequest, Model, @RequestParam, @ModelAttribute, @RequestBody, HttpEntity
    • RequestMappingHandlerAdapter는 ArgumentResolver에 핸들러가 필요로 하는 다양한 파라미터 값(객체)을 생성해 줄 수 있는지 물어봄

    • ArgumentResolver는 핸들러(컨트롤러)가 필요로하는 값들을 생성

  2. 파라미터 값이 준비되면 RequestMappingHandlerAdapter는 핸들러(컨트롤러)를 호출하여 필요한 파라미터 값을 넘겨받음

  3. ReturnValeHandler(HandlerMethodReturnValueHandler)가 컨트롤러의 반환 값을 변환
    ex) ModelAndView , @ResponseBody , HttpEntity , String ...


🔷 HTTP 메시지 컨버터는 ArgumentResolver, ReternValueHandler가 사용

  • Request
    @RequsetBody, HttpEntity를 처리하는 각각의 Argument Resolver 존재
    이 각각의 Argument Resolver들이 HTTP 메시지 컨버터를 사용하여 필요한 객체 생성

  • Response
    @ResponseBody, HttpEntity를 처리하는 각각의 Return Value Handler 존재
    이 각각의 Return Value Handle들이 HTTP 메시지 컨버터를 호출해서 응답 결과 생성



인프런 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 (김영한) 참조

profile
DDeo99

0개의 댓글