-
Notifications
You must be signed in to change notification settings - Fork 43
/
httphandler.go
144 lines (128 loc) · 3.8 KB
/
httphandler.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
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
package desync
import (
"bytes"
"fmt"
"io"
"net/http"
"path"
"strings"
"github.com/pkg/errors"
)
// HTTPHandler is the server-side handler for a HTTP chunk store.
type HTTPHandler struct {
HTTPHandlerBase
s Store
SkipVerifyWrite bool
// Storage-side of the converters in this case is towards the client
converters Converters
// Use the file extension for compressed chunks
compressed bool
}
// NewHTTPHandler initializes and returns a new HTTP handler for a chunks server.
func NewHTTPHandler(s Store, writable, skipVerifyWrite bool, converters Converters, auth string) http.Handler {
compressed := converters.hasCompression()
return HTTPHandler{HTTPHandlerBase{"chunk", writable, auth}, s, skipVerifyWrite, converters, compressed}
}
func (h HTTPHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if h.authorization != "" && r.Header.Get("Authorization") != h.authorization {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
id, err := h.idFromPath(r.URL.Path)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
switch r.Method {
case "GET":
h.get(id, w)
case "HEAD":
h.head(id, w)
case "PUT":
h.put(id, w, r)
default:
w.WriteHeader(http.StatusMethodNotAllowed)
_, _ = w.Write([]byte("only GET, PUT and HEAD are supported"))
}
}
func (h HTTPHandler) get(id ChunkID, w http.ResponseWriter) {
var b []byte
chunk, err := h.s.GetChunk(id)
if err == nil {
// Optimization for when the chunk modifiers match those
// of the chunk server. In that case it's not necessary
// to convert back and forth. Just use the raw data as loaded
// from the store.
if len(chunk.storage) > 0 && h.converters.equal(chunk.converters) {
b = chunk.storage
} else {
b, err = chunk.Data()
if err == nil {
b, err = h.converters.toStorage(b)
}
}
}
h.HTTPHandlerBase.get(id.String(), b, err, w)
}
func (h HTTPHandler) head(id ChunkID, w http.ResponseWriter) {
hasChunk, err := h.s.HasChunk(id)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if hasChunk {
w.WriteHeader(http.StatusOK)
return
}
w.WriteHeader(http.StatusNotFound)
}
func (h HTTPHandler) put(id ChunkID, w http.ResponseWriter, r *http.Request) {
err := h.HTTPHandlerBase.validateWritable(h.s.String(), w, r)
if err != nil {
return
}
// The upstream store needs to support writing as well
s, ok := h.s.(WriteStore)
if !ok {
w.WriteHeader(http.StatusBadRequest)
fmt.Fprintf(w, "upstream chunk store '%s' does not support writing\n", h.s)
return
}
// Read the raw chunk data into memory
b := new(bytes.Buffer)
if _, err := io.Copy(b, r.Body); err != nil {
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprintln(w, err)
return
}
// Turn it into a chunk, and validate the ID unless verification is disabled
chunk, err := NewChunkFromStorage(id, b.Bytes(), h.converters, h.SkipVerifyWrite)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// Store it upstream
if err := s.StoreChunk(chunk); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
}
func (h HTTPHandler) idFromPath(p string) (ChunkID, error) {
ext := CompressedChunkExt
if !h.compressed {
if strings.HasSuffix(p, CompressedChunkExt) {
return ChunkID{}, errors.New("compressed chunk requested from http chunk store serving uncompressed chunks")
}
ext = UncompressedChunkExt
}
sID := strings.TrimSuffix(path.Base(p), ext)
if len(sID) < 4 {
return ChunkID{}, fmt.Errorf("expected format '/<prefix>/<chunkid>%s", ext)
}
// Make sure the prefix does match the first characters of the ID.
if p != path.Join("/", sID[0:4], sID+ext) {
return ChunkID{}, fmt.Errorf("expected format '/<prefix>/<chunkid>%s", ext)
}
return ChunkIDFromString(sID)
}