Revision 654ee915

View differences:

sdk/go/arvados/collection_fs.go
10 10
	"os"
11 11
	"path"
12 12
	"strings"
13
	"sync"
13 14
	"time"
14 15

  
15 16
	"git.curoverse.com/arvados.git/sdk/go/manifest"
......
150 151
	collection *Collection
151 152
	client     *Client
152 153
	kc         keepClient
154
	sizes      map[string]int64
155
	sizesOnce  sync.Once
153 156
}
154 157

  
155 158
// FileSystem returns an http.FileSystem for the collection.
......
225 228
// fileSizes returns a map of files that can be opened. Each key
226 229
// starts with "./".
227 230
func (c *collectionFS) fileSizes() map[string]int64 {
228
	var sizes map[string]int64
229
	m := manifest.Manifest{Text: c.collection.ManifestText}
230
	for ms := range m.StreamIter() {
231
		for _, fss := range ms.FileStreamSegments {
232
			if sizes == nil {
233
				sizes = map[string]int64{}
231
	c.sizesOnce.Do(func() {
232
		c.sizes = map[string]int64{}
233
		m := manifest.Manifest{Text: c.collection.ManifestText}
234
		for ms := range m.StreamIter() {
235
			for _, fss := range ms.FileStreamSegments {
236
				c.sizes[ms.StreamName+"/"+fss.Name] += int64(fss.SegLen)
234 237
			}
235
			sizes[ms.StreamName+"/"+fss.Name] += int64(fss.SegLen)
236 238
		}
237
	}
238
	return sizes
239
	})
240
	return c.sizes
239 241
}
services/keep-web/handler.go
24 24
	"git.curoverse.com/arvados.git/sdk/go/health"
25 25
	"git.curoverse.com/arvados.git/sdk/go/httpserver"
26 26
	"git.curoverse.com/arvados.git/sdk/go/keepclient"
27
	"golang.org/x/net/webdav"
27 28
)
28 29

  
29 30
type handler struct {
......
31 32
	clientPool    *arvadosclient.ClientPool
32 33
	setupOnce     sync.Once
33 34
	healthHandler http.Handler
35
	webdavLS      webdav.LockSystem
34 36
}
35 37

  
36 38
// parseCollectionIDFromDNSName returns a UUID or PDH if s begins with
......
79 81
		Token:  h.Config.ManagementToken,
80 82
		Prefix: "/_health/",
81 83
	}
84

  
85
	h.webdavLS = webdav.NewMemLS()
82 86
}
83 87

  
84 88
func (h *handler) serveStatus(w http.ResponseWriter, r *http.Request) {
......
90 94
	json.NewEncoder(w).Encode(status)
91 95
}
92 96

  
97
var (
98
	webdavMethod = map[string]bool{
99
		"OPTIONS":  true,
100
		"PROPFIND": true,
101
		"LOCK":     true,
102
		"UNLOCK":   true,
103
	}
104
	fsMethod = map[string]bool{
105
		"GET":  true,
106
		"HEAD": true,
107
		"POST": true,
108
	}
109
)
110

  
93 111
// ServeHTTP implements http.Handler.
94 112
func (h *handler) ServeHTTP(wOrig http.ResponseWriter, r *http.Request) {
95 113
	h.setupOnce.Do(h.setup)
......
123 141
		return
124 142
	}
