Annotated Controllers[1]

2024. 10. 6. 16:55Spring Framework/Web on Servlet Stack

[https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-controller.html]

 

Spring MVC는 @Controller와 @RestController 컴포넌트가 요청 매핑, 요청 입력, 예외 처리 등을 애노테이션을 통해 표현할 수 있는 애노테이션 기반 프로그래밍 모델을 제공합니다. 애노테이션 컨트롤러는 유연한 메서드 시그니처를 가지며, 기본 클래스를 확장하거나 특정 인터페이스를 구현할 필요가 없습니다. 다음은 애노테이션으로 정의된 컨트롤러의 예입니다:

@Controller
public class HelloController {

    @GetMapping("/hello")
    public String handle(Model model) {
        model.addAttribute("message", "Hello World!");
        return "index";
    }
}

위 예시에서, 메서드는 Model을 받아들이고 String으로 뷰 이름을 반환하지만, 이 장의 뒷부분에서 설명할 더 많은 옵션들이 존재합니다.

Tip: spring.io의 가이드와 튜토리얼은 이 섹션에서 설명하는 애노테이션 기반 프로그래밍 모델을 사용합니다.

Declaration

Spring MVC에서 컨트롤러 빈(Controller Bean)을 정의하려면 Servlet의 WebApplicationContext에서 표준 Spring 빈 정의를 사용할 수 있습니다. @Controller 애노테이션은 컨트롤러 클래스에 사용되며, 이 클래스가 웹 컴포넌트임을 나타내는 역할을 합니다. 또한, Spring의 일반적인 @Component 클래스를 클래스 경로에서 자동으로 감지하고, 이를 빈 정의로 자동 등록하는 방식과 동일하게 @Controller 애노테이션도 자동 감지가 가능합니다.

자동 감지 설정

@Controller 빈을 자동으로 감지하고 등록하려면, Java 설정에 컴포넌트 스캔을 추가할 수 있습니다. 아래 예시는 @ComponentScan을 사용하여 @Controller 애노테이션이 붙은 클래스를 스캔하는 방법을 보여줍니다:

@Configuration
@ComponentScan("org.example.web")
public class WebConfig {

    // 추가 설정 가능
}

위 코드는 org.example.web 패키지를 스캔하여 해당 패키지 내의 @Controller 클래스들을 자동으로 감지하고 빈으로 등록합니다.

XML 설정 방식

Java 설정 대신 XML 설정을 사용할 수도 있습니다. 다음은 위와 동일한 기능을 하는 XML 설정 예시입니다:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="org.example.web"/>
</beans>

이 XML 설정은 Java 설정과 마찬가지로 org.example.web 패키지를 스캔하여 @Controller 클래스를 감지하고 등록합니다.

@RestController

@RestController는 복합 애노테이션으로, 이 자체가 @Controller@ResponseBody로 메타 애노테이션되어 있습니다. 즉, @RestController가 붙은 컨트롤러 클래스는 모든 메서드가 기본적으로 @ResponseBody를 상속받아 메서드의 반환 값을 HTML 템플릿을 통한 뷰 렌더링 대신 HTTP 응답의 본문으로 직접 작성하게 됩니다. RESTful API를 개발할 때 많이 사용됩니다.

AOP 프록시 (AOP Proxies)

컨트롤러에 AOP 프록시를 런타임에 적용해야 하는 경우가 있습니다. 예를 들어, 컨트롤러에 직접 @Transactional 애노테이션을 사용하는 경우가 있습니다. 이 경우 컨트롤러에 대한 프록시는 클래스 기반 프록시(class-based proxy)를 사용하는 것이 권장됩니다. 이는 애노테이션이 컨트롤러에 직접적으로 적용된 경우 자동으로 적용됩니다.

인터페이스 기반 프록시

만약 컨트롤러가 인터페이스를 구현하고 있고, AOP 프록시가 필요하다면 클래스 기반 프록시를 명시적으로 설정해야 할 수 있습니다. 예를 들어 @EnableTransactionManagement 설정에서 @EnableTransactionManagement(proxyTargetClass = true)로 변경하거나, XML 설정의 <tx:annotation-driven/><tx:annotation-driven proxy-target-class="true"/>로 변경할 수 있습니다.

참고: Spring Framework 6.0부터는 인터페이스 프록시를 사용할 때, 인터페이스에만 @RequestMapping이 적용된 경우 Spring MVC는 이를 컨트롤러로 인식하지 않습니다. 이 문제를 해결하려면 클래스 기반 프록시를 활성화하거나, 인터페이스에도 @Controller 애노테이션을 명시적으로 추가해야 합니다.

@Controller
@RequestMapping("/api")
public interface MyController {
    @GetMapping("/hello")
    String sayHello();
}

 

이러한 내용을 바탕으로 Spring MVC에서 컨트롤러를 정의하고 사용하는 방법은 매우 유연하며, 상황에 맞는 다양한 설정 방법을 제공합니다.

Mapping Requests

@RequestMapping


1. @RequestMapping

@RequestMapping 애너테이션은 클라이언트 요청을 특정 컨트롤러 메서드에 매핑하는 역할을 합니다. 서버로 들어오는 요청의 경로, HTTP 메서드(GET, POST 등), 요청 파라미터, 헤더, 그리고 미디어 타입 등을 기준으로 요청을 특정 컨트롤러의 메서드와 연결합니다.

어노테이션 주요 속성

  • value 또는 path: 요청을 매핑할 URL 경로를 지정합니다.
  • method: 요청을 처리할 HTTP 메서드(예: GET, POST 등)를 지정합니다.
  • params: 특정 요청 파라미터에 따라 요청을 매핑할 수 있습니다.
  • headers: 특정 요청 헤더를 기준으로 매핑할 수 있습니다.
  • consumes: 요청 본문의 콘텐츠 타입을 지정합니다. (예: application/json)
  • produces: 응답의 콘텐츠 타입을 지정합니다. (예: application/json)

@RequestMapping클래스 레벨메서드 레벨에서 모두 사용할 수 있습니다.

클래스 레벨 사용

클래스 레벨에 @RequestMapping을 사용하면 그 클래스에 포함된 모든 메서드에 대해 공통된 경로를 정의할 수 있습니다. 예를 들어, 모든 메서드가 /persons로 시작하는 요청 경로에 대해 응답하도록 설정할 수 있습니다.

@RestController
@RequestMapping("/persons")
class PersonController {
    // 클래스 레벨에서 공통된 경로 매핑
}

 

 

2. HTTP 메서드별 단축 애너테이션

Spring은 HTTP 메서드에 따라 요청을 처리할 수 있는 단축 애너테이션을 제공합니다. @RequestMapping과 동일한 기능을 제공하지만, 메서드에 매핑되는 HTTP 메서드를 명시적으로 지정하여 사용하기 편리하도록 만든 애너테이션들입니다. 단축 애너테이션은 다음과 같습니다:

  • @GetMapping: GET 요청을 처리
  • @PostMapping: POST 요청을 처리
  • @PutMapping: PUT 요청을 처리
  • @DeleteMapping: DELETE 요청을 처리
  • @PatchMapping: PATCH 요청을 처리
