在 Roku 上请求后看不到任何 cookie

Not seeing any cookies after request on Roku

我有一个 Roku 应用程序,它会在播放视频时对后端服务进行多次异步调用。 可能其中一些呼叫可能同时发生。这些调用都依赖于 cookie 进行身份验证。

因为调用依赖于 cookie 进行身份验证,所以我需要使用单个 roUrlTransfer 对象。如果我为每个请求创建一个新的 roUrlTransfer 实例,它们将不会共享 cookie 缓存

但是因为两个调用可能同时发生,所以我不能使用单个 roUrlTransfer 对象。根据 Roku documentation:

Each roUrlTransfer object can perform only one asynchronous operation at one time. After starting an asynchronous operation, you cannot perform any other data transfer operations using that object until the asynchronous operation has completed, as indicated by receiving an roUrlEvent message whose GetSourceIdentity value matches the GetIdentity value of the roUrlTransfer.

为了解决这两个问题,我创建了一个包装器对象。我维护一个名为“cookieJar”的单例 roUrlTransfer 对象,然后根据请求启动一个新的 roUrlTransfer 实例:

' ********************************************************************
' ** URL class to streamline adding query parameters and making requests.
' ********************************************************************

Function Downloader() As Object
   return {
      Send: Downloader_Send
      AsyncWait: Downloader_AsyncWait
   }
End Function

Function CookieJar() As Object
   if m.cookiejar = invalid
      m.cookiejar = CreateObject("roUrlTransfer")
      m.cookiejar.EnableCookies()
   end if
   return m.cookiejar
End Function

Function Downloader_Send(req={} As Object)
   if req.url <> invalid
      m.req = req

      m.http = CreateObject("roUrlTransfer")
      m.port = CreateObject("roMessagePort")
      m.http.SetPort(m.port)
      m.http.EnableEncodings(true)

      ' ParseURL is a helper function I wrote that just breaks a URL into
      ' its components: scheme, host, path, query, and fragment
      m.url = ParseURL(req.url)
      m.http.EnableCookies()
      m.http.AddCookies(CookieJar().GetCookies(m.url.host, m.url.path))

      m.http.SetUrl(req.url)

      if req.method <> invalid
         m.http.SetRequest(req.method)
      else
         m.http.SetRequest("GET")
      end if

      if req.timeout <> invalid
         m.timeout = req.timeout
      else
         m.timeout = 5000
      end if

      data = {}

      if req.body <> invalid
         if m.http.AsyncPostFromString(FormatJSON(req.body))
            data = m.AsyncWait()
         end if
      else
         if m.http.AsyncGetToString()
            data = m.AsyncWait()
         end if
      end if

   end if

   return data
End Function

Function Downloader_AsyncWait() As Dynamic
   event = wait(m.timeout, m.port)
   if type(event) = "roUrlEvent"
      print "Headers:"
      print event.GetResponseHeaders()
      print "Adding cookies to cookie jar:"
      print m.http.GetCookies(m.url.host, m.url.path)
      print m.http.GetCookies("", "/")
      CookieJar().AddCookies(m.http.GetCookies(m.url.host, m.url.path))
      return {
         statusCode: event.GetResponseCode()
         error: event.getFailureReason()
         body: event.GetString()
         headers: event.GetResponseHeaders()
      }
   else if event = invalid
      m.http.AsyncCancel()
   end if
   return invalid
End Function

为了测试我的代码,我启动了一个非常简单的 ExpressJS 服务器,它除了设置一个 cookie 什么都不做:

var express = require("express");
var cookieParser = require("cookie-parser");
var app = express();
app.use(cookieParser());

app.get("/", function (req, res) {
    res.cookie("key", "value", {
        domain: "192.168.1.102:3333",
        path: "/",
        expire: Date.now() + 3600000
    });
    res.send("Done");
});

var server = app.listen(3333, function () {
    var host = server.address().address;
    var port = server.address().port;

    console.log("Example app listening at http://%s:%s", host, port);
});

然后在我的 Roku 应用程序中(在主屏幕上)我有以下内容:

httpClient = Downloader()

httpClient.Send({
    ' My local IP address
    url: "http://192.168.1.102:3333"
})

当我在我的 Roku 上 运行 时,我在调试日志中看到以下内容:

Headers:
<Component: roAssociativeArray> =
{
    connection: "keep-alive"
    content-length: "13"
    content-type: "text/html; charset=utf-8"
    date: "Tue, 11 Aug 2020 17:29:52 GMT"
    etag: "W/"d-w+Ote5otCKx40dtR446Vz7wX66U""
    set-cookie: "key=value; Path=/"
    x-powered-by: "Express"
}
Adding cookies to cookie jar:
<Component: roArray> =
[
]
<Component: roArray> =
[
]

m.http.GetCookies(m.url.host, m.url.path)m.http.GetCookies("", "/") 均未返回任何 cookie。但是你可以清楚地看到header中的cookie!那为什么我不能访问它?

请注意,m.url.host192.168.1.102:3333m.url.path (空白),以防有助于理解任何内容...

在为此工作了一整天后,我发现 Roku 无法正确处理 cookie 域中的 ports。当 192.168.1.102:3333 提供 cookie 时,Roku 在内部将 cookie 标记为属于 192.168.1.102(无端口)。

这有两个含义:

  1. 如果使用 ...; domain=...; 属性提供 cookie,并且此域包含端口,Roku 将完全无法解析 set-cookie header 并且不会提取任何 cookie
  2. 如果提供的 cookie 没有 此属性,那么 Roku 将解析 set-cookie header,但是 GetCookies() 只会 return 如果未给定域 ("") 或给定 port-less 域,则为 cookie。 GetCookies("192.168.1.102", "/") return cookie 但 GetCookies("192.168.1.102:3333", "/") 没有

因为我的代码的目的是在多个 roUrlTransfer 实例之间共享 cookie,所以使用 GetCookies("", "/") 获取 all cookie 是可以接受的,所以我们将修改我们的back-end 从我们的 set-cookie headers

中删除 ...; domain=...; 属性