1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
16
17
18 package gethandler
19
20 import (
21 "fmt"
22 "io"
23 "log"
24 "net/http"
25 "os"
26 "regexp"
27 "strings"
28 "time"
29
30 "perkeep.org/internal/httputil"
31 "perkeep.org/pkg/blob"
32
33 "go4.org/readerutil"
34 )
35
36 const (
37 HTTP_CACHE_DURATION = 10 * 356 * 24 * time.Hour
38 )
39
40 var getPattern = regexp.MustCompile(`/camli/` + blob.Pattern + `$`)
41
42
43 type Handler struct {
44 Fetcher blob.Fetcher
45 }
46
47
48 func CreateGetHandler(fetcher blob.Fetcher) http.Handler {
49 return &Handler{Fetcher: fetcher}
50 }
51
52 func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
53 blobRef := blobFromURLPath(req.URL.Path)
54 if !blobRef.Valid() {
55 http.Error(rw, "Malformed GET URL.", http.StatusBadRequest)
56 return
57 }
58
59 ServeBlobRef(rw, req, blobRef, h.Fetcher)
60 }
61
62
63 func ServeBlobRef(rw http.ResponseWriter, req *http.Request, blobRef blob.Ref, fetcher blob.Fetcher) {
64 ctx := req.Context()
65 if fetcher == nil {
66 log.Printf("gethandler: no fetcher configured for %s (ref=%v)", req.URL.Path, blobRef)
67 rw.WriteHeader(http.StatusNotFound)
68 io.WriteString(rw, "no fetcher configured")
69 return
70 }
71 rc, size, err := fetcher.Fetch(ctx, blobRef)
72 switch err {
73 case nil:
74 break
75 case os.ErrNotExist:
76 rw.WriteHeader(http.StatusNotFound)
77 fmt.Fprintf(rw, "Blob %q not found", blobRef)
78 return
79 default:
80 httputil.ServeError(rw, req, err)
81 return
82 }
83 defer rc.Close()
84 rw.Header().Set("Content-Type", "application/octet-stream")
85 rw.Header().Set("Cache-Control", fmt.Sprintf("max-age=%d, immutable", int(HTTP_CACHE_DURATION.Seconds())))
86
87 var content io.ReadSeeker = readerutil.NewFakeSeeker(rc, int64(size))
88 rangeHeader := req.Header.Get("Range") != ""
89 const small = 32 << 10
90 var b *blob.Blob
91 if rangeHeader || size < small {
92
93
94 b, err = blob.FromReader(ctx, blobRef, rc, size)
95 if err != nil {
96 httputil.ServeError(rw, req, err)
97 return
98 }
99 content, err = b.ReadAll(ctx)
100 if err != nil {
101 httputil.ServeError(rw, req, err)
102 return
103 }
104 }
105 if !rangeHeader && size < small {
106
107
108
109
110 isUTF8, err := b.IsUTF8(ctx)
111 if err != nil {
112 httputil.ServeError(rw, req, err)
113 return
114 }
115 if isUTF8 {
116 rw.Header().Set("Content-Type", "text/plain; charset=utf-8")
117 }
118 }
119 http.ServeContent(rw, req, "", dummyModTime, content)
120 }
121
122
123
124
125
126 var dummyModTime = time.Unix(1276213335, 0)
127
128 func blobFromURLPath(path string) blob.Ref {
129 matches := getPattern.FindStringSubmatch(path)
130 if len(matches) != 3 {
131 return blob.Ref{}
132 }
133 return blob.ParseOrZero(strings.TrimPrefix(matches[0], "/camli/"))
134 }