X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/eae1286badb67ee63888633ff59bda9cb736131e..95ec747218f048e5bbfb986ff4eaeba2d3d2f80b:/services/keep-web/handler.go diff --git a/services/keep-web/handler.go b/services/keep-web/handler.go index fd36218bc1..c9148a562e 100644 --- a/services/keep-web/handler.go +++ b/services/keep-web/handler.go @@ -10,6 +10,7 @@ import ( "html" "html/template" "io" + "log" "net/http" "net/url" "os" @@ -96,10 +97,62 @@ func (h *handler) serveStatus(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(status) } +// updateOnSuccess wraps httpserver.ResponseWriter. If the handler +// sends an HTTP header indicating success, updateOnSuccess first +// calls the provided update func. If the update func fails, a 500 +// response is sent, and the status code and body sent by the handler +// are ignored (all response writes return the update error). +type updateOnSuccess struct { + httpserver.ResponseWriter + update func() error + sentHeader bool + err error +} + +func (uos *updateOnSuccess) Write(p []byte) (int, error) { + if uos.err != nil { + return 0, uos.err + } + if !uos.sentHeader { + uos.WriteHeader(http.StatusOK) + } + return uos.ResponseWriter.Write(p) +} + +func (uos *updateOnSuccess) WriteHeader(code int) { + if !uos.sentHeader { + uos.sentHeader = true + if code >= 200 && code < 400 { + if uos.err = uos.update(); uos.err != nil { + code := http.StatusInternalServerError + if err, ok := uos.err.(*arvados.TransactionError); ok { + code = err.StatusCode + } + log.Printf("update() changes response to HTTP %d: %T %q", code, uos.err, uos.err) + http.Error(uos.ResponseWriter, uos.err.Error(), code) + return + } + } + } + uos.ResponseWriter.WriteHeader(code) +} + var ( + writeMethod = map[string]bool{ + "DELETE": true, + "MKCOL": true, + "MOVE": true, + "PUT": true, + "RMCOL": true, + } webdavMethod = map[string]bool{ + "DELETE": true, + "MKCOL": true, + "MOVE": true, "OPTIONS": true, "PROPFIND": true, + "PUT": true, + "RMCOL": true, } browserMethod = map[string]bool{ "GET": true, @@ -147,7 +200,7 @@ func (h *handler) ServeHTTP(wOrig http.ResponseWriter, r *http.Request) { return } w.Header().Set("Access-Control-Allow-Headers", "Authorization, Content-Type, Range") - w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PROPFIND") + w.Header().Set("Access-Control-Allow-Methods", "DELETE, GET, MKCOL, MOVE, OPTIONS, POST, PROPFIND, PUT, RMCOL") w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Max-Age", "86400") statusCode = http.StatusOK @@ -352,21 +405,39 @@ func (h *handler) ServeHTTP(wOrig http.ResponseWriter, r *http.Request) { } applyContentDispositionHdr(w, r, basename, attachment) - fs := collection.FileSystem(&arvados.Client{ + client := &arvados.Client{ APIHost: arv.ApiServer, AuthToken: arv.ApiToken, Insecure: arv.ApiInsecure, - }, kc) + } + fs, err := collection.FileSystem(client, kc) + if err != nil { + statusCode, statusText = http.StatusInternalServerError, err.Error() + return + } if webdavMethod[r.Method] { + writing := !arvadosclient.PDHMatch(targetID) && writeMethod[r.Method] + if writing { + // Save the collection only if/when all + // webdav->filesystem operations succeed -- + // and send a 500 error if the modified + // collection can't be saved. + w = &updateOnSuccess{ + ResponseWriter: w, + update: func() error { + return h.Config.Cache.Update(client, *collection, fs) + }} + } h := webdav.Handler{ - Prefix: "/" + strings.Join(pathParts[:stripParts], "/"), - FileSystem: &webdavFS{collfs: fs}, + Prefix: "/" + strings.Join(pathParts[:stripParts], "/"), + FileSystem: &webdavFS{ + collfs: fs, + writing: writing, + }, LockSystem: h.webdavLS, Logger: func(_ *http.Request, err error) { - if os.IsNotExist(err) { - statusCode, statusText = http.StatusNotFound, err.Error() - } else if err != nil { - statusCode, statusText = http.StatusInternalServerError, err.Error() + if err != nil { + log.Printf("error from webdav handler: %q", err) } }, }