简体   繁体   English

如果启用cookie,Symfony ESI将中断Varnish缓存

[英]Symfony ESI breaks Varnish cache if cookies are enabled

I don't know what I am doing anymore. 我不知道我在做什么。 I had so many issues I don't know where to start. 我遇到了很多问题,我不知道从哪里开始。 Here is my configuration: 这是我的配置:

varnishd (varnish-3.0.3 revision 9e6a70f)
Server version: Apache/2.2.22 (Unix)
Symfony 2.3.1

First I've disabled Symfony AppCache in the app.php file which was used as a reverse proxy instead of Varnish. 首先,我在app.php文件中禁用了Symfony AppCache ,该文件用作反向代理而不是Varnish。

Here is my Varnish configuration: 这是我的Varnish配置:

Varnish (80) <--> Apache (8080)

# /etc/varnish/default.vcl
backend default {
    .host = "127.0.0.1";
    .port = "8080";
}

sub vcl_recv {
    if (req.http.Cache-Control ~ "no-cache") {
        return (pass);
    }

    if (!(req.url ~ "^/dashboard/")) {
        unset req.http.Cookie;
    }

    # Remove has_js and Google Analytics __* cookies.
    set req.http.Cookie = regsuball(req.http.Cookie, "(^|;\s*)(_[_a-z]+|has_js)=[^;]*", "");
    # Remove a ";" prefix, if present.
    set req.http.Cookie = regsub(req.http.Cookie, "^;\s*", "");

    #set req.http.Cookie = regsuball(req.http.Cookie, "(^|;\s*)(__[a-z]+|has_js)=[^;]*", "");
    set req.http.Surrogate-Capability = "abc=ESI/1.0";

    if (req.restarts == 0) {
        if (req.http.x-forwarded-for) {
            set req.http.X-Forwarded-For =
            req.http.X-Forwarded-For + ", " + client.ip;
        } else {
            set req.http.X-Forwarded-For = client.ip;
        }
    }

    if (req.request != "GET" &&
        req.request != "HEAD" &&
        req.request != "PUT" &&
        req.request != "POST" &&
        req.request != "TRACE" &&
        req.request != "OPTIONS" &&
        req.request != "DELETE") {
        /* Non-RFC2616 or CONNECT which is weird. */
        return (pipe);
    }

f (req.request != "GET" && req.request != "HEAD") {
        /* We only deal with GET and HEAD by default */
        return (pass);
    }

    if (req.http.Authorization) {
        /* Not cacheable by default */
        return (pass);
    }

    return (lookup);
}

sub vcl_pipe {
     return (pipe);
}

sub vcl_pass {
    return (pass);
}

sub vcl_hash {
    hash_data(req.url);

    if (req.http.host) {
        hash_data(req.http.host);
    } else {
        hash_data(server.ip);
    }

    return (hash);
}

sub vcl_fetch {
    if (beresp.http.Surrogate-Control ~ "ESI/1.0") {
        unset beresp.http.Surrogate-Control;
        set beresp.do_esi = true;
    }

    # Varnish determined the object was not cacheable
    if (beresp.ttl <= 0s) {
        set beresp.http.X-Varnish-Cacheable = "NO:Not Cacheable";

    # You don't wish to cache content for logged in users
    } elsif (req.http.Cookie ~ "(UserID|_session)") {
        set beresp.http.X-Varnish-Cacheable = "NO:Got Session";
        return(hit_for_pass);

    # You are respecting the Cache-Control=private header from the backend
    } elsif (beresp.http.Cache-Control ~ "private") {
        set beresp.http.X-Varnish-Cacheable = "NO:Cache-Control=private";
        return(hit_for_pass);

    # Varnish determined the object was cacheable
    } else {
        set beresp.http.X-Varnish-Cacheable = "YES";
    }

    if (beresp.status >= 300) {
        return (hit_for_pass);
    }

    if (beresp.http.Pragma ~ "no-cache" ||
        beresp.http.Cache-Control ~ "no-cache" ||
        beresp.http.Cache-Control ~ "private") {
        return (hit_for_pass);
    }

    return (deliver);
}

sub vcl_hit {
    return (deliver);
}

sub vcl_deliver {
    if (obj.hits > 0) {
        set resp.http.X-Varnish-Cached = "HIT";
    } else {
        set resp.http.X-Varnish-Cached = "MISS";
    }
    return (deliver);
}

