Merge branch '17989-pysdk-timeout' into main. Refs #17989
[arvados.git] / lib / controller / router / router.go
index a313ebc8bed94c5b5b7e32b6c086644b4faae77f..9826c1e7448e548bc41c6f14dd092bacd2046742 100644 (file)
@@ -7,6 +7,7 @@ package router
 import (
        "context"
        "fmt"
+       "math"
        "net/http"
        "strings"
 
@@ -20,24 +21,32 @@ import (
 )
 
 type router struct {
-       mux       *mux.Router
-       backend   arvados.API
-       wrapCalls func(api.RoutableFunc) api.RoutableFunc
+       mux     *mux.Router
+       backend arvados.API
+       config  Config
+}
+
+type Config struct {
+       // Return an error if request body exceeds this size. 0 means
+       // unlimited.
+       MaxRequestSize int
+
+       // If wrapCalls is not nil, it is called once for each API
+       // method, and the returned method is used in its place. This
+       // can be used to install hooks before and after each API call
+       // and alter responses; see localdb.WrapCallsInTransaction for
+       // an example.
+       WrapCalls func(api.RoutableFunc) api.RoutableFunc
 }
 
 // New returns a new router (which implements the http.Handler
 // interface) that serves requests by calling Arvados API methods on
 // the given backend.
-//
-// If wrapCalls is not nil, it is called once for each API method, and
-// the returned method is used in its place. This can be used to
-// install hooks before and after each API call and alter responses;
-// see localdb.WrapCallsInTransaction for an example.
-func New(backend arvados.API, wrapCalls func(api.RoutableFunc) api.RoutableFunc) *router {
+func New(backend arvados.API, config Config) *router {
        rtr := &router{
-               mux:       mux.NewRouter(),
-               backend:   backend,
-               wrapCalls: wrapCalls,
+               mux:     mux.NewRouter(),
+               backend: backend,
+               config:  config,
        }
        rtr.addRoutes()
        return rtr
@@ -389,13 +398,6 @@ func (rtr *router) addRoutes() {
                                return rtr.backend.UserGet(ctx, *opts.(*arvados.GetOptions))
                        },
                },
-               {
-                       arvados.EndpointUserUpdateUUID,
-                       func() interface{} { return &arvados.UpdateUUIDOptions{} },
-                       func(ctx context.Context, opts interface{}) (interface{}, error) {
-                               return rtr.backend.UserUpdateUUID(ctx, *opts.(*arvados.UpdateUUIDOptions))
-                       },
-               },
                {
                        arvados.EndpointUserUpdate,
                        func() interface{} { return &arvados.UpdateOptions{} },
@@ -433,8 +435,8 @@ func (rtr *router) addRoutes() {
                },
        } {
                exec := route.exec
-               if rtr.wrapCalls != nil {
-                       exec = rtr.wrapCalls(exec)
+               if rtr.config.WrapCalls != nil {
+                       exec = rtr.config.WrapCalls(exec)
                }
                rtr.addRoute(route.endpoint, route.defaultOpts, exec)
        }
@@ -524,8 +526,26 @@ func (rtr *router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
        if r.Method == "OPTIONS" {
                return
        }
+       if r.Body != nil {
+               // Wrap r.Body in a http.MaxBytesReader(), otherwise
+               // r.ParseForm() uses a default max request body size
+               // of 10 megabytes. Note we rely on the Nginx
+               // configuration to enforce the real max body size.
+               max := int64(rtr.config.MaxRequestSize)
+               if max < 1 {
+                       max = math.MaxInt64 - 1
+               }
+               r.Body = http.MaxBytesReader(w, r.Body, max)
+       }
        if r.Method == "POST" {
-               r.ParseForm()
+               err := r.ParseForm()
+               if err != nil {
+                       if err.Error() == "http: request body too large" {
+                               err = httpError(http.StatusRequestEntityTooLarge, err)
+                       }
+                       rtr.sendError(w, err)
+                       return
+               }
                if m := r.FormValue("_method"); m != "" {
                        r2 := *r
                        r = &r2