1
// Copyright 2013 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
18
// storageBackend provides HTTP access to a defined path. The local
19
// provider otimally would use a much simpler Storage, but this
20
// code may be useful in storage-free environs. Here it requires
21
// additional authentication work before it's viable.
22
type storageBackend struct {
26
// ServeHTTP handles the HTTP requests to the container.
27
func (s *storageBackend) ServeHTTP(w http.ResponseWriter, req *http.Request) {
30
if strings.HasSuffix(req.URL.Path, "*") {
38
s.handleDelete(w, req)
40
http.Error(w, "method "+req.Method+" is not supported", http.StatusMethodNotAllowed)
44
// handleGet returns a storage file to the client.
45
func (s *storageBackend) handleGet(w http.ResponseWriter, req *http.Request) {
46
data, err := ioutil.ReadFile(filepath.Join(s.dir, req.URL.Path))
48
http.Error(w, fmt.Sprintf("404 %v", err), http.StatusNotFound)
51
w.Header().Set("Content-Type", "application/octet-stream")
55
// handleList returns the file names in the storage to the client.
56
func (s *storageBackend) handleList(w http.ResponseWriter, req *http.Request) {
57
fp := filepath.Join(s.dir, req.URL.Path)
58
dir, prefix := filepath.Split(fp)
59
names, err := readDirs(dir, prefix[:len(prefix)-1], len(s.dir)+1)
61
http.Error(w, fmt.Sprintf("404 %v", err), http.StatusNotFound)
65
data := []byte(strings.Join(names, "\n"))
66
w.Header().Set("Content-Type", "application/octet-stream")
70
// readDirs reads the directory hierarchy and compares the found
71
// names with the given prefix.
72
func readDirs(dir, prefix string, start int) ([]string, error) {
74
fis, err := ioutil.ReadDir(dir)
78
for _, fi := range fis {
80
if strings.HasPrefix(name, prefix) {
82
dnames, err := readDirs(filepath.Join(dir, name), prefix, start)
86
names = append(names, dnames...)
89
fullname := filepath.Join(dir, name)[start:]
90
names = append(names, fullname)
96
// handlePut stores data from the client in the storage.
97
func (s *storageBackend) handlePut(w http.ResponseWriter, req *http.Request) {
98
fp := filepath.Join(s.dir, req.URL.Path)
99
dir, _ := filepath.Split(fp)
100
err := os.MkdirAll(dir, 0777)
102
http.Error(w, fmt.Sprintf("500 %v", err), http.StatusInternalServerError)
105
out, err := os.Create(fp)
107
http.Error(w, fmt.Sprintf("500 %v", err), http.StatusInternalServerError)
111
if _, err := io.Copy(out, req.Body); err != nil {
112
http.Error(w, fmt.Sprintf("500 %v", err), http.StatusInternalServerError)
115
w.WriteHeader(http.StatusCreated)
118
// handleDelete removes a file from the storage.
119
func (s *storageBackend) handleDelete(w http.ResponseWriter, req *http.Request) {
120
fp := filepath.Join(s.dir, req.URL.Path)
121
if err := os.Remove(fp); err != nil && !os.IsNotExist(err) {
122
http.Error(w, fmt.Sprintf("500 %v", err), http.StatusInternalServerError)
125
w.WriteHeader(http.StatusOK)
128
// Serve runs a storage server on the given network address, storing
129
// data under the given directory. It returns the network listener.
130
// This can then be attached to with Client.
131
func Serve(addr, dir string) (net.Listener, error) {
132
backend := &storageBackend{
135
info, err := os.Stat(dir)
137
return nil, fmt.Errorf("cannot stat directory: %v", err)
140
return nil, fmt.Errorf("%q is not a directory", dir)
142
listener, err := net.Listen("tcp", addr)
144
return nil, fmt.Errorf("cannot start listener: %v", err)
146
mux := http.NewServeMux()
147
mux.Handle("/", backend)
148
go http.Serve(listener, mux)