src/server/pfs/s3/s3.go
func Server(pc *client.APIClient, port uint16) *http.Server {
	router := mux.NewRouter()
	router.Handle(`/`, newRootHandler(pc)).Methods("GET", "HEAD")

	// Bucket-related routes. Repo validation regex is the same that the aws
	// cli uses. There's two routers - one with a trailing a slash and one
	// without. Both route to the same handlers, i.e. a request to `/foo` is
	// the same as `/foo/`. This is used instead of mux's builtin "strict
	// slash" functionality, because that uses redirects which doesn't always
	// play nice with s3 clients.
	bucketHandler := newBucketHandler(pc)
	trailingSlashBucketRouter := router.Path(`/{branch:[a-zA-Z0-9\-_]{1,255}}.{repo:[a-zA-Z0-9\-_]{1,255}}/`).Subrouter()
	attachBucketRoutes(trailingSlashBucketRouter, bucketHandler)
	bucketRouter := router.Path(`/{branch:[a-zA-Z0-9\-_]{1,255}}.{repo:[a-zA-Z0-9\-_]{1,255}}`).Subrouter()
	attachBucketRoutes(bucketRouter, bucketHandler)

	// object-related routes
	objectRouter := router.Path(`/{branch:[a-zA-Z0-9\-_]{1,255}}.{repo:[a-zA-Z0-9\-_]{1,255}}/{file:.+}`).Subrouter()

	objectRouter.Methods("GET", "PUT").Queries("acl", "").HandlerFunc(notImplementedError)
	objectRouter.Methods("GET", "PUT").Queries("legal-hold", "").HandlerFunc(notImplementedError)
	objectRouter.Methods("GET", "PUT").Queries("retention", "").HandlerFunc(notImplementedError)
	objectRouter.Methods("GET", "PUT", "DELETE").Queries("tagging", "").HandlerFunc(notImplementedError)
	objectRouter.Methods("GET").Queries("torrent", "").HandlerFunc(notImplementedError)
	objectRouter.Methods("POST").Queries("restore", "").HandlerFunc(notImplementedError)
	objectRouter.Methods("POST").Queries("select", "").HandlerFunc(notImplementedError)
	objectRouter.Methods("PUT").Headers("x-amz-copy-source", "").HandlerFunc(notImplementedError) // maybe worth implementing at some point
	objectRouter.Methods("GET", "HEAD").Queries("uploadId", "").HandlerFunc(notImplementedError)
	objectRouter.Methods("POST").Queries("uploads", "").HandlerFunc(notImplementedError)
	objectRouter.Methods("POST").Queries("uploadId", "").HandlerFunc(notImplementedError)
	objectRouter.Methods("PUT").Queries("uploadId", "").HandlerFunc(notImplementedError)
	objectRouter.Methods("DELETE").Queries("uploadId", "").HandlerFunc(notImplementedError)

	objectHandler := newObjectHandler(pc)
	objectRouter.Methods("GET", "HEAD").HandlerFunc(objectHandler.get)
	objectRouter.Methods("PUT").HandlerFunc(objectHandler.put)
	objectRouter.Methods("DELETE").HandlerFunc(objectHandler.del)

	router.MethodNotAllowedHandler = http.HandlerFunc(methodNotAllowedError)
	router.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		requestLogger(r).Infof("not found: %+v", r.URL.Path)
		if bucketNameValidator.MatchString(r.URL.Path) {
			noSuchKeyError(w, r)
		} else {
			invalidBucketNameError(w, r)
		}
	})

	// NOTE: this is not closed. If the standard logger gets customized, this will need to be fixed
	serverErrorLog := logrus.WithFields(logrus.Fields{
		"source": "s3gateway",
	}).Writer()

	var lastEnterpriseCheck time.Time
	isEnterprise := false

	return &http.Server{
		Addr: fmt.Sprintf(":%d", port),
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			// Set a request ID, if it hasn't been set by the client already.
			// This can be used for tracing, and is included in error
			// responses.
			requestID := r.Header.Get("X-Request-ID")
			if requestID == "" {
				requestID = uuid.NewWithoutDashes()
				r.Header.Set("X-Request-ID", requestID)
			}
			w.Header().Set("x-amz-request-id", requestID)

			// Log that a request was made
			requestLogger(r).Debugf("http request: %s %s", r.Method, r.RequestURI)

			// Ensure enterprise is enabled
			now := time.Now()
			if !isEnterprise || now.Sub(lastEnterpriseCheck) > enterpriseTimeout {
				resp, err := pc.Enterprise.GetState(context.Background(), &enterpriseclient.GetStateRequest{})
				if err != nil {
					err = fmt.Errorf("could not get Enterprise status: %v", grpcutil.ScrubGRPC(err))
					internalError(w, r, err)
					return
				}

				isEnterprise = resp.State == enterpriseclient.State_ACTIVE
			}
			if !isEnterprise {
				enterpriseDisabledError(w, r)
				return
			}

			router.ServeHTTP(w, r)
		}),
		ErrorLog: stdlog.New(serverErrorLog, "", 0),
	}
}
func Server(pc *client.APIClient, port uint16) *http.Server {
	router := mux.NewRouter()
	router.Handle(`/`, newRootHandler(pc)).Methods("GET", "HEAD")

	// Bucket-related routes. Repo validation regex is the same that the aws
	// cli uses. There's two routers - one with a trailing a slash and one
	// without. Both route to the same handlers, i.e. a request to `/foo` is
	// the same as `/foo/`. This is used instead of mux's builtin "strict
	// slash" functionality, because that uses redirects which doesn't always
	// play nice with s3 clients.
	bucketHandler := newBucketHandler(pc)
	trailingSlashBucketRouter := router.Path(`/{branch:[a-zA-Z0-9\-_]{1,255}}.{repo:[a-zA-Z0-9\-_]{1,255}}/`).Subrouter()
	attachBucketRoutes(trailingSlashBucketRouter, bucketHandler)
	bucketRouter := router.Path(`/{branch:[a-zA-Z0-9\-_]{1,255}}.{repo:[a-zA-Z0-9\-_]{1,255}}`).Subrouter()
	attachBucketRoutes(bucketRouter, bucketHandler)

	// object-related routes
	objectRouter := router.Path(`/{branch:[a-zA-Z0-9\-_]{1,255}}.{repo:[a-zA-Z0-9\-_]{1,255}}/{file:.+}`).Subrouter()

	objectRouter.Methods("GET", "PUT").Queries("acl", "").HandlerFunc(notImplementedError)
	objectRouter.Methods("GET", "PUT").Queries("legal-hold", "").HandlerFunc(notImplementedError)
	objectRouter.Methods("GET", "PUT").Queries("retention", "").HandlerFunc(notImplementedError)
	objectRouter.Methods("GET", "PUT", "DELETE").Queries("tagging", "").HandlerFunc(notImplementedError)
	objectRouter.Methods("GET").Queries("torrent", "").HandlerFunc(notImplementedError)
	objectRouter.Methods("POST").Queries("restore", "").HandlerFunc(notImplementedError)
	objectRouter.Methods("POST").Queries("select", "").HandlerFunc(notImplementedError)
	objectRouter.Methods("PUT").Headers("x-amz-copy-source", "").HandlerFunc(notImplementedError) // maybe worth implementing at some point
	objectRouter.Methods("GET", "HEAD").Queries("uploadId", "").HandlerFunc(notImplementedError)
	objectRouter.Methods("POST").Queries("uploads", "").HandlerFunc(notImplementedError)
	objectRouter.Methods("POST").Queries("uploadId", "").HandlerFunc(notImplementedError)
	objectRouter.Methods("PUT").Queries("uploadId", "").HandlerFunc(notImplementedError)
	objectRouter.Methods("DELETE").Queries("uploadId", "").HandlerFunc(notImplementedError)

	objectHandler := newObjectHandler(pc)
	objectRouter.Methods("GET", "HEAD").HandlerFunc(objectHandler.get)
	objectRouter.Methods("PUT").HandlerFunc(objectHandler.put)
	objectRouter.Methods("DELETE").HandlerFunc(objectHandler.del)

	router.MethodNotAllowedHandler = http.HandlerFunc(methodNotAllowedError)
	router.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		requestLogger(r).Infof("not found: %+v", r.URL.Path)
		if bucketNameValidator.MatchString(r.URL.Path) {
			noSuchKeyError(w, r)
		} else {
			invalidBucketNameError(w, r)
		}
	})

	// NOTE: this is not closed. If the standard logger gets customized, this will need to be fixed
	serverErrorLog := logrus.WithFields(logrus.Fields{
		"source": "s3gateway",
	}).Writer()

	var lastEnterpriseCheck time.Time
	isEnterprise := false

	return &http.Server{
		Addr: fmt.Sprintf(":%d", port),
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			// Set a request ID, if it hasn't been set by the client already.
			// This can be used for tracing, and is included in error
			// responses.
			requestID := r.Header.Get("X-Request-ID")
			if requestID == "" {
				requestID = uuid.NewWithoutDashes()
				r.Header.Set("X-Request-ID", requestID)
			}
			w.Header().Set("x-amz-request-id", requestID)

			// Log that a request was made
			requestLogger(r).Debugf("http request: %s %s", r.Method, r.RequestURI)

			// Ensure enterprise is enabled
			now := time.Now()
			if !isEnterprise || now.Sub(lastEnterpriseCheck) > enterpriseTimeout {
				resp, err := pc.Enterprise.GetState(context.Background(), &enterpriseclient.GetStateRequest{})
				if err != nil {
					err = fmt.Errorf("could not get Enterprise status: %v", grpcutil.ScrubGRPC(err))
					internalError(w, r, err)
					return
				}

				isEnterprise = resp.State == enterpriseclient.State_ACTIVE
			}
			if !isEnterprise {
				enterpriseDisabledError(w, r)
				return
			}

			router.ServeHTTP(w, r)
		}),
		ErrorLog: stdlog.New(serverErrorLog, "", 0),
	}
}
func Server ( pc * client . APIClient , port uint16 ) * http . Server { router := mux . NewRouter ( ) router . Handle ( `/` , newRootHandler ( pc ) ) . Methods ( " " , " " ) // Bucket-related routes. Repo validation regex is the same that the aws // cli uses. There's two routers - one with a trailing a slash and one // without. Both route to the same handlers, i.e. a request to `/foo` is // the same as `/foo/`. This is used instead of mux's builtin "strict // slash" functionality, because that uses redirects which doesn't always // play nice with s3 clients. bucketHandler := newBucketHandler ( pc ) trailingSlashBucketRouter := router . Path ( `/{branch:[a-zA-Z0-9\-_]{1,255}}.{repo:[a-zA-Z0-9\-_]{1,255}}/` ) . Subrouter ( ) attachBucketRoutes ( trailingSlashBucketRouter , bucketHandler ) bucketRouter := router . Path ( `/{branch:[a-zA-Z0-9\-_]{1,255}}.{repo:[a-zA-Z0-9\-_]{1,255}}` ) . Subrouter ( ) attachBucketRoutes ( bucketRouter , bucketHandler ) // object-related routes objectRouter := router . Path ( `/{branch:[a-zA-Z0-9\-_]{1,255}}.{repo:[a-zA-Z0-9\-_]{1,255}}/{file:.+}` ) . Subrouter ( ) objectRouter . Methods ( " " , " " ) . Queries ( " " , " " ) . HandlerFunc ( notImplementedError ) objectRouter . Methods ( " " , " " ) . Queries ( " " , " " ) . HandlerFunc ( notImplementedError ) objectRouter . Methods ( " " , " " ) . Queries ( " " , " " ) . HandlerFunc ( notImplementedError ) objectRouter . Methods ( " " , " " , " " ) . Queries ( " " , " " ) . HandlerFunc ( notImplementedError ) objectRouter . Methods ( " " ) . Queries ( " " , " " ) . HandlerFunc ( notImplementedError ) objectRouter . Methods ( " " ) . Queries ( " " , " " ) . HandlerFunc ( notImplementedError ) objectRouter . Methods ( " " ) . Queries ( " " , " " ) . HandlerFunc ( notImplementedError ) objectRouter . Methods ( " " ) . Headers ( " " , " " ) . HandlerFunc ( notImplementedError ) // maybe worth implementing at some point objectRouter . Methods ( " " , " " ) . Queries ( " " , " " ) . HandlerFunc ( notImplementedError ) objectRouter . Methods ( " " ) . Queries ( " " , " " ) . HandlerFunc ( notImplementedError ) objectRouter . Methods ( " " ) . Queries ( " " , " " ) . HandlerFunc ( notImplementedError ) objectRouter . Methods ( " " ) . Queries ( " " , " " ) . HandlerFunc ( notImplementedError ) objectRouter . Methods ( " " ) . Queries ( " " , " " ) . HandlerFunc ( notImplementedError ) objectHandler := newObjectHandler ( pc ) objectRouter . Methods ( " " , " " ) . HandlerFunc ( objectHandler . get ) objectRouter . Methods ( " " ) . HandlerFunc ( objectHandler . put ) objectRouter . Methods ( " " ) . HandlerFunc ( objectHandler . del ) router . MethodNotAllowedHandler = http . HandlerFunc ( methodNotAllowedError ) router . NotFoundHandler = http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) { requestLogger ( r ) . Infof ( " " , r . URL . Path ) if bucketNameValidator . MatchString ( r . URL . Path ) { noSuchKeyError ( w , r ) } else { invalidBucketNameError ( w , r ) } } ) // NOTE: this is not closed. If the standard logger gets customized, this will need to be fixed serverErrorLog := logrus . WithFields ( logrus . Fields { " " : " " , } ) . Writer ( ) var lastEnterpriseCheck time . Time isEnterprise := false return & http . Server { Addr : fmt . Sprintf ( " " , port ) , Handler : http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) { // Set a request ID, if it hasn't been set by the client already. // This can be used for tracing, and is included in error // responses. requestID := r . Header . Get ( " " ) if requestID == " " { requestID = uuid . NewWithoutDashes ( ) r . Header . Set ( " " , requestID ) } w . Header ( ) . Set ( " " , requestID ) // Log that a request was made requestLogger ( r ) . Debugf ( " " , r . Method , r . RequestURI ) // Ensure enterprise is enabled now := time . Now ( ) if ! isEnterprise || now . Sub ( lastEnterpriseCheck ) > enterpriseTimeout { resp , err := pc . Enterprise . GetState ( context . Background ( ) , & enterpriseclient . GetStateRequest { } ) if err != nil { err = fmt . Errorf ( " " , grpcutil . ScrubGRPC ( err ) ) internalError ( w , r , err ) return } isEnterprise = resp . State == enterpriseclient . State_ACTIVE } if ! isEnterprise { enterpriseDisabledError ( w , r ) return } router . ServeHTTP ( w , r ) } ) , ErrorLog : stdlog . New ( serverErrorLog , " " , 0 ) , } }
// Server runs an HTTP server with an S3-like API for PFS. This allows you to
// use s3 clients to acccess PFS contents.
//
// This returns an `http.Server` instance. It is the responsibility of the
// caller to start the returned server. It's possible for the caller to
// gracefully shutdown the server if desired; see the `http` package for details.
//
// Note: server errors are redirected to logrus' standard log writer. The log
// writer is never closed. This should not be a problem with logrus' default
// configuration, which just writes to stdio. But if the standard logger is
// overwritten (e.g. to write to a socket), it's possible for this to cause
// problems.
//
// Note: In `s3cmd`, you must set the access key and secret key, even though
// this API will ignore them - otherwise, you'll get an opaque config error:
// https://github.com/s3tools/s3cmd/issues/845#issuecomment-464885959
Server runs an HTTP server with an S3 - like API for PFS . This allows you to use s3 clients to acccess PFS contents . This returns an http . Server instance . It is the responsibility of the caller to start the returned server . It s possible for the caller to gracefully shutdown the server if desired ; see the http package for details . Note : server errors are redirected to logrus standard log writer . The log writer is never closed . This should not be a problem with logrus default configuration which just writes to stdio . But if the standard logger is overwritten ( e . g . to write to a socket ) it s possible for this to cause problems . Note : In s3cmd you must set the access key and secret key even though this API will ignore them - otherwise you ll get an opaque config error : https : // github . com / s3tools / s3cmd / issues / 845#issuecomment - 464885959