-
Notifications
You must be signed in to change notification settings - Fork 0
/
object.go
98 lines (83 loc) · 2.43 KB
/
object.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
package main
import (
"fmt"
"io"
"log/slog"
"net/http"
"strings"
"time"
)
func handleObject(w http.ResponseWriter, r *http.Request) {
var mountPoint = findMountPoint(r.URL.Path)
if mountPoint == nil {
w.WriteHeader(http.StatusNotFound)
return
}
bucket := client.Bucket(mountPoint.Bucket)
obj := bucket.Object(mountPoint.Prefix + strings.TrimPrefix(r.URL.Path, mountPoint.Path))
attrs, err := obj.Attrs(r.Context())
if err != nil {
slog.Error("failed to get object attributes",
"bucket", obj.BucketName(),
"object", obj.ObjectName(),
"err", err)
w.WriteHeader(http.StatusNotFound)
return
}
var h = w.Header()
h.Set("ETag", fmt.Sprintf("\"%s\"", attrs.Etag))
h.Set("Last-Modified", attrs.Updated.Format(http.TimeFormat))
// Conditional requests
if inm := r.Header.Get("If-None-Match"); inm != "" {
inm = strings.Trim(strings.TrimPrefix(inm, "W/"), "\"")
if inm == attrs.Etag {
w.WriteHeader(http.StatusNotModified)
return
}
}
if t, err := time.Parse(http.TimeFormat, r.Header.Get("If-Modified-Since")); err == nil {
if !attrs.Updated.Truncate(time.Second).After(t) {
w.WriteHeader(http.StatusNotModified)
return
}
}
// Set headers
h.Set("Content-Length", fmt.Sprintf("%d", attrs.Size))
setHeaderIfNotEmpty(h, "Content-Type", attrs.ContentType)
setHeaderIfNotEmpty(h, "Content-Encoding", attrs.ContentEncoding)
setHeaderIfNotEmpty(h, "Content-Disposition", attrs.ContentDisposition)
if !setHeaderIfNotEmpty(h, "Cache-Control", attrs.CacheControl) {
h.Set("Cache-Control", defaultCacheControl)
}
for k, v := range attrs.Metadata {
setHeaderIfNotEmpty(h, k, v)
}
h.Set("X-Fetched-At", time.Now().Format(http.TimeFormat))
if r.Method == http.MethodHead {
return
}
slog.Info("serving object", "bucket", obj.BucketName(), "object", obj.ObjectName())
reader, err := obj.NewReader(r.Context())
if err != nil {
slog.Error("failed to read object",
"bucket", obj.BucketName(),
"object", obj.ObjectName(),
"err", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
defer reader.Close()
// Reset Content-Length (just in case?)
h.Set("Content-Length", fmt.Sprintf("%d", reader.Attrs.Size))
if _, err := io.Copy(w, reader); err != nil {
slog.Error("failed to write object", "err", err)
w.WriteHeader(http.StatusInternalServerError)
}
}
func setHeaderIfNotEmpty(h http.Header, key, value string) bool {
if value != "" {
h.Set(key, value)
return true
}
return false
}