"git.arvados.org/arvados.git/sdk/go/arvados"
"git.arvados.org/arvados.git/sdk/go/auth"
"git.arvados.org/arvados.git/sdk/go/httpserver"
+ "git.arvados.org/arvados.git/sdk/go/keepclient"
"github.com/gorilla/mux"
)
}
r := mux.NewRouter()
+ // Without SkipClean(true), the gorilla/mux package responds
+ // to "PUT //foo" with a 301 redirect to "/foo", which causes
+ // a (Fetch Standard compliant) client to repeat the request
+ // as "GET /foo", which is clearly inappropriate in this case.
+ // It's less confusing if we just return 400.
+ r.SkipClean(true)
locatorPath := `/{locator:[0-9a-f]{32}.*}`
get := r.Methods(http.MethodGet, http.MethodHead).Subrouter()
get.HandleFunc(locatorPath, rtr.handleBlockRead)
+ get.HandleFunc("/"+locatorPath, rtr.handleBlockRead) // for compatibility -- see TestBlockRead_DoubleSlash
get.HandleFunc(`/index`, adminonly(rtr.handleIndex))
get.HandleFunc(`/index/{prefix:[0-9a-f]{0,32}}`, adminonly(rtr.handleIndex))
get.HandleFunc(`/mounts`, adminonly(rtr.handleMounts))
touch.HandleFunc(locatorPath, adminonly(rtr.handleBlockTouch))
delete := r.Methods(http.MethodDelete).Subrouter()
delete.HandleFunc(locatorPath, adminonly(rtr.handleBlockTrash))
+ options := r.Methods(http.MethodOptions).Subrouter()
+ options.NewRoute().PathPrefix(`/`).HandlerFunc(rtr.handleOptions)
r.NotFoundHandler = http.HandlerFunc(rtr.handleBadRequest)
r.MethodNotAllowedHandler = http.HandlerFunc(rtr.handleBadRequest)
- rtr.Handler = auth.LoadToken(r)
+ rtr.Handler = corsHandler(auth.LoadToken(r))
return rtr
}
// Intervening proxies must not return a cached GET response
// to a prior request if a X-Keep-Signature request header has
// been added or changed.
- w.Header().Add("Vary", "X-Keep-Signature")
+ w.Header().Add("Vary", keepclient.XKeepSignature)
var localLocator func(string)
- if strings.SplitN(req.Header.Get("X-Keep-Signature"), ",", 2)[0] == "local" {
+ if strings.SplitN(req.Header.Get(keepclient.XKeepSignature), ",", 2)[0] == "local" {
localLocator = func(locator string) {
- w.Header().Set("X-Keep-Locator", locator)
+ w.Header().Set(keepclient.XKeepLocator, locator)
}
}
out := w
if req.Method == http.MethodHead {
out = discardWrite{ResponseWriter: w}
- } else if li, err := parseLocator(mux.Vars(req)["locator"]); err != nil {
+ } else if li, err := getLocatorInfo(mux.Vars(req)["locator"]); err != nil {
rtr.handleError(w, req, err)
return
} else if li.size == 0 && li.hash != "d41d8cd98f00b204e9800998ecf8427e" {
func (rtr *router) handleBlockWrite(w http.ResponseWriter, req *http.Request) {
dataSize, _ := strconv.Atoi(req.Header.Get("Content-Length"))
- replicas, _ := strconv.Atoi(req.Header.Get("X-Arvados-Replicas-Desired"))
+ replicas, _ := strconv.Atoi(req.Header.Get(keepclient.XKeepDesiredReplicas))
resp, err := rtr.keepstore.BlockWrite(req.Context(), arvados.BlockWriteOptions{
Hash: mux.Vars(req)["locator"],
Reader: req.Body,
DataSize: dataSize,
RequestID: req.Header.Get("X-Request-Id"),
- StorageClasses: trimSplit(req.Header.Get("X-Keep-Storage-Classes"), ","),
+ StorageClasses: trimSplit(req.Header.Get(keepclient.XKeepStorageClasses), ","),
Replicas: replicas,
})
if err != nil {
rtr.handleError(w, req, err)
return
}
- w.Header().Set("X-Keep-Replicas-Stored", fmt.Sprintf("%d", resp.Replicas))
+ w.Header().Set(keepclient.XKeepReplicasStored, fmt.Sprintf("%d", resp.Replicas))
scc := ""
for k, n := range resp.StorageClasses {
if n > 0 {
if scc != "" {
- scc += "; "
+ scc += ", "
}
scc += fmt.Sprintf("%s=%d", k, n)
}
}
- w.Header().Set("X-Keep-Storage-Classes-Confirmed", scc)
+ w.Header().Set(keepclient.XKeepStorageClassesConfirmed, scc)
w.WriteHeader(http.StatusOK)
fmt.Fprintln(w, resp.Locator)
}
prefix = mux.Vars(req)["prefix"]
}
cw := &countingWriter{writer: w}
- err := rtr.keepstore.Index(req.Context(), IndexOptions{
+ err := rtr.keepstore.Index(req.Context(), indexOptions{
MountUUID: mux.Vars(req)["uuid"],
Prefix: prefix,
WriteTo: cw,
http.Error(w, "Bad Request", http.StatusBadRequest)
}
+func (rtr *router) handleOptions(w http.ResponseWriter, req *http.Request) {
+}
+
func (rtr *router) handleError(w http.ResponseWriter, req *http.Request, err error) {
if req.Context().Err() != nil {
w.WriteHeader(499)
func (discardWrite) Write(p []byte) (int, error) {
return len(p), nil
}
+
+func corsHandler(h http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ SetCORSHeaders(w)
+ h.ServeHTTP(w, r)
+ })
+}
+
+var corsHeaders = map[string]string{
+ "Access-Control-Allow-Methods": "GET, HEAD, PUT, OPTIONS",
+ "Access-Control-Allow-Origin": "*",
+ "Access-Control-Allow-Headers": "Authorization, Content-Length, Content-Type, " + keepclient.XKeepDesiredReplicas + ", " + keepclient.XKeepSignature + ", " + keepclient.XKeepStorageClasses,
+ "Access-Control-Expose-Headers": keepclient.XKeepLocator + ", " + keepclient.XKeepReplicasStored + ", " + keepclient.XKeepStorageClassesConfirmed,
+ "Access-Control-Max-Age": "86486400",
+}
+
+func SetCORSHeaders(w http.ResponseWriter) {
+ for k, v := range corsHeaders {
+ w.Header().Set(k, v)
+ }
+}