【问题标题】:How to use GetObject when key has leading slashes当键有前导斜杠时如何使用GetObject
【发布时间】:2026-01-22 19:55:01
【问题描述】:

我需要使用 Go AWS SDK 中的 GetObject 系列函数从 S3 存储桶中获取对象,其中对象的键可能以一个或多个斜杠开头。但是,SDK 似乎删除了那些前导斜杠,从而更改了密钥。

我创建了存储桶并将一些数据放入如下:

$ aws s3 mb <TEST BUCKET>
$ aws s3 cp <SOME FILE> s3://<TEST BUCKET>//leadingslash

以下代码显示ListObjects 正确返回带有前导斜杠的密钥,但是在运行时日志显示GET 请求在没有前导斜杠的情况下完成。

package main

import (
    "log"

    "github.com/aws/aws-sdk-go/aws"
    "github.com/aws/aws-sdk-go/aws/session"
    "github.com/aws/aws-sdk-go/service/s3"
)

func main() {
    bucket := "<TEST BUCKET>"
    region := "<TEST BUCKET REGION>"
    config := (&aws.Config{Region: &region}).WithLogLevel(aws.LogDebugWithHTTPBody)
    s3svc := s3.New(session.New(config))

    listInput := s3.ListObjectsInput{
        Bucket: &bucket,
    }
    listOutput, err := s3svc.ListObjects(&listInput)
    if err != nil {
        log.Fatalf("Failed to list objects: %v", err)
    } else {
        log.Printf("Good: %v", listOutput)
    }

    for _, object := range listOutput.Contents {
        getInput := s3.GetObjectInput{
            Bucket: &bucket,
            Key:    object.Key,
        }
        getOutput, err := s3svc.GetObject(&getInput)
        if err != nil {
            log.Fatalf("Failed to HEAD object: %v", err)
        } else {
            log.Printf("Good: %v", getOutput)
        }
    }
}

在启用调试日志记录的情况下调用 GetObject 表明 SDK 执行了以下请求:

GET /leadingslash HTTP/1.1

这缺少前导斜杠并返回 404 错误。

我应该如何使用 Go SDK 获取此类对象?我无法控制对象的密钥。

在将密钥传递给 GetObject 之前,我曾尝试对密钥进行 URL 转义,但是百分号被转义并且密钥发生了变化。

我使用 Go 1.9 linux/amd64 和 SDK 1.12.62。

【问题讨论】:

  • 键中的前导斜杠从根本上说是不正确的,因为从概念上讲,URL 中的/ 位于对象键中的第一个字符之前,而不是第一个字符密钥本身(很像服务器根目录中的文件实际命名foo而不是/foo,前者是名称,后者是路径)...但不幸的是,当开发人员错误地提供斜线时,大多数 SDK 都会通过吞下前导斜线来掩盖这一点。如果您无法更正错误的不正确密钥,您可能不得不破解 SDK 或滚动您自己的签名代码。

标签: amazon-s3 aws-sdk-go


【解决方案1】:

按照@michael-sqlbot 的建议,通过使用自定义逻辑构建请求解决了这个问题:

package main

import (
    "fmt"
    "io/ioutil"
    "net/http"
    "time"

    "github.com/aws/aws-sdk-go/aws/credentials"
    "github.com/aws/aws-sdk-go/aws/signer/v4"
    "github.com/aws/aws-sdk-go/private/protocol/rest"
)

const (
    BUCKET = "<TEST BUCKET>"
    KEY    = "/leadingslashkey"
    REGION = "<TEST BUCKET REGION>"
)

func main() {
    credentials := credentials.NewEnvCredentials()
    signer := v4.NewSigner(credentials)
    request, err := http.NewRequest(
        http.MethodGet,
        fmt.Sprintf("https://%s.s3.%s.amazonaws.com/%s", BUCKET, REGION, rest.EscapePath(KEY, false)),
        nil,
    )
    if err != nil {
        println(err.Error())
        return
    }
    header, err := signer.Sign(
        request,
        nil,
        "s3",
        REGION,
        time.Now(),
    )
    if err != nil {
        println(err, err.Error())
        return
    }
    fmt.Printf("%#v\n", header)
    fmt.Printf("%#v\n", request)
    client := http.Client{}
    response, err := client.Do(request)
    if err != nil {
        println(err, err.Error())
        return
    }
    fmt.Printf("%#v\n", response)
    out, _ := ioutil.ReadAll(response.Body)
    println(string(out))
}

【讨论】:

    【解决方案2】:

    刚遇到同样的问题,但经过一段时间的挣扎和调试,我找到了另一个解决方案。

    解决方案是将其添加到您的 s3 客户端配置中。

    DisableRestProtocolURICleaning: aws.Bool(true),
    

    来自AWS SDK for Go API Reference

    自动 URI 清理

    与键包含相邻斜杠的对象交互(例如 bucketname/foo//bar/objectname) 需要设置 在使用的 aws.Config 结构中将 DisableRestProtocolURICleaning 设置为 true 由服务客户端提供。

    svc := s3.New(sess, &aws.Config{
        DisableRestProtocolURICleaning: aws.Bool(true),
    })
    out, err := svc.GetObject(&s3.GetObjectInput {
        Bucket: aws.String("bucketname"),
            Key: aws.String("//foo//bar//moo"),
    })
    

    【讨论】: