From 26f1df27213fd1208d1691ffda80ee21f90c34a3 Mon Sep 17 00:00:00 2001 From: Evgeniy Kulikov Date: Tue, 11 Aug 2020 14:27:31 +0300 Subject: [PATCH 1/3] Refactoring request info - simplify - more usable Signed-off-by: Evgeniy Kulikov --- api/reqinfo.go | 123 ++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 106 insertions(+), 17 deletions(-) diff --git a/api/reqinfo.go b/api/reqinfo.go index cc4dc2e..606ada4 100644 --- a/api/reqinfo.go +++ b/api/reqinfo.go @@ -2,7 +2,14 @@ package api import ( "context" + "net" + "net/http" + "net/url" + "regexp" + "strings" "sync" + + "github.com/gorilla/mux" ) type ( @@ -25,6 +32,12 @@ type ( ObjectName string // Object name tags []KeyVal // Any additional info not accommodated by above fields } + + ObjectRequest struct { + Bucket string + Object string + Method string + } ) // Key used for Get/SetReqInfo @@ -32,17 +45,97 @@ type contextKeyType string const ctxRequestInfo = contextKeyType("NeoFS-S3-Gate") +var ( + // De-facto standard header keys. + xForwardedFor = http.CanonicalHeaderKey("X-Forwarded-For") + xRealIP = http.CanonicalHeaderKey("X-Real-IP") +) + +var ( + // RFC7239 defines a new "Forwarded: " header designed to replace the + // existing use of X-Forwarded-* headers. + // e.g. Forwarded: for=192.0.2.60;proto=https;by=203.0.113.43 + forwarded = http.CanonicalHeaderKey("Forwarded") + // Allows for a sub-match of the first value after 'for=' to the next + // comma, semi-colon or space. The match is case-insensitive. + forRegex = regexp.MustCompile(`(?i)(?:for=)([^(;|, )]+)(.*)`) +) + +// GetSourceIP retrieves the IP from the X-Forwarded-For, X-Real-IP and RFC7239 +// Forwarded headers (in that order), falls back to r.RemoteAddr when all +// else fails. +func GetSourceIP(r *http.Request) string { + var addr string + + if fwd := r.Header.Get(xForwardedFor); fwd != "" { + // Only grab the first (client) address. Note that '192.168.0.1, + // 10.1.1.1' is a valid key for X-Forwarded-For where addresses after + // the first may represent forwarding proxies earlier in the chain. + s := strings.Index(fwd, ", ") + if s == -1 { + s = len(fwd) + } + addr = fwd[:s] + } else if fwd := r.Header.Get(xRealIP); fwd != "" { + // X-Real-IP should only contain one IP address (the client making the + // request). + addr = fwd + } else if fwd := r.Header.Get(forwarded); fwd != "" { + // match should contain at least two elements if the protocol was + // specified in the Forwarded header. The first element will always be + // the 'for=' capture, which we ignore. In the case of multiple IP + // addresses (for=8.8.8.8, 8.8.4.4, 172.16.1.20 is valid) we only + // extract the first, which should be the client IP. + if match := forRegex.FindStringSubmatch(fwd); len(match) > 1 { + // IPv6 addresses in Forwarded headers are quoted-strings. We strip + // these quotes. + addr = strings.Trim(match[1], `"`) + } + } + + if addr != "" { + return addr + } + + // Default to remote address if headers not set. + addr, _, _ = net.SplitHostPort(r.RemoteAddr) + return addr +} + +func prepareContext(w http.ResponseWriter, r *http.Request) context.Context { + vars := mux.Vars(r) + bucket := vars["bucket"] + object, err := url.PathUnescape(vars["object"]) + if err != nil { + object = vars["object"] + } + prefix, err := url.QueryUnescape(vars["prefix"]) + if err != nil { + prefix = vars["prefix"] + } + if prefix != "" { + object = prefix + } + return SetReqInfo(r.Context(), + // prepare request info + NewReqInfo(w, r, ObjectRequest{ + Bucket: bucket, + Object: object, + Method: mux.CurrentRoute(r).GetName(), + })) +} + // NewReqInfo : -func NewReqInfo(remoteHost, userAgent, deploymentID, requestID, api, bucket, object string) *ReqInfo { - req := ReqInfo{} - req.RemoteHost = remoteHost - req.UserAgent = userAgent - req.API = api - req.DeploymentID = deploymentID - req.RequestID = requestID - req.BucketName = bucket - req.ObjectName = object - return &req +func NewReqInfo(w http.ResponseWriter, r *http.Request, req ObjectRequest) *ReqInfo { + return &ReqInfo{ + API: req.Method, + BucketName: req.Bucket, + ObjectName: req.Object, + UserAgent: r.UserAgent(), + RemoteHost: GetSourceIP(r), + RequestID: GetRequestID(w), + DeploymentID: deploymentID.String(), + } } // AppendTags - appends key/val to ReqInfo.tags @@ -99,13 +192,9 @@ func SetReqInfo(ctx context.Context, req *ReqInfo) context.Context { // GetReqInfo returns ReqInfo if set. func GetReqInfo(ctx context.Context) *ReqInfo { - if ctx != nil { - r, ok := ctx.Value(ctxRequestInfo).(*ReqInfo) - if ok { - return r - } - r = &ReqInfo{} - SetReqInfo(ctx, r) + if ctx == nil { + return nil + } else if r, ok := ctx.Value(ctxRequestInfo).(*ReqInfo); ok { return r } return nil From 59e26d45fd2e0071d30cbf8ef4f5bbb89ac06473 Mon Sep 17 00:00:00 2001 From: Evgeniy Kulikov Date: Tue, 11 Aug 2020 14:30:02 +0300 Subject: [PATCH 2/3] Refactoring API router - method to fetch request id - middleware to set request id and info - fixes for getAPIErrorResponse (fix possible NPE) Signed-off-by: Evgeniy Kulikov --- api/errors.go | 9 +- api/router.go | 227 ++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 170 insertions(+), 66 deletions(-) diff --git a/api/errors.go b/api/errors.go index c4d236b..c011533 100644 --- a/api/errors.go +++ b/api/errors.go @@ -1623,12 +1623,15 @@ func GetAPIError(code ErrorCode) Error { // getErrorResponse gets in standard error and resource value and // provides a encodable populated response values func getAPIErrorResponse(ctx context.Context, err Error, resource, requestID, hostID string) ErrorResponse { - reqInfo := GetReqInfo(ctx) + info := GetReqInfo(ctx) + if info == nil { + info = &ReqInfo{} + } return ErrorResponse{ Code: err.Code, Message: err.Description, - BucketName: reqInfo.BucketName, - Key: reqInfo.ObjectName, + BucketName: info.BucketName, + Key: info.ObjectName, Resource: resource, RequestID: requestID, HostID: hostID, diff --git a/api/router.go b/api/router.go index 9b8667d..0f47817 100644 --- a/api/router.go +++ b/api/router.go @@ -1,12 +1,15 @@ package api import ( + "context" "net/http" + "github.com/google/uuid" "github.com/gorilla/mux" "github.com/nspcc-dev/neofs-s3-gate/api/metrics" "github.com/nspcc-dev/neofs-s3-gate/auth" "go.uber.org/zap" + "google.golang.org/grpc/metadata" ) type ( @@ -90,8 +93,44 @@ const ( mimeXML mimeType = "application/xml" ) +func setRequestID(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // generate random UUIDv4 + id, _ := uuid.NewRandom() + + // set request id into response header + w.Header().Set(hdrAmzRequestID, id.String()) + + // set request id into gRPC meta header + r = r.WithContext(metadata.AppendToOutgoingContext( + r.Context(), hdrAmzRequestID, id.String(), + )) + + // set request info into context + r = r.WithContext(prepareContext(w, r)) + + // continue execution + h.ServeHTTP(w, r) + }) +} + +func GetRequestID(v interface{}) string { + switch t := v.(type) { + case context.Context: + return GetReqInfo(t).RequestID + case http.ResponseWriter: + return t.Header().Get(hdrAmzRequestID) + default: + panic("unknown type") + } +} + func Attach(r *mux.Router, m MaxClients, h Handler, center *auth.Center, log *zap.Logger) { api := r.PathPrefix(SlashSeparator).Subrouter() + + // Attach behaviors: RequestID, ... + api.Use(setRequestID) + // Attach user authentication for all S3 routes. AttachUserAuth(api, center, log) @@ -100,203 +139,265 @@ func Attach(r *mux.Router, m MaxClients, h Handler, center *auth.Center, log *za // Object operations // HeadObject bucket.Methods(http.MethodHead).Path("/{object:.+}").HandlerFunc( - m.Handle(metrics.APIStats("headobject", h.HeadObjectHandler))) + m.Handle(metrics.APIStats("headobject", h.HeadObjectHandler))).Name("HeadObject") // CopyObjectPart - bucket.Methods(http.MethodPut).Path("/{object:.+}").HeadersRegexp(hdrAmzCopySource, ".*?(\\/|%2F).*?").HandlerFunc(m.Handle(metrics.APIStats("copyobjectpart", h.CopyObjectPartHandler))).Queries("partNumber", "{partNumber:[0-9]+}", "uploadId", "{uploadId:.*}") + bucket.Methods(http.MethodPut).Path("/{object:.+}").HeadersRegexp(hdrAmzCopySource, ".*?(\\/|%2F).*?").HandlerFunc(m.Handle(metrics.APIStats("copyobjectpart", h.CopyObjectPartHandler))).Queries("partNumber", "{partNumber:[0-9]+}", "uploadId", "{uploadId:.*}"). + Name("CopyObjectPart") // PutObjectPart bucket.Methods(http.MethodPut).Path("/{object:.+}").HandlerFunc( - m.Handle(metrics.APIStats("putobjectpart", h.PutObjectPartHandler))).Queries("partNumber", "{partNumber:[0-9]+}", "uploadId", "{uploadId:.*}") + m.Handle(metrics.APIStats("putobjectpart", h.PutObjectPartHandler))).Queries("partNumber", "{partNumber:[0-9]+}", "uploadId", "{uploadId:.*}"). + Name("PutObjectObject") // ListObjectParts bucket.Methods(http.MethodGet).Path("/{object:.+}").HandlerFunc( - m.Handle(metrics.APIStats("listobjectparts", h.ListObjectPartsHandler))).Queries("uploadId", "{uploadId:.*}") + m.Handle(metrics.APIStats("listobjectparts", h.ListObjectPartsHandler))).Queries("uploadId", "{uploadId:.*}"). + Name("ListObjectParts") // CompleteMultipartUpload bucket.Methods(http.MethodPost).Path("/{object:.+}").HandlerFunc( - m.Handle(metrics.APIStats("completemutipartupload", h.CompleteMultipartUploadHandler))).Queries("uploadId", "{uploadId:.*}") + m.Handle(metrics.APIStats("completemutipartupload", h.CompleteMultipartUploadHandler))).Queries("uploadId", "{uploadId:.*}"). + Name("CompleteMultipartUpload") // NewMultipartUpload bucket.Methods(http.MethodPost).Path("/{object:.+}").HandlerFunc( - m.Handle(metrics.APIStats("newmultipartupload", h.NewMultipartUploadHandler))).Queries("uploads", "") + m.Handle(metrics.APIStats("newmultipartupload", h.NewMultipartUploadHandler))).Queries("uploads", ""). + Name("NewMultipartUpload") // AbortMultipartUpload bucket.Methods(http.MethodDelete).Path("/{object:.+}").HandlerFunc( - m.Handle(metrics.APIStats("abortmultipartupload", h.AbortMultipartUploadHandler))).Queries("uploadId", "{uploadId:.*}") + m.Handle(metrics.APIStats("abortmultipartupload", h.AbortMultipartUploadHandler))).Queries("uploadId", "{uploadId:.*}"). + Name("AbortMultipartUpload") // GetObjectACL - this is a dummy call. bucket.Methods(http.MethodGet).Path("/{object:.+}").HandlerFunc( - m.Handle(metrics.APIStats("getobjectacl", h.GetObjectACLHandler))).Queries("acl", "") + m.Handle(metrics.APIStats("getobjectacl", h.GetObjectACLHandler))).Queries("acl", ""). + Name("GetObjectACL") // PutObjectACL - this is a dummy call. bucket.Methods(http.MethodPut).Path("/{object:.+}").HandlerFunc( - m.Handle(metrics.APIStats("putobjectacl", h.PutObjectACLHandler))).Queries("acl", "") + m.Handle(metrics.APIStats("putobjectacl", h.PutObjectACLHandler))).Queries("acl", ""). + Name("PutObjectACL") // GetObjectTagging bucket.Methods(http.MethodGet).Path("/{object:.+}").HandlerFunc( - m.Handle(metrics.APIStats("getobjecttagging", h.GetObjectTaggingHandler))).Queries("tagging", "") + m.Handle(metrics.APIStats("getobjecttagging", h.GetObjectTaggingHandler))).Queries("tagging", ""). + Name("GetObjectTagging") // PutObjectTagging bucket.Methods(http.MethodPut).Path("/{object:.+}").HandlerFunc( - m.Handle(metrics.APIStats("putobjecttagging", h.PutObjectTaggingHandler))).Queries("tagging", "") + m.Handle(metrics.APIStats("putobjecttagging", h.PutObjectTaggingHandler))).Queries("tagging", ""). + Name("PutObjectTagging") // DeleteObjectTagging bucket.Methods(http.MethodDelete).Path("/{object:.+}").HandlerFunc( - m.Handle(metrics.APIStats("deleteobjecttagging", h.DeleteObjectTaggingHandler))).Queries("tagging", "") + m.Handle(metrics.APIStats("deleteobjecttagging", h.DeleteObjectTaggingHandler))).Queries("tagging", ""). + Name("DeleteObjectTagging") // SelectObjectContent bucket.Methods(http.MethodPost).Path("/{object:.+}").HandlerFunc( - m.Handle(metrics.APIStats("selectobjectcontent", h.SelectObjectContentHandler))).Queries("select", "").Queries("select-type", "2") + m.Handle(metrics.APIStats("selectobjectcontent", h.SelectObjectContentHandler))).Queries("select", "").Queries("select-type", "2"). + Name("SelectObjectContent") // GetObjectRetention bucket.Methods(http.MethodGet).Path("/{object:.+}").HandlerFunc( - m.Handle(metrics.APIStats("getobjectretention", h.GetObjectRetentionHandler))).Queries("retention", "") + m.Handle(metrics.APIStats("getobjectretention", h.GetObjectRetentionHandler))).Queries("retention", ""). + Name("GetObjectRetention") // GetObjectLegalHold bucket.Methods(http.MethodGet).Path("/{object:.+}").HandlerFunc( - m.Handle(metrics.APIStats("getobjectlegalhold", h.GetObjectLegalHoldHandler))).Queries("legal-hold", "") + m.Handle(metrics.APIStats("getobjectlegalhold", h.GetObjectLegalHoldHandler))).Queries("legal-hold", ""). + Name("GetObjectLegalHold") // GetObject bucket.Methods(http.MethodGet).Path("/{object:.+}").HandlerFunc( - m.Handle(metrics.APIStats("getobject", h.GetObjectHandler))) + m.Handle(metrics.APIStats("getobject", h.GetObjectHandler))). + Name("GetObject") // CopyObject - bucket.Methods(http.MethodPut).Path("/{object:.+}").HeadersRegexp(hdrAmzCopySource, ".*?(\\/|%2F).*?").HandlerFunc(m.Handle(metrics.APIStats("copyobject", h.CopyObjectHandler))) + bucket.Methods(http.MethodPut).Path("/{object:.+}").HeadersRegexp(hdrAmzCopySource, ".*?(\\/|%2F).*?").HandlerFunc(m.Handle(metrics.APIStats("copyobject", h.CopyObjectHandler))). + Name("CopyObject") // PutObjectRetention bucket.Methods(http.MethodPut).Path("/{object:.+}").HandlerFunc( - m.Handle(metrics.APIStats("putobjectretention", h.PutObjectRetentionHandler))).Queries("retention", "") + m.Handle(metrics.APIStats("putobjectretention", h.PutObjectRetentionHandler))).Queries("retention", ""). + Name("PutObjectRetention") // PutObjectLegalHold bucket.Methods(http.MethodPut).Path("/{object:.+}").HandlerFunc( - m.Handle(metrics.APIStats("putobjectlegalhold", h.PutObjectLegalHoldHandler))).Queries("legal-hold", "") + m.Handle(metrics.APIStats("putobjectlegalhold", h.PutObjectLegalHoldHandler))).Queries("legal-hold", ""). + Name("PutObjectLegalHold") // PutObject bucket.Methods(http.MethodPut).Path("/{object:.+}").HandlerFunc( - m.Handle(metrics.APIStats("putobject", h.PutObjectHandler))) + m.Handle(metrics.APIStats("putobject", h.PutObjectHandler))). + Name("PutObject") // DeleteObject bucket.Methods(http.MethodDelete).Path("/{object:.+}").HandlerFunc( - m.Handle(metrics.APIStats("deleteobject", h.DeleteObjectHandler))) + m.Handle(metrics.APIStats("deleteobject", h.DeleteObjectHandler))). + Name("DeleteObject") // Bucket operations // GetBucketLocation bucket.Methods(http.MethodGet).HandlerFunc( - m.Handle(metrics.APIStats("getbucketlocation", h.GetBucketLocationHandler))).Queries("location", "") + m.Handle(metrics.APIStats("getbucketlocation", h.GetBucketLocationHandler))).Queries("location", ""). + Name("GetBucketLocation") // GetBucketPolicy bucket.Methods(http.MethodGet).HandlerFunc( - m.Handle(metrics.APIStats("getbucketpolicy", h.GetBucketPolicyHandler))).Queries("policy", "") + m.Handle(metrics.APIStats("getbucketpolicy", h.GetBucketPolicyHandler))).Queries("policy", ""). + Name("GetBucketPolicy") // GetBucketLifecycle bucket.Methods(http.MethodGet).HandlerFunc( - m.Handle(metrics.APIStats("getbucketlifecycle", h.GetBucketLifecycleHandler))).Queries("lifecycle", "") + m.Handle(metrics.APIStats("getbucketlifecycle", h.GetBucketLifecycleHandler))).Queries("lifecycle", ""). + Name("GetBucketLifecycle") // GetBucketEncryption bucket.Methods(http.MethodGet).HandlerFunc( - m.Handle(metrics.APIStats("getbucketencryption", h.GetBucketEncryptionHandler))).Queries("encryption", "") + m.Handle(metrics.APIStats("getbucketencryption", h.GetBucketEncryptionHandler))).Queries("encryption", ""). + Name("GetBucketEncryption") // Dummy Bucket Calls // GetBucketACL -- this is a dummy call. bucket.Methods(http.MethodGet).HandlerFunc( - m.Handle(metrics.APIStats("getbucketacl", h.GetBucketACLHandler))).Queries("acl", "") + m.Handle(metrics.APIStats("getbucketacl", h.GetBucketACLHandler))).Queries("acl", ""). + Name("GetBucketACL") // PutBucketACL -- this is a dummy call. bucket.Methods(http.MethodPut).HandlerFunc( - m.Handle(metrics.APIStats("putbucketacl", h.PutBucketACLHandler))).Queries("acl", "") + m.Handle(metrics.APIStats("putbucketacl", h.PutBucketACLHandler))).Queries("acl", ""). + Name("PutBucketACL") // GetBucketCors - this is a dummy call. bucket.Methods(http.MethodGet).HandlerFunc( - m.Handle(metrics.APIStats("getbucketcors", h.GetBucketCorsHandler))).Queries("cors", "") + m.Handle(metrics.APIStats("getbucketcors", h.GetBucketCorsHandler))).Queries("cors", ""). + Name("GetBucketCors") // GetBucketWebsiteHandler - this is a dummy call. bucket.Methods(http.MethodGet).HandlerFunc( - m.Handle(metrics.APIStats("getbucketwebsite", h.GetBucketWebsiteHandler))).Queries("website", "") + m.Handle(metrics.APIStats("getbucketwebsite", h.GetBucketWebsiteHandler))).Queries("website", ""). + Name("GetBucketWebsite") // GetBucketAccelerateHandler - this is a dummy call. bucket.Methods(http.MethodGet).HandlerFunc( - m.Handle(metrics.APIStats("getbucketaccelerate", h.GetBucketAccelerateHandler))).Queries("accelerate", "") + m.Handle(metrics.APIStats("getbucketaccelerate", h.GetBucketAccelerateHandler))).Queries("accelerate", ""). + Name("GetBucketAccelerate") // GetBucketRequestPaymentHandler - this is a dummy call. bucket.Methods(http.MethodGet).HandlerFunc( - m.Handle(metrics.APIStats("getbucketrequestpayment", h.GetBucketRequestPaymentHandler))).Queries("requestPayment", "") + m.Handle(metrics.APIStats("getbucketrequestpayment", h.GetBucketRequestPaymentHandler))).Queries("requestPayment", ""). + Name("GetBucketRequestPayment") // GetBucketLoggingHandler - this is a dummy call. bucket.Methods(http.MethodGet).HandlerFunc( - m.Handle(metrics.APIStats("getbucketlogging", h.GetBucketLoggingHandler))).Queries("logging", "") + m.Handle(metrics.APIStats("getbucketlogging", h.GetBucketLoggingHandler))).Queries("logging", ""). + Name("GetBucketLogging") // GetBucketLifecycleHandler - this is a dummy call. bucket.Methods(http.MethodGet).HandlerFunc( - m.Handle(metrics.APIStats("getbucketlifecycle", h.GetBucketLifecycleHandler))).Queries("lifecycle", "") + m.Handle(metrics.APIStats("getbucketlifecycle", h.GetBucketLifecycleHandler))).Queries("lifecycle", ""). + Name("GetBucketLifecycle") // GetBucketReplicationHandler - this is a dummy call. bucket.Methods(http.MethodGet).HandlerFunc( - m.Handle(metrics.APIStats("getbucketreplication", h.GetBucketReplicationHandler))).Queries("replication", "") + m.Handle(metrics.APIStats("getbucketreplication", h.GetBucketReplicationHandler))).Queries("replication", ""). + Name("GetBucketReplication") // GetBucketTaggingHandler bucket.Methods(http.MethodGet).HandlerFunc( - m.Handle(metrics.APIStats("getbuckettagging", h.GetBucketTaggingHandler))).Queries("tagging", "") + m.Handle(metrics.APIStats("getbuckettagging", h.GetBucketTaggingHandler))).Queries("tagging", ""). + Name("GetBucketTagging") // DeleteBucketWebsiteHandler bucket.Methods(http.MethodDelete).HandlerFunc( - m.Handle(metrics.APIStats("deletebucketwebsite", h.DeleteBucketWebsiteHandler))).Queries("website", "") + m.Handle(metrics.APIStats("deletebucketwebsite", h.DeleteBucketWebsiteHandler))).Queries("website", ""). + Name("DeleteBucketWebsite") // DeleteBucketTaggingHandler bucket.Methods(http.MethodDelete).HandlerFunc( - m.Handle(metrics.APIStats("deletebuckettagging", h.DeleteBucketTaggingHandler))).Queries("tagging", "") + m.Handle(metrics.APIStats("deletebuckettagging", h.DeleteBucketTaggingHandler))).Queries("tagging", ""). + Name("DeleteBucketTagging") // GetBucketObjectLockConfig bucket.Methods(http.MethodGet).HandlerFunc( - m.Handle(metrics.APIStats("getbucketobjectlockconfiguration", h.GetBucketObjectLockConfigHandler))).Queries("object-lock", "") + m.Handle(metrics.APIStats("getbucketobjectlockconfiguration", h.GetBucketObjectLockConfigHandler))).Queries("object-lock", ""). + Name("GetBucketObjectLockConfig") // GetBucketVersioning bucket.Methods(http.MethodGet).HandlerFunc( - m.Handle(metrics.APIStats("getbucketversioning", h.GetBucketVersioningHandler))).Queries("versioning", "") + m.Handle(metrics.APIStats("getbucketversioning", h.GetBucketVersioningHandler))).Queries("versioning", ""). + Name("GetBucketVersioning") // GetBucketNotification bucket.Methods(http.MethodGet).HandlerFunc( - m.Handle(metrics.APIStats("getbucketnotification", h.GetBucketNotificationHandler))).Queries("notification", "") + m.Handle(metrics.APIStats("getbucketnotification", h.GetBucketNotificationHandler))).Queries("notification", ""). + Name("GetBucketNotification") // ListenBucketNotification - bucket.Methods(http.MethodGet).HandlerFunc(metrics.APIStats("listenbucketnotification", h.ListenBucketNotificationHandler)).Queries("events", "{events:.*}") + bucket.Methods(http.MethodGet).HandlerFunc(metrics.APIStats("listenbucketnotification", h.ListenBucketNotificationHandler)).Queries("events", "{events:.*}"). + Name("ListenBucketNotification") // ListMultipartUploads bucket.Methods(http.MethodGet).HandlerFunc( - m.Handle(metrics.APIStats("listmultipartuploads", h.ListMultipartUploadsHandler))).Queries("uploads", "") + m.Handle(metrics.APIStats("listmultipartuploads", h.ListMultipartUploadsHandler))).Queries("uploads", ""). + Name("ListMultipartUploads") // ListObjectsV2M bucket.Methods(http.MethodGet).HandlerFunc( - m.Handle(metrics.APIStats("listobjectsv2M", h.ListObjectsV2MHandler))).Queries("list-type", "2", "metadata", "true") + m.Handle(metrics.APIStats("listobjectsv2M", h.ListObjectsV2MHandler))).Queries("list-type", "2", "metadata", "true"). + Name("ListObjectsV2M") // ListObjectsV2 bucket.Methods(http.MethodGet).HandlerFunc( - m.Handle(metrics.APIStats("listobjectsv2", h.ListObjectsV2Handler))).Queries("list-type", "2") + m.Handle(metrics.APIStats("listobjectsv2", h.ListObjectsV2Handler))).Queries("list-type", "2"). + Name("ListObjectsV2") // ListBucketVersions bucket.Methods(http.MethodGet).HandlerFunc( - m.Handle(metrics.APIStats("listbucketversions", h.ListBucketObjectVersionsHandler))).Queries("versions", "") + m.Handle(metrics.APIStats("listbucketversions", h.ListBucketObjectVersionsHandler))).Queries("versions", ""). + Name("ListBucketVersions") // ListObjectsV1 (Legacy) bucket.Methods(http.MethodGet).HandlerFunc( - m.Handle(metrics.APIStats("listobjectsv1", h.ListObjectsV1Handler))) + m.Handle(metrics.APIStats("listobjectsv1", h.ListObjectsV1Handler))). + Name("ListObjectsV1") // PutBucketLifecycle bucket.Methods(http.MethodPut).HandlerFunc( - m.Handle(metrics.APIStats("putbucketlifecycle", h.PutBucketLifecycleHandler))).Queries("lifecycle", "") + m.Handle(metrics.APIStats("putbucketlifecycle", h.PutBucketLifecycleHandler))).Queries("lifecycle", ""). + Name("PutBucketLifecycle") // PutBucketEncryption bucket.Methods(http.MethodPut).HandlerFunc( - m.Handle(metrics.APIStats("putbucketencryption", h.PutBucketEncryptionHandler))).Queries("encryption", "") + m.Handle(metrics.APIStats("putbucketencryption", h.PutBucketEncryptionHandler))).Queries("encryption", ""). + Name("PutBucketEncryption") // PutBucketPolicy bucket.Methods(http.MethodPut).HandlerFunc( - m.Handle(metrics.APIStats("putbucketpolicy", h.PutBucketPolicyHandler))).Queries("policy", "") + m.Handle(metrics.APIStats("putbucketpolicy", h.PutBucketPolicyHandler))).Queries("policy", ""). + Name("PutBucketPolicy") // PutBucketObjectLockConfig bucket.Methods(http.MethodPut).HandlerFunc( - m.Handle(metrics.APIStats("putbucketobjectlockconfig", h.PutBucketObjectLockConfigHandler))).Queries("object-lock", "") + m.Handle(metrics.APIStats("putbucketobjectlockconfig", h.PutBucketObjectLockConfigHandler))).Queries("object-lock", ""). + Name("PutBucketObjectLockConfig") // PutBucketTaggingHandler bucket.Methods(http.MethodPut).HandlerFunc( - m.Handle(metrics.APIStats("putbuckettagging", h.PutBucketTaggingHandler))).Queries("tagging", "") + m.Handle(metrics.APIStats("putbuckettagging", h.PutBucketTaggingHandler))).Queries("tagging", ""). + Name("PutBucketTagging") // PutBucketVersioning bucket.Methods(http.MethodPut).HandlerFunc( - m.Handle(metrics.APIStats("putbucketversioning", h.PutBucketVersioningHandler))).Queries("versioning", "") + m.Handle(metrics.APIStats("putbucketversioning", h.PutBucketVersioningHandler))).Queries("versioning", ""). + Name("PutBucketVersioning") // PutBucketNotification bucket.Methods(http.MethodPut).HandlerFunc( - m.Handle(metrics.APIStats("putbucketnotification", h.PutBucketNotificationHandler))).Queries("notification", "") + m.Handle(metrics.APIStats("putbucketnotification", h.PutBucketNotificationHandler))).Queries("notification", ""). + Name("PutBucketNotification") // PutBucket bucket.Methods(http.MethodPut).HandlerFunc( - m.Handle(metrics.APIStats("putbucket", h.PutBucketHandler))) + m.Handle(metrics.APIStats("putbucket", h.PutBucketHandler))). + Name("PutBucket") // HeadBucket bucket.Methods(http.MethodHead).HandlerFunc( - m.Handle(metrics.APIStats("headbucket", h.HeadBucketHandler))) + m.Handle(metrics.APIStats("headbucket", h.HeadBucketHandler))). + Name("HeadBucket") // PostPolicy bucket.Methods(http.MethodPost).HeadersRegexp(hdrContentType, "multipart/form-data*").HandlerFunc( - m.Handle(metrics.APIStats("postpolicybucket", h.PostPolicyBucketHandler))) + m.Handle(metrics.APIStats("postpolicybucket", h.PostPolicyBucketHandler))). + Name("PostPolicyBucket") // DeleteMultipleObjects bucket.Methods(http.MethodPost).HandlerFunc( - m.Handle(metrics.APIStats("deletemultipleobjects", h.DeleteMultipleObjectsHandler))).Queries("delete", "") + m.Handle(metrics.APIStats("deletemultipleobjects", h.DeleteMultipleObjectsHandler))).Queries("delete", ""). + Name("DeleteMultipleObjects") // DeleteBucketPolicy bucket.Methods(http.MethodDelete).HandlerFunc( - m.Handle(metrics.APIStats("deletebucketpolicy", h.DeleteBucketPolicyHandler))).Queries("policy", "") + m.Handle(metrics.APIStats("deletebucketpolicy", h.DeleteBucketPolicyHandler))).Queries("policy", ""). + Name("DeleteBucketPolicy") // DeleteBucketLifecycle bucket.Methods(http.MethodDelete).HandlerFunc( - m.Handle(metrics.APIStats("deletebucketlifecycle", h.DeleteBucketLifecycleHandler))).Queries("lifecycle", "") + m.Handle(metrics.APIStats("deletebucketlifecycle", h.DeleteBucketLifecycleHandler))).Queries("lifecycle", ""). + Name("DeleteBucketLifecycle") // DeleteBucketEncryption bucket.Methods(http.MethodDelete).HandlerFunc( - m.Handle(metrics.APIStats("deletebucketencryption", h.DeleteBucketEncryptionHandler))).Queries("encryption", "") + m.Handle(metrics.APIStats("deletebucketencryption", h.DeleteBucketEncryptionHandler))).Queries("encryption", ""). + Name("DeleteBucketEncryption") // DeleteBucket bucket.Methods(http.MethodDelete).HandlerFunc( - m.Handle(metrics.APIStats("deletebucket", h.DeleteBucketHandler))) + m.Handle(metrics.APIStats("deletebucket", h.DeleteBucketHandler))). + Name("DeleteBucket") // Root operation // ListBuckets api.Methods(http.MethodGet).Path(SlashSeparator).HandlerFunc( - m.Handle(metrics.APIStats("listbuckets", h.ListBucketsHandler))) + m.Handle(metrics.APIStats("listbuckets", h.ListBucketsHandler))). + Name("ListBuckets") // S3 browser with signature v4 adds '//' for ListBuckets request, so rather // than failing with UnknownAPIRequest we simply handle it for now. api.Methods(http.MethodGet).Path(SlashSeparator + SlashSeparator).HandlerFunc( - m.Handle(metrics.APIStats("listbuckets", h.ListBucketsHandler))) + m.Handle(metrics.APIStats("listbuckets", h.ListBucketsHandler))). + Name("ListBuckets") // If none of the routes match add default error handler routes api.NotFoundHandler = metrics.APIStats("notfound", errorResponseHandler) From 64429d736dc797d30f1aec5c2edfbfebefa136e4 Mon Sep 17 00:00:00 2001 From: Evgeniy Kulikov Date: Tue, 11 Aug 2020 14:32:04 +0300 Subject: [PATCH 3/3] Refactoring API layer - logging RequestID - should return error, when headers already received Signed-off-by: Evgeniy Kulikov --- api/layer/container.go | 12 ++++++++++++ api/layer/object.go | 11 +++++++---- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/api/layer/container.go b/api/layer/container.go index 5d3308d..550ae05 100644 --- a/api/layer/container.go +++ b/api/layer/container.go @@ -7,6 +7,7 @@ import ( "github.com/nspcc-dev/neofs-api-go/container" "github.com/nspcc-dev/neofs-api-go/refs" "github.com/nspcc-dev/neofs-api-go/service" + "github.com/nspcc-dev/neofs-s3-gate/api" "github.com/nspcc-dev/neofs-s3-gate/auth" "go.uber.org/zap" ) @@ -28,9 +29,11 @@ type ( ) func (n *layer) containerInfo(ctx context.Context, cid refs.CID) (*BucketInfo, error) { + rid := api.GetRequestID(ctx) bearer, err := auth.GetBearerToken(ctx) if err != nil { n.log.Error("could not receive bearer token", + zap.String("request_id", rid), zap.Error(err)) return nil, err } @@ -44,6 +47,7 @@ func (n *layer) containerInfo(ctx context.Context, cid refs.CID) (*BucketInfo, e if err = service.SignRequestData(n.key, req); err != nil { n.log.Error("could not prepare request", + zap.String("request_id", rid), zap.Error(err)) return nil, err } @@ -51,6 +55,7 @@ func (n *layer) containerInfo(ctx context.Context, cid refs.CID) (*BucketInfo, e conn, err := n.cli.GetConnection(ctx) if err != nil { n.log.Error("could not prepare client", + zap.String("request_id", rid), zap.Error(err)) return nil, err } @@ -62,6 +67,7 @@ func (n *layer) containerInfo(ctx context.Context, cid refs.CID) (*BucketInfo, e res, err := container.NewServiceClient(conn).Get(ctx, req) if err != nil { n.log.Error("could not list buckets", + zap.String("request_id", rid), zap.Error(err)) return nil, err } @@ -76,9 +82,11 @@ func (n *layer) containerInfo(ctx context.Context, cid refs.CID) (*BucketInfo, e } func (n *layer) containerList(ctx context.Context) ([]BucketInfo, error) { + rid := api.GetRequestID(ctx) bearer, err := auth.GetBearerToken(ctx) if err != nil { n.log.Error("could not receive bearer token", + zap.String("request_id", rid), zap.Error(err)) return nil, err } @@ -92,6 +100,7 @@ func (n *layer) containerList(ctx context.Context) ([]BucketInfo, error) { if err := service.SignRequestData(n.key, req); err != nil { n.log.Error("could not prepare request", + zap.String("request_id", rid), zap.Error(err)) return nil, err } @@ -99,6 +108,7 @@ func (n *layer) containerList(ctx context.Context) ([]BucketInfo, error) { conn, err := n.cli.GetConnection(ctx) if err != nil { n.log.Error("could not prepare client", + zap.String("request_id", rid), zap.Error(err)) return nil, err } @@ -110,6 +120,7 @@ func (n *layer) containerList(ctx context.Context) ([]BucketInfo, error) { res, err := container.NewServiceClient(conn).List(ctx, req) if err != nil { n.log.Error("could not list buckets", + zap.String("request_id", rid), zap.Error(err)) return nil, err } @@ -119,6 +130,7 @@ func (n *layer) containerList(ctx context.Context) ([]BucketInfo, error) { info, err := n.containerInfo(ctx, cid) if err != nil { n.log.Error("could not fetch container info", + zap.String("request_id", rid), zap.Error(err)) continue } diff --git a/api/layer/object.go b/api/layer/object.go index 55b7be1..c7d160d 100644 --- a/api/layer/object.go +++ b/api/layer/object.go @@ -278,8 +278,9 @@ func receiveObject(cli object.Service_GetClient) (*object.Object, error) { switch o := resp.R.(type) { case *object.GetResponse_Object: - - if _, hdr := o.Object.LastHeader(object.HeaderType(object.TombstoneHdr)); hdr != nil { + if obj != nil { + return nil, errors.New("object headers already received") + } else if _, hdr := o.Object.LastHeader(object.HeaderType(object.TombstoneHdr)); hdr != nil { return nil, errors.New("object already removed") } @@ -480,9 +481,11 @@ func (n *layer) storageGroupPut(ctx context.Context, p sgParams) (*object.Object return nil, err } - client := object.NewServiceClient(conn) // todo: think about timeout - putClient, err := client.Put(ctx) + ctx, cancel := context.WithTimeout(ctx, 30*time.Second) + defer cancel() + + putClient, err := object.NewServiceClient(conn).Put(ctx) if err != nil { return nil, err }