Merge branch '9005-share-discovery'
[arvados.git] / services / keep-web / handler.go
index 11d0d96b298de5e4369474418f9f78583634510e..b7e39c6041d256188350d464da86ee39fb29bdeb 100644 (file)
@@ -4,14 +4,14 @@ import (
        "fmt"
        "html"
        "io"
-       "mime"
        "net/http"
        "net/url"
        "os"
-       "regexp"
+       "path"
        "strconv"
        "strings"
        "sync"
+       "time"
 
        "git.curoverse.com/arvados.git/sdk/go/arvadosclient"
        "git.curoverse.com/arvados.git/sdk/go/auth"
@@ -64,6 +64,7 @@ func parseCollectionIDFromURL(s string) string {
 
 func (h *handler) setup() {
        h.clientPool = arvadosclient.MakeClientPool()
+       keepclient.RefreshServiceDiscoveryOnSIGHUP()
 }
 
 // ServeHTTP implements http.Handler.
@@ -94,6 +95,20 @@ func (h *handler) ServeHTTP(wOrig http.ResponseWriter, r *http.Request) {
                httpserver.Log(remoteAddr, statusCode, statusText, w.WroteBodyBytes(), r.Method, r.Host, r.URL.Path, r.URL.RawQuery)
        }()
 
+       if r.Method == "OPTIONS" {
+               method := r.Header.Get("Access-Control-Request-Method")
+               if method != "GET" && method != "POST" {
+                       statusCode = http.StatusMethodNotAllowed
+                       return
+               }
+               w.Header().Set("Access-Control-Allow-Headers", "Range")
+               w.Header().Set("Access-Control-Allow-Methods", "GET, POST")
+               w.Header().Set("Access-Control-Allow-Origin", "*")
+               w.Header().Set("Access-Control-Max-Age", "86400")
+               statusCode = http.StatusOK
+               return
+       }
+
        if r.Method != "GET" && r.Method != "POST" {
                statusCode, statusText = http.StatusMethodNotAllowed, r.Method
                return
@@ -143,17 +158,19 @@ func (h *handler) ServeHTTP(wOrig http.ResponseWriter, r *http.Request) {
        } else if len(pathParts) >= 3 && pathParts[0] == "collections" {
                if len(pathParts) >= 5 && pathParts[1] == "download" {
                        // /collections/download/ID/TOKEN/PATH...
-                       targetID = pathParts[2]
+                       targetID = parseCollectionIDFromURL(pathParts[2])
                        tokens = []string{pathParts[3]}
                        targetPath = pathParts[4:]
                        pathToken = true
                } else {
                        // /collections/ID/PATH...
-                       targetID = pathParts[1]
+                       targetID = parseCollectionIDFromURL(pathParts[1])
                        tokens = h.Config.AnonymousTokens
                        targetPath = pathParts[2:]
                }
-       } else {
+       }
+
+       if targetID == "" {
                statusCode = http.StatusNotFound
                return
        }
@@ -319,12 +336,6 @@ func (h *handler) ServeHTTP(wOrig http.ResponseWriter, r *http.Request) {
                statusCode, statusText = http.StatusInternalServerError, err.Error()
                return
        }
-       if kc.Client != nil && kc.Client.Transport != nil {
-               // Workaround for https://dev.arvados.org/issues/9005
-               if t, ok := kc.Client.Transport.(*http.Transport); ok {
-                       defer t.CloseIdleConnections()
-               }
-       }
        rdr, err := kc.CollectionFileReader(collection, filename)
        if os.IsNotExist(err) {
                statusCode = http.StatusNotFound
@@ -335,51 +346,15 @@ func (h *handler) ServeHTTP(wOrig http.ResponseWriter, r *http.Request) {
        }
        defer rdr.Close()
 
-       basenamePos := strings.LastIndex(filename, "/")
-       if basenamePos < 0 {
-               basenamePos = 0
-       }
-       extPos := strings.LastIndex(filename, ".")
-       if extPos > basenamePos {
-               // Now extPos is safely >= 0.
-               if t := mime.TypeByExtension(filename[extPos:]); t != "" {
-                       w.Header().Set("Content-Type", t)
-               }
-       }
-       if rdr, ok := rdr.(keepclient.ReadCloserWithLen); ok {
-               w.Header().Set("Content-Length", fmt.Sprintf("%d", rdr.Len()))
-       }
-
-       applyContentDispositionHdr(w, r, filename[basenamePos:], attachment)
-       rangeRdr, statusCode := applyRangeHdr(w, r, rdr)
+       basename := path.Base(filename)
+       applyContentDispositionHdr(w, r, basename, attachment)
 
-       w.WriteHeader(statusCode)
-       _, err = io.Copy(w, rangeRdr)
+       modstr, _ := collection["modified_at"].(string)
+       modtime, err := time.Parse(time.RFC3339Nano, modstr)
        if err != nil {
-               statusCode, statusText = http.StatusBadGateway, err.Error()
-       }
-}
-
-var rangeRe = regexp.MustCompile(`^bytes=0-([0-9]*)$`)
-
-func applyRangeHdr(w http.ResponseWriter, r *http.Request, rdr keepclient.ReadCloserWithLen) (io.Reader, int) {
-       w.Header().Set("Accept-Ranges", "bytes")
-       hdr := r.Header.Get("Range")
-       fields := rangeRe.FindStringSubmatch(hdr)
-       if fields == nil {
-               return rdr, http.StatusOK
-       }
-       rangeEnd, err := strconv.ParseInt(fields[1], 10, 64)
-       if err != nil {
-               // Empty or too big for int64 == send entire content
-               return rdr, http.StatusOK
-       }
-       if uint64(rangeEnd) >= rdr.Len() {
-               return rdr, http.StatusOK
+               modtime = time.Now()
        }
-       w.Header().Set("Content-Length", fmt.Sprintf("%d", rangeEnd+1))
-       w.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", 0, rangeEnd, rdr.Len()))
-       return &io.LimitedReader{R: rdr, N: rangeEnd + 1}, http.StatusPartialContent
+       http.ServeContent(w, r, basename, modtime, rdr)
 }
 
 func applyContentDispositionHdr(w http.ResponseWriter, r *http.Request, filename string, isAttachment bool) {