125 143

  
126
	if r.Method == "OPTIONS" {
127
		method := r.Header.Get("Access-Control-Request-Method")
128
		if method != "GET" && method != "POST" {
144
	if method := r.Header.Get("Access-Control-Request-Method"); method != "" && r.Method == "OPTIONS" {
145
		if !fsMethod[method] && !webdavMethod[method] {
129 146
			statusCode = http.StatusMethodNotAllowed
130 147
			return
131 148
		}
132 149
		w.Header().Set("Access-Control-Allow-Headers", "Range")
133
		w.Header().Set("Access-Control-Allow-Methods", "GET, POST")
150
		w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PROPFIND, LOCK, UNLOCK")
134 151
		w.Header().Set("Access-Control-Allow-Origin", "*")
135 152
		w.Header().Set("Access-Control-Max-Age", "86400")
136 153
		statusCode = http.StatusOK
137 154
		return
138 155
	}
139 156

  
140
	if r.Method != "GET" && r.Method != "POST" {
157
	if !fsMethod[r.Method] && !webdavMethod[r.Method] {
141 158
		statusCode, statusText = http.StatusMethodNotAllowed, r.Method
142 159
		return
143 160
	}
......
337 354
		AuthToken: arv.ApiToken,
338 355
		Insecure:  arv.ApiInsecure,
339 356
	}, kc)
357
	if webdavMethod[r.Method] {
358
		h := webdav.Handler{
359
			Prefix:     "/" + strings.Join(pathParts[:stripParts], "/"),
360
			FileSystem: &webdavFS{httpfs: fs},
361
			LockSystem: h.webdavLS,
362
			Logger: func(_ *http.Request, err error) {
363
				if os.IsNotExist(err) {
364
					statusCode, statusText = http.StatusNotFound, err.Error()
365
				} else if err != nil {
366
					statusCode, statusText = http.StatusInternalServerError, err.Error()
367
				}
368
			},
369
		}
370
		h.ServeHTTP(w, r)
371
		return
372
	}
373

  
340 374
	openPath := "/" + strings.Join(targetPath, "/")
341 375
	if f, err := fs.Open(openPath); os.IsNotExist(err) {
342 376
		// Requested non-existent path
services/keep-web/handler_test.go
43 43
	c.Check(resp.Code, check.Equals, http.StatusOK)
44 44
	c.Check(resp.Body.String(), check.Equals, "")
45 45
	c.Check(resp.Header().Get("Access-Control-Allow-Origin"), check.Equals, "*")
46
	c.Check(resp.Header().Get("Access-Control-Allow-Methods"), check.Equals, "GET, POST")
46
	c.Check(resp.Header().Get("Access-Control-Allow-Methods"), check.Equals, "GET, POST, OPTIONS, PROPFIND, LOCK, UNLOCK")
47 47
	c.Check(resp.Header().Get("Access-Control-Allow-Headers"), check.Equals, "Range")
48 48

  
49 49
	// Check preflight for a disallowed request
services/keep-web/webdav.go
1
// Copyright (C) The Arvados Authors. All rights reserved.
2
//
3
// SPDX-License-Identifier: AGPL-3.0
4

  
5
package main
6

  
7
import (
8
	"errors"
9
	"net/http"
10
	"os"
11

  
12
	"golang.org/x/net/context"
13
	"golang.org/x/net/webdav"
14
)
15

  
16
var errReadOnly = errors.New("read-only filesystem")
17

  
18
// webdavFS implements a read-only webdav.FileSystem by wrapping
19
// http.Filesystem.
20
type webdavFS struct {
21
	httpfs http.FileSystem
22
}
23

  
24
var _ webdav.FileSystem = &webdavFS{}
25

  
26
func (fs *webdavFS) Mkdir(ctx context.Context, name string, perm os.FileMode) error {
27
	return errReadOnly
28
}
29

  
30
func (fs *webdavFS) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (webdav.File, error) {
31
	f, err := fs.httpfs.Open(name)
32
	if err != nil {
33
		return nil, err
34
	}
35
	return &webdavFile{File: f}, nil
36
}
37

  
38
func (fs *webdavFS) RemoveAll(ctx context.Context, name string) error {
39
	return errReadOnly
40
}
41

  
42
func (fs *webdavFS) Rename(ctx context.Context, oldName, newName string) error {
43
	return errReadOnly
44
}
45

  
46
func (fs *webdavFS) Stat(ctx context.Context, name string) (os.FileInfo, error) {
47
	if f, err := fs.httpfs.Open(name); err != nil {
48
		return nil, err
49
	} else {
50
		return f.Stat()
51
	}
52
}
53

  
54
// webdavFile implements a read-only webdav.File by wrapping
55
// http.File. Writes fail.
56
type webdavFile struct {
57
	http.File
58
}
59

  
60
func (f *webdavFile) Write([]byte) (int, error) {
61
	return 0, errReadOnly
62
}
vendor/vendor.json
302 302
			"revisionTime": "2017-05-17T20:48:28Z"
303 303
		},
304 304
		{
305
			"checksumSHA1": "yppNZB5y0GmJrt/TYOASrhe2oVc=",
306
			"path": "golang.org/x/net/webdav",
307
			"revision": "f01ecb60fe3835d80d9a0b7b2bf24b228c89260e",
308
			"revisionTime": "2017-07-11T11:58:19Z"
309
		},
310
		{
311
			"checksumSHA1": "XgtZlzd39qIkBHs6XYrq9dhTCog=",
312
			"path": "golang.org/x/net/webdav/internal/xml",
313
			"revision": "f01ecb60fe3835d80d9a0b7b2bf24b228c89260e",
314
			"revisionTime": "2017-07-11T11:58:19Z"
315
		},
316
		{
305 317
			"checksumSHA1": "7EZyXN0EmZLgGxZxK01IJua4c8o=",
306 318
			"path": "golang.org/x/net/websocket",
307 319
			"revision": "513929065c19401a1c7b76ecd942f9f86a0c061b",

Also available in: Unified diff