URL 재작성 지침서 - Apache HTTP Server

Apache Server 2.0

Apache HTTP Server Version 2.0

<-

URL 재작성 지침서

이 문서는 최신판 번역이 아닙니다. 최근에 변경된 내용은 영어 문서를 참고하세요.

원저자
Ralf S. Engelschall <[email protected]>
1997년 12월

이 문서는 mod_rewrite 참조 문서를 보충한다. 이 문서는 웹관리자가 실제 작업에서 부딪치게되는 전형적인 URL관련 문제를 해결하기위해서 어떻게 아파치 mod_rewrite를 사용하는지 설명한다. URL 재작성 규칙을 설정하여 문제를 해결하는 방법을 자세히 설명한다.

top

mod_rewrite 소개

아파치 mod_rewrite 모듈은 굉장하다. 즉, URL을 조작할 수 있는 강력하고 실로 정교한 모듈이다. 상상해왔던 거의 모든 종류의 URL 조작이 가능하다. 그러나 그 대가로 사용하기 복잡하다. mod_rewrite의 최대 단점은 초보자가 이해하고 사용하기 쉽지 않다는 점이다. 심지어 아파치 전문가도 종종 mod_rewrite의 새로운 용도를 발견한다.

다른 말로: mod_rewrite에 대해 당신은 처음에 겁을 먹고 절대로 다시 사용하지 않거나, 강력함에 매료되어 앞으로 삶 동안 사랑에 빠질 것이다. 이 글은 첫번째 경우를 막기위해 이미 알려진 몇가지 성공사례를 소개하려고 한다.

top

실용적인 해결책

이제 내가 직접 만들었거나 다른 사람들이 만든 많은 실용적인 해결책이 나온다. 예제에서 URL 재작성의 흑마술을 마음껏 배우길 바란다.

주의: 서버 설정에 따라 상황에 맞게 예제를 조금 수정해야 할 경우가 있다. 예를 들어, 추가로 mod_alias, mod_userdir 등을 사용한다면 [PT] 플래그를 추가한다. 혹은 주서버설정/가상호스트 사용장소가 아닌 .htaccess 사용장소에 알맞게 규칙을 수정할 수도 있다. 사용하기 전에 항상 규칙이 어떤 기능을 하는지 이해하도록 해라. 그러면 문제를 피할 수 있다.
top

URL 구조

기준이 되는 URL

상황설명:

한 리소스에 대해 여러 URL을 가지는 웹서버가 있다. 보통 (실제 사용하고 알려져야 할) 기준이 되는 URL과, 단축 혹은 내부 용도의 URL이 있다. 사용자가 요청에 어떤 URL을 사용하던지 기준이 되는 URL만을 보여줘야 한다.

해결책:

기준이 되지않는 모든 URL을 브라우저가 알도록 고치기위해 외부 HTTP 리다이렉션한다. 예를 들어 아래 규칙은 /~user를 기준이 되는 /u/user로 대체하고, /u/user 마지막에 슬래쉬가 없다면 추가한다.

RewriteRule   ^/~([^/]+)/?(.*)    /u/$1/$2  [R]
RewriteRule   ^/([uge])/([^/]+)$  /$1/$2/   [R]

기준이 되는 호스트명

상황설명:
이 규칙은 동일한 사이트에 도달할 수 있는 다른 호스트명 대신 특정 호스트명을 사용하도록 강제한다. 예를 들어, example.com 대신 www.example.com을 사용하도록 강제하고 싶다면 다음과 같은 규칙을 사용할 수 있다.
해결책:
# 80번이 아닌 포트에서 실행하는 사이트용
RewriteCond %{HTTP_HOST}   !^fully\.qualified\.domain\.name [NC]
RewriteCond %{HTTP_HOST}   !^$
RewriteCond %{SERVER_PORT} !^80$
RewriteRule ^/(.*)         http://fully.qualified.domain.name:%{SERVER_PORT}/$1 [L,R]

# 그리고, 80번 포트에서 실행하는 사이트용
RewriteCond %{HTTP_HOST}   !^fully\.qualified\.domain\.name [NC]
RewriteCond %{HTTP_HOST}   !^$
RewriteRule ^/(.*)         http://fully.qualified.domain.name/$1 [L,R]

DocumentRoot를 옮긴 경우

상황설명:

웹서버의 DocumentRoot는 보통 URL "/"과 직접 관련있다. 그러나 이곳에 모든 자료가 있지 않고, 자료가 다른 여러 곳에 흩어져있는 경우가 있다. 예를 들어 인트라넷 사이트에 (외부를 위한 홈페이지) /e/www/와 (인트라넷을 위한 홈페이지) /e/sww/가 있다고 하자. 이제 DocumentRoot/e/www/이기때문에, 요청에서 페이지에 포함된 그림 등을 이곳에서 가져와야 한다.

해결책:

우리는 URL //e/www/로 리다이렉션만 하면 된다. 사소해 보이지만 실제로 mod_rewrite를 사용해서만 가능하다. (mod_alias 등이 제공하는) URL Alias 같은 전형적인 방법은 앞부분만 찾는다. DocumentRoot가 모든 URL의 앞부분이기때문에 이 방법을 사용하여 리다이렉션을 할 수 없다. mod_rewrite를 사용하면 진짜 간단하다:

RewriteEngine on
RewriteRule   ^/$  /e/www/  [R]

마지막 슬래쉬 문제

상황설명:

디렉토리를 지칭하는 URL의 마지막 슬래쉬 문제가 없다면 모든 웹관리자는 환호할 것이다. 슬래쉬가 없다면, 즉 /~quux/foo/ 대신 /~quux/foo를 사용하면 서버가 foo라는 파일을 찾기때문에 오류가 발생한다. 파일이 디렉토리이기때문에 받아들이지 않는다. 대부분의 경우 보통 서버가 자동으로 URL을 고치지만, 가끔 직접 해줘야 할 경우가 있다. 예를 들어, CGI 스크립트 등으로 복잡한 URL 재작성을 한 후에 그러하다.

해결책:

이 미묘한 문제의 해결방법은 서버가 자동으로 마지막 슬래쉬를 추가하는 것이다. 브라우저가 나머지 그림 등을 올바로 요청할 수 있도록, 외부 리다이렉션을 해야 한다. 내부 리다이렉션을 한다면 디렉토리 페이지에만 동작하여 이 페이지가 상대 URL로 포함하는 그림을 브라우저가 요청할때 찾을 수 없다. 예를 들어, 외부 리다이렉션을 사용하지 않을때 /~quux/foo/index.html에서 image.gif를 요청하면 /~quux/image.gif를 요청하게 된다!

그래서 이를 해결하기위해 다음과 같이 설정한다:

RewriteEngine  on
RewriteBase    /~quux/
RewriteRule    ^foo$  foo/  [R]

홈디렉토리의 최상위 .htaccess 파일에 다음과 같이 설정할 수도 있다. 그러나 처리하는데 부담이 된다.

RewriteEngine  on
RewriteBase    /~quux/
RewriteCond    %{REQUEST_FILENAME}  -d
RewriteRule    ^(.+[^/])$           $1/  [R]

일관된 URL 구조로 만든 웹클러스터

상황설명:

인트라넷 웹서버군의 모든 웹서버에 동일하고 일관된 URL 구조를 만들고 싶다. 즉, 모든 (정의상 서버에 속하여 서버에 의존적인!) URL을 서버 독립적으로 만든다! 웹 이름공간에 서버독립적인 동일한 구조를 부여해야 한다: URL은 실제 서버를 지칭하면 안된다. 서버군이 자동으로 실제 서버로 유도한다.

해결책:

먼저 사용자, 그룹, 독립체의 위치 정보를 저장한 (분산된) 외부맵에 실제 서버 정보를 얻어온다. 외부맵은 다음과 같은 형식이다

user1  server_of_user1
user2  server_of_user2
:      :

우리는 이 정보를 각각 map.xxx-to-host 파일에 저장했다. 다음으로 모든 서버에서 URL이 서버에 없다면 다음과 같은 URL을,

/u/user/anypath
/g/group/anypath
/e/entity/anypath

다음과 같이 리다이렉션한다

http://physical-host/u/user/anypath
http://physical-host/g/group/anypath
http://physical-host/e/entity/anypath

아래 규칙은 맵파일을 사용하여 이 작업을 한다 (server0은 맵에 항목이 없는 경우 사용할 기본서버라고 가정한다):

RewriteEngine on

RewriteMap      user-to-host   txt:/path/to/map.user-to-host
RewriteMap     group-to-host   txt:/path/to/map.group-to-host
RewriteMap    entity-to-host   txt:/path/to/map.entity-to-host

RewriteRule   ^/u/([^/]+)/?(.*)   http://${user-to-host:$1|server0}/u/$1/$2
RewriteRule   ^/g/([^/]+)/?(.*)  http://${group-to-host:$1|server0}/g/$1/$2
RewriteRule   ^/e/([^/]+)/?(.*) http://${entity-to-host:$1|server0}/e/$1/$2

RewriteRule   ^/([uge])/([^/]+)/?$          /$1/$2/.www/
RewriteRule   ^/([uge])/([^/]+)/([^.]+.+)   /$1/$2/.www/$3\

홈디렉토리를 다른 웹서버로 이전

상황설명:

많은 웹관리자는 웹서버의 모든 홈디렉토리를 다른 웹서버로 이전한 경우 해결책을 물어본다. 이 방법은 이전 서버를 대체할 새로운 서버를 구성하는데 시간이 걸리는 경우에 필요하다.

해결책:

mod_rewrite를 사용하면 간단하다. 이전 웹서버는 모든 /~user/anypath URL을 http://newserver/~user/anypath로 리다이렉션하면 된다.

RewriteEngine on
RewriteRule   ^/~(.+)  http://newserver/~$1  [R,L]

홈디렉토리 구조 만들기

상황설명:

사용자가 수천명인 사이트는 보통 홈디렉토리 구조를 만든다. 즉, 예를 들어 이름이 사용자명의 첫번째 문자인 하위디렉토리에 홈디렉토리를 둔다. 그래서, /~foo/anypath/home/f/foo/.www/anypath이고, /~bar/anypath/home/b/bar/.www/anypath이다.

해결책:

물결표시가 있는 URL을 위와 같은 구조로 변환하기위해 다음 규칙을 사용한다.

RewriteEngine on
RewriteRule   ^/~(([a-z])[a-z0-9]+)(.*)  /home/$2/$1/.www$3

파일시스템 재구성

상황설명:

이 예는 실로 하드코어적이다: 디렉토리별 RewriteRules를 매우 많이 사용하여 자료 자체는 그대로 둔체로 웹에 자료를 자연스럽게 브라우징하도록 한다. 배경: 나는 1992년 부터 자유롭게 사용할 수 있는 유닉스 소프트웨어들을 net.sw에 모아두고 있었다. 이는 내가 컴퓨터과학을 공부하면서 여러해동안 여가시간에 시스템 관리자와 네트웍 관리자를 해왔기때문에 내 취미이자 일이다. 매주마다 새로 소프트웨어가 추가될 때마다 디렉토리를 깊게 만들어왔다:

drwxrwxr-x   2 netsw  users    512 Aug  3 18:39 Audio/
drwxrwxr-x   2 netsw  users    512 Jul  9 14:37 Benchmark/
drwxrwxr-x  12 netsw  users    512 Jul  9 00:34 Crypto/
drwxrwxr-x   5 netsw  users    512 Jul  9 00:41 Database/
drwxrwxr-x   4 netsw  users    512 Jul 30 19:25 Dicts/
drwxrwxr-x  10 netsw  users    512 Jul  9 01:54 Graphic/
drwxrwxr-x   5 netsw  users    512 Jul  9 01:58 Hackers/
drwxrwxr-x   8 netsw  users    512 Jul  9 03:19 InfoSys/
drwxrwxr-x   3 netsw  users    512 Jul  9 03:21 Math/
drwxrwxr-x   3 netsw  users    512 Jul  9 03:24 Misc/
drwxrwxr-x   9 netsw  users    512 Aug  1 16:33 Network/
drwxrwxr-x   2 netsw  users    512 Jul  9 05:53 Office/
drwxrwxr-x   7 netsw  users    512 Jul  9 09:24 SoftEng/
drwxrwxr-x   7 netsw  users    512 Jul  9 12:17 System/
drwxrwxr-x  12 netsw  users    512 Aug  3 20:15 Typesetting/
drwxrwxr-x  10 netsw  users    512 Jul  9 14:08 X11/

1996년 7월 이 저장소를 멋있는 웹 인터페이스를 통해 세상에 공개하기로 결정햇다. "멋있다"는 말은, 최상위 디렉토리에 CGI 스크립트를 두지 않고도, 저장소 계층구조를 직접 브라우질하길 바란다는 뜻이다. 왜? 저장소를 나중에 FTP로도 접근할 수 있도록 만들 예정이였기때문에 웹이나 CGI와 관련된 내용을 같이 두기 싫었다.

해결책:

해결책은 두 부분으로 나뉜다: 먼저 디렉토리 수준에서 필요한 모든 페이지를 동적으로 만드는 CGI 스크립트가 필요하다. 나는 이 스크립트들을 다음과 같이 /e/netsw/.www/에 두었다:

-rw-r--r--   1 netsw  users    1318 Aug  1 18:10 .wwwacl
drwxr-xr-x  18 netsw  users     512 Aug  5 15:51 DATA/
-rw-rw-rw-   1 netsw  users  372982 Aug  5 16:35 LOGFILE
-rw-r--r--   1 netsw  users     659 Aug  4 09:27 TODO
-rw-r--r--   1 netsw  users    5697 Aug  1 18:01 netsw-about.html
-rwxr-xr-x   1 netsw  users     579 Aug  2 10:33 netsw-access.pl
-rwxr-xr-x   1 netsw  users    1532 Aug  1 17:35 netsw-changes.cgi
-rwxr-xr-x   1 netsw  users    2866 Aug  5 14:49 netsw-home.cgi
drwxr-xr-x   2 netsw  users     512 Jul  8 23:47 netsw-img/
-rwxr-xr-x   1 netsw  users   24050 Aug  5 15:49 netsw-lsdir.cgi
-rwxr-xr-x   1 netsw  users    1589 Aug  3 18:43 netsw-search.cgi
-rwxr-xr-x   1 netsw  users    1885 Aug  1 17:41 netsw-tree.cgi
-rw-r--r--   1 netsw  users     234 Jul 30 16:35 netsw-unlimit.lst

DATA/ 하위디렉토리에 위에서 말한 저장소가 있다. 실제 net.sw의 내용은 보통 rdist를 사용하여 자동으로 가져온다. 두번째 부분이 남았다: 어떻게 이 두 구조를 하나의 자연스러운 URL 구조로 연결하는가? 사용자에게 DATA/ 디렉토리를 감추고, URL마다 적절한 CGI 스크립트를 실행하고 싶다. 해결책은 다음과 같다: 먼저 서버의 DocumentRoot에서 공개된 URL /net.sw/를 내부 경로 /e/netsw로 재작성하기위해 디렉토리별 설정파일에 다음과 같이 설정한다:

RewriteRule  ^net.sw$       net.sw/        [R]
RewriteRule  ^net.sw/(.*)$  e/netsw/$1

첫번째 규칙은 마지막에 슬래쉬가 없는 요청을 위해서 사용했다! 두번째 규칙이 실제 작업을 한다. 그리고 디렉토리별 설정파일 /e/netsw/.www/.wwwacl에 결정적인 설정이 나온다:

Options       ExecCGI FollowSymLinks Includes MultiViews

RewriteEngine on

#  앞 부분이 /net.sw/ 로 접근한다
RewriteBase   /net.sw/

#  먼저 최상위 디렉토리를
#  cgi 스크립트로 재작성한다
RewriteRule   ^$                       netsw-home.cgi     [L]
RewriteRule   ^index\.html$            netsw-home.cgi     [L]

#  브라우저가 디렉토리별 페이지를 요청한 경우
#  하위디렉토리를 추출한다
RewriteRule   ^.+/(netsw-[^/]+/.+)$    $1                 [L]

#  이제 재작성을 마친다
RewriteRule   ^netsw-home\.cgi.*       -                  [L]
RewriteRule   ^netsw-changes\.cgi.*    -                  [L]
RewriteRule   ^netsw-search\.cgi.*     -                  [L]
RewriteRule   ^netsw-tree\.cgi$        -                  [L]
RewriteRule   ^netsw-about\.html$      -                  [L]
RewriteRule   ^netsw-img/.*$           -                  [L]

#  다른 cgi 스크립트가 처리할
#  하위디렉토리가 남았다
RewriteRule   !^netsw-lsdir\.cgi.*     -                  [C]
RewriteRule   (.*)                     netsw-lsdir.cgi/$1

해석을 위한 힌트:

  1. 네번째 부분에서 대체 필드('-')가 없고 L (last) 플래그가 있음을 주목하라
  2. 마지막 부분에서 첫번째 규칙에 ! (not) 문자와 C (chain) 플래그를 주목하라
  3. 마지막 규칙에서 기타 해당하지 않는 모든 경우를 잡아내는 패턴을 주목하라

NCSA imagemap을 아파치 mod_imap으로

상황설명:

사람들은 NCSA 웹서버에서 현대적인 아파치 웹서버로 자연스럽게 옮겨가길 바란다. 그래서 오래된 NCSA imagemap 프로그램을 사용한 페이지를 현대적인 아파치 mod_imap로 처리하길 바란다. 문제는 imagemap 프로그램을 /cgi-bin/imagemap/path/to/page.map과 같이 참조하는 하이퍼링크가 많다는 것이다. 아파치는 /path/to/page.map과 같은 요청을 받아야 한다.

해결책:

모든 요청에서 앞부분을 동적으로 제거하는 전역 규칙을 사용한다:

RewriteEngine  on
RewriteRule    ^/cgi-bin/imagemap(.*)  $1  [PT]

여러 디렉토리에서 페이지 검색

상황설명:

가끔 웹서버가 여러 디렉토리에서 파일을 찾아야 할 때가 있다. 이 경우 MultiViews나 다른 방법은 도움이 안된다.

해결책:

여러 디렉토리에서 파일을 찾는 규칙을 직접 프로그램한다.

RewriteEngine on

#   먼저 custom/에서 찾길 시도하고...
#   ...찾으면 끝!
RewriteCond         /your/docroot/dir1/%{REQUEST_FILENAME}  -f
RewriteRule  ^(.+)  /your/docroot/dir1/$1  [L]

#   두번째로 pub/에서 찾길 시도한다...
#   ...찾으면 끝!
RewriteCond         /your/docroot/dir2/%{REQUEST_FILENAME}  -f
RewriteRule  ^(.+)  /your/docroot/dir2/$1  [L]

#   못찾으면 다른 Alias나 ScriptAlias 지시어 등으로 진행한다.
RewriteRule   ^(.+)  -  [PT]

URL에 따라 환경변수를 설정한다

상황설명:

요청들간에 상태정보를 유지하기위해 URL에 정보를 인코딩하는 방법도 있다. 그러나 단지 이 정보를 제거하기위해 모든 페이지에 CGI wrapper를 사용하고 싶지 않다.

해결책:

재작성 규칙을 사용하여 상태정보를 추출하고, 추출한 정보를 나중에 XSSI나 CGI에서 사용하기위해 환경변수에 저장한다. 그래서 URL /foo/S=java/bar//foo/bar/로 변환되고 STATUS라는 환경변수 값을 "java"로 설정한다.

RewriteEngine on
RewriteRule   ^(.*)/S=([^/]+)/(.*)    $1/$3 [E=STATUS:$2]

가상 사용자 호스트

상황설명:

가상호스트를 사용하지 않고 같은 컴퓨터로 DNS A 레코드를 설정하여 www.username.host.domain.com을 사용자의 홈페이지로 제공하고 싶다.

해결책:

HTTP/1.0 요청의 경우 방법이 없지만, Host: HTTP 헤더를 포함한 HTTP/1.1 요청은 다음 규칙을 사용하여 내부적으로 http://www.username.host.com/anypath/home/username/anypath로 재작성할 수 있다:

RewriteEngine on
RewriteCond   %{HTTP_HOST}                 ^www\.[^.]+\.host\.com$
RewriteRule   ^(.+)                        %{HTTP_HOST}$1          [C]
RewriteRule   ^www\.([^.]+)\.host\.com(.*) /home/$1$2

홈디렉토리를 외부 서버로 리다이렉션

상황설명:

지역 도메인 ourdomain.com 밖에서 요청이 들어오면 홈디렉토리 URL을 다른 웹서버 www.somewhere.com으로 리다리렉션하길 바란다. 종종 가상호스트 사용장소에서 사용한다.

해결책:

재작성 조건을 사용하면 된다:

RewriteEngine on
RewriteCond   %{REMOTE_HOST}  !^.+\.ourdomain\.com$
RewriteRule   ^(/~.+)         http://www.somewhere.com/$1 [R,L]

실패한 URL을 다른 웹서버로 리다이렉션

상황설명:

URL 재작성에 대해서 웹서버 A에 해당 파일이 없는 경우 웹서버 B로 요청을 리다이렉션하는 방법을 자주 물어본다. 보통 Perl로 작성한 ErrorDocument CGI 스크립트를 사용하지만, mod_rewrite를 사용하는 방법도 있다. 그러나 성능은 ErrorDocument CGI 스크립트보다 떨어짐을 명심하라!

해결책:

첫번째 방법은 빠르지만 유연성이 떨어지고 완전하지 않다:

RewriteEngine on
RewriteCond   /your/docroot/%{REQUEST_FILENAME} !-f
RewriteRule   ^(.+)                             http://webserverB.dom/$1

이 방법의 단점은 DocumentRoot 안에 있는 페이지만 가능하다는 점이다. (예를 들어 홈디렉토리 등을 위해) 조건을 추가할 수 있지만, 더 좋은 방법이 있다:

RewriteEngine on
RewriteCond   %{REQUEST_URI} !-U
RewriteRule   ^(.+)          http://webserverB.dom/$1

mod_rewrite의 URL 전방참조(look-ahead)를 사용한다. 그래서 모든 URL에 동작하고 안전하다. 그러나 모든 요청마다 내부 하위요청을 한번 더 하기때문에 웹서버 성능에 악영향을 준다. 그래서 강력한 CPU에서 웹서버를 실행한다면 사용하라. 컴퓨터가 느리다면 첫번째 방법이나 더 나은 ErrorDocument CGI 스크립트를 사용하라.

확장 리다이렉션

상황설명:

가끔 리다이렉션하는 URL을 더 조절할 필요가 있다. 아파치 내부 URL escape 함수는 "url#anchor" 같은 URL의 anchor도 escape한다. 아파치의 uri_escape() 함수는 우물정자(#)도 같이 escape하므로 사용할 수 없다. 그러면 어떻게 이런 URL로 리다이렉션할 수 있나?

해결책:

직접 리다이렉션하는 NPH-CGI 스크립트를 사용한 해결책이 필요하다. escape를 하지 않기때문이다 (NPH=non-parseable headers). 먼저 다음 서버설정을 하여 (재작성 규칙의 끝부분에 사용해야 한다) 새로운 URL scheme xredirect:를 도입한다:

RewriteRule ^xredirect:(.+) /path/to/nph-xredirect.cgi/$1 \
            [T=application/x-httpd-cgi,L]

그러면 xredirect:로 시작하는 모든 URL은 nph-xredirect.cgi 프로그램을 통하게 된다. 프로그램은 다음과 같다:

#!/path/to/perl
##
##  nph-xredirect.cgi -- NPH/CGI script for extended redirects
##  Copyright (c) 1997 Ralf S. Engelschall, All Rights Reserved.
##

$| = 1;
$url = $ENV{'PATH_INFO'};

print "HTTP/1.0 302 Moved Temporarily\n";
print "Server: $ENV{'SERVER_SOFTWARE'}\n";
print "Location: $url\n";
print "Content-type: text/html\n";
print "\n";
print "<html>\n";
print "<head>\n";
print "<title>302 Moved Temporarily (EXTENDED)</title>\n";
print "</head>\n";
print "<body>\n";
print "<h1>Moved Temporarily (EXTENDED)</h1>\n";
print "The document has moved <a HREF=\"$url\">here</a>.<p>\n";
print "</body>\n";
print "</html>\n";

##EOF##

그러면 mod_rewrite가 직접 받지못하는 모든 URL scheme으로 리다이렉션할 수 있다. 예를 들어, 다음과 같이 news:newsgroup으로 리다이렉션할 수 있다

RewriteRule ^anyurl  xredirect:news:newsgroup
주의: 위의 특별한 "통과" 규칙을 사용하여 xredirect:를 마지막에 확장해야 하기때문에 규칙에 [R]이나 [R,L]을 사용하면 안된다.

저장소 접근 중계(multiplexer)

상황설명:

http://www.perl.com/CPAN에 있는 대단한 CPAN (Comprehensive Perl Archive Network)을 아는가? 이 주소는 세계에 흩어진 여러 CPAN 미러 FTP 서버중 클라이언트에 가까이 있는 서버로 리다이렉션한다. 이를 FTP 접근 중계 서비스라고 한다. CPAN은 CGI 스크립트를 사용하지만, mod_rewrite를 사용하여 비슷하게 만들 수 있을까?

해결책:

먼저 mod_rewrite 3.0.0 버전부터 리다이렉션에 "ftp:" scheme을 사용할 수 있다. 다음으로 클라이언트의 최상위 도메인을 RewriteMap과 같이 사용하여 위치를 추정할 수 있다. 복잡히 엮인 규칙에서 최상위 도메인을 중계맵의 키로 사용한다.

RewriteEngine on
RewriteMap    multiplex                txt:/path/to/map.cxan
RewriteRule   ^/CxAN/(.*)              %{REMOTE_HOST}::$1                 [C]
RewriteRule   ^.+\.([a-zA-Z]+)::(.*)$  ${multiplex:$1|ftp.default.dom}$2  [R,L]
##
##  map.cxan -- Multiplexing Map for CxAN
##

de        ftp://ftp.cxan.de/CxAN/
uk        ftp://ftp.cxan.uk/CxAN/
com       ftp://ftp.cxan.com/CxAN/
 :
##EOF##

시간에 따른 재작성

상황설명:

시간에 따라 다른 내용을 서비스하는 경우 많은 웹관리자는 잠시 특별한 페이지로 리다이렉션하기위해 CGI 스크립트를 사용한다. mod_rewrite로는 어떻게 할 수 있는가?

해결책:

재작성 조건에서 사용할 수 있는 여러 TIME_xxx 변수가 있다. 변수와 특별한 사전순서 비교 <STRING, >STRING, =STRING을 사용하여 시간에 따라 리다이렉션할 수 있다:

RewriteEngine on
RewriteCond   %{TIME_HOUR}%{TIME_MIN} >0700
RewriteCond   %{TIME_HOUR}%{TIME_MIN} <1900
RewriteRule   ^foo\.html$             foo.day.html
RewriteRule   ^foo\.html$             foo.night.html

URL foo.html을 요청하면 07:00-19:00 동안 foo.day.html 내용을 서비스하고, 나머지 시간 동안 foo.night.html 내용을 서비스한다. 홈페이지에서 사용하기 좋은 기능이다...

YYYY를 XXXX로 이전한 경우 역호환

상황설명:

여러 .html 파일을 .phtml로 변환하는 등 document.YYYYdocument.XXXX로 이전한후 역호환(backward compatibility) URL을 (가상적으로 존재하게) 만들 수 있나?

해결책:

이름을 기본이름으로 재작성한후 새로운 확장자를 가진 파일이 있는지 검사한다. 있다면 그 파일명을 사용하고, 없으면 URL을 원래 상태로 재작성한다.

#   문서.html 이 없고
#   문서.phtml 만 있는 경우
#   문서.html 을 문서.phtml 로
#   재작성하는 역호환 규칙
RewriteEngine on
RewriteBase   /~quux/
#   기본이름을 찾고, 찾았다는 사실을 기억한다
RewriteRule   ^(.*)\.html$              $1      [C,E=WasHTML:yes]
#   파일이 있다면 문서.phtml 로 재작성한다
RewriteCond   %{REQUEST_FILENAME}.phtml -f
RewriteRule   ^(.*)$ $1.phtml                   [S=1]
#   아니면 앞에서 찾은 기본이름을 되돌린다
RewriteCond   %{ENV:WasHTML}            ^yes$
RewriteRule   ^(.*)$ $1.html
top

컨텐츠 다루기

새로 이전 (감추기)

상황설명:

최근 foo.htmlbar.html로 변경하고 역호환성을 위해 이전 URL을 계속 제공하고 싶다고 가정하자. 사용자는 이전 URL이 변경되었다는 사실을 눈치채지 못한다.

해결책:

다음 규칙으로 이전 URL을 내부적으로 새로운 URL로 재작성한다:

RewriteEngine  on
RewriteBase    /~quux/
RewriteRule    ^foo\.html$  bar.html

새로 이전 (알리기)

상황설명:

다시 foo.htmlbar.html로 변경하고 역호환성을 위해 이전 URL을 계속 제공하고 싶다고 가정하자. 그러나 이제는 이전 URL을 사용하면 사용자에게 새로운 URL을 힌트로 알려준다. 즉, 브라우저 주소창이 변한다.

해결책:

새로운 URL로 HTTP 리다이렉션하다. 그러면 브라우저가 새로운 URL를 보이고 변경사실을 사용자가 알게된다:

RewriteEngine  on
RewriteBase    /~quux/
RewriteRule    ^foo\.html$  bar.html  [R]

브라우저에 따른 내용

상황설명:

최소한 중요한 최상위 페이지는 브라우저에 최적화된 내용으로 서비스해야할 경우가 있다. 즉, 최신 Netscape 브라우저에게는 최상의 버전을, Lynx 브라우저에게는 최저 버전을, 나머지 브라우저에는 평균적인 버전을 제공한다.

해결책:

브라우저가 내용협상을 위해 자신의 종류에 대한 정보를 제공하지 않기때문에 내용협상을 사용할 수 없다. 대신 HTTP "User-Agent" 헤더를 사용한다. 다음 규칙은 HTTP "User-Agent" 헤더가 "Mozilla/3"으로 시작하면 foo.html 페이지를 foo.NS.html로 재작성하고 재작성을 중단한다. 브라우저가 "Lynx"나 "Mozilla" 버전 1 혹은 2라면 URL은 foo.20.html이 된다. 나머지 브라우저는 foo.32.html 페이지를 받는다. 아래 규칙이 이 작업을 한다:

RewriteCond %{HTTP_USER_AGENT}  ^Mozilla/3.*
RewriteRule ^foo\.html$         foo.NS.html          [L]

RewriteCond %{HTTP_USER_AGENT}  ^Lynx/.*         [OR]
RewriteCond %{HTTP_USER_AGENT}  ^Mozilla/[12].*
RewriteRule ^foo\.html$         foo.20.html          [L]

RewriteRule ^foo\.html$         foo.32.html          [L]

동적 미러

상황설명:

외부 호스트에 우리 사이트로 가져오고 싶은 좋은 웹페이지가 있다고 가정하자. FTP 서버의 경우 직접 외부 자료의 최신복사본을 유지하는 mirror 프로그램을 사용할 수 있고, 웹서버라면 HTTP로 비슷한 작업을 하는 webcopy 프로그램을 사용할 수 있다. 그러나 두 방법 모두 단점이 있다: 복사본은 가끔씩 프로그램을 실행해줄 때만 최신판으로 유지된다. 직접 구성해야하는 정적인 미러가 아니라면 좋겠다. 대신 (외부 호스트에서 자료가 갱신되면) 필요할때 자동으로 자료를 갱신하는 동적 미러가 필요하다.

해결책:

이를 위해 Proxy Throughput 기능을 (플래그 [P]) 사용하여 외부 웹페이지 혹은 외부 웹공간 전체를 우리 이름공간으로 대응한다:

RewriteEngine  on
RewriteBase    /~quux/
RewriteRule    ^hotsheet/(.*)$  http://www.tstimpreso.com/hotsheet/$1  [P]
RewriteEngine  on
RewriteBase    /~quux/
RewriteRule    ^usa-news\.html$   http://www.quux-corp.com/news/index.html  [P]

동적 역미러

상황설명:
...
해결책:
RewriteEngine on
RewriteCond   /mirror/of/remotesite/$1           -U
RewriteRule   ^http://www\.remotesite\.com/(.*)$ /mirror/of/remotesite/$1

없는 자료를 인트라넷에서 가져오기

상황설명:

실제 자료를 방화벽이 보호하는 (내부) 인트라넷 웹서버에 (www2.quux-corp.dom) 저장하면서, 기업의 (외부) 인터넷 웹서버를 (www.quux-corp.dom) 실행하는 것처럼 보이게 한다. 외부 웹서버는 요청한 자료를 내부 웹서버에서 가져온다.

해결책:

먼저 방화벽이 내부 웹서버를 보호하고 외부 웹서버만이 내부 웹서버에서 자료를 얻을 수 있게 한다. 다음과 같이 패킷필터링 방화벽을 설정한다:

ALLOW Host www.quux-corp.dom Port >1024 --> Host www2.quux-corp.dom Port 80
DENY  Host *                 Port *     --> Host www2.quux-corp.dom Port 80

실제 설정문법에 알맞게 고쳐라. 없는 자료를 내부적으로 proxy throughput 기능을 통해 요청하는 mod_rewrite 규칙을 작성한다:

RewriteRule ^/~([^/]+)/?(.*)          /home/$1/.www/$2
RewriteCond %{REQUEST_FILENAME}       !-f
RewriteCond %{REQUEST_FILENAME}       !-d
RewriteRule ^/home/([^/]+)/.www/?(.*) http://www2.quux-corp.dom/~$1/pub/$2 [P]

로드밸런싱 (부하 분산하기)

상황설명:

www.foo.com의 통신량을 www[0-5].foo.com (총 서버 6대)으로 분산하고 싶다. 어떻게 하는가?

해결책:

매우 다양한 방법으로 이 문제를 해결할 수 있다. 먼저 DNS를 사용한 잘 알려진 방법을 설명하고, mod_rewrite를 사용하는 경우를 살펴보자:

  1. DNS Round-Robin

    가장 간단한 로드밸런싱 방법은 BIND의 DNS round-robin 방식을 사용하는 것이다. 다음과 같이 DNS A(address) 레코드에 www[0-9].foo.com을 설정한다.

    www0   IN  A       1.2.3.1
    www1   IN  A       1.2.3.2
    www2   IN  A       1.2.3.3
    www3   IN  A       1.2.3.4
    www4   IN  A       1.2.3.5
    www5   IN  A       1.2.3.6
    

    그리고 다음 항목을 추가한다:

    www    IN  CNAME   www0.foo.com.
           IN  CNAME   www1.foo.com.
           IN  CNAME   www2.foo.com.
           IN  CNAME   www3.foo.com.
           IN  CNAME   www4.foo.com.
           IN  CNAME   www5.foo.com.
           IN  CNAME   www6.foo.com.
    

    잘못된 것처럼 보이지만, 실제로 BIND의 의도된 기능이다. 이제 www.foo.com을 찾으면, BIND는 매번 순서를 조금씩 바꿔가며 www0-www6을 반환한다. 그래서 클라이언트들을 여러 서버로 분산한다. 그러나 DNS 검색 결과가 네트웍의 다른 네임서버에 캐쉬되여 www.foo.com을 찾은 결과가 특정 wwwN.foo.com이면 클라이언트의 다음 요청들도 같은 wwwN.foo.com으로 보내지기때문에 완벽한 로드밸런싱 기법이 아님을 주의하라. 그러나 크게 보면 요청이 여러 웹서버에 분산되므로 효과가 좋다.

  2. DNS 로드밸런싱

    http://www.stanford.edu/~schemers/docs/lbnamed/lbnamed.html에 있는 lbnamed 프로그램을 사용하여 정교한 DNS기반 로드밸런싱을 할 수 있다. DNS가 실제 로드밸런싱을 하도록 만드는 여러 도구와 Perl 5 프로그램이다.

  3. Proxy Throughput Round-Robin

    이 방법은 mod_rewrite와 proxy throughput 기능을 사용한다. 먼저 DNS에 다음 항목을 사용하여 www0.foo.com이 실제 www.foo.com을 전담하게 한다

    www    IN  CNAME   www0.foo.com.
    

    그리고 www0.foo.com을 프록시전용 서버로 변경한다. 즉, URL을 받으면 서버는 내부 프록시를 통해 다른 5대 서버중 (www1-www5) 한대로 보내기만 한다. 이를 위해 먼저 모든 URL을 로드밸런싱 스크립트 lb.pl로 보내는 규칙을 만든다.

    RewriteEngine on
    RewriteMap    lb      prg:/path/to/lb.pl
    RewriteRule   ^/(.+)$ ${lb:$1}           [P,L]
    

    lb.pl을 작성한다:

    #!/path/to/perl
    ##
    ##  lb.pl -- 로드밸런싱 스크립트
    ##
    
    $| = 1;
    
    $name   = "www";     # 기본 호스트명
    $first  = 1;         # 첫번째 서버 (자신이 0이기 때문에, 0을 사용하지 않는다)
    $last   = 5;         # round-robin에서 마지막 서버
    $domain = "foo.dom"; # 도메인명
    
    $cnt = 0;
    while (<STDIN>) {
        $cnt = (($cnt+1) % ($last+1-$first));
        $server = sprintf("%s%d.%s", $name, $cnt+$first, $domain);
        print "http://$server/$_";
    }
    
    ##EOF##
    
    마지막 주의: 왜 이 방법이 유용한가? www0.foo.com에 부담이 가지않는가? 물론, 부담이 된다. 그러나 단순한 proxy throughput 요청만 하기때문에 괜찮다! 모든 SSI, CGI, ePerl 등은 전적으로 다른 서버가 처리한다. 이것이 핵심이다.
  4. 하드웨어/TCP Round-Robin

    하드웨어를 사용한 해결책도 있다. Cisco는 TCP/IP 수준에서 로드밸런싱을 하는 LocalDirector라는 괴물을 판다. 실제로는 웹서버군 앞단에 위치하는 일종의 회로수준 게이트웨이다. 자금이 충분하고 고성능 해결책이 필요하다면 이것을 사용하라.

새로운 MIME-type, 새로운 서비스

상황설명:

네트웍에는 멋진 CGI 프로그램들이 많다. 그러나 사용하기 번거러워서 많은 웹관리자가 사용하지 않는다. 아파치의 MIME-type에 따른 Action 핸들러 기능도 CGI 프로그램이 특별한 URL을 (정확히 PATH_INFOQUERY_STRINGS) 프로그램의 입력으로 사용하지 않을 때만 적절하다. 먼저, 확장자가 (secure CGI를 줄여) .scgi인 파일을 유명한 cgiwrap 프로그램으로 처리하기위해 새로운 type을 설정한다. 문제는 (위에서 본) 일관된 URL 구조를 사용하는 경우 사용자 홈디렉토리가 /u/user/foo/bar.scgi같은 URL인 점이다. cgiwrap/~user/foo/bar.scgi/ 형식의 URL을 원하기때문이다. 다음 규칙이 문제를 해결한다:

RewriteRule ^/[uge]/([^/]+)/\.www/(.+)\.scgi(.*) ...
... /internal/cgi/user/cgiwrap/~$1/$2.scgi$3  [NS,T=application/x-http-cgi]

이제 다른 멋진 프로그램, (URL 하위트리에 대한 access.log를 출력하는) wwwlog와 (URL 하위트리에 Glimpse를 실행하는) wwwidx가 있다고 가정하자. 우리는 프로그램에게 작업할 대상인 URL 영역을 알려줘야 한다. 그러나 요청할때마다 항상 적어줘야 하기때문에 깔끔하지 않다. 즉, 보통 /u/user/foo/에 대해 swwidx 프로그램을 실행한다면 다음과 같은 링크를 사용한다

/internal/cgi/user/swwidx?i=/u/user/foo/

깔끔하지 않다. 링크에 영역의 위치 CGI 위치를 모두 적어야 하기때문이다. 영역을 재구성한다면 여러 하이퍼링크를 수정하는데 많은 시간이 걸릴 것이다.

해결책:

해결책은 자동으로 적절한 CGI를 실행하는 새로운 특별한 URL 형식을 만드는 것이다. 다음과 같이 설정한다:

RewriteRule   ^/([uge])/([^/]+)(/?.*)/\*  /internal/cgi/user/wwwidx?i=/$1/$2$3/
RewriteRule   ^/([uge])/([^/]+)(/?.*):log /internal/cgi/user/wwwlog?f=/$1/$2$3

이제 /u/user/foo/을 검색하는 링크는 다음과 같다

HREF="*"
/u/user/foo/* (???)

내부적으로 다음과 같이 자동변환된다

/internal/cgi/user/wwwidx?i=/u/user/foo/

같은 방법으로 링크 뒤에 :log를 사용하여 접근 로그 CGI 프로그램을 실행할 수 있다.

정적에서 동적으로

상황설명:

어떻게 브라우저와 사용자가 모르게 자연스럽게 정적 페이지 foo.html을 동적인 foo.cgi로 변경할 수 있나.

해결책:

URL을 CGI 스크립트로 재작성하고, MIME-type을 수정하여 CGI 스크립트로 실행하게 한다. 그래서 /~quux/foo.html를 요청하면 내부적으로 /~quux/foo.cgi를 실행하게 된다.

RewriteEngine  on
RewriteBase    /~quux/
RewriteRule    ^foo\.html$  foo.cgi  [T=application/x-httpd-cgi]

즉석 컨텐츠 재생성

상황설명:

이 방법은 실로 비기이다: 동적으로 페이지를 생성하지만, 정적으로 페이지를 서비스한다. 즉, 페이지는 순수하게 (파일시스템에서 읽은 내용을 그대로) 정적 페이지로 전달되지만, 없을 경우 웹서버가 동적으로 생성한다. 그러면 누가 (혹은 cron 작업이) 정적 컨텐츠를 지우지않는 한 CGI가 생성한 페이지를 정적으로 서비스한다. 컨텐츠를 지우면 내용을 갱신한다.

해결책:
다음 규칙을 사용한다:
RewriteCond %{REQUEST_FILENAME}   !-s
RewriteRule ^page\.html$          page.cgi   [T=application/x-httpd-cgi,L]

여기서 page.html를 요청할때 page.html이 없거나 파일크기가 0인 경우 내부적으로 page.cgi를 실행한다. 여기서 비결은 page.cgi가 일반적인 CGI 스크립트와 같이 STDOUT에 출력하고, 추가로 출력을 page.html 파일에 적는다. 한번 실행한후 서버는 page.html의 정보를 보낸다. 웹관리자가 강재로 내용을 갱신하고 싶다면, (보통 cron 작업이) page.html을 지우기만 하면 된다.

자동으로 새로 고침하는 문서

상황설명:

복잡한 웹페이지를 만들때 편집자가 내용을 수정할 때마다 자동으로 페이지를 새로 고침하는 웹브라우저가 있으면 얼마나 좋을까? 불가능한가?

해결책:

가능하다! MIME multipart 기능과 웹서버 NPH 기능, mod_rewrite의 URL 조작 능력을 결합하면 된다. 먼저, 새로운 URL 기능을 만든다: URL에 :refresh를 추가하기만 하면 파일시스템에서 수정될 때마다 새로 고침한다.

RewriteRule   ^(/[uge]/[^/]+/?.*):refresh  /internal/cgi/apache/nph-refresh?f=$1

이제 다음 URL에 접근하면

/u/foo/bar/page.html:refresh

다음 URL을 내부적으로 부른다

/internal/cgi/apache/nph-refresh?f=/u/foo/bar/page.html

이제 NPH-CGI 스크립트만 남았다. 보통 "독자에게 연습으로 남겨둠"이라고 말하지만 ;-) 나는 이것도 제공한다.

#!/sw/bin/perl
##
##  nph-refresh -- NPH/CGI script for auto refreshing pages
##  Copyright (c) 1997 Ralf S. Engelschall, All Rights Reserved.
##
$| = 1;

#   split the QUERY_STRING variable
@pairs = split(/&/, $ENV{'QUERY_STRING'});
foreach $pair (@pairs) {
    ($name, $value) = split(/=/, $pair);
    $name =~ tr/A-Z/a-z/;
    $name = 'QS_' . $name;
    $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
    eval "\$$name = \"$value\"";
}
$QS_s = 1 if ($QS_s eq '');
$QS_n = 3600 if ($QS_n eq '');
if ($QS_f eq '') {
    print "HTTP/1.0 200 OK\n";
    print "Content-type: text/html\n\n";
    print "&lt;b&gt;ERROR&lt;/b&gt;: No file given\n";
    exit(0);
}
if (! -f $QS_f) {
    print "HTTP/1.0 200 OK\n";
    print "Content-type: text/html\n\n";
    print "&lt;b&gt;ERROR&lt;/b&gt;: File $QS_f not found\n";
    exit(0);
}

sub print_http_headers_multipart_begin {
    print "HTTP/1.0 200 OK\n";
    $bound = "ThisRandomString12345";
    print "Content-type: multipart/x-mixed-replace;boundary=$bound\n";
    &print_http_headers_multipart_next;
}

sub print_http_headers_multipart_next {
    print "\n--$bound\n";
}

sub print_http_headers_multipart_end {
    print "\n--$bound--\n";
}

sub displayhtml {
    local($buffer) = @_;
    $len = length($buffer);
    print "Content-type: text/html\n";
    print "Content-length: $len\n\n";
    print $buffer;
}

sub readfile {
    local($file) = @_;
    local(*FP, $size, $buffer, $bytes);
    ($x, $x, $x, $x, $x, $x, $x, $size) = stat($file);
    $size = sprintf("%d", $size);
    open(FP, "&lt;$file");
    $bytes = sysread(FP, $buffer, $size);
    close(FP);
    return $buffer;
}

$buffer = &readfile($QS_f);
&print_http_headers_multipart_begin;
&displayhtml($buffer);

sub mystat {
    local($file) = $_[0];
    local($time);

    ($x, $x, $x, $x, $x, $x, $x, $x, $x, $mtime) = stat($file);
    return $mtime;
}

$mtimeL = &mystat($QS_f);
$mtime = $mtime;
for ($n = 0; $n &lt; $QS_n; $n++) {
    while (1) {
        $mtime = &mystat($QS_f);
        if ($mtime ne $mtimeL) {
            $mtimeL = $mtime;
            sleep(2);
            $buffer = &readfile($QS_f);
            &print_http_headers_multipart_next;
            &displayhtml($buffer);
            sleep(5);
            $mtimeL = &mystat($QS_f);
            last;
        }
        sleep($QS_s);
    }
}

&print_http_headers_multipart_end;

exit(0);

##EOF##

대량의 가상호스트

상황설명:

가상호스트가 몇개만 있다면 아파치의 <VirtualHost> 기능이 잘 동작한다. 그러나 가상호스트가 수백개 있는 ISP라면 이 기능이 최선은 아니다.

해결책:

이 기능을 제공하려면 Proxy Throughput 기능을 (플래그 [P]) 사용하여 외부 웹페이지 혹은 전체 외부 웹영역을 우리의 이름공간에 대응한다:

##
##  vhost.map
##
www.vhost1.dom:80  /path/to/docroot/vhost1
www.vhost2.dom:80  /path/to/docroot/vhost2
     :
www.vhostN.dom:80  /path/to/docroot/vhostN
##
##  httpd.conf
##
    :
#   리다이렉트할때 정규 호스트명을 사용한다.
UseCanonicalName on

    :
#   가상호스트를 CLF 형식 앞에 추가한다
CustomLog  /path/to/access_log  "%{VHOST}e %h %l %u %t \"%r\" %>s %b"
    :

#   주서버에서 재작성 엔진을 사용한다
RewriteEngine on

#   두 맵을 정의한다: 하나는 URL을 고치고,
#   다른 하나는 가상호스트별 DocumentRoot를
#   정의한다.
RewriteMap    lowercase    int:tolower
RewriteMap    vhost        txt:/path/to/vhost.map

#   이제 크고 복잡한 규칙 한개를 사용하여
#   가상호스트로 대응한다.
#
#   1. 가상호스트들이 같이 사용하는 위치는 대응하지 않는다
RewriteCond   %{REQUEST_URI}  !^/commonurl1/.*
RewriteCond   %{REQUEST_URI}  !^/commonurl2/.*
    :
RewriteCond   %{REQUEST_URI}  !^/commonurlN/.*
#
#   2. 우리가 현재 사용하는 방법이 Host 헤더를
#      가상호스트를 지원하므로
#      Host 헤더가 있는지 확인한다
RewriteCond   %{HTTP_HOST}  !^$
#
#   3. 호스트명을 소문자로 만든다
RewriteCond   ${lowercase:%{HTTP_HOST}|NONE}  ^(.+)$
#
#   4. vhost.map에서 호스트명을 찾고
#      경로일때만 기억한다
#      (위에서 "NONE"은 아니다)
RewriteCond   ${vhost:%1}  ^(/.*)$
#
#   5. 마지막으로 URL을 문서 위치로 대응하고
#      로그에 남기기위해 가상호스트를 기억해 둔다
RewriteRule   ^/(.*)$   %1/$1  [E=VHOST:${lowercase:%{HTTP_HOST}}]
    :
top

접근 제한

로봇 막기

상황설명:

어떻게 하면 특정 웹공간의 페이지를 긁어모으는 귀찮은 로봇을 막을 수 있나? "Robot Exclusion Protocol" 항목을 저장한 /robots.txt 파일은 보통 이런 로봇을 막는데 충분하지 않다.

해결책:

(아마도 디렉토리가 깊어서 로봇이 돌아다니면 서버에 부담이 큰 경우) 웹공간 /~quux/foo/arc/에 있는 URL들을 거부하는 규칙을 사용한다. 우리는 특정 로봇의 접근을 막아야 한다. 즉, 로봇을 실행하는 호스트를 막는 것으로는 불충분하며, 그 호스트의 사용자도 막아버리게 된다. User-Agent HTTP 헤더 정보도 비교한다.

RewriteCond %{HTTP_USER_AGENT}   ^NameOfBadRobot.*
RewriteCond %{REMOTE_ADDR}       ^123\.45\.67\.[8-9]$
RewriteRule ^/~quux/foo/arc/.+   -   [F]

그림 퍼가기 방지

상황설명:

http://www.quux-corp.de/~quux/에 있는 페이지들이 GIF 그림을 포함한다고 가정하자. 이 그림이 멋있어서, 다른 사람들이 자신의 페이지에 직접 링크를 건다. 서버에 불필요한 부담이 되므로 막고 싶다.

해결책:

그림을 100% 보호할 수는 없지만, 최소한 브라우저가 HTTP Referer 헤더를 보내는 경우 제한할 수 있다.

RewriteCond %{HTTP_REFERER} !^$
RewriteCond %{HTTP_REFERER} !^http://www.quux-corp.de/~quux/.*$ [NC]
RewriteRule .*\.gif$        -                                    [F]
RewriteCond %{HTTP_REFERER}         !^$
RewriteCond %{HTTP_REFERER}         !.*/foo-with-gif\.html$
RewriteRule ^inlined-in-foo\.gif$   -                        [F]

호스트 거부

상황설명:

어떻게 외부에서 서버에 접근할 수 없는 호스트 목록을 설정할 수 있나?

해결책:

아파치 >= 1.3b6에서:

RewriteEngine on
RewriteMap    hosts-deny  txt:/path/to/hosts.deny
RewriteCond   ${hosts-deny:%{REMOTE_HOST}|NOT-FOUND} !=NOT-FOUND [OR]
RewriteCond   ${hosts-deny:%{REMOTE_ADDR}|NOT-FOUND} !=NOT-FOUND
RewriteRule   ^/.*  -  [F]

아파치 <= 1.3b6에서:

RewriteEngine on
RewriteMap    hosts-deny  txt:/path/to/hosts.deny
RewriteRule   ^/(.*)$ ${hosts-deny:%{REMOTE_HOST}|NOT-FOUND}/$1
RewriteRule   !^NOT-FOUND/.* - [F]
RewriteRule   ^NOT-FOUND/(.*)$ ${hosts-deny:%{REMOTE_ADDR}|NOT-FOUND}/$1
RewriteRule   !^NOT-FOUND/.* - [F]
RewriteRule   ^NOT-FOUND/(.*)$ /$1
##
##  hosts.deny
##
##  주의! 이것은 목록처럼 보이지만 목록이 아니라 맵이다.
##        mod_rewrite는 이 정보를 키/값 쌍으로 해석하기때문에,
##        각 항목의 값 자리에 최소한 "-"가 필요하다.
##

193.102.180.41 -
bsdti1.sdm.de  -
192.76.162.40  -

프록시 거부

상황설명:

어떻게 특정 호스트 혹은 특정 호스트의 사용자가 아파치 프록시를 사용할 수 없도록 하나?

해결책:

먼저 아파치 웹서버를 컴파일할때 구성파일에서 mod_rewritemod_proxy 아래에(!) 있어야 한다. 그러면 mod_rewritemod_proxy 이전에 불린다. 이제 다음과 같이 특정 호스트를 거부하도록 설정한다...

RewriteCond %{REMOTE_HOST} ^badhost\.mydomain\.com$
RewriteRule !^http://[^/.]\.mydomain.com.*  - [F]

...그리고 다음은 user@host에 따라 거부한다:

RewriteCond %{REMOTE_IDENT}@%{REMOTE_HOST}  ^badguy@badhost\.mydomain\.com$
RewriteRule !^http://[^/.]\.mydomain.com.*  - [F]

특별한 인증 방식

상활설명:

가끔 매우 특별한 인증이 필요할 때가 있다. 예를 들어, 미리 설정해둔 사용자인지 검사한다. 이들에게만 (mod_auth의 Basic Auth를 사용한 경우와 달리) 별다른 물음없이 접근을 허용한다.

해결책:

친구만 접근이 가능하도록 재작성 규칙들을 사용한다:

RewriteCond %{REMOTE_IDENT}@%{REMOTE_HOST} !^[email protected]\.com$
RewriteCond %{REMOTE_IDENT}@%{REMOTE_HOST} !^friend2@client2.quux-corp\.com$
RewriteCond %{REMOTE_IDENT}@%{REMOTE_HOST} !^friend3@client3.quux-corp\.com$
RewriteRule ^/~quux/only-for-friends/      -                                 [F]

Referer기반 변환기(deflector)

상황설명:

"Referer" HTTP 헤더에 따라 원하는대로 참조페이지를 설정할 수 있는 유연한 URL 변환기를 만들 수 있는가?

해결책:

다음과 같이 복잡한 규칙을...

RewriteMap  deflector txt:/path/to/deflector.map

RewriteCond %{HTTP_REFERER} !=""
RewriteCond ${deflector:%{HTTP_REFERER}} ^-$
RewriteRule ^.* %{HTTP_REFERER} [R,L]

RewriteCond %{HTTP_REFERER} !=""
RewriteCond ${deflector:%{HTTP_REFERER}|NOT-FOUND} !=NOT-FOUND
RewriteRule ^.* ${deflector:%{HTTP_REFERER}} [R,L]

... 재작성 맵과 같이 사용한다:

##
##  deflector.map
##

http://www.badguys.com/bad/index.html    -
http://www.badguys.com/bad/index2.html   -
http://www.badguys.com/bad/index3.html   http://somewhere.com/

그러면 요청을 자동으로 (맵에서 값으로 "-"를 사용한 경우) 참조페이지나 (URL이 맵에 있는 경우 두번째 아규먼트로) 특정 URL로 리다이렉션한다.

top

기타

외부 재작성 엔진

상황설명:

FAQ: 어떻게 이런저런 잡다한 문제를 풀 수 있는가? mod_rewrite로는 해결책이 안보인다...

해결책:

외부 RewriteMap을 사용하라. 즉, 프로그램이 RewriteMap 역할을 한다. 프로그램은 아파치가 시작할때 시작하여 STDIN에서 요청한 URL을 받고, (같은 순서로!) 결과 (보통 재작성된) URL을 STDOUT에 출력한다.

RewriteEngine on
RewriteMap    quux-map       prg:/path/to/map.quux.pl
RewriteRule   ^/~quux/(.*)$  /~quux/${quux-map:$1}
#!/path/to/perl

#   아파치 서버가 멈추지 않도록
#   입출력 버퍼를 사용하지 않는다
$| = 1;

#   stdin에서 한줄씩 URL을 읽고
#   stdout에 변환한 URL을 출력한다
while (<>) {
    s|^foo/|bar/|;
    print $_;
}

설명하기위해 모든 /~quux/foo/... URL을 /~quux/bar/...로 재작성하는 스크립트를 예로 들었다. 실제로 마음대로 프로그래밍할 수 있다. 그러나 일반 사용자가 이런 맵을 사용할 수 있다고 하더라고, 오직 시스템 관리자만이 맵을 정의해야 함을 주의하라.