예시
@GetMapping("/{id}")
public Person getPerson(@PathVariable Long id) {
    // GET 요청에 대해 응답
}

@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public void addPerson(@RequestBody Person person) {
    // POST 요청에 대해 응답
}

여기서 @GetMapping("/{id}")은 URL 경로 /persons/{id}로 들어오는 GET 요청을 처리하며, @PostMapping/persons 경로로 들어오는 POST 요청을 처리합니다.

단축 애너테이션을 사용하면 가독성이 높아지고, 메서드별로 명확한 요청 매핑이 가능해지기 때문에, 보통의 경우 @RequestMapping 대신 단축 애너테이션을 사용하는 것이 권장됩니다.


3. 클래스 레벨 @RequestMapping과 메서드 레벨 @RequestMapping의 결합

클래스 레벨에서 @RequestMapping을 사용하여 공통 경로를 설정한 후, 메서드 레벨에서 세부 경로와 HTTP 메서드를 정의할 수 있습니다. 이 방식은 중복되는 URL 경로를 줄이고, 각 메서드가 처리할 구체적인 요청을 정의하는 데 유용합니다.

예시
@RestController
@RequestMapping("/persons")
class PersonController {

    @GetMapping("/{id}")
    public Person getPerson(@PathVariable Long id) {
        // GET 요청 처리
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public void add(@RequestBody Person person) {
        // POST 요청 처리
    }
}

이 코드에서는 클래스 레벨에서 /persons라는 경로를 설정했기 때문에, 각 메서드는 /persons 경로를 공유하면서 각기 다른 경로와 HTTP 메서드를 처리하게 됩니다. 예를 들어:

  • /persons/{id} 경로로 GET 요청을 보내면 getPerson() 메서드가 실행됩니다.
  • /persons 경로로 POST 요청을 보내면 add() 메서드가 실행됩니다.

4. 다중 @RequestMapping 애너테이션 경고

@RequestMapping 또는 @GetMapping, @PostMapping 등과 같은 단축 애너테이션은 동일한 클래스나 메서드에 중복으로 사용될 수 없습니다. 하나의 클래스, 인터페이스, 또는 메서드에 여러 개의 @RequestMapping 애너테이션을 적용하면 경고가 발생하며, 첫 번째 매핑만 사용됩니다.


5. 정리

  • @RequestMapping은 URL, HTTP 메서드, 파라미터, 헤더 등을 기준으로 요청을 컨트롤러 메서드에 매핑하는 강력한 도구입니다.
  • HTTP 메서드별 단축 애너테이션(@GetMapping, @PostMapping 등)은 사용이 간편하고 가독성을 높여주므로, 자주 사용됩니다.
  • 클래스 레벨과 메서드 레벨에서 각각 요청 매핑을 설정할 수 있으며, 클래스 레벨에서는 공통 경로를, 메서드 레벨에서는 세부적인 매핑을 정의합니다.
  • 동일한 요소에 여러 @RequestMapping 애너테이션을 적용하면 경고가 발생하므로 주의해야 합니다.

이러한 방식으로 Spring MVC에서는 애너테이션을 사용해 클라이언트의 요청을 효과적으로 처리하고, 서버의 엔드포인트를 간결하게 관리할 수 있습니다.

URI patterns

이 섹션에서는 Spring MVC@RequestMapping 메서드에서 URI 패턴을 어떻게 사용하는지 설명하고 있습니다. @RequestMapping 애너테이션으로 경로를 매핑할 때, 두 가지 주요 패턴 매칭 방법을 사용할 수 있으며, URI 패턴에서 변수를 캡처하는 방법, 정규 표현식을 사용하는 방법 등을 상세히 다룹니다. 이를 하나씩 차근차근 설명하겠습니다.

1. URI 패턴 매핑 방법

@RequestMapping을 사용해 특정 경로로 들어오는 HTTP 요청을 처리할 때, Spring은 URL 패턴 매칭을 통해 요청 경로를 컨트롤러 메서드에 연결합니다. 이때 Spring에서는 두 가지 매칭 방식이 제공됩니다:

1.1 PathPattern
  • 설명: PathPattern사전 처리된 패턴을 URL 경로와 매칭하는 방식입니다. URL 경로는 PathContainer로 사전 처리되며, 이 방식은 웹 애플리케이션에서의 인코딩 처리와 경로 파라미터 처리에 적합하고 효율적입니다.
  • 특징: 경로와 패턴이 미리 파싱되기 때문에 효율적인 매칭을 제공하며, Spring WebFlux에서는 유일한 선택입니다.
  • 사용 권장: Spring MVC 5.3부터 사용 가능하며, Spring MVC 6.0부터는 기본적으로 사용되도록 설정되었습니다.
1.2 AntPathMatcher
  • 설명: AntPathMatcher문자열 패턴을 문자열 경로와 매칭하는 방식입니다. 기존에 Spring에서 파일 시스템, 클래스패스 등에서 리소스를 선택할 때 사용되던 방식입니다.
  • 특징: 이 방식은 인코딩이나 URL의 복잡한 구조를 처리하는 데 있어서 다소 비효율적일 수 있습니다.
  • 비교: PathPattern이 성능이나 사용성 측면에서 더 나은 결과를 제공하므로, 웹 애플리케이션에서는 PathPattern이 더 적합하고 권장됩니다.

2. PathPattern의 패턴 문법

PathPatternAntPathMatcher와 동일한 패턴 문법을 지원하지만, 몇 가지 추가 기능과 제한 사항이 있습니다:

2.1 패턴 문법 예시
  • "/resources/ima?e.png": 경로의 한 문자와 매칭. 예를 들어 /resources/image.png/resources/imaae.png와 매칭됩니다.
  • "/resources/*.png": 경로 세그먼트에서 0개 이상의 문자를 매칭. .png 확장자로 끝나는 파일을 찾습니다.
  • "/resources/**": 여러 개의 경로 세그먼트를 매칭. 예를 들어 /resources/dir1/dir2/file.png와 매칭됩니다.
  • "/projects/{project}/versions": 경로 세그먼트를 변수로 매칭. {project}는 경로에서 특정 값을 캡처하여 변수로 저장합니다.
  • "/projects/{project:[a-z]}/versions": 정규 표현식으로 변수를 매칭. {project}는 소문자 알파벳 하나로만 이루어진 값을 캡처합니다.
2.2 PathPattern만의 특징
  • 캡처 패턴: "{*spring}"처럼 경로 끝에서 0개 이상의 경로 세그먼트를 캡처할 수 있습니다. 이는 경로의 끝에서 여러 경로를 매칭해야 할 때 유용합니다.
  • 제약:PathPattern에서는를 사용하여 여러 경로 세그먼트를 매칭할 수 있지만, 이는 경로 끝에서만 허용됩니다. 이를 통해 요청 경로에서 최적의 패턴을 선택할 때 발생할 수 있는 모호성을 줄입니다.

3. URI 변수 캡처와 사용

URI 패턴에서 경로 세그먼트를 변수로 캡처하는 방법을 제공합니다. 이를 통해 클라이언트가 보낸 경로에서 변수를 추출하고, 해당 변수를 메서드에서 사용할 수 있습니다.

3.1 @PathVariable 애너테이션

URI에서 캡처된 변수는 메서드 파라미터로 전달되며, 이때 @PathVariable 애너테이션을 사용합니다.

@GetMapping("/owners/{ownerId}/pets/{petId}")
public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
    // 요청 경로에서 ownerId와 petId 변수를 추출하여 사용
}

위 코드에서는 /owners/{ownerId}/pets/{petId} 경로로 들어온 요청에서 {ownerId}{petId}에 해당하는 값을 추출하여 메서드의 파라미터 ownerIdpetId에 전달합니다.

3.2 클래스와 메서드 레벨에서 변수 선언

클래스 레벨과 메서드 레벨에서 모두 URI 변수를 선언할 수 있습니다. 클래스 레벨에서 선언된 변수는 해당 클래스의 모든 메서드에서 사용할 수 있습니다.

@Controller
@RequestMapping("/owners/{ownerId}")
public class OwnerController {

    @GetMapping("/pets/{petId}")
    public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
        // 클래스 레벨에서 선언한 ownerId와 메서드 레벨에서 선언한 petId를 사용
    }
}

위 코드에서는 /owners/{ownerId}/pets/{petId}와 같은 경로로 들어온 요청에서 ownerIdpetId를 각각 추출하여 사용합니다. 클래스 레벨에서 선언된 ownerId는 모든 메서드에서 사용 가능하며, 메서드에서 선언한 petId는 해당 메서드에서만 사용됩니다.

3.3 타입 변환

URI 변수는 자동으로 해당하는 타입으로 변환됩니다. 예를 들어, @PathVariable Long ownerId는 URI에서 추출한 값을 Long 타입으로 변환합니다. 만약 변환에 실패하면 TypeMismatchException이 발생합니다. 기본적으로 지원되는 타입은 int, long, Date 등이 있으며, 추가적인 타입 지원이 필요할 경우 커스텀 변환기를 등록할 수 있습니다.

 

4. 정규 표현식으로 URI 변수 매칭

정규 표현식을 사용하여 더 정밀한 URI 패턴 매칭을 할 수 있습니다. {varName:regex} 문법을 사용하면 URI 변수에 대해 정규 표현식 기반으로 조건을 정의할 수 있습니다.

4.1 정규 표현식 예시
@GetMapping("/{name:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{ext:\\.[a-z]+}")
public void handle(@PathVariable String name, @PathVariable String version, @PathVariable String ext) {
    // 요청 경로에서 name, version, ext 변수를 추출하여 사용
}

위 코드에서는 경로가 /spring-web-3.0.5.jar일 때:

  • namespring-web,
  • version3.0.5,
  • ext.jar로 추출됩니다.

정규 표현식을 사용하면 보다 복잡한 패턴도 처리할 수 있어 다양한 경로 구조에 유연하게 대응할 수 있습니다.

 

5. 프로퍼티 기반 경로 매칭

URI 패턴에서 ${…} 형식을 사용하여 플레이스홀더를 정의할 수 있습니다. 이 플레이스홀더는 애플리케이션이 시작될 때 외부 설정 파일이나 시스템 환경 변수 등의 값을 기반으로 동적으로 치환됩니다. 이를 통해 애플리케이션 구성에 따라 경로를 유연하게 설정할 수 있습니다.

5.1 플레이스홀더 예시
@RequestMapping("${base.url}/owners")
public class OwnerController {
    // ...
}

 

위 코드에서 ${base.url}외부 설정에 따라 실제 URL 경로로 치환됩니다. 이를 통해 설정에 따라 동적으로 경로를 변경할 수 있습니다.

6. 정리

Spring MVC에서 URI 패턴을 활용하면 매우 유연한 요청 매핑이 가능합니다. PathPattern을 사용하면 더 효율적인 경로 매칭이 가능하며, 정규 표현식과 플레이스홀더를 사용해 다양한 경로 구조를 처리할 수 있습니다. @PathVariable 애너테이션을 통해 경로에서 변수를 쉽게 추출하고 사용할 수 있으며, URI 패턴 문법은 단순한 매칭부터 복잡한 패턴 처리까지 널리 활용됩니다.

Pattern Comparison

이 섹션에서는 여러 패턴이 동일한 URL과 매칭될 때 가장 적합한 패턴을 선택하는 방법에 대해 설명하고 있습니다. 이는 PathPattern 또는 AntPathMatcher를 사용할 때 발생할 수 있는 문제로, 두 가지 방법 중 하나를 사용하여 패턴을 정렬하고, 더 구체적인 패턴을 선택하게 됩니다. 각각의 패턴 비교 방식과 더 구체적인 설명은 다음과 같습니다.

1. 패턴 비교의 필요성

Spring MVC에서 @RequestMapping 애너테이션을 통해 URL을 컨트롤러 메서드에 매핑할 때, 여러 가지 패턴이 동일한 URL과 매칭될 수 있습니다. 예를 들어, /resources/*/resources/file.png가 동일한 URL에 매칭될 수 있습니다. 이런 상황에서는 가장 적합한 (가장 구체적인) 패턴이 선택되어야 합니다.

이를 처리하기 위해 Spring에서는 두 가지 패턴 비교 방식을 사용합니다:

  • PathPattern.SPECIFICITY_COMPARATOR
  • AntPathMatcher.getPatternComparator(String path)

2. 패턴 비교 방식

위 두 가지 방식 모두 패턴을 정렬하여, 더 구체적인 패턴이 상위에 오도록 합니다. 이때 패턴의 구체성을 판단하는 기준은 다음과 같습니다:

2.1 URI 변수
  • 패턴에 포함된 URI 변수는 그 패턴을 덜 구체적으로 만듭니다. 예를 들어 /users/{id}/users/admin보다 덜 구체적입니다.
  • URI 변수의 수가 적을수록 패턴은 더 구체적입니다. 예를 들어, /users/{id}/orders/{orderId}/users/{id}보다 덜 구체적입니다.
2.2 단일 와일드카드 (*)
  • *는 한 개의 경로 세그먼트를 매칭하는 와일드카드입니다. 단일 와일드카드는 URI 변수보다는 덜 구체적이며, 이는 /users/*/users/{id}보다 덜 구체적이라는 의미입니다.
