네트워크와 웹

[Apache] Rewrite 모듈을 사용하여 클린 URL 구현하기 + 특정 디렉토리 예외처리 하는 방법

캡스락 2020. 3. 13. 14:37

※ 해당 글은 개인적인 학습 및 정리 목적에서 작성한 글이므로 참고 수준의 글로만 읽어주시기 바랍니다.
설명에 오류가 있는 경우 댓글로 지적해 주시면 감사하겠습니다.


아파치 웹서버에서 기본적으로 제공하는 모듈 중에서 Rewrite 모듈을 활용하면 특정 규칙을 가진 주소를 서버 내부적으로 다른 주소로 리다이렉션시킬 수 있습니다.
URI 재작성을 사용하면 사용자가 쉽게 이해하고 외울 수 있는 주소를 만들 수 있을 뿐 아니라, 서버상 리소스의 실제 경로와 외부로 노출되는 주소를 같지 않게 하거나 숨김으로써 서버의 보안성을 향상시킬 수 있습니다.
또한, PHP 에서 MVC 패턴을 적용한 웹 서비스를 만들 때 하나의 프론트 컨트롤러에서 일차적으로 모든 요청을 모으기 위한 목적으로도 유용하게 사용됩니다.


먼저 아래 예시 주소를 통해 Rewrite 모듈의 활용 방법을 알아보습니다.


http://hellocomputer.shop/item/viewitem.php?category=digital&itemno=58294
이 URL에서는 viewitem.php에 GET으로 'category=digital&itemno=58294' 라는 쿼리스트링을 넘겨주고 있습니다.
GET으로 파라미터를 넘겨주는 방식은 개발자 입장에서는 쉽고 빠르게 서버에 값을 전달할 수 있는 방식이겠으나,
이 경우 기술적인 부분을 잘 모르는 일반인들은 해당 주소를 기억하기도 어렵고 보기에도 그닥 깔끔해 보이지 않습니다.
더구나 쿼리스트링이 노출됨에 따라 어느정도 기술적인 이해가 있는 사람이라면 이상한 값을 집어넣어 보는 사람도 있을 수 있습니다.


이런 문제를 해결하기 위해 아파치의 rewrite 모듈은 쿼리스트링을 포함한 URI에서 특정 규칙과 패턴이 일치할 경우 서버 내부적으로 특정 경로로 리다이렉션 시키는 기능을 제공합니다.
먼저 Rewrite 모듈을 사용하여 위 주소를 간단하게 줄인 예제를 볼까요?


http://hellocomputer.shop/item/digital/58294
파라미터 표기를 위한 구분자가 사라짐에 따라 주소가 훨씬 간단해졌고 외우기도 쉬워졌습니다.
이제 Rewrite 모듈을 사용하여 쿼리스트링이 드러난 주소를 깔끔하게 바꾸는 방법을 알아보겠습니다.


Step1 RewriteEngine 활성화

먼저 루트 권한으로 rewrite 모듈을 활성화 해 줍니다.

1
$ sudo a2enmod rewrite
cs

 

Step2 설정 파일에서 RewriteEngine 사용하기

Rewrite 설정은 운영중인 사이트가 여러개인 경우 모든 사이트에 적용을 원한다면 httpd.conf에(우분투는 /etc/apache2/apache2.conf) 설정을 하시면 되고,
버추얼호스트가 적용된 특정 사이트에만 적용을 원할 경우 아파치가 설치된 경로의 sites-available 디렉터리 내의 가상호스트 설정 파일에 적용을 해 주면 됩니다.


Step3 URI 조건 검사 : RewriteCond 구문

 

1
RewriteCond Teststring Condition [Flags]
 
RewriteCond 구문은 들어오는 URI를 일차적으로 검사하는 역할을 합니다.

여기서 URI가 특정 조건에 일치(요청 URI가 파일인지 아닌지, 특정 문자열이 포함되어 있는지, 리퍼러가 특정 사이트가 아닌 경우 등) 하는 경우,
바로 다음에 나타나는 RewriteRule 규칙에 따라 URI를 내부적으로 특정 경로로 리다이렉션합니다.
Teststring에 올 수 있는 서버 변수는 다음과 같습니다.

%{REMOTE_ADDR}
%{REMOTE_HOST}
%{REMOTE_PORT}
%{REMOTE_USER}
%{REMOTE_IDENT}
%{REQUEST_METHOD}
%{SCRIPT_FILENAME}
%{PATH_INFO}
%{QUERY_STRING}
%{AUTH_TYPE}
%{HTTP_USER_AGENT}
%{HTTP_REFERER}
%{HTTP_COOKIE}
%{HTTP_FORWARDED}
%{HTTP_HOST}
%{HTTP_PROXY_CONNECTION}
%{HTTP_ACCEPT}

Condition에는 정규식이 올 수도 있고 특정 텍스트가 맞는지 아닌지 판단할 수도 있습니다.
또한, RewriteCond 구문은 꼭 한 줄일 필요는 없으며, 경우에 따라 여러 줄이 될 수도 있습니다.

Flags는 Teststring 자체에 적용할 일종의 조건이며 여러개인 경우 쉼표로 구분합니다.
[NC] Case Insensitive 라는 의미로 Teststring의 대소문자를 구분하고 싶지 않을 때 사용합니다.
[OR] 여러개의 RewriteCond 구문을 사용하는 경우, 기본적으로 각 줄의 수행 결과를  AND 연산하여 참 또는 거짓을 판단합니다.
그러나 OR을 사용하는 경우 OR 연산을 합니다. (여러 RewriteCond 중 하나만 참이거나 모두 참일때 참 반환)

간단하게 정리해 보자면 다음과 같습니다.

