urllib.request 在行为上与 curl 或 httpx 有何不同?在对 Google Container Registry 的请求中获取 401

How does urllib.request differ from curl or httpx in behaviour? Getting a 401 in a request to the Google Container Registry

我目前正在编写一些代码来与 Google Container Registry 上的图像进行交互。我有使用普通 curlhttpx 的工作代码。我正在尝试构建一个没有第三方依赖项的包。我的好奇心是围绕一个特定的端点,我从该端点获得了 curl 和 httpx 的成功响应,但是 401 Unauthorized using urllib.request.

bash 脚本演示了我要实现的目标,如下所示。它从注册表 API 检索访问令牌,然后使用该令牌验证 API 确实是 运行 版本 2,并尝试访问特定的 Docker 图像配置。恐怕为了对此进行测试,您将需要访问私人 GCR 图像和其中一个标签的摘要。

#!/usr/bin/env bash

set -eu

token=$(gcloud auth print-access-token)
image=...
digest=sha256:...

get_token() {
    curl -sSL \
        -G \
        --http1.1 \
        -H "Authorization: Bearer ${token}" \
        -H "Accept: application/vnd.docker.distribution.manifest.v2+json" \
        --data-urlencode "scope=repository::pull" \
        --data-urlencode "service=gcr.io" \
        "https://gcr.io/v2/token" | jq -r '.token'
}

echo "---"
echo "Retrieving access token."
access_token=$(get_token ${image})

echo
echo "---"
echo "Testing version 2 capability with access token."
curl -sSL \
    --http1.1 \
    -o /dev/null \
    -w "%{http_code}" \
    -H "Authorization: Bearer ${access_token}" \
    -H "Accept: application/vnd.docker.distribution.manifest.v2+json" \
    https://gcr.io/v2/

echo
echo "---"
echo "Retrieving image configuration with access token."
curl -vL \
    --http1.1 \
    -o /dev/null \
    -w "%{http_code}" \
    -H "Authorization: Bearer ${access_token}" \
    -H "Accept: application/vnd.docker.distribution.manifest.v2+json" \
    "https://gcr.io/v2/${image}/blobs/${digest}"

我在 httpx 中另外创建了 two Jupyter notebooks demonstrating my solutions 并裸露了 urllib.request。 httpx 可以完美运行,而 urllib 以某种方式在图像配置请求上失败。我 运行 想找出不同之处。如果您自己 运行 笔记本,您会看到被调用的 URL 包含一个令牌作为查询参数(这是安全问题吗?)。当我打开 link 时,我实际上可以自己成功下载数据。也许 urllib 仍然通过 Bearer 令牌传递授权 header,使最后一次调用失败并显示 401 Unauthorized?

非常感谢任何见解。

我做了一些调查,我认为不同之处在于最后一次调用 "https://gcr.io/v2/${image}/blobs/${digest}" 实际上包含重定向。检查 curlhttpx 调用显示,在第二个重定向请求中,两者都不包含 Authorization header,而在我设置 urllib.request 在笔记本中,这个 header 总是包含在内。这导致 401 有点奇怪,但现在我知道如何解决它了。

编辑:我现在可以通过构建一个 urllib.request.Request 实例来确认,与链接的笔记本不同,使用请求的 add_unredirected_header 方法添加授权 header,一切都按预期进行.