Varnish。如何使用 cookie 根据语言环境进行重定向?

Varnish。如何使用 cookie 根据语言环境进行重定向?

我需要为多语言网站实现基于 cookie 的重定向逻辑。我有 Next.js 中间件的可用代码,但不幸的是,它无法在 Docker 的独立设置中使用(我不使用 Vercel)。

import { NextRequest, NextResponse } from 'next/server';

export const config = {
    matcher: [
        /*
         * Match all request paths except for the ones starting with:
         * - api (API routes)
         * - _next (static files)
         * - URLs ending with a file extension (e.g., .html, .js, .css, etc.)
         */
        '/((?!api|_next|\\.\\.).+)',
    ],
};

export function middleware(request: NextRequest) {
    const { pathname, search, defaultLocale, locale } = request.nextUrl;

    const localeCookie = request.cookies.get('NEXT_LOCALE')?.value;

    if (!localeCookie && locale !== defaultLocale) {
        return NextResponse.redirect(new URL(`/${defaultLocale}${pathname}${search}`, request.url));
    }

    if (localeCookie && locale !== localeCookie) {
        return NextResponse.redirect(new URL(`/${localeCookie}${pathname}${search}`, request.url));
    }

    return NextResponse.next();
}

我想实现 Varnish 设置来代替 Next.js 解决方案:

vcl 4.1;

import cookie;

sub vcl_recv {
    ...

    if (req.url ~ "^/_next" || req.url ~ "/api/" || req.url ~ "/\.(.*)$/") {
        return (pass);
    }

    # Extract the locale from the URL, the default locale is hidden
    if (req.url ~ "^/[a-z]{2}/") {
        set req.http.locale = regsub(req.url, "^/([a-z]{2})(/.*)?$", "\1");
    } else {
        set req.http.locale = "";
    }

    # Extract NEXT_LOCALE cookie
    if (req.http.cookie) {
        cookie.parse(req.http.cookie);
        set req.http.localeCookie = cookie.get("NEXT_LOCALE");
        set req.http.cookie = cookie.get_string();
    }

    // If locale is specified and NEXT_LOCALE is missing, redirect to the default locale.
    if (req.http.locale != "" && !req.http.localeCookie) {
        # set req.http.X-Redirect-Url = ...;
        return (synth(302));
    }
    // If NEXT_LOCALE is present and the locale is not equal to the locale from cookie, then redirect to the cookie locale
    else if (req.http.locale != "" && req.http.locale != req.http.localeCookie) {
        # set req.http.X-Redirect-Url = ...;
        return (synth(302));
    }

    ...

    return (hash);
}

sub vcl_synth {
    # Perform the actual redirection based on the X-Redirect-Url header
    if (resp.status == 302 && req.http.X-Redirect-Url) {
        set resp.http.location = req.http.X-Redirect-Url;
        set resp.reason = "Moved";
        return (deliver);
    }
}

答案1

如果有人对原始问题感兴趣,“强制”重定向到默认语言环境,而不管请求语言环境如何,除非设置了 cookie:

vcl 4.1;

import std;
import cookie;

sub vcl_recv {
    ...

    // Bypass redirection logic for certain request paths
    if (req.url ~ "^/_next" || req.url ~ "/api/" || req.url ~ "/\.(.*)$/") {
        return (pass);
    }

    // Extract the locale from the URL
    if (req.url ~ "^/([a-z]{2})(/|$|\?)") {
        set req.http.X-Request-Locale = regsub(req.url, "^/([a-z]{2})(/|$|\?).*", "\1");
    }

    // Extract NEXT_LOCALE cookie
    if (req.http.cookie) {
        cookie.parse(req.http.cookie);
        set req.http.X-Next-Locale = cookie.get("NEXT_LOCALE");
        set req.http.cookie = cookie.get_string();
    }

    // If X-Next-Locale is `uk` (default) unset it
    if (req.http.X-Next-Locale == "uk") {
        unset req.http.X-Next-Locale;
    }

    // If X-Next-Locale is not present and request locale is `uk` (default) perform a 301 (Moved Permanently) redirect
    if (req.http.X-Next-Locale !~ "^(uk|ru)$" && req.http.X-Request-Locale == "uk") {
        set req.http.X-Status-Code = 301;

        // Remove the locale from the URL
        set req.url = regsub(req.url, "^/([a-z]{2})(/|$|\?)", "/\2");

        // Remove double slashes if present in the URL
        set req.url = regsuball(req.url, "//+", "/");

        return (synth(750));
    }

    // If X-Request-Locale is within existing locales or X-Next-Locale is within existing locales and X-Request-Locale is missing and
    // X-Request-Locale (url locale) does not match X-Next-Locale (cookie locale)
    // redirect to X-Next-Locale (cookie) if present, or to the default locale if absent
    if (
        (req.http.X-Request-Locale ~ "^(uk|ru)$" || (req.http.X-Next-Locale ~ "^(uk|ru)$" && !req.http.X-Request-Locale)) &&
        (req.http.X-Next-Locale != req.http.X-Request-Locale)
    ) {
        // Perform a 307 (Temporary Redirect)
        set req.http.X-Status-Code = 307;

        // If the URL doesn't contain a locale, add the X-Next-Locale to it (the default locale is empty)
        if (req.url !~ "^/([a-z]{2})(/|$|\?)") {
            set req.url = "/" + req.http.X-Next-Locale + req.url;
        }
        // If the URL already contains a locale, update it with X-Next-Locale
        else {
            set req.url = regsub(req.url, "^/([a-z]{2})(/|$|\?)", "/" + req.http.X-Next-Locale + "\2");
        }

        // Remove double slashes if present in the URL
        set req.url = regsuball(req.url, "//+", "/");

        return (synth(750));
    }

    // Remove any cookies left
    unset req.http.Cookie;

    return (hash);
}

sub vcl_synth {
    ...
}

答案2

在 Chat-GPT 正则表达式的帮助下,我最终得到了更简单的基于 cookie 的重定向逻辑和默认(隐藏)语言环境处理。谢谢您的建议。

vcl 4.1;

import std;
import cookie;

...

sub vcl_recv {
    ...

    // Bypass redirection logic for certain request paths
    if (req.url ~ "^/_next" || req.url ~ "/api/" || req.url ~ "/\.(.*)$/") {
        return (pass);
    }

    // Extract the locale from the URL
    if (req.url ~ "^/([a-z]{2})(/|$|\?)") {
        set req.http.X-Request-Locale = regsub(req.url, "^/([a-z]{2})(/|$|\?).*", "\1");
    }

    // Extract NEXT_LOCALE cookie
    if (req.http.cookie) {
        cookie.parse(req.http.cookie);
        set req.http.X-Next-Locale = cookie.get("NEXT_LOCALE");
        set req.http.cookie = cookie.get_string();
    }

    // If X-Next-Locale is `uk` (default) unset it
    if (req.http.X-Next-Locale == "uk") {
        unset req.http.X-Next-Locale;
    }

    // If X-Request-Locale is `uk` perform a 301 (Moved Permanently) redirect
    if (req.http.X-Request-Locale == "uk" && req.http.X-Next-Locale !~ "^(uk|ru)$") {
        set req.http.X-Status-Code = 301;

        // Remove the locale from the URL
        set req.url = regsub(req.url, "^/([a-z]{2})(/|$|\?)", "/\2");

        // Remove double slashes if present in the URL
        set req.url = regsuball(req.url, "//+", "/");

        return (synth(750));
    }

    // Perform a 307 (Temporary Redirect) redirect to X-Next-Locale (cookie locale) if:
    // X-Next-Locale is within existing locales and
    // X-Request-Locale is within existing locales or is missing and
    // X-Request-Locale (url locale) does not match X-Next-Locale (cookie locale)
    if (
        (req.http.X-Next-Locale ~ "^(uk|ru)$") &&
        (req.http.X-Request-Locale ~ "^(uk|ru)$" || !req.http.X-Request-Locale) &&
        (req.http.X-Next-Locale != req.http.X-Request-Locale)
    ) {
        set req.http.X-Status-Code = 307;

        if (req.url !~ "^/([a-z]{2})(/|$|\?)") {
            // If the URL doesn't contain a locale, add the X-Next-Locale to it
            set req.url = "/" + req.http.X-Next-Locale + req.url;
        } else {
            // If the URL already contains a locale, update it with X-Next-Locale
            set req.url = regsub(req.url, "^/([a-z]{2})(/|$|\?)", "/" + req.http.X-Next-Locale + "\2");
        }

        // Remove double slashes if present in the URL
        set req.url = regsuball(req.url, "//+", "/");

        return (synth(750));
    }

    // Remove any cookies left
    unset req.http.Cookie;

    return (hash);
}

sub vcl_synth {
    // Modify response status and location header for redirects
    if (resp.status == 750) {
        set resp.status = std.integer(req.http.X-Status-Code);
        set resp.http.location = "https://" + req.http.Host + req.url;
        set resp.reason = "Moved";
        return (deliver);
    }
}

PS 不要忘记绕过机器人用户代理的重定向。

相关内容