-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcmd_blob_server.go
150 lines (130 loc) · 3.52 KB
/
cmd_blob_server.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
145
146
147
148
149
150
package main
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"regexp"
"github.com/gorilla/mux"
)
// STORAGE holds a handle to our selected storage-method.
var STORAGE StorageHandler
func HealthHandler(res http.ResponseWriter, req *http.Request) {
fmt.Fprintf(res, "alive")
}
// GetHandler allows a blob to be retrieved by name.
// This is called with requests like `GET /blob/XXXXXX`.
func GetHandler(res http.ResponseWriter, req *http.Request) {
var (
status int
err error
)
defer func() {
if nil != err {
http.Error(res, err.Error(), status)
}
}()
vars := mux.Vars(req)
id := vars["id"]
r, _ := regexp.Compile("^([a-z0-9]+)$")
if !r.MatchString(id) {
status = http.StatusInternalServerError
err = errors.New("alphanumeric IDs only")
return
}
if req.Method == "HEAD" {
res.Header().Set("Connection", "close")
if !STORAGE.Exists(id) {
res.WriteHeader(http.StatusNotFound)
}
return
}
data, meta := STORAGE.Get(id)
if data == nil {
http.NotFound(res, req)
} else {
if meta != nil {
for k, v := range meta {
// Special case to set the content-type of the returned value.
if k == "X-Mime-Type" {
res.Header().Set(k, v)
k = "Content-Type"
}
res.Header().Set(k, v)
}
}
io.Copy(res, bytes.NewReader(*data))
}
}
func MissingHandler(res http.ResponseWriter, req *http.Request) {
res.WriteHeader(http.StatusNotFound)
fmt.Fprintf(res, "404 - content is not hosted here.")
}
func ListHandler(res http.ResponseWriter, req *http.Request) {
list := STORAGE.Existing()
mapB, _ := json.Marshal(list)
fmt.Fprintf(res, string(mapB))
}
func UploadHandler(res http.ResponseWriter, req *http.Request) {
var (
status int
err error
)
defer func() {
if nil != err {
http.Error(res, err.Error(), status)
}
}()
// Get the name of the blob to upload.
vars := mux.Vars(req)
id := vars["id"]
// Ensure the ID is entirely alphanumeric, to prevent traversal attacks.
r, _ := regexp.Compile("^([a-z0-9]+)$")
if !r.MatchString(id) {
err = errors.New("alphanumeric IDs only")
status = http.StatusInternalServerError
return
}
// Read the body of the request.
content, err := ioutil.ReadAll(req.Body)
if err != nil {
err = errors.New("failed to read body")
status = http.StatusInternalServerError
return
}
if ok := STORAGE.Store(id, content); !ok {
err = errors.New("failed to write to storage")
status = http.StatusInternalServerError
return
}
// Output the result.
// { "id": "foo",
// "size": 1234,
// "status": "ok",
// }
out := fmt.Sprintf("{\"id\":\"%s\",\"status\":\"OK\",\"size\":%d}", id, len(content))
fmt.Fprintf(res, string(out))
}
// blobServer is our entry-point to the sub-command.
func blobServer(options blobServerCmd) {
// Create a storage system.
STORAGE = new(FilesystemStorage)
STORAGE.Setup(options.store)
// See https://github.com/gorilla/mux.
router := mux.NewRouter()
router.HandleFunc("/alive", HealthHandler).Methods("GET")
router.HandleFunc("/blob/{id}", GetHandler).Methods("GET")
router.HandleFunc("/blob/{id}", GetHandler).Methods("HEAD")
router.HandleFunc("/blob/{id}", UploadHandler).Methods("POST")
router.HandleFunc("/blobs", ListHandler).Methods("GET")
router.PathPrefix("/").HandlerFunc(MissingHandler)
http.Handle("/", router)
// Launch the server
fmt.Printf("blob-server available at http://%s:%d/\nUploads will be written beneath: %s\n",
options.host, options.port, options.store)
log.Fatal(http.ListenAndServe(fmt.Sprintf("%s:%d", options.host, options.port), nil))
}