router에서 path에 바로 regex를 적용할수 있다는 걸 알고 다음과 같은 정규식을 넣어줬다.
하고 싶었던 것
orderRouter에서 path로 받는 orderNo가 orderNo 형식에 맞는 애들만 통과시켜주고 싶었다.
orderNo형식 : prefix로 OD가 있을수도 있고 없을수도 있음 + 숫자 18자리 => 18자리 or 20자리
org.springframework.web.reactive.function.server.RouterFunction
class OrderRouter(
private val orderHandler: OrderHandler,
){
@Bean
fun routeOrder(): RouterFunction<ServerResponse> {
return coRouter {
(accept(MediaType.APPLICATION_JSON) and "/orders").nest {
"/{orderNo:${ORDER_NO_REGEX}}".nest {
GET("", orderHandler::getOrder)
}
}
}
}
}
실패한 정규식 ORDER_NO_REGEX
^(OD)?[0-9]{18}$
정규식을 이렇게 만들어서 넣어주니 regex만 봤을 때는 문제가 없었는데 다음과 같은 에러가 발생했다.
java.lang.illegalArgumentException : No capture groups allowed in the constraint regex
Original Stack Trace:
at org.springframework.web.util.pattern.CaptureVariablePathElement.matches(CaptureVariablePathElement.java:86)
at org.springframework.web.util.pattern.SeparatorPathElement.matches(SeparatorPathElement.java:54)
at org.springframework.web.util.pattern.PathPattern.matchStartOfPath(PathPattern.java:260)
지금까지 정규식이 필요하면 누군가 이미 만들어 놓은거 쓰거나, https://regex101.com/ 의 도움을 받아왔지만
조금더 깊이 있게 정규식에 대해 알아야 될 때가 왔다.
에러에서 한번에 와닿지 않았던 부분이 capture group 이었다.
그래서 정규식의 capture group이 도대체 뭔데 ... ?
정규식의 group에 대해 먼저 알아보자.
정규식의 그룹이란?
The main purpose of using groups and subgroups is that we can separate the input string into sections and extract those sections if they are matched with the defined pattern.
In java regular expressions, every pattern can have groups. And groups are indexed like in array’s elements. The first group index starts at 0 just like array indexes.
There are two types of groups: explicit and implicit groups
자바의 regular expression의 모든 pattern은 그룹을 가집니다. 이러한 그룹은 배열의 원소처럼 인덱싱 됩니다.
이러한 그룹에는 두가지 종류가 있습니다. explicit 그룹과 implicit 그룹. (explict와 implict 느낌이 오는 한국어를 못찾았다.)
- explicit group : () 괄호 안에 위치한 부분은 explicit 합니다.
- implicit group : () 괄호 안에 위치하지 않았다면 부분은 implicit 합니다.
그리고 이러한 group들은 각각 index를 부여받는다.
예시 그림을 보면 explict와 implict에 대해 느낌이 올 것이다.
그럼 capture group은 무엇이냐? 간단하게 괄호로 둘러쌓인 그룹이라고 이해했다.
좀더 자세히 알고싶다면 https://www.regular-expressions.info/brackets.html 싸이트를 한번 보는것을 추천.
해결 방법
capture group이 뭔지도 알았으니, 이제 에러에서 하는 말이 이해 된다.
capture group은 다음과 같이 non-capturing 하게 만들수 있다.
앞에 ?: 를 붙여주면 된다.
?: 를 붙여주면 ()괄호가 동작하게는 하지만 index를 붙여주지 않게 만들어줍니다. (=그룹으로 카운팅 안함)
(?:...)
A non-capturing group allows you to apply quantifiers to part of your regex but does not capture/assign an ID.
non-capturing하게 바꿔준 정규식 ORDER_NO_REGEX
^(?:OD)?[0-9]{18}\$
코드 뜯어보기
코드를 타고 들어가 보니 다음과 같은 부분이 존재한다.
패턴에 groupCount 가 0이 아니면 에러를 내려줍니다.
기존 정규식의 ^(OD)?[0-9]{18}$
다음 부분이 (OD) 그룹으로 카운팅이 되어 groupCount != 0 에 걸려서 에러가 발생했습니다.
groupCounting은 기본값이 -1, entire pattern 만 존재할 경우 0을 돌려주고 있네요.
그런데 왜 ! router의 path에 적용되는 regex는 capturing group을 허용하지 않을까?
한줄요약
URL 패턴 매칭 알고리즘의 효율성을 위해 캡처링 그룹 사용을 못하게 함
그래서 본문의 에러원인에 groupCount() !=0 일때 에러를 던져버립니다.
스프링 프레임워크 역사탐방
1. 라우터 단에서는 URL matching 이 목적임.
2. URL matching 목적에 있어서는 capturing group이 필요없다.
3. Capturing group은 정규식에서 일치하는 부분들에 대해서 추가작업을 하고싶을때 주로 사용된다. (Grouping에 대해 더 알고싶다면...)
4. URL matching은 해당 path가 전체적으로 일치하는지 여부에 대해서만, 즉 T/F만 알면된다.
5. Capturing group을 허용하지 않음으로써 URL pattern matching 알고리즘의 경량화가 가능해진다.
6. spring-framework에 http request pattern matching 추가 pr의 본문에서도 확인가능하듯이 해당 feature 추가시 matching algorithm의 효율성에 중점을 두고 있음을 알 수 있다.
Also, ensuring single segment tokens could help with creating a more efficient matching algorithm
'개발 > 스프링 부트' 카테고리의 다른 글
request에 null이 왔을때 empty list 로 받고싶어요 (0) | 2023.09.17 |
---|---|
[Spring Boot] Servlet Web Application - (1) (0) | 2022.10.02 |