2.3 이중 와일드카드 (``)**
  • **여러 경로 세그먼트를 매칭하는 와일드카드입니다. 이는 경로 전체에 걸쳐 여러 세그먼트를 매칭할 수 있으므로, 패턴을 덜 구체적으로 만듭니다. 예를 들어, /users/**/users/*보다 덜 구체적입니다.
2.4 길이
  • 구체성 점수가 같은 경우, 패턴의 길이가 더 긴 쪽이 더 구체적으로 간주됩니다. 예를 들어, /users/profile/users보다 더 구체적입니다.
2.5 URI 변수 vs 와일드카드
  • 구체성 점수와 길이가 같을 경우, URI 변수가 와일드카드보다 더 구체적으로 간주됩니다. 즉, /users/{id}/users/*보다 더 구체적입니다.

3. 패턴 구체성 평가

패턴을 평가할 때는 다음과 같은 규칙이 적용됩니다:

  1. URI 변수는 1점으로 계산됩니다.
  2. 단일 와일드카드 (*)는 1점으로 계산됩니다.
  3. 이중 와일드카드 (``)**는 2점으로 계산됩니다.
  4. 점수가 같을 경우 더 긴 패턴이 우선됩니다.
  5. 길이가 같고 점수가 같을 경우 URI 변수가 와일드카드보다 우선됩니다.

예를 들어, 다음과 같은 두 패턴이 있다고 가정합시다:

  • /users/{id} (URI 변수 1개 = 1점)
  • /users/* (단일 와일드카드 1개 = 1점)

두 패턴은 점수가 동일하지만, URI 변수가 와일드카드보다 구체적이므로 /users/{id}가 선택됩니다.


4. 특수한 경우: 기본 매핑 패턴

특정 패턴은 항상 우선 순위가 낮게 설정됩니다:

  • /**: 기본 매핑 패턴으로, 모든 경로를 매칭하는 패턴입니다. 이 패턴은 구체성 평가에서 제외되며, 항상 마지막에 정렬됩니다.
  • prefix 패턴: 예를 들어 /public/**와 같은 접두사 패턴은 구체성 측면에서 덜 구체적인 패턴으로 간주됩니다. 따라서, 더 구체적인 패턴보다 후순위로 매칭됩니다.

5. 패턴 비교 예시

다음과 같은 세 가지 패턴을 비교해 보겠습니다:

  1. /users/{id}/orders (URI 변수 1개 = 1점)
  2. /users/*/orders (단일 와일드카드 1개 = 1점)
  3. /users/** (이중 와일드카드 1개 = 2점)

이 세 패턴은 모두 같은 경로 /users/123/orders와 매칭될 수 있습니다. 이를 구체성 순서로 정렬하면:

  1. /users/{id}/orders (URI 변수 1개)
  2. /users/*/orders (단일 와일드카드 1개)
  3. /users/** (이중 와일드카드 1개)

따라서 가장 구체적인 패턴인 /users/{id}/orders가 선택됩니다.


6. 패턴 비교 시 주의 사항

  • Spring은 여러 패턴이 동일한 경로와 매칭될 수 있을 때, 가장 구체적인 패턴을 선택해야 합니다. 이를 위해 패턴 비교 알고리즘을 사용하여 패턴을 정렬하고, 가장 구체적인 패턴이 선택되도록 보장합니다.
  • 이중 와일드카드는 구체성이 낮으며, 특정 경로의 일부만을 매칭하는 접두사 패턴도 구체성이 낮게 평가됩니다.
  • 기본 매핑 패턴(/**)은 항상 최후에 매칭됩니다.

7. 정리

Spring MVC에서 여러 패턴이 동일한 경로에 매핑될 수 있을 때, 패턴 비교 알고리즘을 통해 가장 구체적인 패턴이 선택됩니다. PathPattern.SPECIFICITY_COMPARATOR 또는 AntPathMatcher.getPatternComparator가 사용되며, URI 변수, 와일드카드, 패턴 길이 등을 기준으로 구체성을 평가합니다. /**와 같은 기본 패턴은 항상 후순위로 처리되며, 이중 와일드카드나 접두사 패턴은 구체성이 낮게 평가됩니다.

이러한 매커니즘을 통해 Spring MVC는 보다 효율적이고 직관적인 URL 매칭을 제공합니다.

Suffix Match

이 섹션에서는 Spring MVC에서 suffix 패턴 매칭에 대한 변경 사항과 그 이유, 그리고 대안에 대해 설명합니다. 5.3 버전 이후, Spring MVC는 기본적으로 .*로 끝나는 suffix 패턴 매칭을 더 이상 수행하지 않으며, 이와 관련된 몇 가지 중요한 개념들을 다룹니다. 이를 단계별로 설명하겠습니다.


1. Suffix 패턴 매칭이란?

기존 Spring MVC에서는 @RequestMapping("/person")과 같은 컨트롤러 매핑이 있을 때, 이 매핑이 /person.* 형식의 URL에도 암묵적으로 적용되었습니다. 예를 들어, /person.pdf, /person.xml과 같은 URL도 매칭되어서, 요청된 파일 확장자에 따라 콘텐츠 타입(Content Type)이 결정되었죠.

그러나 Spring 5.3 이후부터는 이런 suffix 패턴 매칭이 기본적으로 비활성화되었습니다. 즉, /person으로 매핑된 컨트롤러가 더 이상 /person.pdf/person.xml과 같은 URL을 자동으로 매핑하지 않습니다.


2. 왜 더 이상 suffix 패턴 매칭을 사용하지 않나?

파일 확장자를 이용한 패턴 매칭은 과거에는 유용했지만, 시간이 지나면서 문제점이 많이 드러났습니다. 이 방식이 문제가 되는 이유는 다음과 같습니다:

2.1 Accept 헤더와의 충돌

과거에는 브라우저가 Accept 헤더를 일관되게 해석하기 어려웠기 때문에, 파일 확장자를 사용해 콘텐츠 타입을 결정하는 방식이 필요했습니다. 예를 들어, /person.pdf는 PDF 형식으로 응답을 보내고, /person.xml은 XML 형식으로 응답하는 방식이었죠.

그러나 최근에는 브라우저들이 Accept 헤더를 더 잘 해석하게 되었고, 따라서 더 이상 파일 확장자를 이용해 콘텐츠 타입을 결정할 필요가 없어졌습니다. 이제는 Accept 헤더를 통해 응답 형식을 결정하는 것이 더 권장됩니다.

2.2 URI 변수, 경로 파라미터와의 충돌

파일 확장자를 URL에 사용하는 것은 URI 변수경로 파라미터와 충돌할 수 있습니다. 예를 들어, /person/{id}.xml 같은 경로에서 {id}가 변수로 사용되면서 파일 확장자와 충돌이 발생할 수 있습니다. 이런 경우에는 URL을 파싱하거나 보안 규칙을 적용하는 데 혼란이 생길 수 있습니다.

2.3 보안 문제

파일 확장자를 이용한 패턴 매칭은 URL 기반 인증 및 보안에 문제를 일으킬 수 있습니다. 특정 파일 확장자가 포함된 URL에 대해 구체적으로 보안 규칙을 설정하기 어렵거나, 예상치 못한 경로가 매칭되는 경우가 발생할 수 있습니다.


3. Suffix 패턴 매칭 비활성화 방법

만약 5.3 이전 버전을 사용하는 경우, 명시적으로 suffix 패턴 매칭을 비활성화할 수 있습니다. 이를 위해서는 다음 두 가지 설정을 사용할 수 있습니다:

  • useSuffixPatternMatching(false): PathMatchConfigurer를 사용해 suffix 패턴 매칭을 비활성화합니다.
  • favorPathExtension(false): ContentNegotiationConfigurer를 사용해 콘텐츠 타입 결정 시 파일 확장자를 사용하지 않도록 설정합니다.

이 두 설정을 통해 Spring MVC는 더 이상 파일 확장자를 사용해 콘텐츠 타입을 결정하지 않으며, URL을 더 명확하고 안전하게 처리할 수 있습니다.


4. 파일 확장자를 대신할 대안

파일 확장자를 사용하는 대신, URL에서 다른 방법을 통해 콘텐츠 타입을 요청하는 것이 더 안전하고 유연한 방법입니다. 이를 위한 대안은 다음과 같습니다:

4.1 쿼리 파라미터 사용

파일 확장자를 사용하는 대신, 쿼리 파라미터를 사용하여 콘텐츠 타입을 요청하는 방법이 있습니다. 예를 들어, /person?format=pdf와 같은 방식으로 콘텐츠 타입을 명시하는 것이 가능합니다. 이렇게 하면 파일 확장자가 아닌 쿼리 파라미터를 통해 응답 형식을 명확하게 지정할 수 있습니다.

4.2 파일 확장자의 제한적 사용

만약 파일 확장자를 반드시 사용해야 한다면, 이를 명시적으로 등록된 확장자 목록으로 제한할 수 있습니다. ContentNegotiationConfigurermediaTypes 속성을 통해 허용할 파일 확장자와 그에 대응하는 미디어 타입을 등록할 수 있습니다. 예를 들어, .pdf, .xml 등의 확장자를 명시적으로 허용하고, 그 외 확장자는 차단할 수 있습니다.


5. 정리

Spring MVC 5.3 이후부터는 기본적으로 suffix 패턴 매칭이 비활성화되었습니다. 이는 파일 확장자를 통한 콘텐츠 타입 결정 방식이 과거에는 필요했지만, 현재는 Accept 헤더를 통한 방식이 더 선호되고 있기 때문입니다. 또한 파일 확장자를 사용하는 방식은 URI 변수, 경로 파라미터, 보안 문제 등 여러 가지 충돌을 일으킬 수 있기 때문에, 이를 비활성화하고 쿼리 파라미터 같은 대안 방식을 사용하는 것이 권장됩니다.

따라서, 파일 확장자를 통한 콘텐츠 타입 요청은 가능한 피하고, 현대적인 웹 애플리케이션에서는 Accept 헤더를 활용하는 방식으로 전환하는 것이 바람직합니다.

Suffix Match and RFD

이 섹션에서는 반사된 파일 다운로드(reflected file download, RFD) 공격과 Spring MVC에서 이를 방지하기 위한 방법에 대해 다룹니다. RFD 공격은 XSS(Cross-Site Scripting)와 유사하지만, 주로 브라우저가 응답을 다운로드할 때 발생합니다. 이와 관련된 세부사항을 단계별로 설명하겠습니다.


1. 반사된 파일 다운로드(RFD) 공격이란?

1.1 개념

RFD 공격은 클라이언트의 요청 입력(예: 쿼리 파라미터나 URI 변수)이 응답에 그대로 반영되는 경우 발생합니다. 일반적으로 XSS 공격에서는 JavaScript 코드를 HTML에 삽입하여 공격하지만, RFD 공격에서는 브라우저가 응답을 다운로드 가능하게 처리하고, 사용자가 이 파일을 실행하는 방식으로 공격이 이루어집니다.

1.2 동작 방식
  • 사용자가 악성 URL을 클릭하거나 직접 입력합니다.
  • 서버는 요청을 처리하고, 요청에 포함된 입력을 그대로 반영한 파일을 생성합니다.
  • 이 파일은 브라우저가 다운로드 가능하게 응답합니다.
  • 사용자가 이 파일을 더블 클릭하여 실행하면, 의도하지 않은 스크립트나 코드가 실행될 수 있습니다.

2. Spring MVC와 RFD 공격의 관계

2.1 취약점

Spring MVC에서는 @ResponseBody 또는 ResponseEntity 메소드를 사용하여 다양한 콘텐츠 타입을 렌더링할 수 있습니다. 사용자가 요청하는 URL 경로 확장자에 따라 다른 콘텐츠 타입을 요청할 수 있기 때문에, 이는 RFD 공격에 노출될 위험이 존재합니다.

2.2 Suffix 패턴 매칭 비활성화의 한계

이전 섹션에서 설명한 바와 같이, suffix 패턴 매칭을 비활성화하고 경로 확장을 콘텐츠 협상에 사용하는 것은 RFD 공격의 위험을 줄이지만, 이러한 조치만으로는 완전히 방지할 수 없습니다.


3. RFD 공격 방지 방법

3.1 Content-Disposition 헤더 추가

Spring MVC에서는 응답 본문을 렌더링하기 전에 Content-Disposition 헤더를 추가하여 다운로드 파일에 대한 제안을 합니다. 이 헤더는 다음과 같은 형식으로 설정됩니다:

Content-Disposition: inline; filename=f.txt

이 헤더는 다음과 같은 조건에서 추가됩니다:

  • URL 경로에 파일 확장자가 포함되어 있으나, 안전한 확장자로 간주되지 않거나 콘텐츠 협상에 명시적으로 등록되지 않은 경우.

이렇게 설정된 Content-Disposition 헤더는 사용자가 응답을 다운로드할 때 안전한 파일 이름을 제안합니다.

3.2 안전한 확장자

Spring MVC는 기본적으로 몇 가지 일반적인 파일 확장자를 안전한 것으로 간주합니다. 예를 들어, .pdf, .jpg, .txt 등의 확장자가 안전하다고 판단됩니다. 따라서 이러한 확장자는 Content-Disposition 헤더를 추가하지 않고 정상적으로 응답될 수 있습니다.

3.3 사용자 정의 HttpMessageConverter

애플리케이션에서 사용자 정의 HttpMessageConverter를 구현하는 경우, 콘텐츠 협상을 위해 특정 파일 확장자를 명시적으로 등록할 수 있습니다. 이를 통해 Content-Disposition 헤더가 추가되지 않도록 설정할 수 있습니다. 이러한 등록은 콘텐츠 타입에 따라 동적으로 처리되므로, 개발자는 응답의 타입에 따라 적절한 처리를 할 수 있습니다.


4. 주의사항

4.1 직접 URL 입력의 부작용

URL을 직접 브라우저에 입력하는 경우, 공격자가 의도하지 않은 결과를 초래할 수 있는 URL을 사용하여 RFD 공격을 수행할 수 있습니다. 이와 관련하여, Spring MVC는 Content-Disposition 헤더를 추가함으로써 사용자가 다운로드한 파일의 안전성을 높이려고 하지만, 사용자가 직접 URL을 입력할 때는 여전히 주의가 필요합니다.

4.2 CVE-2015-5211

RFD 공격과 관련하여, 특정 취약점(CVE-2015-5211)에 대한 추가 권장 사항이 있습니다. 이 CVE는 RFD 공격에 대한 보안 권장 사항을 제시하며, 개발자는 이 정보를 참고하여 애플리케이션의 보안을 강화해야 합니다.


5. 정리

반사된 파일 다운로드(RFD) 공격은 요청 입력이 응답에 반영되는 경우 발생하며, Spring MVC의 다양한 응답 처리 방식에서 취약점이 존재합니다. RFD 공격을 방지하기 위해 Spring MVC는 Content-Disposition 헤더를 사용하여 안전한 파일 다운로드를 제안하고, 기본적으로 몇 가지 확장자를 안전하다고 간주합니다. 그러나 사용자 정의 HttpMessageConverter를 통해 특정 파일 확장자를 명시적으로 등록하는 등의 추가 조치가 필요하며, URL을 직접 입력할 때 발생할 수 있는 위험에 대해 개발자들은 항상 주의해야 합니다.

Consumable Media Types

Consumable Media Types는 Spring MVC에서 요청을 처리할 때, 클라이언트가 전송하는 데이터의 콘텐츠 유형(Content-Type)에 따라 매핑을 세분화할 수 있도록 해주는 기능입니다. 이 기능을 통해 개발자는 특정 미디어 타입에 대한 요청만 처리할 수 있도록 컨트롤러 메소드를 구성할 수 있습니다. 아래에서 이 기능을 상세하게 설명하겠습니다.


1. Content-Type 기반 요청 매핑

1.1 기본 개념

Content-Type은 HTTP 요청의 헤더 중 하나로, 클라이언트가 서버에 보내는 데이터의 유형을 나타냅니다. 예를 들어, JSON 데이터를 보내는 경우 Content-Typeapplication/json이 됩니다. Spring MVC에서는 이 Content-Type을 기반으로 요청을 매핑할 수 있습니다.

1.2 예제

아래의 예제는 Content-Typeapplication/json인 요청만 처리하는 메소드를 보여줍니다:

@PostMapping(path = "/pets", consumes = "application/json")
public void addPet(@RequestBody Pet pet) {
    // ...
}

위 코드는 POST 요청이 /pets 경로로 들어오고, Content-Typeapplication/json일 때만 addPet 메소드를 호출합니다.

2. consumes 속성

2.1 속성의 역할
  • consumes 속성은 요청 매핑에서 특정 미디어 타입을 명시적으로 지정합니다.
  • 요청의 Content-Type 헤더가 이 값과 일치하는 경우에만 해당 메소드가 실행됩니다.
2.2 부정 표현식
  • consumes 속성은 부정 표현식을 지원합니다. 예를 들어 !text/plaintext/plain 외의 모든 콘텐츠 유형을 의미합니다.
@PostMapping(path = "/pets", consumes = "!text/plain")
public void addPet(@RequestBody Pet pet) {
    // ...
}

위 코드는 Content-Typetext/plain이 아닌 요청만 처리합니다.

3. 클래스 레벨의 consumes : @Consumes

3.1 공유 속성
  • consumes 속성을 클래스 레벨에 선언하여 모든 메소드에서 사용할 수 있습니다. 이렇게 하면 클래스 내의 모든 메소드가 동일한 Content-Type을 사용할 수 있습니다.
@RestController
@RequestMapping("/pets")
@Consumes("application/json")
public class PetController {

    @PostMapping
    public void addPet(@RequestBody Pet pet) {
        // ...
    }

    @PutMapping
    public void updatePet(@RequestBody Pet pet) {
        // ...
    }
}

위의 예제에서 PetController 클래스의 모든 메소드는 Content-Typeapplication/json일 때만 호출됩니다.

3.2 메소드 레벨의 오버라이드
  • 클래스 레벨에서 설정한 consumes 속성은 메소드 레벨에서 오버라이드됩니다. 즉, 특정 메소드에서 consumes 속성을 설정하면 클래스 레벨의 설정이 무시되고 해당 메소드에서 지정한 설정이 적용됩니다.
@RestController
@RequestMapping("/pets")
@Consumes("application/json")
public class PetController {

    @PostMapping(consumes = "application/xml")
    public void addPet(@RequestBody Pet pet) {
        // 이 메소드는 application/xml을 소비합니다.
    }
}

위의 예제에서 addPet 메소드는 application/xml 콘텐츠 유형을 소비하므로 클래스 레벨에서 설정한 application/json은 무시됩니다.

4. MediaType 상수

  • Spring MVC에서는 자주 사용되는 미디어 타입에 대한 상수를 제공하는 MediaType 클래스를 제공합니다. 이를 통해 코드의 가독성을 높이고 오타를 방지할 수 있습니다.
import org.springframework.http.MediaType;

@PostMapping(path = "/pets", consumes = MediaType.APPLICATION_JSON_VALUE)
public void addPet(@RequestBody Pet pet) {
    // ...
}

위와 같이 MediaType.APPLICATION_JSON_VALUE를 사용하여 미디어 타입을 지정할 수 있습니다.


5. 정리

Spring MVC에서 Consumable Media Types는 클라이언트의 요청 데이터 타입에 따라 컨트롤러 메소드를 세분화하여 매핑할 수 있도록 해줍니다. consumes 속성을 사용하여 특정 Content-Type에 대한 요청만 처리할 수 있으며, 부정 표현식도 지원하여 더 유연한 매핑이 가능합니다. 클래스 레벨과 메소드 레벨에서 설정을 통해 요구사항에 맞게 효과적으로 요청을 처리할 수 있습니다. MediaType 상수를 활용하면 코드의 가독성과 유지보수성을 높일 수 있습니다.

Producible Media Types

물론입니다! 아래 내용을 번역해 드리겠습니다.

Spring에서는 produces 속성을 사용하여 메서드가 HTTP 요청에 대한 응답으로 생성할 수 있는 미디어 유형을 지정합니다. 이는 REST API에서 클라이언트가 데이터의 다양한 형식을 요청할 수 있기 때문에 특히 중요합니다(예: JSON, XML).

주요 포인트:

  1. Request Mapping: @GetMapping 주석은 @RequestMapping(method = RequestMethod.GET)의 단축형입니다. HTTP GET 요청을 컨트롤러의 특정 핸들러 메서드에 매핑하는 데 사용됩니다.
  2. Path Variable: @PathVariable 주석은 URI에서 값을 추출하는 데 사용됩니다. 제공된 예에서 petId는 URL 경로 /pets/{petId}에서 추출된 변수입니다.
  3. Produces 속성:
    • 예제에서 @GetMapping 주석의 produces 속성은 "application/json"으로 설정되어 있습니다. 이는 이 메서드가 HTTP 요청의 Accept 헤더에 application/json이 포함된 경우에만 처리된다는 것을 의미합니다.
    • 만약 이 미디어 유형과 일치하지 않는 Accept 헤더가 있는 요청이 들어오면 Spring은 이 메서드를 사용하지 않으며 406 Not Acceptable 응답을 반환합니다.
  4. 문자 세트: 미디어 유형의 일부로 문자 세트를 지정할 수 있습니다. 예를 들어, produces = "application/json; charset=UTF-8"은 이 메서드가 UTF-8로 인코딩된 JSON 데이터를 생성함을 나타냅니다.
  5. Negated Expressions: 특정 미디어 유형을 제외하기 위해 부정 표현을 사용할 수 있습니다. 예를 들어, produces = "!text/plain"은 이 메서드가 text/plain이 아닌 모든 콘텐츠 유형을 처리함을 의미합니다.
  6. Class-Level vs. Method-Level:
    • produces 속성을 클래스 수준에서 선언할 수 있으며, 이는 해당 클래스의 모든 메서드에 적용됩니다.
    • 그러나 메서드 수준에서 produces 속성을 지정하면 클래스 수준 선언을 무시합니다. 이는 각 메서드가 생성할 수 있는 내용을 보다 세밀하게 제어할 수 있게 해줍니다.
  7. MediaType 상수:
    • Spring은 MediaType 클래스에서 일반적으로 사용되는 미디어 유형에 대한 상수를 제공합니다. 예를 들면:
      • MediaType.APPLICATION_JSON_VALUE: "application/json"에 해당합니다.
      • MediaType.APPLICATION_XML_VALUE: "application/xml"에 해당합니다.
    • 이러한 상수를 사용하면 오타를 방지하고 코드의 가독성을 높일 수 있습니다.

코드 예제 설명

다음은 제공된 Java 코드 예제의 설명입니다:

@GetMapping(path = "/pets/{petId}", produces = "application/json") 
@ResponseBody
public Pet getPet(@PathVariable String petId) {
    // ...
}
  • @GetMapping: 이 메서드는 /pets/{petId} 엔드포인트에서 GET 요청을 처리합니다.
  • produces = "application/json": 이 메서드는 JSON을 수신할 수 있는 요청에 대해서만 응답합니다.
  • @ResponseBody: 이는 메서드의 반환 값이 HTTP 응답 본문에 직접 작성됨을 나타내며, 주어진 produces 속성에 따라 일반적으로 JSON으로 반환됩니다.
  • @PathVariable String petId: 이는 URL의 {petId} 부분을 캡처하여 메서드 매개변수로 사용 가능하게 합니다.

리액티브 스택에서의 동등한 예

리액티브 스택(스프링 WebFlux 사용)에서는 동일한 개념이 적용됩니다. 일반적으로 @GetMapping 주석과 유사한 produces 속성을 사용합니다. 그러나 반환 유형은 구체적인 유형 대신 Mono<Pet> 또는 Flux<Pet>과 같은 리액티브 유형이 됩니다.

유사한 메서드를 리액티브 컨트롤러에서 정의하는 방법은 다음과 같습니다:

@GetMapping(path = "/pets/{petId}", produces = MediaType.APPLICATION_JSON_VALUE)
public Mono<Pet> getPet(@PathVariable String petId) {
    // ... Mono를 반환하여 Pet 포함
}

정리

  • Producible Media Types은 API가 응답할 수 있는 형식을 제어할 수 있게 해주어, API를 유연하고 클라이언트의 요구에 응답할 수 있도록 합니다.
  • produces 속성을 효과적으로 사용하면 콘텐츠 협상을 관리하여 클라이언트가 기대하는 형식으로 데이터를 받을 수 있도록 합니다.
  • 이러한 개념이 전통적인 MVC와 리액티브 스택 모두에 어떻게 적용되는지를 이해하는 것은 강력한 Spring 애플리케이션을 구축하는 데 중요합니다.

Parameters, headers

Spring MVC에서 요청 매핑을 파라미터 및 헤더에 기반하여 세분화할 수 있습니다. 이를 통해 특정 파라미터의 존재 여부(예: myParam), 부재 여부(!myParam), 또는 특정 값(예: myParam=myValue)에 따라 어떤 요청을 처리할지 제어할 수 있습니다.

주요 개념

  1. 요청 파라미터:
    • 요청 매핑을 요청 파라미터와 관련된 조건으로 세분화할 수 있습니다.
    • 조건에는 다음이 포함됩니다:
      • 존재: 파라미터가 존재하는지 확인 (예: myParam).
      • 부재: 파라미터가 존재하지 않는지 확인 (예: !myParam).
      • 특정 값: 파라미터가 특정 값과 일치하는지 확인 (예: myParam=myValue).
    다음은 특정 값에 대해 테스트하는 코드 예제입니다:
    • 이 예제에서 findPet 메서드는 요청에 myParam이라는 쿼리 파라미터가 myValue라는 값으로 포함되어 있는 경우에만 호출됩니다.
    해당 URL:여기서 123petId의 값이며, myParammyValue로 포함됩니다.
  2. /pets/123?myParam=myValue
  3. @GetMapping(path = "/pets/{petId}", params = "myParam=myValue") public void findPet(@PathVariable String petId) { // ... }
  4. 요청 헤더:
    • 요청 파라미터와 유사하게, 요청 헤더와 관련된 조건으로 요청 매핑을 세분화할 수 있습니다.
    • 조건으로는 다음을 지정할 수 있습니다:
      • 특정 헤더 값: 헤더가 특정 값과 동일한지 확인 (예: myHeader=myValue).
    다음은 예제입니다:
    • 이 경우 findPet 메서드는 요청에 myHeader라는 헤더가 myValue라는 값으로 포함되어 있을 때만 호출됩니다.
    해당 URL:여기서 URL은 헤더를 표시하지 않지만, 브라우저 또는 클라이언트는 요청과 함께 myHeader 헤더를 전송해야 합니다.
  5. /pets/123 (헤더: myHeader: myValue 포함)
  6. @GetMapping(path = "/pets/{petId}", headers = "myHeader=myValue") public void findPet(@PathVariable String petId) { // ... }
  7. Content-Type 및 Accept 매칭:
    • headers 조건을 사용하여 Content-TypeAccept 헤더와 일치시킬 수도 있습니다.
    • 그러나 일반적으로 콘텐츠 협상을 위해 consumesproduces 속성을 사용하는 것이 더 좋습니다. 이는 서버가 수용할 수 있는 데이터 유형과 생성할 수 있는 데이터 유형에 중점을 둡니다.

리액티브 스택에서의 동등한 예

리액티브 스택(스프링 WebFlux 사용)에서는 유사한 방식으로 @GetMapping 주석을 사용하여 동일한 동작을 수행할 수 있습니다. 요청 파라미터와 헤더를 다루는 방식은 개념적으로 동일하지만, 반환 유형은 일반적으로 리액티브 유형(예: Mono 또는 Flux)이 됩니다.

리액티브 컨트롤러에서 유사한 메서드를 정의하는 방법은 다음과 같습니다:

@GetMapping(path = "/pets/{petId}", params = "myParam=myValue")
public Mono<Void> findPet(@PathVariable String petId) {
    // ... 리액티브 구현
}
@GetMapping(path = "/pets/{petId}", headers = "myHeader=myValue")
public Mono<Void> findPet(@PathVariable String petId) {
    // ... 리액티브 구현
}

정리

  • 요청 파라미터: 파라미터에 기반하여 요청을 세분화하면 메서드가 처리할 요청을 보다 정밀하게 제어할 수 있습니다.
  • 요청 헤더: 파라미터와 유사하게 헤더를 사용하여 요청 처리를 제어할 수 있습니다.
  • 모범 사례: 콘텐츠 유형을 처리할 때는 Content-TypeAccept 헤더를 직접 매칭하기보다는 consumesproduces 속성을 사용하는 것이 좋습니다.
  • 리액티브 스택: 리액티브 스택에서도 유사한 접근 방식이 유지되며, 반환 값으로는 리액티브 유형을 사용합니다.

HTTP HEAD, OPTIONS

1. HTTP HEAD 요청:

  • 목적: HTTP HEAD 메서드는 GET 메서드와 유사하지만, 응답의 본문이 아닌 헤더만 요청합니다. 이는 전체 리소스를 다운로드하지 않고도 메타데이터(예: Content-Length 또는 Content-Type)를 가져오는 데 유용합니다.
  • 자동 지원:
    • Spring MVC는 @GetMapping@RequestMapping(method = RequestMethod.GET)을 통해 HEAD 메서드를 투명하게 지원합니다.
    • 이는 GET 요청을 처리하기 위해 정의된 컨트롤러 메서드가 HEAD 요청에도 자동으로 응답한다는 것을 의미합니다. 추가적인 코드나 별도의 메서드 정의가 필요하지 않습니다.
  • 응답 처리:
    • HEAD 요청을 수신하면, Spring MVC는 jakarta.servlet.http.HttpServlet 기반의 응답 래퍼를 사용하여 응답의 Content-Length 헤더가 GET 응답에서 전송될 바이트 수로 올바르게 설정되도록 보장합니다.
    • 중요하게도, 실제로는 본문이 전송되지 않고 헤더만 포함됩니다.

예제:
다음과 같은 컨트롤러가 있다고 가정해 보겠습니다:

@GetMapping("/pets/{petId}")
public Pet getPet(@PathVariable String petId) {
    // ... Pet 객체 반환
}

/pets/123에 대한 HEAD 요청은 헤더(포함된 Content-Length)를 반환하지만 Pet 객체 자체는 반환하지 않습니다.

2. HTTP OPTIONS 요청:

  • 목적: HTTP OPTIONS 메서드는 대상 리소스에 대한 통신 옵션을 설명하는 데 사용됩니다. 이를 통해 클라이언트는 특정 엔드포인트에 대해 서버가 지원하는 HTTP 메서드를 확인할 수 있습니다.
  • 기본 처리:
    • OPTIONS 요청을 수신하면, Spring MVC는 Allow 응답 헤더를 설정합니다. 이 헤더는 컨트롤러 메서드에서 매칭된 URL 패턴에 대해 지원되는 HTTP 메서드를 나열합니다.
    • 다양한 @RequestMapping 메서드가 정의되어 있다면, 해당 메서드에 따라 Allow 헤더에 이 메서드들이 반영됩니다.
  • 메서드 선언 없음:
    • HTTP 메서드 선언이 없는 @RequestMapping의 경우 (예: @RequestMapping("/pets/{petId}")), 기본 Allow 헤더는 다음과 같이 설정됩니다:
      GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS
  • 모범 사례:
    • 컨트롤러 메서드는 항상 지원하는 HTTP 메서드를 명시적으로 선언하는 것이 좋습니다. 이를 통해 코드 가독성이 향상되고, 엔드포인트의 의도가 명확해집니다.

예제:
다음과 같은 매핑이 있을 경우:

@RequestMapping("/pets/{petId}")
public Pet getPet(@PathVariable String petId) {
    // ... Pet 객체 반환
}

/pets/123에 대한 OPTIONS 요청은 지원되는 메서드를 나타내는 Allow 헤더와 함께 응답합니다.

3. 명시적 매핑:

  • @RequestMapping 메서드를 HTTP HEAD 및 OPTIONS 요청에 명시적으로 매핑할 수 있지만, 일반적인 경우에는 필요하지 않습니다. 위에서 설명한 기본 동작이 대부분의 경우 효과적으로 처리됩니다.

명시적 매핑 예제:
HEAD 및 OPTIONS 요청을 명시적으로 처리하고 싶다면 다음과 같이 할 수 있습니다:

@RequestMapping(path = "/pets/{petId}", method = RequestMethod.OPTIONS)
public ResponseEntity<?> optionsPet() {
    return ResponseEntity.ok()
            .allow(HttpMethod.GET, HttpMethod.HEAD, HttpMethod.POST)
            .build();
}

@RequestMapping(path = "/pets/{petId}", method = RequestMethod.HEAD)
public ResponseEntity<?> headPet(@PathVariable String petId) {
    // 일반적으로 응답 본문의 길이를 가져옵니다.
    return ResponseEntity.ok()
            .contentLength(123) // 실제 콘텐츠 길이로 대체
            .build();
}

정리

  • HTTP HEAD:
    • @GetMapping@RequestMapping(method = RequestMethod.GET)에 의해 자동으로 지원됩니다.
    • 본문 없이 헤더만 반환하며, Content-Length를 포함합니다.
  • HTTP OPTIONS:
    • 요청된 URL 패턴에 대해 지원되는 메서드를 나열하는 Allow 헤더를 자동으로 제공합니다.
    • 기본 동작이 대부분의 경우를 처리하므로, 명시적 매핑이 필요하지 않습니다.
  • 모범 사례: 명확성과 유지 관리를 위해 항상 지원하는 HTTP 메서드를 명시적으로 선언하는 것이 좋습니다.


Spring MVC가 HTTP HEAD 및 OPTIONS 요청을 처리하는 방식을 이해하면, 보다 유익하고 효율적인 API를 생성할 수 있습니다. 또한 클라이언트가 HTTP 표준을 준수하면서 API와 상호작용할 수 있도록 도와줍니다.

Custom Annotations

Spring MVC는 요청 매핑을 위한 구성 어노테이션(composed annotations)의 사용을 지원합니다. 이러한 어노테이션은 @RequestMapping으로 메타 어노테이션되어 있으며, 보다 좁고 구체적인 목적을 가진 @RequestMapping 속성의 하위 집합(또는 전체)을 재선언하기 위해 구성됩니다.

@GetMapping, @PostMapping, @PutMapping, @DeleteMapping, @PatchMapping이 구성 어노테이션의 예입니다. 이들은 대부분의 컨트롤러 메서드가 @RequestMapping을 사용하는 대신 특정 HTTP 메서드에 매핑되어야 한다고 주장할 수 있기 때문에 제공됩니다. 기본적으로 @RequestMapping은 모든 HTTP 메서드와 매칭됩니다. 구성 어노테이션을 구현하는 방법에 대한 예가 필요하다면, 이러한 어노테이션이 어떻게 선언되는지를 살펴보면 됩니다.

참고: @RequestMapping은 동일한 요소(클래스, 인터페이스 또는 메서드)에서 선언된 다른 @RequestMapping 어노테이션과 함께 사용할 수 없습니다. 동일한 요소에서 여러 개의 @RequestMapping 어노테이션이 감지되면 경고가 로그에 기록되며, 첫 번째 매핑만 사용됩니다. 이는 @GetMapping, @PostMapping 등과 같은 구성된 @RequestMapping 어노테이션에도 적용됩니다.

Spring MVC는 또한 사용자 정의 요청 매핑 속성과 사용자 정의 요청 매칭 로직을 지원합니다. 이는 더 고급 옵션으로, RequestMappingHandlerMapping을 서브클래싱하고 getCustomMethodCondition 메서드를 오버라이드하여 사용자 정의 속성을 확인하고 자체 RequestCondition을 반환할 수 있습니다.

Explicit Registrations

생략.

@HttpExchange

Spring Cloud에서 설명

'Spring Framework > Web on Servlet Stack' 카테고리의 다른 글

Annotated Controllers[3] - @CookieValue  (0) 2024.10.09
@RequestHeader  (0) 2024.10.09
Filter  (0) 2023.05.19
Dispatcher Servlet  (0) 2023.05.02
WebDataBinder  (0) 2023.05.01