sub vcl_error {
    set obj.http.Content-Type = "text/html; charset=utf-8";
    set obj.http.Retry-After = "5";

    synthetic {"
 <?xml version="1.0" encoding="utf-8"?>
 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
 <html>
   <head>
     <title>"} + obj.status + " " + obj.response + {"</title>
   </head>
   <body>
     <h1>Error "} + obj.status + " " + obj.response + {"</h1>
     <p>"} + obj.response + {"</p>
     <h3>Guru Meditation:</h3>
     <p>XID: "} + req.xid + {"</p>
     <hr>
     <p>Varnish cache server</p>
   </body>
 </html>
 "};

    return (deliver);
}

sub vcl_init {
    return (ok);
}

sub vcl_fini {
    return (ok);
}

Because of: 因为:

if (!(req.url ~ "^/dashboard/")) {
     unset req.http.Cookie;
}

Varnish removes all the Cookies of the public pages and HITs the Cache, which is good but... Since Varnish deletes all the cookies I cannot manage to stay logged in (no session cookie means no session). Varnish删除了公共页面的所有Cookie,并命中了缓存,这很好,但是...由于Varnish删除了所有我无法保持登录状态的cookie(没有会话cookie意味着没有会话)。

I have the login button in an ESI block with no-cache header but this still does not fix it. 我在没有缓存头的ESI块中有登录按钮,但这仍然无法解决。 Here is the frontend controller: 这是前端控制器:

public function indexAction()
{
    $response = $this->render('AcmeCoreBundle:Default:index.html.twig');
    $response->setSharedMaxAge(21600); // 6 hours
    return $response;
}

The template extends the layout.html.twig : 该模板扩展了layout.html.twig

<html lang="en-US">
<head>
    ...
</head>
<body>
    ...

    <div class="top-right">
        {{ render_esi(controller('AcmeUserBundle:Default:loginBox')) }}
    </div>

    ...
</body>

and the controller AcmeUserBundle:Default:loginBox : 和控制器AcmeUserBundle:Default:loginBox

public function loginBoxAction()
{
    $response = $this->render('AcmeUserBundle:Block:home_login.html.twig');
    $response->setVary('Cookies', false);
    $response->setMaxAge(0);
    $response->setPrivate();

    return $response;
}

I don't know how to manage the session cookie for Symfony. 我不知道如何管理Symfony的会话cookie。 Because I have a facebook connect button on every page I need to have the user session. 因为我在每个页面上都有一个Facebook连接按钮,所以我需要进行用户会话。 And because Symfony is creating a cookie even for anonymous user I have session cookies on all requests. 而且由于Symfony甚至为匿名用户都创建了一个cookie,所以我对所有请求都有会话cookie。

Help would be greatly appreciated :) 帮助将不胜感激:)

Thanks, Maxime 谢谢马克西姆


UPDATE : New VCL files after recommendations: 更新 :建议后的新VCL文件:

...

sub vcl_recv {

   if (!(req.url ~ "^/dashboard") && !(req.url ~ "^/logout") && !(req.url ~ "^/_fragment") && req.esi_level == 0 ) {
       set req.http.Esi-Cookie = req.http.Cookie;
       unset req.http.Cookie;
   }

   if (!(req.url ~ "^/dashboard") && req.esi_level > 0 ) {
       set req.http.Cookie = req.http.Esi-Cookie;
   }

    # Remove has_js and Google Analytics __* cookies.
    set req.http.Cookie = regsuball(req.http.Cookie, "(^|;\s*)(_[_a-z]+|has_js)=[^;]*", "");

    # Remove a ";" prefix, if present.
    set req.http.Cookie = regsub(req.http.Cookie, "^;\s*", "");

    # Force ESI capability header
    set req.http.Surrogate-Capability = "abc=ESI/1.0";

    if (req.restarts == 0) {
        if (req.http.x-forwarded-for) {
            set req.http.X-Forwarded-For =
            req.http.X-Forwarded-For + ", " + client.ip;
        } else {
            set req.http.X-Forwarded-For = client.ip;
        }
    }

    if (req.request != "GET" &&
        req.request != "HEAD" &&
        req.request != "PUT" &&
        req.request != "POST" &&
        req.request != "TRACE" &&
        req.request != "OPTIONS" &&
        req.request != "DELETE") {
        /* Non-RFC2616 or CONNECT which is weird. */
        return (pipe);
    }

    # if Authorization or no-cache header we skip the cache
    if (req.http.Authorization || req.http.Cache-Control ~ "no-cache") {
        return (pass);
    }

    # If not GET or HEAD request we skip the cache
    if (req.request != "GET" && req.request != "HEAD") {
        return (pass);
    }

    return (lookup);
}

