12483: Delay flushing collection until successful http response.
[arvados.git] / services / keep-web / handler.go
index f10cb5977b6dda2d89d83b385d2e5ede934ab697..a30d40d217132127a3b62b0eaf0bfbbd6216b9fc 100644 (file)
@@ -6,6 +6,7 @@ package main
 
 import (
        "encoding/json"
+       "errors"
        "fmt"
        "html"
        "html/template"
@@ -96,6 +97,44 @@ 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 errors).
+type updateOnSuccess struct {
+       httpserver.ResponseWriter
+       update     func() error
+       sentHeader bool
+       dropBody   bool
+}
+
+var errUpdateFailed = errors.New("update failed")
+
+func (uos *updateOnSuccess) Write(p []byte) (int, error) {
+       if uos.dropBody {
+               return 0, errUpdateFailed
+       }
+       if !uos.sentHeader {
+               uos.WriteHeader(http.StatusOK)
+       }
+       return uos.ResponseWriter.Write(p)
+}
+
+func (uos *updateOnSuccess) WriteHeader(code int) {
+       if !uos.sentHeader {
+               if code >= 200 && code < 400 {
+                       if err := uos.update(); err != nil {
+                               http.Error(uos.ResponseWriter, err.Error(), http.StatusInternalServerError)
+                               uos.dropBody = true
+                               return
+                       }
+               }
+               uos.sentHeader = true
+       }
+       uos.ResponseWriter.WriteHeader(code)
+}
+
 var (
        webdavMethod = map[string]bool{
                "DELETE":   true,
@@ -368,17 +407,23 @@ func (h *handler) ServeHTTP(wOrig http.ResponseWriter, r *http.Request) {
                return
        }
        if webdavMethod[r.Method] {
-               var update func() error
-               if !arvadosclient.PDHMatch(targetID) {
-                       update = func() error {
-                               return h.Config.Cache.Update(client, *collection, fs)
-                       }
+               writing := !arvadosclient.PDHMatch(targetID)
+               if writing {
+                       // Save the collection only if/when all
+                       // webdav->filesystem operations succeed --
+                       // and send a 500 error 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,
-                               update: update,
+                               collfs:  fs,
+                               writing: writing,
                        },
                        LockSystem: h.webdavLS,
                        Logger: func(_ *http.Request, err error) {