mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-09-03 22:57:23 +00:00
fix(replication/s3sink): forward entry mime as ContentType (#9710)
* fix(replication/s3sink): forward entry.Attributes.Mime as ContentType
Same gap as the remote_storage S3 client: filer.replicate uploads via
s3manager.Uploader without populating ContentType, so replicated objects
on S3-compatible backends (e.g. Backblaze B2) store binary/octet-stream
and browsers refuse to render HTML, CSS, etc.
Pass entry.Attributes.Mime through to UploadInput.ContentType, leaving
the header unset when no Mime is recorded so the remote keeps its own
default.
* fix(replication/s3sink): nil-guard entry.Attributes when reading Mime
* Revert "fix(replication/s3sink): nil-guard entry.Attributes when reading Mime"
This reverts commit 08c3698e44.
The function already dereferences entry.Attributes.Mtime and
entry.Attributes.Md5 unconditionally on the same path, so a nil guard
on Mime alone is inconsistent and provides no real safety.
This commit is contained in:
@@ -209,6 +209,9 @@ func (s3sink *S3Sink) CreateEntry(key string, entry *filer_pb.Entry, signatures
|
||||
if tags != "" {
|
||||
uploadInput.Tagging = aws.String(tags)
|
||||
}
|
||||
if entry.Attributes.Mime != "" {
|
||||
uploadInput.ContentType = aws.String(entry.Attributes.Mime)
|
||||
}
|
||||
if len(entry.Attributes.Md5) > 0 {
|
||||
uploadInput.ContentMD5 = aws.String(base64.StdEncoding.EncodeToString([]byte(entry.Attributes.Md5)))
|
||||
}
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
package S3Sink
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
awss3 "github.com/aws/aws-sdk-go/service/s3"
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
||||
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestBuildTaggingString_ShouldStripTagPrefix(t *testing.T) {
|
||||
@@ -44,6 +52,72 @@ func TestBuildTaggingString_ShouldURLEncodeValues(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// capturePutRoundTripper records the s3manager.Uploader's PUT and replies 200
|
||||
// so the SDK is satisfied without making a real network call.
|
||||
type capturePutRoundTripper struct {
|
||||
uploadReq *http.Request
|
||||
}
|
||||
|
||||
func (c *capturePutRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
if req.Method == http.MethodPut {
|
||||
c.uploadReq = req.Clone(req.Context())
|
||||
}
|
||||
if req.Body != nil {
|
||||
_, _ = io.Copy(io.Discard, req.Body)
|
||||
_ = req.Body.Close()
|
||||
}
|
||||
return &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Body: io.NopCloser(strings.NewReader("")),
|
||||
Header: http.Header{"ETag": []string{"\"etag\""}},
|
||||
Request: req,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func newCapturingS3Sink(t *testing.T) (*S3Sink, *capturePutRoundTripper) {
|
||||
t.Helper()
|
||||
rt := &capturePutRoundTripper{}
|
||||
sess, err := session.NewSession(&aws.Config{
|
||||
Region: aws.String("us-east-1"),
|
||||
Endpoint: aws.String("https://example.invalid"),
|
||||
S3ForcePathStyle: aws.Bool(true),
|
||||
Credentials: credentials.NewStaticCredentials("k", "s", ""),
|
||||
HTTPClient: &http.Client{Transport: rt},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
s := &S3Sink{
|
||||
conn: awss3.New(sess),
|
||||
bucket: "bucket",
|
||||
uploaderConcurrency: 1,
|
||||
uploaderPartSizeMb: 5,
|
||||
}
|
||||
return s, rt
|
||||
}
|
||||
|
||||
func TestS3SinkCreateEntryPassesMimeAsContentType(t *testing.T) {
|
||||
sink, rt := newCapturingS3Sink(t)
|
||||
entry := &filer_pb.Entry{
|
||||
Attributes: &filer_pb.FuseAttributes{Mime: "text/html"},
|
||||
Content: []byte("<html></html>"),
|
||||
}
|
||||
require.NoError(t, sink.CreateEntry("/dir/test.html", entry, nil))
|
||||
|
||||
require.NotNil(t, rt.uploadReq, "uploader should have issued a PUT")
|
||||
require.Equal(t, "text/html", rt.uploadReq.Header.Get("Content-Type"))
|
||||
}
|
||||
|
||||
func TestS3SinkCreateEntryOmitsContentTypeWhenMimeMissing(t *testing.T) {
|
||||
sink, rt := newCapturingS3Sink(t)
|
||||
entry := &filer_pb.Entry{
|
||||
Attributes: &filer_pb.FuseAttributes{},
|
||||
Content: []byte("data"),
|
||||
}
|
||||
require.NoError(t, sink.CreateEntry("/dir/test.bin", entry, nil))
|
||||
|
||||
require.NotNil(t, rt.uploadReq, "uploader should have issued a PUT")
|
||||
require.Equal(t, "", rt.uploadReq.Header.Get("Content-Type"))
|
||||
}
|
||||
|
||||
func TestBuildTaggingString_EmptyWhenNoTags(t *testing.T) {
|
||||
extended := map[string][]byte{
|
||||
"Content-Encoding": []byte("gzip"),
|
||||
|
||||
Reference in New Issue
Block a user