forked from folbricht/desync
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathhttphandler.go
127 lines (113 loc) · 3.15 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
package desync
import (
"bytes"
"fmt"
"io"
"net/http"
"path/filepath"
"strings"
"github.com/pkg/errors"
)
// HTTPHandler is the server-side handler for a HTTP chunk store.
type HTTPHandler struct {
HTTPHandlerBase
s Store
SkipVerifyWrite bool
Uncompressed bool
}
// NewHTTPHandler initializes and returns a new HTTP handler for a chunks erver.
func NewHTTPHandler(s Store, writable, skipVerifyWrite, uncompressed bool) http.Handler {
return HTTPHandler{HTTPHandlerBase{"chunk", writable}, s, skipVerifyWrite, uncompressed}
}
func (h HTTPHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
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 {
if h.Uncompressed {
b, err = chunk.Uncompressed()
} else {
b, err = chunk.Compressed()
}
}
h.HTTPHandlerBase.get(id.String(), b, err, w)
}
func (h HTTPHandler) head(id ChunkID, w http.ResponseWriter) {
if h.s.HasChunk(id) {
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 chunk 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
var chunk *Chunk
if h.Uncompressed {
chunk, err = NewChunkWithID(id, b.Bytes(), nil, h.SkipVerifyWrite)
} else {
chunk, err = NewChunkWithID(id, nil, b.Bytes(), 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(path string) (ChunkID, error) {
ext := CompressedChunkExt
if h.Uncompressed {
if strings.HasSuffix(path, CompressedChunkExt) {
return ChunkID{}, errors.New("compressed chunk requested from http chunk store serving uncompressed chunks")
}
ext = UncompressedChunkExt
}
sID := strings.TrimSuffix(filepath.Base(path), 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 path != filepath.Join("/", sID[0:4], sID+ext) {
return ChunkID{}, fmt.Errorf("expected format '/<prefix>/<chunkid>%s", ext)
}
return ChunkIDFromString(sID)
}