Cachowanie stron z formularzem logowania
Najczęsciej gdy robi się strony, które mają być dostępne dla określonego grona odbiorców (posiadających login i hasło) nie pozwala się na to żeby serwer proxujący cachował je. Cachowanie takiej strony mogłoby spowodować, że użytkownicy, którzy nie powinni mieć dostępu do zawartości, taki dostęp uzyskają.
W tym poście pokażę, że jednak da się przygotować taką konfigurację serwera proxującego, żeby cachować stronę per użytkownik (naszczęście w tym przypadku mamy tylko 2 grupy: zalogowany, niezalogowany. Przy większej ilości grupy takie podejście nie miałoby sensu.)
Jako serwer proxujący został użyty NGINX.
Cachowanie w NGINX
Na początku informacja jak działa cachowanie w NGINX. Posiada on dyrektywę proxy_cache_key, która domyślnie złożona jest z $scheme$proxy_host$request_uri;
Czyli kluczem do cache jest protokołu host i url. Przykładowo strona https://mkaciuba.pl/uri Bedzie zapisać przy użyciu klucza httpsmkaciuba.pluri. Takie podejście nie daje dużo możliwości. Ale dzięki temu, że w NGINX można użyć języka LUA znika problem ze statyczna konfiguracja, zmienne i prawie całą konfigurację można modyfikować dynamicznie.
NGINX i LUA
Zwykle konfiguracja NGINX zawiera się w statycznym pliku z konfiguracja, jednak dzięki modułuowi nginx-lua możemy rozszerzyć obsługę zapytać przez pisanie skryptów operujących na requestach. LUA w NGINX może uruchamiać się na kilku fazach.
Nas będzie interesować faza rewrite by lua.
Użycie LUA w NGINX daje bardzo dużo możliwości obsługi zapytań. Dzięki niej nie potrzebujemy już statycznego podejścia (wszystko zdefiniowane w konfiguracji) lecz możemy wykonywać zapytania HTTP, łaczyć się do redis itd.
Modyfikacja klucza do cache
Wiemy już jak NGINX generuje klucz do cacha oraz że można użyć LUA do modyfikacji zmiennych NGINX. Pora zebrać to wszystko w całość i przygotować konfigurację, które bedzię modyfikowało klucz do cache w zależności od tego czy użytkownik powinien mieć dostęp czy nie.
Kilka początkowych założeń:
-
Użytkownik zalogowany będzie posiadał ciastko w którym będziemy przechowywać dane potrzebne do weryfikacji czy ma dostęp do strony
-
Nginx będzie komunikował się z CacheAPI w celu wyliczenia klucza do cacha. Klucz będzie modyfikowany jeśli użytkownik ma dostęp do strony
Poniższy diagram przedstawia proponowany przepływ sterowania dla aplikacji backendowej.
W przypadku pobrania strony z cache
A gdy nie mamy strony w cachu nginxa
Konfiguracja NGINX
Na początku powinniśmy zdefiniować kilka zmiennych w nginx dla dzięki którym będziemy mogli w razie błędu z CacheAPI nie cachowac odpowiedzi
set $no_cache 0; set $bypass_cache 0; # Zmienna cache_version w niej zapiszemy wersje cacha set $cache_version ""; # Użycie zmiennych do pomienięcia cacha oraz nie cachowania opdowiedzi proxy_cache_bypass $bypass_cache; proxy_no_cache $no_cache; # Nasz głowny skryt do obsługi requestów jest zbyt duzy żeby go zamieszczać w konfiguracji nginx. Wygodniej edytować go w osobnym pliku rewrite_by_lua_file /var/lib/nginx/lua/rewrite.lua; # Nazwa naszego bucketu z cachem proxy_cache dynamic; # Nasz klucz do cache set $cache_key "$method!$host!$uri!$is_args$args!$content_encoding!$cache_version"; proxy_cache_key $cache_key;
Sam skrypt wygląda następująco
ngx.var.no_cache = '0' local user_id = ngx.var.cookie_session_cookie if user_id == nil then return end local path = ngx.var.uri -- dziala na okreslonych sciezkac if not re.match(path, '^/(path1|path)/(.*)', 'ijo') then return end local http = require "resty.http" local httpc = http.new() -- timeouts do api httpc:set_timeouts(50, 450, 450) httpc:connect('unix:/var/run/nginx.sock') local res, err = httpc:request({ path = '/api/v1/lambda/?userid=' .. user_id .. '&path=' .. path, headers = { ["Host"] = "mkaciuba.com", ['x-cache-api-user-id'] = user_id, ['x-cache-api-path'] = path }, }) if not res then -- in case of error no cache response ngx.log(ngx.ERR, 'request to lambda failed ' .. err ) ngx.var.no_cache = '1' return end if res.status ~= 200 then ngx.log(ngx.ERR, 'request to lambda invalid status ' .. res.status) ngx.var.no_cache = '1' return end local info = res.headers['x-cache-api-info'] if info == 'no-cache' then ngx.var.no_cache = '1' return end if res.headers['x-cache-api-cache-param'] == nil then return end if info == 'no-change-cache-key' then return end -- update cache key local cache_key = ngx.var.cache_key ngx.var.cache_key = cache_key .. '!' .. res.headers['x-cache-api-cache-param']
Podsumowanie
Cachowanie stron per segment użytkownika nie zadaniem prostym, ale gdy się je już wykona można bardzo przyspieszyć działanie strony w przypadku wolnego backendu.
PS Kod backendu i konfiguracja nginx wykorzystująca opisaną logikę można zobaczyć tutaj