Teststring의 문자열 중(주로 서버변수. 후술할 예정입니다) Condition에서 정규식으로 일치되는 문자(열)이 있는 경우 참이 되고, 이후 첫 번째로 등장하는 RewriteRule이 실행된다.

 

Step4 특정 URI로 리다이렉트 : RewriteRule 구문

1
RewriteRule Pattern Substitution [Flags]
cs

RewriteRule 구문은 요청받은 URI에서 Pattern에 작성될 정규식을 통해 특정한 패턴을 가진 부분이 있는지 검사하는 것입니다.
Pattern의 정규식에 일치되는 URI를 찾았으면 'Substitution' 에서 일치하는 해당 URI를 리다이렉션시킬 경로를 작성해 주면 됩니다.

Pattern에는 다음 문법을 가진 정규식이 옵니다. 정규식은 정해진 표준은 없지만 대개 큰 틀에서 벗어나지 않는데요, 아파치도 별반 다르지 않기에 이미 정규식을 알고 계신 분이라면 쉽게 이해하실 수 있겠습니다.

^ 정규식 시작
$ 정규식 끝
. 한 개의 문자
(a|b) a 또는 b
(...) 문자열
[ ~~~ ] 괄호 안에 오는 문자 중 하나라도 맞는 것이 있으면 해당 문자를 일치시킵니다. '()' 은 문자열을 나타내지만, '[]' 는 문자 한 개를 뜻한다는 점에서 구분됩니다.
[^~~~] ~~~ 에 포함되는 문자는 올 수 없음
\s 공백
a? 0 또는 1개의 a
a* 0개 이상의 a
a+ 1개 이상의 a (비 탐욕적)
※ 여기서 탐욕적이지 않다는 것은 URI에서 일치하는 부분이 여러개인 경우 앞부분부터 하나만 일치시키고 나머지는 무시한다는 의미입니다.
a{3} 3개의 a를 의미합니다. '(aaa)' 와 정확히 같은 표현입니다. 그럼 반대로 탐욕적인 경우는 모두 일치시키게 되겠죠?
a{3,} 3개 이상의 a
a{3,6} 3 ~ 6 개 사이의 a
a{,6} 최대 6개까지의 a
a{3,6} 3 ~ 6 까지의 a (비 탐욕적)
a-zA-Z 범위 지정하기 a~z, A~Z 별다른 구분자는 사용하지 않고 문자~문자 의 형태로 구분짓습니다.
\ 이스케이프 문자. 특수기호 '^' (캐럿) 을 일치시킬 수 있습니다.
[:punct:] 모든 종류의 문장 부호 (쉼표, 온점, 작은따옴표, 쌍따옴표 등)
[:space:] 모든 종류의 공백 문자
[:blank:] 공백이나 탭 문자

이렇게 정규식을 통해 일치시킨 문자 혹은 문자열들은 $1 에서 $9 까지의 변수에 순서대로 자동 저장되며, 이 변수의 값들은 'Substitutuion' 부분에서 불러와 재작성될 URI의 일부분의 내용을 치환할 때 사용할 수 있습니다.

Flags 에서는 Pattern 자체에 적용될 조건을 줄 수 있습니다. 여러개의 조건은 쉼표로 구분합니다.
C 다음 Rule과 연결합니다.
CO=cookie 특정 쿠키를 만듭니다.
F Fobidden 에러를 출력합니다.
N 다음 Rule 처리를 계속합니다.
L URI 재작성 조건 검사를 중단합니다.
QSA 재작성된 URI 뒤에 쿼리스트링을 붙입니다. 기본적으로 Rewrite에서 모든 쿼리스트링은 제거되지만, QSA 변수에 복사되어 있기 때문에 필요한 상황에서 꺼내어 쓸 수 있습니다.
S=n 다음 n개의 Rule을 건너뜁니다.
NC 대소문자를 구분하지 않습니다.

실제로 연습하실 때 너무 막막하게 느껴지시면 RewriteRule 구문부터 연습하시는 것을 추천드립니다
RewriteCond는 다음에 올 RewriteRule을 적용하기 앞서 전처리를 위해 URI의 조건을 한번 먼저 검사한 뒤, Rule의 실행 여부를 판단하는 역할을 합니다.
즉, 실제로 URI를 재작성하는 기능은 없기 때문에 RewriteRule을 사용해서 충분하다면 RewriteCond는 없어도 되는 구문이기 때문입니다.


아래 정규식의 기본적인 문법을 정리해 두었으니 쭉 읽어보시면서 머릿속으로 정리해 보시고,
연습 문제도 만들어 봤으니 풀어보면서 제대로 이해했는지 확인해 보세요!!

  1. http://example.com/board/12 와 같은 URI가 있을 때 /board/(여기) 에 오는 모든 숫자값들을 /board/viewArticle.php?no=(여기) 로 이동하도록 처리하고 숫자가 아닌 경우에는 메인 페이지로 이동하도록 RewriteRule을 작성하여라.
    더보기
    RewriteRule /board/([0-9]+?) /board/viewArticle.php?no=$1
    설명: [0-9]로 숫자만 올 수 있도록 하고 +?를 사용하여 1개 이상의 숫자가 오는 것을 일치시킵니다(비탐욕적)
  2. $n 변수를 이용하여 호스트명/(foo)/(bar) 형태로 요청되는 URI를 내부적으로 /index.php?req=(foo)&num=(bar) 와 같이 처리하도록 하는 RewriteRule 구문을 작성하여라
    더보기
    RewriteRule ^/(.+)/(.+)$ /index.php?req=$1&num=$2
    설명: (foo)와 (bar)를 각각 (.+)로 일치시킨 다음 $1과 $2로 치환시킵니다.

잘못된 점이나 빼먹은 부분이 있다면 댓글 남겨주시면 감사하겠습니다.