Mod Rewrite Args

Andrzej Galiński · 2021-02-05

Cel: Ustawić mod-rewrite tak, żeby żądanie postaci post/foo/123 trafiało na stronę post.php?user=foo&val=123.

Rozwiązanie

Struktura:

www
├── .htaccess
└── post.php

.htaccess

RewriteEngine On
RewriteBase /

RewriteRule post/([0-9]+)/(\w+) "/post.php?category=$1&item=$2" 
RewriteRule post/([0-9]+) "/post.php?category=$1" 

RewriteRule post2/([0-9]+)/(\w+)$ "/post.php?category=$1&item=$2"

post.php

post.php, args: <?php print_r($_GET); ?>
  • RewriteEngine On włącza obsługę przekierowań (w rewrite’ach na poziomie .htaccess nie należy polegać na ustawieniach domyślnych)
  • Reguły są stosowane iteracyjnie. Żeby uniknąć zastąpienia bardziej szczegółowej reguły dla post/ trzeba ją umieścić przed ogólniejszą - inaczej pierwsze przepisanie zmieni adres i kolejna reguła nie będzie pasowała
  • Reguła dla post/: pierwszy element ścieżki musi być liczbą ([0-9]+), drugi
    • opcjonalny - ciągiem znaków (\w)
  • Reguła dla post2/ wymusza dokładny format URLa: post/ może zastąpić tylko część URLa, np, /post/1/2/3 -> /post.php?category=1&item=$2/3 (czy takie przekierowanie ma sens, to już inna sprawa).

Skrypt do testowania:

#!/usr/bin/env bash
shopt -s expand_aliases
alias quietcurl="curl -so /dev/null -w '%{http_code}\n'"
set -x

# prawidłowe przekierowania
curl localhost:8111/post/123
curl localhost:8111/post/1/2
curl localhost:8111/post/1/asd

# nieprawidłowe
quietcurl localhost:8111/bad        # nie ma takiego zasobu/przekierowania
quietcurl localhost:8111/post/asd   # Not Found (pierwszy argument nie jest liczbą)

# post2 wymusza odp. zakończenie URLa
quietcurl localhost:8111/post/1/asd/123  # post/ akceptuje dalszą część ścieżki
quietcurl localhost:8111/post2/1/asd/123 # Not Found (wymuszone przez post2/)
quietcurl localhost:8111/post2/1/asd     # to oczywiście działa

Wyniki:

$ ./curls.sh
+ curl localhost:8111/post/123
post.php, args: Array
(
    [category] => 123
)
+ curl localhost:8111/post/1/2
post.php, args: Array
(
    [category] => 1
    [item] => 2
)
+ curl localhost:8111/post/1/asd
post.php, args: Array
(
    [category] => 1
    [item] => asd
)
+ curl -so /dev/null -w '%{http_code}\n' localhost:8111/bad
404
+ curl -so /dev/null -w '%{http_code}\n' localhost:8111/post/asd
404
+ curl -so /dev/null -w '%{http_code}\n' localhost:8111/post/1/asd/123
200
+ curl -so /dev/null -w '%{http_code}\n' localhost:8111/post2/1/asd/123
404
+ curl -so /dev/null -w '%{http_code}\n' localhost:8111/post2/1/asd
200

Środowisko

rewrite_url_args
├── 000-default.conf
├── docker-compose.yml
├── Dockerfile
└── www
    ├── .htaccess
    └── post.php

Dockerfile

FROM php:apache
RUN a2enmod rewrite
CMD /usr/sbin/apache2ctl -D FOREGROUND

docker-compose.yml

version: '3'

services:
  004_php:
    build: .
    ports:
      - 8111:80
    volumes:
      - ./000-default.conf:/etc/apache2/sites-available/000-default.conf
      - ./www:/var/www/html

000-default.conf

<VirtualHost *:80>
	ServerAdmin webmaster@localhost
	DocumentRoot /var/www/html

	LogLevel info rewrite:trace3

	ErrorLog ${APACHE_LOG_DIR}/error.log
	CustomLog ${APACHE_LOG_DIR}/access.log combined
    <Directory />
        Options Indexes FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>

Rozmaitości

  • Apache musi mieć włączony mod_rewrite, najprościej zrobić to a2enmodem
    • a2enmod rewrite
  • mod_rewrite nie wie nic o unikodzie. Żądanie /post2/ąę będzie najpierw zdekodowane do /post2/%C4%85%C4%99, a dopiero potem będą na to nakładane reguły. Oczywiście
  • Przy pisaniu reguł bardzo, bardzo, bardzo przydaje się ustawienie odpowiedniego poziomu logowania. W Apache można to zrobić dla pojedynczego modułu: LogLevel info rewrite:trace3; więcej w dokumentacji Apache
  • Domyślnie aliasy są wyłączone w skryptach shellowych (ściślej - w sesjach nieinteraktywnych):
(man bash) 
Aliases are not expanded when the shell is not interactive, unless the
expand_aliases shell option is set using shopt.

(jak włączyć?)
shopt -s expand_aliases