...

sub vcl_fetch {

    if (!(req.url ~ "^/dashboard") && !(req.url ~ "^/login_check")) {
        unset beresp.http.set-cookie;
    }

    if (beresp.http.Surrogate-Control ~ "ESI/1.0") {
        unset beresp.http.Surrogate-Control;
        set beresp.do_esi = true;
    }

    # Varnish determined the object was not cacheable
    if (beresp.ttl <= 0s) {
        set beresp.http.X-Varnish-Cacheable = "NO:Not Cacheable";

    # You don't wish to cache content for logged in users
    } elsif (req.http.Cookie ~ "(UserID|_session)") {
        set beresp.http.X-Varnish-Cacheable = "NO:Got Session";
        return(hit_for_pass);

    # You are respecting the Cache-Control=private header from the backend
    } elsif (beresp.http.Cache-Control ~ "private") {
        set beresp.http.X-Varnish-Cacheable = "NO:Cache-Control=private";
        return(hit_for_pass);

    # Varnish determined the object was cacheable
    } else {
        set beresp.http.X-Varnish-Cacheable = "YES";
    }

    if (beresp.status >= 300) {
        return (hit_for_pass);
    }

    if (beresp.http.Pragma ~ "no-cache" ||
        beresp.http.Cache-Control ~ "no-cache" ||
        beresp.http.Cache-Control ~ "private") {
        return (hit_for_pass);
    }

    return (deliver);
}

Every public page is now cached properly. 现在每个公共页面都已正确缓存。 I have cache HITs everywhere excepti on the login page but it's not a big deal for now. 除了登录页面上的所有地方,我到处都有缓存HIT,但现在还不算什么。

The problem I have is with the ESI block in the header. 我的问题是标题中的ESI块。 I can see in the apache access log that varnish is requesting the <esi:include> calling the /_fragment url. 我可以在apache访问日志中看到varnish正在请求<esi:include>调用/_fragment url。

The ESI block rendered on the home page is not correct (it's displaying login url as if the user is not logged in) however if I call the /_fragment directly, the returned block is correct (with the user information). 主页上呈现的ESI块不正确(它显示登录url就像用户未登录一样),但是如果我直接调用/_fragment ,则返回的块是正确的(带有用户信息)。

I don't really know where it's coming from but I'm so close :) 我真的不知道它来自哪里,但是我很近:)

Removing the Cookie header from an inbound request removes it from all resulting ESI include requests. 从入站请求中删除Cookie标头会将其从所有产生的ESI包含请求中删除。 Since you want access to the cookie header in included resources, but not the parent, which is cached, try this: 由于您要访问包含的资源中的cookie标头,而不要访问已缓存的父资源,请尝试以下操作:

if (!(req.url ~ "^/dashboard/") && req.esi_level == 0 ) {
    set req.http.Esi-Cookie = req.http.Cookie;
    unset req.http.Cookie;
}
if (!(req.url ~ "^/dashboard/") && req.esi_level > 0 ) {
    set req.http.Cookie = req.http.Esi-Cookie;
} 

This strips the browser cookie from the request for the parent page, but re-adds it to esi requests resulting from esi:include tags in the returned page. 这会将浏览器cookie从父页面的请求中剥离,但是将其重新添加到由返回页面中的esi:include标记产生的esi请求中。 I didn't lint the code above so it may not be 100% perfect. 我没有抹上上面的代码,因此它可能不是100%完美的。

Update 更新资料

In vcl_recv, if you don't ever want to cache or recursively esi process an esi fragment, you can change the second if block to: 在vcl_recv中,如果您不想缓存或递归进行esi片段处理,则可以将第二个if块更改为:

if (!(req.url ~ "^/dashboard/") && req.esi_level > 0 ) {
    set req.http.Cookie = req.http.Esi-Cookie;
    return (pass);
} 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM