티스토리 뷰

입력된 데이터에 가장 부합하는 조건 찾는 코드.

Collection.sort() 와 Compartor.java 를 활용한 것이 인상적이였으며 Spring 에서 HTTP Request 를 Handling 할 RequestMapping 을 찾을때 사용되는 코드를 눈으로 확인하여서 흥미로웠다.

 

RequestMapping 에는 여러가지 조건이 들어갈 수 있다.

@RequestMapping(value = { "/main" }, method = { RequestMethod.GET, RequestMethod.POST }, headers="X-PROXY-DEVICE=MOBILE")

@RequestMapping(value = { "/main" }, method = { RequestMethod.GET, RequestMethod.POST }, headers="X-PROXY-DEVICE=PC")

위 코드 처럼 URL, HTTP Method, Header 등의 조건들을 적용할 수 있다.

 

내가 개발중인 서버에서 Mobile Web 을 지원하기 시작하면서 해당 서버의 앞단에 있는 Proxy 서버에서 X-PROXY-DEVICE 라는 헤더 값을 내려주기 시작하였다.

 

따라서 X-PROXY-DEVICE=MOBILE 인 요청은 Mobile web view 를 리턴하고 X-PROXY-DEVICE=PC 인 경우에는 PC web view 를 리턴하도록 코드를 작성하였다.

 

하지만 한가지 문제가 발생하였는데 local 에서 테스트를 할때는 Proxy 를 거치지 않아 위와 같이 /main 요청에 대해서 Header 조건을 설정해 놓으면 프록시도 로컬에서 띄워서 테스트하지 않는 이상 매칭되는 RequesMapping 이 없으므로 404 가 리턴된다.

 

크롬 확장프로그램을 사용하여 Header 를 전달하는 방법도 있지만 모든 개발자들이 확장 프로그램을 설치하고 로컬 테스트를 진행하는 것은 낭비라고 생각하여 다른 방법을 생각해보았다.

 

@RequestMapping(value = { "/main" }, method = { RequestMethod.GET, RequestMethod.POST }, headers="X-PROXY-DEVICE=MOBILE")

@RequestMapping(value = { "/main" }, method = { RequestMethod.GET, RequestMethod.POST })

조건을 위와 같이 변경하여 헤더가 없는경우, PC 헤더인 경우는 디폴트로 PC view 를 리턴하는 RequestMapping 으로 요청이 전달되도록 수정해보았다.

 

1. header 가 없는 경우 -> default 인 PC 로

2. pc header 가 있는 경우 -> default 인 PC 로

3. mobile header 가 있는 경우 -> mobile 로

 

요청이 들어갔다.

 

더 구체적으로 매칭되는 RequestMapping 에 요청이 전달되는 것을 로컬 테스트로 확인하였지만 이를 보장하기 위해 확신을 가지고 싶었다.

 

Spring 에서 HandlerMethod 를 찾기 위한 AbstractHandlerMethodMapping.java 의 lookupHandlerMethod() 코드이다.

protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
	List<Match> matches = new ArrayList<Match>();
	List<T> directPathMatches = this.urlMap.get(lookupPath);
	if (directPathMatches != null) {
		addMatchingMappings(directPathMatches, matches, request);
	}
	if (matches.isEmpty()) {
		// No choice but to go through all mappings...
		addMatchingMappings(this.handlerMethods.keySet(), matches, request);
	}
	
    if (!matches.isEmpty()) {
		Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
		Collections.sort(matches, comparator);
		if (logger.isTraceEnabled()) {
			logger.trace("Found " + matches.size() + " matching mapping(s) for [" + lookupPath + "] : " + matches);
		}
		Match bestMatch = matches.get(0);
		if (matches.size() > 1) {
			Match secondBestMatch = matches.get(1);
			if (comparator.compare(bestMatch, secondBestMatch) == 0) {
				Method m1 = bestMatch.handlerMethod.getMethod();
				Method m2 = secondBestMatch.handlerMethod.getMethod();
				throw new IllegalStateException(
						"Ambiguous handler methods mapped for HTTP path '" + request.getRequestURL() + "': {" +
						m1 + ", " + m2 + "}");
			}
		}
		handleMatch(bestMatch.mapping, lookupPath, request);
		return bestMatch.handlerMethod;
    }
	else {
		return handleNoMatch(handlerMethods.keySet(), lookupPath, request);
    }
 }

우선 URL 이 일치하는 Match.java 를 찾는다. directpathMatches 에 저장

directpathMatches 중 현재 조건에 부합하는 Match 를 addMatchingMappings() 를 통해서 matches 에 저장한다.

 

즉 matches 에는 현재 조건에 푸합하는 Match 들만 저장되어있는 상태

matches 을 MatchComparator.java 를 이용해서 Sorting 을 한 후에 첫 번째 인자를 사용하도록 리턴한다.

 

MatchComparator 에서 호출하는 compareTo 메서드는 아래와 같다.

	public int compareTo(RequestMappingInfo other, HttpServletRequest request) {
		int result = this.patternsCondition.compareTo(other.getPatternsCondition(), request);
		if (result != 0) {
			return result;
		}
		result = this.paramsCondition.compareTo(other.getParamsCondition(), request);
		if (result != 0) {
			return result;
		}
		result = this.headersCondition.compareTo(other.getHeadersCondition(), request);
		if (result != 0) {
			return result;
		}
		result = this.consumesCondition.compareTo(other.getConsumesCondition(), request);
		if (result != 0) {
			return result;
		}
		result = this.producesCondition.compareTo(other.getProducesCondition(), request);
		if (result != 0) {
			return result;
		}
		result = this.methodsCondition.compareTo(other.getMethodsCondition(), request);
		if (result != 0) {
			return result;
		}
		result = this.customConditionHolder.compareTo(other.customConditionHolder, request);
		if (result != 0) {
			return result;
		}
		return 0;
	}

위와 같은 조건으로 sorting 을 진행하고 있기 때문에 더 구체적인 Match 가 먼저 사용된다.

 

다만  compareTo 함수의 조건이 걸린 순서대로 구체적인 Match 가 사용된다.

예를 들어서 Pattern 이 더 구체적인 Match 가 있고 Headers 가 더 구체적인 Match 가 있다면

compareTo 조건문들의 순서에 의해서 Pattern 이 더 구체적인 Match 가 사용된다.

'Spring Framework' 카테고리의 다른 글

Spring JPA 와 Dynamic Proxy  (0) 2022.04.24
Spring Bean 과 CGLIB proxy  (0) 2022.04.23
Spring Framework - RedirectAttributes 설명  (0) 2021.12.17
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함