最後更新: 2024-02-05
目錄
- mod_alias
-
mod_rewrite
- RewriteCond
- RewriteRule - Rewrite Log
- Rewrite Example
- rewrite 內的 AND 與 OR
-
Rewrite 進階
- RewriteBase - 將 URL 轉細階
- Secret Header
- RewriteMap
介紹
mod_alias directives (Redirect, RedirectTemp, RedirectPermanent, RedirectMatch)
mod_alias ALWAYS takes precedence over mod_rewrite. (in the same context)
mod_rewrite directives (RewriteEngine, RewriteOptions, RewriteBase, RewriteCond, RewriteRule),
Aliases and Redirects are processed in the order they appear in the configuration files (first win)
Load module
Apache/2.4
LoadModule rewrite_module modules/mod_rewrite.so
mod_alias
mod_alias provides directives
- Alias (server config, virtual host)
- AliasMatch
- Redirect (server config, virtual host, directory, .htaccess)
- RedirectMatch
alias 的功能
存取的文件夾可以不在 DocumentRoot 內 !!
(例子: Alias /phpmyadmin /usr/share/phpmyadmin)
alias syntax
Alias URL-path Directory-path
當有多個 Alias 設定時, First Win 原則
Alias /foo/bar /baz Alias /foo /gaq
P.S.
- Redirects > Aliases
- First match taking precedence
- /icons/ 與 /icons 是有所不同
Folder permission
當 alias 後的 folder 不在 DocumentRoot 時, 那一定要加下以下設定, 給 apache permission access
<Directory /usr/share/phpmyadmin> Order allow,deny Allow from all </Directory>
注意
Setting 1
Alias /phpmyadmin /usr/share/phpmyadmin
"/phpmyadmin" 與以下設定係有所不同的 !! "https://domain/phpmyadmin" 不會被 map
Alias /phpmyadmin/ /usr/share/phpmyadmin
Setting 2
Alias /phpmyadmin/ /usr/share/phpmyadmin
此外它們都是不同
Alias /phpmyadmin/ /usr/share/phpmyadmin/
Redirect
asking the client to refetch the resource at the new location
Usage
Redirect [status] URL-path URL
- 301 (permanent)
- 302 (temporary) <-- Default
- 303 (seeother)
- 410 (gone) indicating that the resource has been permanently removed.
* URL-path is a case-sensitive, path beginning with a slash, relative path is not allowed
* URL should be an absolute URL beginning with a scheme
Example
[1] http://datahunter.org/zh/ 會去 http://hk.yahoo.com/zh/
Redirect /zh/ https://hk.yahoo.com
[2] http://datahunter.org/* 會去 http://hk.yahoo.com
Redirect / https://hk.yahoo.com
應用
<VirtualHost *:80> ServerName www.example.com Redirect / https://www.example.com/ </VirtualHost>
Remark
如果唔想 keep "zh" 那就要
RewriteEngine On RewriteRule ^(.*)$ http://hk.yahoo.com [R=301,L]
RedirectMatch
Usage:
RedirectMatch [status] regex URL
會有 url 後面那段
RedirectMatch "(.*)\.gif$" "http://other.example.com$1.jpg"
vhost override global alias
<VirtualHost _default_:8080> RewriteEngine On RewriteCond %{REQUEST_URI} ^/pma/ [OR,NC] RewriteCond %{REQUEST_URI} ^/phpMyAdmin/ [OR,NC] RewriteCond %{REQUEST_URI} ^/phpmyadmin/ [OR,NC] RewriteCond %{REQUEST_URI} ^/awstats/ [OR,NC] RewriteCond %{REQUEST_URI} ^/roundcubemail/ [OR,NC] RewriteCond %{REQUEST_URI} ^/webmail/ RewriteRule ^.*$ - [F] </VirtualHost>
SSL verify bypass redirect.
RewriteEngine On
RewriteCond %{REQUEST_URI} !^/.well-known/pki-validation/
RewriteRule .* https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
應用: Global 的 /.well-known/
<DirectoryMatch "\/\.(?!well-known)">
Require all denied
</DirectoryMatch>
Alias /.well-known/ /var/www/well-known/
<Directory "/var/www/well-known">
Options FollowSymLinks
AllowOverride None
Require all granted
</Directory>
mod_rewrite - Rewrite requests internally
* Options FollowSymLinks or SymLinksIfOwnerMatch is off which implies that RewriteRule directive is forbidden
By default, mod_rewrite maps a URL to a filesystem path.
However, it can also be used to redirect one URL to another URL,
or to invoke an internal proxy fetch.
mod_rewrite operates on the full URL path, including the path-info section(query string).
以下是 drupal 的一個例子
<IfModule mod_rewrite.c> RewriteEngine on RewriteBase / # Rewrite current-style URLs of the form 'index.php?q=x'. RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^(.*)$ index.php?q=$1 [L,QSA] </IfModule>
RewriteCond TestString CondPattern [Flags]
Flags:
[OR], [AND]
* 如果沒有[Flags], 則每 RewriteCond 之間隱含的 "AND" 關係, 不過每條 Rule 都係 One by One 地行
TestString:
Grouped parts
%1 to %9 provide access to the grouped parts
Server-Variables
%{NAME_OF_VARIABLE}
分別有: HTTP headers, connection & request, server internals, specials
Request
%{REQUEST_FILENAME} # The full local filesystem path to the file (/home/vhost/domain
/web/file.ext)
%{REQUEST_URI} # /path/to/file.txt <-- 相對於 DocumentRoot 的 Path
* 它是 "INTERNAL REDIRECT" 後的 Path 來
%{THE_REQUEST} # The full HTTP request line sent by the browser to the server
(e.g., "GET /index.html HTTP/1.1")
This value has not been unescaped (decoded)
%{SCRIPT_FILENAME} # /home/vhost/datahunter.org/web/index.php
HTTP Header
%{HTTP_HOST} # datahunter.org
Connection
%{HTTPS} # text "on" if the connection is using SSL/TLS
%{REMOTE_ADDR} # 1.1.1.1
%{REQUEST_METHOD}
Server Internals
%{SERVER_PORT} 80
%{SERVER_PROTOCOL}
%{SERVER_NAME}
Environment variable
%{ENV:VariableName}
自定 Header
%{HTTP:XXXX} # ie. MySecret: xxxx
CondPattern:
* CondPattern is a perl compatible regular expression with some additions
- ! Not
- -f 測試此文件存不存在
- -d 測試此目錄存不存在
- -s regular file
- -l symbolic link
RewriteRule:
RewriteRule Pattern target [Flag1,Flag2,Flag3]
Pattern is a perl compatible regular expression.
RewriteFlags:
合併 Flag
RewriteRule .* - [ENV=test1:1] RewriteRule .* - [ENV=test2:1]
可以寫成
RewriteRule .* - [ENV=test1:1,ENV=test2:1]
[L] 最後一條 rule
L = terminates the current round of rewrite processing
looping 問題
情況
using RewriteRule in either .htaccess files or in <Directory> sections
即使用了 [L]
The rules have been processed, the rewritten request is handed back to the URL parsing engine to do what it may with it.
It is possible that as the rewritten request is handled, the .htaccess file or <Directory> section may be encountered again,
and thus the ruleset may be run again from the start.
RewriteBase "/" RewriteCond "%{REQUEST_URI}" "!=/index.php" RewriteRule "^(.*)" "/index.php?req=$1" [L,PT]
* The PT flag implies the L flag: rewriting will be stopped in order to pass the request to the next phase of processing.
RewriteCond 確保沒有 looping
[END]
It can be used to terminate not only the current round of rewrite processing
but prevent any subsequent rewrite processing from occurring in per-directory (htaccess) context.
This does not apply to new requests resulting from external redirects.
[R] 表示 redirect
# redirection status code, in the range 300-400, 302=MOVED TEMPORARILY RewriteRule (.*) http://other.example.com$1 [R=303]
[F] 表示 forbidden
[NC] nocase
[QSA]
QSA|qsappend # Query String Append
When the replacement URI contains a query string,
The default behavior of RewriteRule is to discard the existing query string, and replace it with the newly generated one.
Using the [QSA] flag causes the query strings to be combined.
Consider the following rule:
RewriteRule "/pages/(.+)" "/page.php?page=$1" [QSA]
A request for "/pages/123?one=two"
# With the [QSA] flag
/page.php?page=123&one=two
# Without the [QSA] flag
/page.php?page=123
* The existing query string(one=two) will be discarded
[PT] passthrough|PT
The target (or substitution string) in a RewriteRule is assumed to be a file path, by default.
The use of the [PT] flag causes it to be treated as a URI instead.
That is to say, the use of the [PT] flag causes the result of the RewriteRule to be passed back through URL mapping,
so that location-based mappings, such as Alias, Redirect, or ScriptAlias, for example, might have a chance to take effect.
i.e.
# Omission of the [PT] flag in this case will cause the Alias to be ignored,
# resulting in a 'File not found' error being returned.
Alias "/icons" "/usr/local/apache/icons" RewriteRule "/pics/(.+)\.jpg$" "/icons/$1.gif" [PT]
* The PT flag implies the L flag
rewriting will be stopped in order to pass the request to the next phase of processing.
* The PT flag is implied in per-directory contexts such as <Directory> sections or in .htaccess files.
[P]
P|proxy
using mod_proxy
# except those for the /images and /css directories,
# to be proxied through to another server
RewriteRule ^(/(images|css).*)$ http://other.server.com:90$1 [P]
# lets Apache httpd adjust the URL in the Location, Content-Location and URI headers on HTTP redirect responses.
# ensure that redirects returning from that server are correctly passed back to the client.
ProxyPassReverse / http://other.server.com:90/
T=, H=, E=
Flags that alter metadata associated with the request (T=, H=, E=) have no affect in per-directory and htaccess context,
when a substitution (other than '-') is performed during the same round of rewrite processing.
type|T=MIME-type
Sets the MIME type with which the resulting response will be sent.
This has the same effect as the AddType directive.
# Files with 'IMG' in the name are jpg images.
RewriteRule "IMG" "-" [T=image/jpg]
RewriteOptions
Context: server config, virtual host, directory, .htaccess
Rewrite Log
* RewriteLog 不可以用在 .htaccess 裡
# Apache 2.4 - LogLevel Directive
2.4 係沒有 RewriteLog 及 RewriteLogLevel 獨立設定. 它是用 LogLevel 做晒
Syntax: LogLevel [module:]level [module:level] # Default: LogLevel warn
rewrite logging of its actions at the trace1 ~ trace8
設定好後, 它會 log 在 ErrorLog 內
i.e.
LogLevel warn rewrite:trace2
# Apache 2.2
RewriteEngine on RewriteRule ^/test/(.+) http://hk.yahoo.com/$1 [R,L] RewriteLog "logs/rewritelog.txt" RewriteLogLevel 3
RewirteLogLevel Directive
Default: RewirteLogLevel 0 <= no log ( range: 0~4 )
rewrite 內的 AND 與 OR
情況 1: Rule 1 沒有 "[L]"
RewriteCond A RewriteRule 1 RewriteRule 2 // 等同於 if (Cond A) { Rule 1 } Rule 2
測試
RewriteEngine on RewriteRule .* - [E=MyTest:a] RewriteRule .* - [E=MyTest:b]
test.php
<?php var_dump(getenv("MyTest")); ?>
情況 2: Cond A 後暗藏了 [AND]
RewriteCond A RewriteCond B RewriteRule 1 // 等同於 if (Cond A && Cond B) { Rule1 }
情況 3: 註明了 [OR]
RewriteCond A [OR] RewriteCond B RewriteRule 1 // 等同於 if (Cond A || Cond B) { Rule 1 }
情況 4:
rewritecond A [OR] rewritecond B rewritecond C [OR] rewritecond D RewriteRule 1 // 等同於 if ( (A or B) and (C or D) ) { Rule 1 }
情況 5:
RewriteCond A [OR] RewriteCond B [OR] RewriteCond C RewriteCond D RewriteRule 1 // 等同於 if ( (A OR B OR C) AND D ) { Rule 1 }
Rewrite Example
禁止訪問某 URL
RewriteRule ^/cms "-" [F]
對 URL 限 IP access
RewriteEngine on RewriteCond %{REQUEST_URI} ^/admin RewriteCond %{REMOTE_ADDR} !^(a\.a\.a\.a|b\.b\.b\.b)$ RewriteRule .* - [F]
直跳入 sub-folder
# jump to cms RewriteEngine on # 當有 Folder 在上層要用時 RewriteRule ^/var/ - [L] RewriteRule ^/upload/ - [L] # 當不是去 cms 時就跳去 cms RewriteCond %{REQUEST_URI} !^/cms/ RewriteRule (.*) /cms/ [R=301,L]
http to https
RewriteEngine On RewriteCond %{HTTPS} off RewriteCond %{REMOTE_ADDR} !^192.168.123.200$ RewriteRule ^.*$ https://%{HTTP_HOST}%{REQUEST_URI} [R,L]
由於用了 "{REQUEST_URI}", 所以沒有加 "()" 去括 .*
www to non-www
# www to non-www RewriteEngine On RewriteCond %{HTTP_HOST} ^www\. [NC] RewriteRule ^.*$ http://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
The RewriteCond captures everything in the HTTP_HOST variable after the "www." and saves it in %1.
The RewriteRule captures the URL (sans leading "/") and saves it in $1.
non-www to www
# non-www to www RewriteEngine On RewriteCond %{HTTP_HOST} !^www\. RewriteRule ^.*$ http://www.%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
www + https
RewriteEngine On
RewriteCond %{HTTP_HOST} !^www\.
RewriteRule ^.*$ https://www.%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
RewriteCond %{HTTPS} off
RewriteRule ^.*$ https://%{HTTP_HOST}%{REQUEST_URI} [R,L]
mail. redirect to webmail
# redirect to webmail RewriteEngine On RewriteCond %{HTTP_HOST} mail\. [NC] RewriteCond %{REQUEST_URI} !^/webmail [NC] RewriteRule ^.*$ http://%{HTTP_HOST}/webmail [R=301,L]
Route page by Client IP
RewriteCond %{REMOTE_ADDR} ^x\.x\.x\.x$ [OR] RewriteCond %{REMOTE_ADDR} ^y\.y\.y\.y$ RewriteCond %{REQUEST_URI} !^sorry\.html RewriteRule .* /sorry.html
maintenance page
# Enable Rewrite Module RewriteEngine On RewriteBase / # Under Maintenance Page RewriteCond %{REMOTE_ADDR} !^x\.x\.x\.x$ RewriteCond %{REQUEST_URI} !=/favicon.ico RewriteCond %{REQUEST_URI} !=/Web_Maintenance.jpg RewriteRule .* maintenance.html [L,QSA] # Allow Admin IP RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_URI} !=/favicon.ico RewriteRule .* index.php [L,QSA]
* 不用 ^(.*)$
HTTP_HOST 與 SERVER_NAME 分別
The HTTP_HOST is obtained from the HTTP request header and this is what the client actually used as "target host" of the request.
The SERVER_NAME is defined in server config. Which one to use depends on what you need it for.
client-controlled value which may thus not be reliable for use in business logic and the other is a server-controlled value which is more reliable.
Bypass rewrite for re-new SSL
RewriteEngine on RewriteCond %{REQUEST_URI} ^/.well-known/pki-validation/ [NC] RewriteRule ^.*$ - [L]
用 rewrite Rule 將 sub-domain 轉頂
RewriteEngine on RewriteCond %{HTTP_HOST} ^devel.x.x$ [NC] RewriteCond %{REQUEST_URI} !^/webdav/ RewriteRule ^/(.*)$ /singtu/$1 [L]
Rewrite 進階
Example Configure
Alias /myapp /home/vhosts/mywebsite/myapp <VirtualHost *:80> DocumentRoot /home/vhosts/mywebsite/web ServerName 192.168.123.251 CustomLog "/dev/null" combined ErrorLog "/dev/null" <Directory /home/vhosts/mywebsite/web> Options +Indexes AllowOverride All </Directory> </VirtualHost>
當 alias 同 real folder 撞了
就算 "/home/vhosts/mywebsite/web/myapp" 存在, Alias 仍然是會 override 它的
http://192.168.123.251/myapp/ -> /home/vhosts/mywebsite/myapp
alias > rewrite
以下 .htaccess 不會有效
# /home/vhosts/mywebsite/myapp/.htaccess RewriteEngine On RewriteBase / RewriteRule myapp/index.html test.txt
* The prefix to be used for per-directory (.htaccess) RewriteRule directives.
* that substitute a relative path.
應用場景
This directive is required when you "use a relative path in a substitution in per-directory (.htaccess) context"
i.e.
# 準備
- echo root > index.html
- mkdir myapp
- echo welcome > myapp/welcome.html
- echo myapp > myapp/index.html
/home/vhosts/mywebsite/public_html/.htaccess
RewriteEngine On RewriteBase /myapp RewriteRule ^index\.html$ welcome.html
http://URL/index.html -> /home/vhosts/mywebsite/myapp/welcome.html
http://URL/myapp/index.html -> /home/vhosts/mywebsite/myapp/index.html
消失了的 icons folder
The icons folder conflicts with the default apache alias
* Apache's icons directory which cannot be overridden in an .htaccess file.
Alias /icons/ "./icons/" <Directory "./icons/">
RewriteRule on subdir
RewriteEngine On RewriteBase / # <-- default RewriteRule test/index.html test.txt
當 /home/vhosts/mywebsite/web/test/index.html 存在時,
Rewrite Rule 仍是優先的
i.e.
http://192.168.123.251/test/index.html -> /home/vhosts/mywebsite/web/test.txt
Override Global Alias
Alias /myapp /home/vhosts/mywebsite/myapp <VirtualHost *:80> Alias /myapp /home/vhosts/mywebsite/web/myapp DocumentRoot /home/vhosts/mywebsite/web .... </VirtualHost>
Website 升級 Page
Code
RewriteEngine on RewriteCond %{REQUEST_URI} !=/favicon.ico # 192.168.88.177 係 admin ip RewriteCond %{REMOTE_ADDR} !^192\.168\.88\.177$ RewriteCond %{REQUEST_URI} !=/underconstruction.html RewriteRule ^.*$ https://%{SERVER_NAME}/underconstruction.html [L,QSA]
多個 .htaccess
[Q]
There are also .htaccess files in subfolders and folders of subfolders, but I want this rewrite rule to work in all directories all over the account whether there exists a .htaccess or not,
without copying this rule in every single file. The rules in the existing .htaccess files should continue working.
[A]
You need to add the following line into each .htaccess in subfolders if you want to have rewrite rules from parent .htaccess to be executed as well:
RewriteOptions inherit
In per-directory context this means that conditions and rules of the parent directory's .htaccess configuration are inherited.
Rules inherited from the parent scope are applied after rules specified in the child scope.
# Sets some special options for the rewrite engine
RewriteOptions Options
fcgid http login
Code
<IfModule mod_fcgid.c> RewriteEngine on RewriteRule .* - [env=HTTP_AUTHORIZATION:%{HTTP:Authorization}] </IfModule>
!
NOT character ('!') is also available as a possible pattern prefix. This enables you to negate a pattern;
- (dash)
A dash indicates that no substitution should be performed
(the existing path is passed through untouched)
This is used when a flag (see below) needs to be applied without changing the path.
i.e. 用 L flag
# framework
RewriteBase /
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} !=/favicon.ico
RewriteRule . /index.html [L]
E=[!]ENV[:VAL]
Causes an environment variable NAME to be set (它沒有 value)
VAL 則設定 VAR 的值
The form !VAR causes the environment variable VAR to be unset.
With the [E], or [env] flag
[env=VAR:VAL] 縮寫: [E=VAR:VAL]
測試
RewriteRule .* - [E=MyTest:1234]
test.php
<? var_dump(getenv("MyTest")); ?>
Result
bool(false) string(0) "" string(4) "1234"
%{ENV:VAL}
where ENV can be any environment variable
VAL is ENV value
唔理打咩都飛去
RedirectMatch 301 "(.*)" http://www.x.com.hk
curl -I http://x.hk/tes
.well-known 目錄
# 不 rewrite 某目錄
RewriteRule ^.well-known - [L,NC]
Jump to sub-folder
假設有 Setting
RewriteEngine on RewriteCond %{REQUEST_URI} !=/favicon.ico RewriteCond %{REQUEST_URI} !=/ RewriteRule ^.*$ /cms [R=301,L]
======
用 "/cms" 不太好, 因為需要跳多一次
RewriteRule ^.*$ /cms [R=301,L]
curl -I http://datahunter.org
Location: http://datahunter.org/cms
curl -I http://datahunter.org/cms
Location: http://datahunter.org/cms/
======
最優化結果
RewriteEngine on RewriteCond %{REQUEST_URI} !=/favicon.ico RewriteCond %{REQUEST_URI} !^/cms/ RewriteRule ^.*$ /cms/ [R=301,L]
Rewrite subdirectory to root
RewriteEngine on RewriteBase / RewriteRule ^blog/(.*)$ /$1 [NC,L]
Per-directory Rewrites
To enable the rewrite engine in this context, you need to set "RewriteEngine On" and
"Options FollowSymLinks" must be enabled.
If your administrator has disabled override of FollowSymLinks for a user's directory, then you cannot use the rewrite engine.
This restriction is required for security reasons.
個別 Path 唔用 SSL
RewriteEngine on # Rule 1 RewriteCond %{HTTPS} off RewriteCond %{THE_REQUEST} "!GET /nic/" RewriteRule ^.*$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L] # Rule 2 RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_URI} !=/favicon.ico RewriteRule ^(.*)$ index.php [L,QSA]
唔用得 "%{REQUEST_URI}" 因為它只會中最終的 Path ("INTERNAL REDIRECT" 後的 Path)
RewriteCond %{REQUEST_URI} "!/nic/"
將 URL 轉細階
# All URL to lowercase & redirect RewriteMap lowercase int:tolower RewriteCond $1 [A-Z] RewriteRule ^/?(.*)$ /${lowercase:$1} [R=301,L]
$1 係 URL, 當 URL 有大寫時, 就將佢轉細寫, 叫 browse 再訪問過新的 URL.
Secret Header
簡易版: SecretHeader 不對直接 Forbidden
RewriteEngine On RewriteCond %{HTTP:MySecretHeader} !=12345 RewriteRule .* - [F]
Test with curl
curl -H "MySecret: 1234" URL
進階: 多組 password, whitelist IP, 就出 maintenance page
# 不對 PW AND 不中 IP AND 不中 URL 才行 Rule
RewriteEngine On
RewriteCond %{HTTP:MySecretHeader} !^(123456|abcdef)$
RewriteCond %{REMOTE_ADDR} !^(IP1|IP2|IP3)$
RewriteCond %{REQUEST_URI} !maintenance.html
RewriteRule .* /maintenance.html [R=302]
RewriteMap
Defines a mapping function for key-lookup
Context: server config, virtual host ( 不可以寫在 .htaccess )
RewriteMap MapName MapType:MapSource
MapType
- Standard Plain Text(txt)
- Randomized Plain Text(rnd)
- Internal Function(int)
- External Rewriting Program(prg)
ie.
[1] Internal Function
RewriteMap lc int:tolower RewriteCond %{DOCUMENT_ROOT}/directory/${lc:$1} -f [NC]
[2] Standard Plain Text
RewriteMap examplemap txt:/path/to/file/map.txt RewriteRule ^/ex/(.*) ${examplemap:$1}
Internal Function(MapType: int)
- toupper: Converts the key to all upper case.
- tolower: Converts the key to all lower case.
- escape: Translates special characters in the key to hex-encodings.
- unescape: Translates hex-encodings in the key back to special characters.
External Rewriting Program(MapType: prg)
...
P.S.
.htaccess
RewriteCond %{REQUEST_FILENAME} !-d [NC] RewriteCond %{REQUEST_FILENAME} !-f [NC]
log
... [warn] RewriteCond: NoCase option for non-regex pattern '-d' is not supported and will be ignored.