使用 .htaccess 将多个域动态指向单个域的子文件夹

Dynamically pointing multiple domains to a single domain's subfolder with .htaccess

在这个例子中,我管理 domain.com。在其中,我在 php 中有一个商店模板,可以动态加载所选商店:

  • domain.com/store1
  • domain.com/store2
  • domain.com/store3

下面的 .htaccess 正在生成漂亮的网址。后面真正加载的是:

  • domain.com/store/index.php?store=store1
  • domain.com/store/index.php?store=store2
  • domain.com/store/index.php?store=store3

每个商店都有内部链接,例如:

  • domain.com/store1/catalogue
  • domain.com/store1/catalogue/instruments
  • domain.com/store1/catalogue/instruments/guitars
  • domain.com/store1/electric-guitar/1337

它完全符合预期。这些是在 domain.com 中工作的 .htaccess 规则。每个 RewriteRule 中的 store 查询字符串(store1store2store3)确定正在加载哪个商店:

# permalinks
RewriteEngine on

# errors
ErrorDocument 404 /error.php?error=404

# ignore existing directories
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule .* - [L]

#################################################

# store
RewriteRule ^([0-9a-zA-Z-]{2,50})/?$ store/index.php?store= [QSA,L]

# store: catalogue
RewriteRule ^([0-9a-zA-Z-]{2,50})/catalogue/?$ store/catalogue.php?store= [QSA,L]
RewriteRule ^([0-9a-zA-Z-]{2,50})/catalogue/([0-9a-zA-Z-]{1,75})/?$ store/catalogue.php?store=&cat= [QSA,L]
RewriteRule ^([0-9a-zA-Z-]{2,50})/catalogue/([0-9a-zA-Z-]{1,75})/([0-9a-zA-Z-]{1,75})/?$ store/catalogue.php?store=&cat=&subcat= [QSA,L]

# store: products
RewriteRule ^([0-9a-zA-Z-]{2,50})/([0-9a-zA-Z-]{1,150})/([0-9]+)$ store/product.php?store=&slug=&id_product= [QSA,L]

现在,棘手的部分来了。一些客户希望使用他们自己的域,以便 store1.com/* 加载与 domain.com/store1/* 相同的内容(不使用重定向)。

示例:

  • store1.com => domain.com/store1/
  • store1.com/catalogue => domain.com/store1/catalogue
  • store1.com/catalogue/instruments => domain.com/store1/catalogue/instruments
  • store1.com/catalogue/instruments/guitars => domain.com/store1/catalogue/instruments/guitars
  • store1.com/electric-guitar/1337 => domain.com/store1/electric-guitar/1337

你懂的。

所有域(domain.comstore1.comstore2.comstore3.com)都配置在相同的 Apache Web 服务器环境中(使用虚拟主机)。

问题是:这可以在每个商店的域根路径中的 .htaccess 文件中动态实现,还是在虚拟主机 .conf 文件中实现?如果是,怎么办?

确保所有自定义“商店”域(例如 store1.comstore2.com 等)在 domain.com vHost 中定义为 ServerAlias 等解析到与 domain.com.

相同的位置

然后您可以重写自定义商店域的请求,在 URL 路径前加上 store-id(域名),然后使用您现有的规则不变。

例如,store1.com/catalogue/instruments 的请求在内部被重写/store1/catalogue/instruments,然后由现有规则照常处理。

以下指令应该 现有 # store 规则之前:

# Internally rewrite the request when a custom domain is used
#  - the URL-path is prefixed with the domain name
RewriteCond %{HTTP_HOST} !^(www\.)?domain\. [NC]
RewriteCond %{REQUEST_URI} !\.\w{2,4}$
RewriteCond %{HTTP_HOST} ^([^.]+)
RewriteCond %{REQUEST_URI}@%1 !^/([^/]+)/.*@
RewriteRule (.*) %1/

基本上就是这样,尽管您也可以决定实施规范重定向 - 见下文。

以上指令的解释:

  • 第一个 条件 简单地排除了对主要 domain.com 的请求(它应该已经有相关的 store-id 作为 URL 路径的前缀)。

  • %{REQUEST_URI} !\.\w{2,4}$ - 第二个条件避免重写对静态资源(图片、JS、CSS等)的请求。具体来说,它排除了任何以 - 看起来像 - 文件扩展名结尾的请求。

  • %{HTTP_HOST} ^([^.]+) - 第三个 条件 在 TLD 之前捕获请求的域名,之后可以使用 %1 反向引用访问并用于作为 URL 路径的前缀。这假设没有 www 子域(如评论中所述)。例如。给定 store1.comstore2.co.uk 的请求,分别捕获 store1store2

  • %{REQUEST_URI}@%1 !^/([^/]+)/.*@ - 第四个 condition 检查 URL-path 是否已经带有域名前缀(上面捕获的) ).这主要是为了保证重写的请求不会被再次重写,造成重写循环。这是使用内部反向引用 (</code>) 实现的,该反向引用将 URL 路径中的第一个路径段与先前捕获的域名 (<code>%1).

    进行比较
  • (.*) %1/ - 最后,请求在内部重写,在请求的 URL-path.

    前加上域名前缀

忽略现有文件(可能不需要)

# ignore existing directories
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule .* - [L]

您没有说明如何引用您的静态资源(图像、JS、CSS 等),但您可能需要修改此规则以匹配对现有文件的请求。 (尽管我在上面的规则中添加的条件可能已经足以排除这些请求。)例如:

# ignore existing directories or files
RewriteCond %{REQUEST_FILENAME} -d [OR]
RewriteCond %{REQUEST_FILENAME} -f
RewriteRule ^ - [L]

规范重定向(可选)

您还应该考虑 重定向 形式 store1.com/store1/catalogue/instruments 的任何直接请求返回规范 URL store1.com/catalogue/instruments - 如果这些 URL 永远是 exposed/discovered。 store1.com/store2/catalogue/instruments 形式的请求自然会导致 404,所以这不是问题。

例如,以下内容将紧跟在您现有规则中的 ErrorDocument 指令之后:

# Redirect to remove the "/store1" URL-prefix when the "store1.com" domain is requested.
# - Only applies to direct requests.
RewriteCond %{ENV:REDIRECT_STATUS} ^$
RewriteCond %{HTTP_HOST} !^domain\. [NC]
RewriteCond %{HTTP_HOST} ^([^.]+)
RewriteCond %{REQUEST_URI}@%1 ^/([^/]+)/.*@
RewriteRule ^(?:[^/]+)(/.*)  [R=301,L]

首先使用 302(临时)重定向进行测试,以避免潜在的缓存问题。

这基本上是上述重写的反向,但仅适用于直接请求。针对 REDIRECT_STATUS env var 的检查可确保仅处理来自客户端的直接请求,而不处理后来重写的重写请求。


总结

有了两个规则块...

# permalinks
RewriteEngine on

# errors
ErrorDocument 404 /error.php?error=404

# Redirect to remove the "/store1" URL-prefix when the "store1.com" domain is requested.
# - Only applies to direct requests.
RewriteCond %{ENV:REDIRECT_STATUS} ^$
RewriteCond %{HTTP_HOST} !^domain\. [NC]
RewriteCond %{HTTP_HOST} ^([^.]+)
RewriteCond %{REQUEST_URI}@%1 ^/([^/]+)/.*@
RewriteRule ^(?:[^/]+)(/.*)  [R=301,L]

# ignore existing directories
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]

# Internally rewrite the request when a custom domain is used
#  - the URL-path is prefixed with the domain name
RewriteCond %{HTTP_HOST} !^(www\.)?domain\. [NC]
RewriteCond %{REQUEST_URI} !\.\w{2,4}$
RewriteCond %{HTTP_HOST} ^([^.]+)
RewriteCond %{REQUEST_URI}@%1 !^/([^/]+)/.*@
RewriteRule (.*) %1/

#################################################

# store

:
: existing directives follow
: