Cloudfront Lambda@edge 根据查看者请求设置 cookie

Cloudfront Lambda@edge set cookie on Viewer Request

更新:更好地收集了我的想法

我在 Viewer Request Lambda 中为每个用户生成一个唯一标识符 (UUID),然后根据该 UUID 选择缓存页面 return。这行得通。

理想情况下,此用户始终具有相同的 UUID。

我必须在查看器请求中生成该 UUID 如果它不存在于该查看器请求的 cookie 中。我还需要将该 UUID 设置为 cookie,这当然发生在响应中而不是请求中。

在没有缓存的情况下,我的服务器只是处理采用自定义 header 并在响应 header 中创建 Set-Cookie。

如果我想缓存页面,我找不到处理这个问题的方法。我可以忽略缓存请求 header 并提供正确的缓存页面,但随后用户不会保留该 UUID,因为没有设置 cookie 用于他们的下一个请求。

有没有人完成过这样的事情?

我正在尝试的事情

我正在研究几个角度,但还没有开始工作:

  1. Cloudfront 中的某种设置我不知道它处理 header 或从查看器请求到查看器响应的其他数据 pass-through,可用于Cloudfront 中的第二个 lambda。

  2. 在查看器请求中抢先修改响应object headers。我认为这是不可能的,因为它们 return header 尚未创建,除非我缺少一些 built-in Cloudfront 方法。

  3. 某种现有的 pass-through header,我不知道这是否是一回事,因为我对 [=112 的这方面并不十分熟悉=] 处理,但值得一试。

  4. 可能(虽然还没有尝试过)我可以在客户端请求 lambda 中创建整个响应 object 并以某种方式从那里提供缓存页面,修改响应 headers 然后将其传递给回调方法。

答案确实有效,但不是可靠的解决方案。如果用户没有存储或提供他们的 cookie,它就会变成一个无限循环,而且如果可以避免的话,我宁愿不在我的所有页面前面抛出一个重定向


Somewhat-working概念

  1. 查看器请求 Lambda,当 UUID 不存在于 cookie 中时,生成 UUID
  2. 查看器请求 Lambda 在请求 object 的 header 上的 cookie 中设置 UUID。回调更新请求 object 传入
  3. UUID cookie 的存在破坏了 Cloudfront 缓存
  4. Origin Request Lambda 在 UUID 存在的情况下被触发
  5. 原始请求 Lambda 通过 http.get 再次调用原始请求 URL 并设置了 UUID cookie(40KB 的限制使得在 Viewer Request Lambda 中执行此操作不切实际)
  6. Viewer Request Lambda 的第二种情况,看到 UUID 现在存在,剥离 UUID cookie,然后正常继续请求
  7. 如果尚未缓存第二个来源请求 - 如果缓存响应则缓存响应,因为 cache-busting UUID 不存在 - return 实际页面 HTML 到第一个来源请求
  8. 第一个来源请求从 http.get 收到包含 HTML
  9. 的响应
  10. First Origin Request 创建自定义响应 object,其中包含来自 http.get 的响应 body 和 Set-Cookie header 设置我们的原始 UUID

后续调用,已经设置了 UUID,将从 cookie 中剥离 UUID(以防止缓存破坏)并直接跳到 Viewer Request Lambda 中的 second-scenario,这将直接加载缓存版本这页纸。

我说 "somewhat" 因为当我尝试到达终点时,我下载了一个二进制文件。

编辑

这是因为我没有设置 content-type header。我现在只有一个 302 重定向问题...如果我解决了这个问题,我会 post 一个完整的答案。


原题

我在查看器请求上有一个函数,它在从缓存或服务器检索请求之前选择一个选项并在请求中设置一些内容。

这行得通,但我希望它能为未来的用户记住该选择。我的想法是简单地设置一个 cookie,我可以在下次用户访问时读取它。由于这是关于查看者请求而不是查看者响应,我还没有想出如何实现它,或者是否可以通过 Lambda 本身实现。

Viewer Request -> 
  Lambda picks options (needs to set cookie) -> 
    gets corresponding content -> 
      returns to Viewer with set-cookie header intact

seen the examples 并且能够通过 Lambda 在查看者响应中成功设置 cookie。这对我没有太大帮助,因为需要根据请求做出决定。毫不奇怪,将此代码添加到查看器请求中不会在响应中显示任何内容。

您是否可以添加另一个目录:使用第一个 cookie setter 请求,return(来自 lambda)重定向,其中包括 cookie-set header,重定向到您的实际内容?

好的,还有很长的路要走但是:

  • 从传入请求中获取 cookie 指令
  • 在某处设置它(缓存等)
  • 让请求得到你的object
  • 在响应中,如果需要,还调用一个读取(缓存)并在响应中设置 set-cookie header 的函数?

我认为设置不存在的 cookie 的真正正确方法是 return 将 302 重定向到与 Set-Cookie 相同的 URI,然后让浏览器重做请求。这可能不会产生太大影响,因为浏览器可以重用相同的连接到 "follow" 重定向。

但如果您坚持不这样做,那么您可以使用查看器请求触发器将 cookie 注入到请求中,然后在查看器响应触发器中发出具有相同值的 Set-Cookie

request object,在查看器 response 事件中,可以在与原始 请求 事件,event.Records[0].cf.request

在viewer-response触发器中,这部分结构包含"request that CloudFront received from the viewer and that might have been modified by the Lambda function that was triggered by a viewer request event."

请谨慎操作以确保正确处理 cookie header。 Cookie request header requires careful and accurate manipulation because the browser can use multiple formats存在多个cookie时

曾几何时,cookie 需要作为单个请求发送 header。

Cookie: foo=bar; buzz=fizz

通过拆分 ; 后跟 <space> 上的值来解析这些值。

但浏览器也可能将它们拆分成多个 header,像这样:

Cookie: foo=bar
Cookie: buzz=fizz

在后一种情况下,数组 event.Records[0].cf.request.headers.cookie 将包含多个成员。您需要检查该数组中每个 object 的 value 属性,检查每个值中的多个值,并考虑到如果不存在 cookie,该数组将完全未定义(非空) .


奖励:这是我编写的一个函数,我相信它可以正确处理所有情况,包括没有 cookie 的情况。它将使用您要查找的名称提取 cookie。 Cookie names are case-sensitive.

// extract a cookie value from request headers, by cookie name
// const my_cookie_value = extract_cookie(event.Records[0].cf.request.headers,'MYCOOKIENAME');
// returns null if the cookie can't be found
// https://whosebug.com/a/55436033/1695906

function extract_cookie(headers, cname) {

    const cookies = headers['cookie'];
    if(!cookies)
    {
        console.log("extract_cookie(): no 'Cookie:' headers in request");
        return null;
    }

    // iterate through each Cookie header in the request, last to first

    for (var n = cookies.length; n--;)
    {
        // examine all values within each header value, last to first

        const cval = cookies[n].value.split(/;\ /);
        const vlen = cval.length;

        for (var m = vlen; m--;)
        {
            const cookie_kv = cval[m].split('=');
            if(cookie_kv[0] === cname)
            {
                return cookie_kv[1];
            }            
        } // for m (each value)    
    } // for n (each header)

    // we have no match if we reach this point
    console.log('extract_cookie(): cookies were found, but the specified cookie is absent');
    return null;

}

问题发表已经一年多了。希望您找到解决方案并与我们分享!

我遇到了同样的问题,我也在考虑无限循环...这个怎么样?

  • 查看器请求事件发回带有 cookie 集的 302 响应,例如uuid=whatever 和一个 GET 参数 添加到位置 header 中的 URL,例如_uuid_set_=1.

  • 在设置 GET 参数 _uuid_set_(等于 1,但这不是必需的)的下一个查看器请求中,将有两个选项:

    • 未设置 cookie uuid,在这种情况下,您可以发回响应 500 以打破循环,或者任何适合您需要的内容,

    • 或设置了 cookie,在这种情况下,您将发送另一个 302 并删除参数 _uuid_set_,这样最终用户就永远不会看到它,并且不能 copy-pasted 和分享,我们都可以在晚上睡觉。