Varnish 升级后 HTML/JSON 接受无效

After Varnish Upgrade HTML/JSON Accept not working

在升级 varnish 4 -> varnish-6.2.1 后,“接受”请求未按预期响应。

如果我用“接受:application/json”发出第一个请求,它 returns HTML,在第二次请求时它会继续工作。

使用 Varnish3,遵循 Snippet 使其工作 - 但不适用于 varnish6

sub vcl_backend_response {
    # Called after the response headers has been successfully retrieved from the backend.

    if (!beresp.http.Vary) { # no Vary at all
        set beresp.http.Vary = "Accept";
    } elseif (beresp.http.Vary !~ "Accept") { # add to existing Vary
        set beresp.http.Vary = beresp.http.Vary + ", Accept";
    }
}

sub normalize {
    # Normalize the header, remove the port (in case you're testing this on various TCP ports)
    set req.http.Host = regsub(req.http.Host, ":[0-9]+", "");
    # Remove the proxy header (see https://httpoxy.org/#mitigate-varnish)
    unset req.http.proxy;
    # Normalize the query arguments
    set req.url = std.querysort(req.url);

    if (req.http.Accept) {
        if (req.http.Accept ~ "application/json") {
            set req.http.Accept = "application/json";
        } else {
            set req.http.Accept = "text/html";
        }
    }
}

它不一样,但非常相似:

我已经测试了你的 VCL 代码,它工作正常。使 sub normalize 工作所需的唯一添加如下:

sub vcl_recv {
    call normalize;
}

PHP测试脚本

我写了一个小测试脚本来复制原点的变化行为。这是代码:

<?php
header('Cache-Control: public, s-maxage=3600');
$timestamp = date('Y-m-d H:i:s');
if(isset($_SERVER['HTTP_ACCEPT']) && preg_match('/application\/json/',$_SERVER['HTTP_ACCEPT'])) {
    header('Content-Type: application/json');
    $obj = new stdClass;
    $obj->timestamp = $timestamp;
    echo json_encode($obj);
} else {
    echo $timestamp;
}

如果在Accept中找到application/json header它returns输出如下:

{"timestamp":"2021-11-24 07:38:35"}

否则它returns下面的纯输出:

2021-11-24 07:38:35

VCL

这是我为此使用的 VCL:

vcl 4.1;

import std;

backend default {
    .host = "localhost";
    .port = "8080";
}

sub vcl_backend_response {
    # Called after the response headers has been successfully retrieved from the backend.

    if (!beresp.http.Vary) { # no Vary at all
        set beresp.http.Vary = "Accept";
    } elseif (beresp.http.Vary !~ "Accept") { # add to existing Vary
        set beresp.http.Vary = beresp.http.Vary + ", Accept";
    }
}

sub normalize {
    # Normalize the header, remove the port (in case you're testing this on various TCP ports)
    set req.http.Host = regsub(req.http.Host, ":[0-9]+", "");
    # Remove the proxy header (see https://httpoxy.org/#mitigate-varnish)
    unset req.http.proxy;
    # Normalize the query arguments
    set req.url = std.querysort(req.url);

    if (req.http.Accept) {
        if (req.http.Accept ~ "application/json") {
            set req.http.Accept = "application/json";
        } else {
            set req.http.Accept = "text/html";
        }
    }
}

sub vcl_recv {
    call normalize;
}

如果这对您不起作用,如何调试?

您可以运行以下varnishlog命令来查看发生了什么:

varnishlog -g request -I ReqHeader:Accept -i requrl -i berequrl -I RespHeader:Vary -I BereqHeader:Accept -I BerespHeader:Vary -i vcl_call -i vcl_return

这是 HTML 内容 现金未命中 的一些示例 varnishlog 输出:

*   << Request  >> 34
-   ReqURL         /test
-   ReqHeader      Accept: */*
-   VCL_call       RECV
-   ReqURL         /test
-   ReqHeader      Accept: text/html
-   VCL_return     hash
-   VCL_call       HASH
-   VCL_return     lookup
-   VCL_call       MISS
-   VCL_return     fetch
-   RespHeader     Vary: Accept
-   VCL_call       DELIVER
-   VCL_return     deliver
**  << BeReq    >> 35
--  BereqURL       /test
--  BereqHeader    Accept: text/html
--  BereqHeader    Accept-Encoding: gzip
--  VCL_call       BACKEND_FETCH
--  VCL_return     fetch
--  VCL_call       BACKEND_RESPONSE
--  BerespHeader   Vary: Accept
--  VCL_return     deliver

这是常规 HTML 的 缓存命中 发生的情况:

*   << Request  >> 32800
-   ReqURL         /test
-   ReqHeader      Accept: */*
-   VCL_call       RECV
-   ReqURL         /test
-   ReqHeader      Accept: text/html
-   VCL_return     hash
-   VCL_call       HASH
-   VCL_return     lookup
-   VCL_call       HIT
-   VCL_return     deliver
-   RespHeader     Vary: Accept
-   VCL_call       DELIVER
-   VCL_return     deliver

即使 HTML 版本仍在缓存中,Accept: application/json 也会导致缓存未命中:

*   << Request  >> 32802
-   ReqURL         /test
-   ReqHeader      Accept:application/json
-   VCL_call       RECV
-   ReqURL         /test
-   ReqHeader      Accept: application/json
-   VCL_return     hash
-   VCL_call       HASH
-   VCL_return     lookup
-   VCL_call       MISS
-   VCL_return     fetch
-   RespHeader     Vary: Accept
-   VCL_call       DELIVER
-   VCL_return     deliver
**  << BeReq    >> 32803
--  BereqURL       /test
--  BereqHeader    Accept: application/json
--  BereqHeader    Accept-Encoding: gzip
--  VCL_call       BACKEND_FETCH
--  VCL_return     fetch
--  VCL_call       BACKEND_RESPONSE
--  BerespHeader   Vary: Accept
--  VCL_return     deliver

下一次,这将导致 JSON 输出命中:

*   << Request  >> 32805
-   ReqURL         /test
-   ReqHeader      Accept:application/json
-   VCL_call       RECV
-   ReqURL         /test
-   ReqHeader      Accept: application/json
-   VCL_return     hash
-   VCL_call       HASH
-   VCL_return     lookup
-   VCL_call       HIT
-   VCL_return     deliver
-   RespHeader     Vary: Accept
-   VCL_call       DELIVER
-   VCL_return     deliver