본문 바로가기

IT/Dev

URL 문자열 정규식으로 분리 하기 - Java 이용 방법


 Java에서 서블릿을 이용하는 경우에는 쿼리 문자열 값 이나, 호스명, 포트 등은 각각 getParameter() ,getServerName(), getServerPort() 등의 메소드를 이용하여 값을 읽어내면 간단하지만 직접 URL을 핸들링해야 하는 경우가  가끔씩 있다.  URL Rewriting을 하고자 한다거나, 경로에 파라메터를 넣고자 하는 경우 이거나 아니면 직접 웹서버를 구현하는 경우라던가 등.

그래서 이번에는 간단하게 URL로 부터 값을 뽑아 내는 방법으로 정규식 (Regular Expression)을 이용하는 방법을 정리 해보고자 한다.
 
더불어 정규식 사용방법을 익히는데도 도움이 되도록 가능하면 상세하게 설명을 달것이다.

URL(Uniform Resource Location)에의 구성은 기본적으로 아래와 같은 구조를 갖는다.

 scheme://username:password@domain:port/path?query_string#anchor

여기에서는 사용자명과 패스워드를 입력받는 경우는 생략하고 scheme 에는 http, https 가 들어오는 경우만 처리하는 것으로 한다.

즉 아래와 같은 경우 만 target으로 할 것이다.
 scheme://domain:port/path?query_string#anchor


  정규식을 활용하는 방법은 다량의 텍스트에서 원하는 패턴만 찾아내고자 할때가 있지만 찾아낸 문자열에서 다시 세부 요소를 뽑아 내고자 하는경우에도 활용 할 수 있다.

  예를 들어 HTML 텍스트에서  <a href="someurl">someurl go!</a>  형태와 같은 <a> 태그를 를 찾아 내고자 하는 경우도 있지만 실제 활용단계에서는 href="" 안에 있는 값을 바로 뽑아 낼 수 있는게 필요하다.

마찬가지로 URL에 정규식의 적용도 실제 URL내에 있는 스키마, 호스트(도메인)명, 포트, 경로, 쿼리문자열, 앵커(anchor)을 추출하는게 실제 의미가 있다.

이러한 경우를 위해 정규식에서는 ()를 이용한 그룹핑을 지원한다. 따라서 각 추출하고자 하는 요소를 () 묶어 주기만 하면 된다.

첫번째 스키마, 스키마는 http와 https 인경우만 처리하기로 했으므로  http:// 또는 https:// 를 찾기 위해

   ^(https?):\/\/

     's'문자는 는 생략가능이고, '/'문자를 Escape 하기 위해 \를 앞에 붙였다. '^' 줄의 시작위치를 뜻한다.

두번째 도메인(호스트)명, 도메인명에는 영숫자, '-'  와 '.' 가 올 수 있다. '_' 문자는 맨 앞에 못오거나 하는 규칙이 있지만 URL을 파싱하려는 서비스에 도달했다는 의미는 정상적인 URL이므로 복잡한 비정상적인 경우는 생각할 필요는 없다.
그래서  콜론(:), 슬래시(/), 빈칸(' ')을 뺀 나머지 문자열이 하나 이상 나타난다고  표현해주면된다.
([^:\/\s]+)

세번째, 포트 포트는 ":8080" 형태로 콜론뒤에 숫자가 나온다. 숫자의 길이는  경로가 시작되는 다음 슬래시(/) 앞까지다. 그리고 포트 값은 생략될 수 있으므로 그룹핑 괄호 뒤에 물음표(?)를 해준다. 안쪽 괄호는 이후 ':"를 뺀 포트값만 추출하기 위함이다.

(:([^\/]*))?

네번째 경로다. 경로는 반드시 슬래시(/)로 시작하고 빈칸과 슬래시가 아닌문자들인 것을 한다. 경로는 반복될 수 있으므로 ()에 *를 붙이고,  중간경로 없이 바로파일명으로 넘어 갈 수 있으므로 물음표를 붙인다.

((\/[^\s/\/]+)*)?

다섯번째 파일명자리는 빈칸, 물음표, 또는 앵커표시인 샵(#)이 나올때까지 거나 아니면 문자열 맨끝까지 이다.
파일명은 마지막 슬래시로 시작해서 빈칸이나 물음표가 아닌문자들이 올 수 있는 것으로 한다. 파일명 문자열은 없을 수 도있으므로 (전체 URL에 경로 / 문자로 끝나는 경우) * 로 문자열 뒤에 붙이고
\/([^#\s\?]*)

여섯번째 쿼리자리는 물음표부터 샵이나오거나 문자열 끝까지 이고 생략될 수 있으므로
(\?([^#\s]*))?

앵커는 샵으로 시작하는 문자열이고 생략될 수 있으므로
(#(\w*))?

마지막으로 문자열의 끝 표시 $를 붙여 준다.
$

최종적으로 앞서 정리한 자리들을 모두 합쳐주면 다음과 같이 된다.
^(https?):\/\/([^:\/\s]+)(:([^\/]*))?((\/[^\s/\/]+)*)?\/([^#\s\?]*)(\?([^#\s]*))?(#(\w*))?$


아래는 java로 작성한 sample 코드.  자바에서 '\' 문자를 다시 escape 해주기 위해 "\\" 로 바뀌었다.

public void extractUrlParts() {
   String testurl = "https://goodidea.tistory.com:8888/qr/aaa/ddd.html?abc=def&ddd=fgf#sharp";
 //                                     http:       //  도메인명             :포트                     경로                                파일명                    쿼리                         앵커
   Pattern urlPattern = Pattern.compile("^(https?):\\/\\/([^:\\/\\s]+)(:([^\\/]*))?((\\/[^\\s/\\/]+)*)?\\/([^#\\s\\?]*)(\\?([^#\\s]*))?(#(\\w*))?$");
  
  Matcher mc = urlPattern.matcher(testurl); if(mc.matches()){ for(int i=0;i<=mc.groupCount();i++) System.out.println("group("+i+") = "+mc.group(i)); } else System.out.println("not found"); }

 

이렇게 해서 실행시키면 다음과 같은 결과가 나오다.


group(0) = https://goodidea.tistory.com:8888/qr/aaa/ddd.html?abc=def&ddd=fgf#sharp
group(1) = https
group(2) = goodidea.tistory.com
group(3) = :8888
group(4) = 8888
group(5) = /qr/aaa
group(6) = /aaa
group(7) = ddd.html
group(8) = ?abc=def&ddd=fgf
group(9) = abc=def&ddd=fgf
group(10) = #sharp
group(11) = sharp


mc.groupCount()는 정규식내에 괄호의 수 만큼 그룹핑을 하게 되고 배열(mc.group)의 각 그룹의 인덱스는 고정이며 해당 위치별로 [1]은 스키마, [7]은 파일명이 되는 것이다.