From 5df041f7d7a020bbc1e0e1cd85051235117aae51 Mon Sep 17 00:00:00 2001 From: Evgeniy Kulikov Date: Thu, 20 Aug 2020 02:27:13 +0300 Subject: [PATCH 01/13] Add func to write headers only Signed-off-by: Evgeniy Kulikov --- api/response.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/api/response.go b/api/response.go index 0e42462..6002958 100644 --- a/api/response.go +++ b/api/response.go @@ -199,6 +199,10 @@ func WriteSuccessResponseXML(w http.ResponseWriter, response []byte) { writeResponse(w, http.StatusOK, response, mimeXML) } +func WriteSuccessResponseHeadersOnly(w http.ResponseWriter) { + writeResponse(w, http.StatusOK, nil, mimeNone) +} + // Error - Returns S3 error string. func (e ErrorResponse) Error() string { if e.Message == "" { From 447a255d18a182668b0965481dffa09b2ca93eb1 Mon Sep 17 00:00:00 2001 From: Evgeniy Kulikov Date: Thu, 20 Aug 2020 02:28:16 +0300 Subject: [PATCH 02/13] Add func to debug requests - logging middleware - response writer with status code Signed-off-by: Evgeniy Kulikov --- api/router.go | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/api/router.go b/api/router.go index 0f47817..c947ac5 100644 --- a/api/router.go +++ b/api/router.go @@ -79,6 +79,11 @@ type ( // mimeType represents various MIME type used API responses. mimeType string + + logResponseWriter struct { + http.ResponseWriter + statusCode int + } ) const ( @@ -93,6 +98,13 @@ const ( mimeXML mimeType = "application/xml" ) +var _ = logErrorResponse + +func (lrw *logResponseWriter) WriteHeader(code int) { + lrw.statusCode = code + lrw.ResponseWriter.WriteHeader(code) +} + func setRequestID(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // generate random UUIDv4 @@ -114,6 +126,24 @@ func setRequestID(h http.Handler) http.Handler { }) } +func logErrorResponse(l *zap.Logger) mux.MiddlewareFunc { + return func(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + lw := &logResponseWriter{ResponseWriter: w} + + // pass execution: + h.ServeHTTP(lw, r) + + // Ignore <300 status codes + if lw.statusCode >= http.StatusMultipleChoices { + l.Error("something went wrong", + zap.Int("status", lw.statusCode), + zap.String("method", mux.CurrentRoute(r).GetName())) + } + }) + } +} + func GetRequestID(v interface{}) string { switch t := v.(type) { case context.Context: @@ -128,8 +158,13 @@ func GetRequestID(v interface{}) string { 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) + api.Use( + // -- prepare request + setRequestID, + + // -- logging error requests + // logErrorResponse(log), + ) // Attach user authentication for all S3 routes. AttachUserAuth(api, center, log) From fb00af23fb09c99b6dec76f04041cbc5b9f4c214 Mon Sep 17 00:00:00 2001 From: Evgeniy Kulikov Date: Thu, 20 Aug 2020 02:30:18 +0300 Subject: [PATCH 03/13] Fixed possible NPE in PutObject Signed-off-by: Evgeniy Kulikov --- api/layer/layer.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/api/layer/layer.go b/api/layer/layer.go index 79994ed..80a05b9 100644 --- a/api/layer/layer.go +++ b/api/layer/layer.go @@ -288,7 +288,10 @@ func (n *layer) PutObject(ctx context.Context, p *PutObjectParams) (*ObjectInfo, _, err = n.objectFindID(ctx, cid, p.Object, true) if err == nil { - return nil, err + return nil, &api.ObjectAlreadyExists{ + Bucket: p.Bucket, + Object: p.Object, + } } oid, err := refs.NewObjectID() From 00a17d8316cd9fc1c613a7e477b47ccdf631e968 Mon Sep 17 00:00:00 2001 From: Evgeniy Kulikov Date: Thu, 20 Aug 2020 02:31:04 +0300 Subject: [PATCH 04/13] CopyObject should contains custom headers Signed-off-by: Evgeniy Kulikov --- api/layer/layer.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/api/layer/layer.go b/api/layer/layer.go index 80a05b9..4cf5633 100644 --- a/api/layer/layer.go +++ b/api/layer/layer.go @@ -10,6 +10,7 @@ import ( "github.com/nspcc-dev/neofs-api-go/object" "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/api/pool" "github.com/pkg/errors" "go.uber.org/zap" @@ -44,6 +45,7 @@ type ( DstBucket string SrcObject string DstObject string + Header map[string]string } NeoFS interface { @@ -355,6 +357,11 @@ func (n *layer) CopyObject(ctx context.Context, p *CopyObjectParams) (*ObjectInf _ = pw.CloseWithError(err) }() + // set custom headers + for k, v := range p.Header { + info.Headers[k] = v + } + return n.PutObject(ctx, &PutObjectParams{ Bucket: p.DstBucket, Object: p.DstObject, From edd44ea981de92af74fd7a86672cdd82750cef69 Mon Sep 17 00:00:00 2001 From: Evgeniy Kulikov Date: Thu, 20 Aug 2020 02:32:42 +0300 Subject: [PATCH 05/13] Check that aws filename wasn't set we should check that filename header wasn't set, otherwise we should not change custom filename Signed-off-by: Evgeniy Kulikov --- api/layer/object.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/api/layer/object.go b/api/layer/object.go index c7d160d..711a815 100644 --- a/api/layer/object.go +++ b/api/layer/object.go @@ -390,7 +390,10 @@ func (n *layer) objectPut(ctx context.Context, p putParams) (*object.Object, err p.userHeaders = make(map[string]string) } - p.userHeaders[AWS3NameHeader] = p.name + // Set object name if not set before + if _, ok := p.userHeaders[AWS3NameHeader]; !ok { + p.userHeaders[AWS3NameHeader] = p.name + } readBuffer := make([]byte, dataChunkSize) obj := &object.Object{ From fb13d1fcb35074507ce6c98767e3bde1e78db8f2 Mon Sep 17 00:00:00 2001 From: Evgeniy Kulikov Date: Thu, 20 Aug 2020 02:33:43 +0300 Subject: [PATCH 06/13] Move ListObjects and ListBuckets in same file Signed-off-by: Evgeniy Kulikov --- api/handler/list-buckets.go | 73 ------------------------ api/handler/{list-objects.go => list.go} | 71 +++++++++++++++++++++-- 2 files changed, 67 insertions(+), 77 deletions(-) delete mode 100644 api/handler/list-buckets.go rename api/handler/{list-objects.go => list.go} (64%) diff --git a/api/handler/list-buckets.go b/api/handler/list-buckets.go deleted file mode 100644 index 2002ec2..0000000 --- a/api/handler/list-buckets.go +++ /dev/null @@ -1,73 +0,0 @@ -package handler - -import ( - "net/http" - "time" - - "github.com/nspcc-dev/neofs-s3-gate/api" - "github.com/nspcc-dev/neofs-s3-gate/auth" - "go.uber.org/zap" -) - -func (h *handler) ListBucketsHandler(w http.ResponseWriter, r *http.Request) { - var ( - res *ListBucketsResponse - rid = api.GetRequestID(r.Context()) - ) - - tkn, err := auth.GetBearerToken(r.Context()) - if err != nil { - h.log.Error("something went wrong", - zap.String("request_id", rid), - zap.Error(err)) - - api.WriteErrorResponse(r.Context(), w, api.Error{ - Code: api.GetAPIError(api.ErrInternalError).Code, - Description: err.Error(), - HTTPStatusCode: http.StatusInternalServerError, - }, r.URL) - - return - } - - list, err := h.obj.ListBuckets(r.Context()) - if err != nil { - h.log.Error("something went wrong", - zap.String("request_id", rid), - zap.Error(err)) - - api.WriteErrorResponse(r.Context(), w, api.Error{ - Code: api.GetAPIError(api.ErrInternalError).Code, - Description: err.Error(), - HTTPStatusCode: http.StatusInternalServerError, - }, r.URL) - - return - } - - res = &ListBucketsResponse{ - Owner: Owner{ - ID: tkn.OwnerID.String(), - DisplayName: tkn.OwnerID.String(), - }, - } - - for _, item := range list { - res.Buckets.Buckets = append(res.Buckets.Buckets, Bucket{ - Name: item.Name, - CreationDate: item.Created.Format(time.RFC3339), - }) - } - - if err = api.EncodeToResponse(w, res); err != nil { - h.log.Error("something went wrong", - zap.String("request_id", rid), - zap.Error(err)) - - api.WriteErrorResponse(r.Context(), w, api.Error{ - Code: api.GetAPIError(api.ErrInternalError).Code, - Description: err.Error(), - HTTPStatusCode: http.StatusInternalServerError, - }, r.URL) - } -} diff --git a/api/handler/list-objects.go b/api/handler/list.go similarity index 64% rename from api/handler/list-objects.go rename to api/handler/list.go index 241151b..78b8b4a 100644 --- a/api/handler/list-objects.go +++ b/api/handler/list.go @@ -5,9 +5,9 @@ import ( "strconv" "time" - "github.com/gorilla/mux" "github.com/nspcc-dev/neofs-s3-gate/api" "github.com/nspcc-dev/neofs-s3-gate/api/layer" + "github.com/nspcc-dev/neofs-s3-gate/auth" "go.uber.org/zap" ) @@ -23,6 +23,69 @@ type listObjectsArgs struct { var maxObjectList = 10000 // Limit number of objects in a listObjectsResponse/listObjectsVersionsResponse. +func (h *handler) ListBucketsHandler(w http.ResponseWriter, r *http.Request) { + var ( + res *ListBucketsResponse + rid = api.GetRequestID(r.Context()) + ) + + tkn, err := auth.GetBearerToken(r.Context()) + if err != nil { + h.log.Error("something went wrong", + zap.String("request_id", rid), + zap.Error(err)) + + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: api.GetAPIError(api.ErrInternalError).Code, + Description: err.Error(), + HTTPStatusCode: http.StatusInternalServerError, + }, r.URL) + + return + } + + list, err := h.obj.ListBuckets(r.Context()) + if err != nil { + h.log.Error("something went wrong", + zap.String("request_id", rid), + zap.Error(err)) + + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: api.GetAPIError(api.ErrInternalError).Code, + Description: err.Error(), + HTTPStatusCode: http.StatusInternalServerError, + }, r.URL) + + return + } + + res = &ListBucketsResponse{ + Owner: Owner{ + ID: tkn.OwnerID.String(), + DisplayName: tkn.OwnerID.String(), + }, + } + + for _, item := range list { + res.Buckets.Buckets = append(res.Buckets.Buckets, Bucket{ + Name: item.Name, + CreationDate: item.Created.Format(time.RFC3339), + }) + } + + if err = api.EncodeToResponse(w, res); err != nil { + h.log.Error("something went wrong", + zap.String("request_id", rid), + zap.Error(err)) + + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: api.GetAPIError(api.ErrInternalError).Code, + Description: err.Error(), + HTTPStatusCode: http.StatusInternalServerError, + }, r.URL) + } +} + func (h *handler) ListObjectsV1Handler(w http.ResponseWriter, r *http.Request) { var ( err error @@ -104,9 +167,9 @@ func (h *handler) ListObjectsV1Handler(w http.ResponseWriter, r *http.Request) { zap.Error(err)) api.WriteErrorResponse(r.Context(), w, api.Error{ - Code: "XNeoFSUnimplemented", - Description: "implement me " + mux.CurrentRoute(r).GetName(), - HTTPStatusCode: http.StatusNotImplemented, + Code: api.GetAPIError(api.ErrInternalError).Code, + Description: err.Error(), + HTTPStatusCode: http.StatusInternalServerError, }, r.URL) } } From c6d4b6d731f84fba3e761f70703be1e9baef568b Mon Sep 17 00:00:00 2001 From: Evgeniy Kulikov Date: Thu, 20 Aug 2020 02:34:47 +0300 Subject: [PATCH 07/13] Implement MarshalXML for StringMap type Signed-off-by: Evgeniy Kulikov --- api/handler/response.go | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/api/handler/response.go b/api/handler/response.go index c178a6d..84d61cf 100644 --- a/api/handler/response.go +++ b/api/handler/response.go @@ -86,3 +86,31 @@ type LocationResponse struct { XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ LocationConstraint" json:"-"` Location string `xml:",chardata"` } + +// MarshalXML - StringMap marshals into XML. +func (s StringMap) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + + tokens := []xml.Token{start} + + for key, value := range s { + t := xml.StartElement{} + t.Name = xml.Name{ + Space: "", + Local: key, + } + tokens = append(tokens, t, xml.CharData(value), xml.EndElement{Name: t.Name}) + } + + tokens = append(tokens, xml.EndElement{ + Name: start.Name, + }) + + for _, t := range tokens { + if err := e.EncodeToken(t); err != nil { + return err + } + } + + // flush to ensure tokens are written + return e.Flush() +} From 9ab0571d2935bd7b88707024fae8bf2b7d7bda94 Mon Sep 17 00:00:00 2001 From: Evgeniy Kulikov Date: Thu, 20 Aug 2020 02:35:51 +0300 Subject: [PATCH 08/13] Implement CopyObject handler Signed-off-by: Evgeniy Kulikov --- api/handler/copy.go | 96 +++++++++++++++++++++++++++++++++++++++++ api/handler/response.go | 7 +++ 2 files changed, 103 insertions(+) create mode 100644 api/handler/copy.go diff --git a/api/handler/copy.go b/api/handler/copy.go new file mode 100644 index 0000000..92235cc --- /dev/null +++ b/api/handler/copy.go @@ -0,0 +1,96 @@ +package handler + +import ( + "net/http" + "net/url" + "strings" + "time" + + "github.com/gorilla/mux" + "github.com/nspcc-dev/neofs-s3-gate/api" + "github.com/nspcc-dev/neofs-s3-gate/api/layer" + "go.uber.org/zap" +) + +// path2BucketObject returns bucket and object. +func path2BucketObject(path string) (bucket, prefix string) { + path = strings.TrimPrefix(path, api.SlashSeparator) + m := strings.Index(path, api.SlashSeparator) + if m < 0 { + return path, "" + } + return path[:m], path[m+len(api.SlashSeparator):] +} + +func (h *handler) CopyObjectHandler(w http.ResponseWriter, r *http.Request) { + var ( + err error + inf *layer.ObjectInfo + + req = mux.Vars(r) + bkt = req["bucket"] + obj = req["object"] + rid = api.GetRequestID(r.Context()) + ) + + src := r.Header.Get("X-Amz-Copy-Source") + // Check https://docs.aws.amazon.com/AmazonS3/latest/dev/ObjectVersioning.html + // Regardless of whether you have enabled versioning, each object in your bucket + // has a version ID. If you have not enabled versioning, Amazon S3 sets the value + // of the version ID to null. If you have enabled versioning, Amazon S3 assigns a + // unique version ID value for the object. + if u, err := url.Parse(src); err == nil { + // Check if versionId query param was added, if yes then check if + // its non "null" value, we should error out since we do not support + // any versions other than "null". + if vid := u.Query().Get("versionId"); vid != "" && vid != "null" { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: api.GetAPIError(api.ErrNoSuchVersion).Code, + Description: "", + HTTPStatusCode: http.StatusBadRequest, + }, r.URL) + return + } + + src = u.Path + } + + srcBucket, srcObject := path2BucketObject(src) + + params := &layer.CopyObjectParams{ + SrcBucket: srcBucket, + DstBucket: bkt, + SrcObject: srcObject, + DstObject: obj, + } + + if inf, err = h.obj.CopyObject(r.Context(), params); err != nil { + h.log.Error("could not copy object", + zap.String("request_id", rid), + zap.String("dst_bucket_name", bkt), + zap.String("dst_object_name", obj), + zap.String("src_bucket_name", srcBucket), + zap.String("src_object_name", srcObject), + zap.Error(err)) + + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: api.GetAPIError(api.ErrInternalError).Code, + Description: err.Error(), + HTTPStatusCode: http.StatusInternalServerError, + }, r.URL) + } else if err = api.EncodeToResponse(w, &CopyObjectResponse{LastModified: inf.Created.Format(time.RFC3339)}); err != nil { + h.log.Error("something went wrong", + zap.String("request_id", rid), + zap.String("dst_bucket_name", bkt), + zap.String("dst_object_name", obj), + zap.String("src_bucket_name", srcBucket), + zap.String("src_object_name", srcObject), + zap.Error(err)) + + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: api.GetAPIError(api.ErrInternalError).Code, + Description: err.Error(), + HTTPStatusCode: http.StatusInternalServerError, + }, r.URL) + } +} diff --git a/api/handler/response.go b/api/handler/response.go index 84d61cf..d29d263 100644 --- a/api/handler/response.go +++ b/api/handler/response.go @@ -87,6 +87,13 @@ type LocationResponse struct { Location string `xml:",chardata"` } +// CopyObjectResponse container returns ETag and LastModified of the successfully copied object +type CopyObjectResponse struct { + XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ CopyObjectResult" json:"-"` + LastModified string // time string of format "2006-01-02T15:04:05.000Z" + ETag string // md5sum of the copied object. +} + // MarshalXML - StringMap marshals into XML. func (s StringMap) MarshalXML(e *xml.Encoder, start xml.StartElement) error { From 9746978b1d555ff2d1c596ac7a31820dd372758f Mon Sep 17 00:00:00 2001 From: Evgeniy Kulikov Date: Thu, 20 Aug 2020 02:36:00 +0300 Subject: [PATCH 09/13] Implement DeleteObject handler Signed-off-by: Evgeniy Kulikov --- api/handler/delete.go | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 api/handler/delete.go diff --git a/api/handler/delete.go b/api/handler/delete.go new file mode 100644 index 0000000..1798317 --- /dev/null +++ b/api/handler/delete.go @@ -0,0 +1,36 @@ +package handler + +import ( + "net/http" + + "github.com/gorilla/mux" + "github.com/nspcc-dev/neofs-s3-gate/api" + "go.uber.org/zap" +) + +func (h *handler) DeleteObjectHandler(w http.ResponseWriter, r *http.Request) { + var ( + req = mux.Vars(r) + bkt = req["bucket"] + obj = req["object"] + rid = api.GetRequestID(r.Context()) + ) + + if err := h.obj.DeleteObject(r.Context(), bkt, obj); err != nil { + h.log.Error("could not delete object", + zap.String("request_id", rid), + zap.String("bucket_name", bkt), + zap.String("object_name", obj), + zap.Error(err)) + + // Ignore delete errors: + + // api.WriteErrorResponse(r.Context(), w, api.Error{ + // Code: api.GetAPIError(api.ErrInternalError).Code, + // Description: err.Error(), + // HTTPStatusCode: http.StatusInternalServerError, + // }, r.URL) + } + + w.WriteHeader(http.StatusNoContent) +} From 9493f3d281b3cb4197c7c73f65d623dd2e91b033 Mon Sep 17 00:00:00 2001 From: Evgeniy Kulikov Date: Thu, 20 Aug 2020 02:36:17 +0300 Subject: [PATCH 10/13] Implement PutObject handler Signed-off-by: Evgeniy Kulikov --- api/handler/put.go | 74 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 api/handler/put.go diff --git a/api/handler/put.go b/api/handler/put.go new file mode 100644 index 0000000..835aa28 --- /dev/null +++ b/api/handler/put.go @@ -0,0 +1,74 @@ +package handler + +import ( + "net/http" + + "github.com/gorilla/mux" + "github.com/nspcc-dev/neofs-s3-gate/api" + "github.com/nspcc-dev/neofs-s3-gate/api/layer" + "go.uber.org/zap" +) + +func (h *handler) PutObjectHandler(w http.ResponseWriter, r *http.Request) { + var ( + err error + req = mux.Vars(r) + bkt = req["bucket"] + obj = req["object"] + rid = api.GetRequestID(r.Context()) + ) + + if _, err := h.obj.GetBucketInfo(r.Context(), bkt); err != nil { + h.log.Error("could not find bucket", + zap.String("request_id", rid), + zap.String("bucket_name", bkt), + zap.Error(err)) + + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: api.GetAPIError(api.ErrBadRequest).Code, + Description: err.Error(), + HTTPStatusCode: http.StatusBadRequest, + }, r.URL) + + return + } else if _, err = h.obj.GetObjectInfo(r.Context(), bkt, obj); err == nil { + h.log.Error("object exists", + zap.String("request_id", rid), + zap.String("bucket_name", bkt), + zap.String("object_name", obj), + zap.Error(err)) + + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: api.GetAPIError(api.ErrMethodNotAllowed).Code, + Description: "Object: " + bkt + "#" + obj + " already exists", + HTTPStatusCode: http.StatusBadRequest, + }, r.URL) + + return + } + + params := &layer.PutObjectParams{ + Bucket: bkt, + Object: obj, + Reader: r.Body, + Size: r.ContentLength, + } + + if _, err = h.obj.PutObject(r.Context(), params); err != nil { + h.log.Error("could not upload object", + zap.String("request_id", rid), + zap.String("bucket_name", bkt), + zap.String("object_name", obj), + zap.Error(err)) + + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: api.GetAPIError(api.ErrInternalError).Code, + Description: err.Error(), + HTTPStatusCode: http.StatusInternalServerError, + }, r.URL) + + return + } + + api.WriteSuccessResponseHeadersOnly(w) +} From bfc4b8786f9b172cf2710d7be26242ce920d121c Mon Sep 17 00:00:00 2001 From: Evgeniy Kulikov Date: Thu, 20 Aug 2020 02:36:34 +0300 Subject: [PATCH 11/13] Implement HeadObject handler Signed-off-by: Evgeniy Kulikov --- api/handler/head.go | 47 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 api/handler/head.go diff --git a/api/handler/head.go b/api/handler/head.go new file mode 100644 index 0000000..2d30d9c --- /dev/null +++ b/api/handler/head.go @@ -0,0 +1,47 @@ +package handler + +import ( + "net/http" + "strconv" + + "github.com/gorilla/mux" + "github.com/nspcc-dev/neofs-s3-gate/api" + "github.com/nspcc-dev/neofs-s3-gate/api/layer" + "go.uber.org/zap" +) + +func (h *handler) HeadObjectHandler(w http.ResponseWriter, r *http.Request) { + var ( + err error + inf *layer.ObjectInfo + + req = mux.Vars(r) + bkt = req["bucket"] + obj = req["object"] + rid = api.GetRequestID(r.Context()) + ) + + if inf, err = h.obj.GetObjectInfo(r.Context(), bkt, obj); err != nil { + h.log.Error("could not fetch object info", + zap.String("request_id", rid), + zap.String("bucket_name", bkt), + zap.String("object_name", obj), + zap.Error(err)) + + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: api.GetAPIError(api.ErrInternalError).Code, + Description: err.Error(), + HTTPStatusCode: http.StatusInternalServerError, + }, r.URL) + + return + } + + w.WriteHeader(http.StatusOK) + + w.Header().Set("Content-Type", inf.ContentType) + w.Header().Set("Content-Length", strconv.FormatInt(inf.Size, 10)) + + w.Header().Set("Last-Modified", inf.Created.Format(http.TimeFormat)) + +} From 1d98c4ecc27cbac505a2e9c0c8b8e33a284adf17 Mon Sep 17 00:00:00 2001 From: Evgeniy Kulikov Date: Thu, 20 Aug 2020 02:36:46 +0300 Subject: [PATCH 12/13] Implement GetObject handler Signed-off-by: Evgeniy Kulikov --- api/handler/get.go | 69 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 api/handler/get.go diff --git a/api/handler/get.go b/api/handler/get.go new file mode 100644 index 0000000..a2f528f --- /dev/null +++ b/api/handler/get.go @@ -0,0 +1,69 @@ +package handler + +import ( + "net/http" + "strconv" + + "github.com/gorilla/mux" + "github.com/nspcc-dev/neofs-s3-gate/api" + "github.com/nspcc-dev/neofs-s3-gate/api/layer" + "go.uber.org/zap" +) + +func (h *handler) GetObjectHandler(w http.ResponseWriter, r *http.Request) { + var ( + err error + inf *layer.ObjectInfo + + req = mux.Vars(r) + bkt = req["bucket"] + obj = req["object"] + rid = api.GetRequestID(r.Context()) + ) + + params := &layer.GetObjectParams{ + Bucket: bkt, + Object: obj, + Writer: w, + } + + if inf, err = h.obj.GetObjectInfo(r.Context(), bkt, obj); err != nil { + h.log.Error("could not find object", + zap.String("request_id", rid), + zap.String("bucket_name", bkt), + zap.String("object_name", obj), + zap.Error(err)) + + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: api.GetAPIError(api.ErrInternalError).Code, + Description: err.Error(), + HTTPStatusCode: http.StatusInternalServerError, + }, r.URL) + + return + } + + params.Length = inf.Size + + if err = h.obj.GetObject(r.Context(), params); err != nil { + h.log.Error("could not get object", + zap.String("request_id", rid), + zap.String("bucket_name", bkt), + zap.String("object_name", obj), + zap.Error(err)) + + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: api.GetAPIError(api.ErrInternalError).Code, + Description: err.Error(), + HTTPStatusCode: http.StatusInternalServerError, + }, r.URL) + + return + } + + w.Header().Set("Content-Type", inf.ContentType) + w.Header().Set("Content-Length", strconv.FormatInt(inf.Size, 10)) + + w.Header().Set("Last-Modified", inf.Created.Format(http.TimeFormat)) + +} From 5bb2c340527d1ee96dcf75a86497e6185b18adf5 Mon Sep 17 00:00:00 2001 From: Evgeniy Kulikov Date: Thu, 20 Aug 2020 02:37:06 +0300 Subject: [PATCH 13/13] Cleanup unimplemented handlers Signed-off-by: Evgeniy Kulikov --- api/handler/unimplemented.go | 40 ------------------------------------ 1 file changed, 40 deletions(-) diff --git a/api/handler/unimplemented.go b/api/handler/unimplemented.go index e0a3c61..39e2c59 100644 --- a/api/handler/unimplemented.go +++ b/api/handler/unimplemented.go @@ -7,14 +7,6 @@ import ( "github.com/nspcc-dev/neofs-s3-gate/api" ) -func (h *handler) HeadObjectHandler(w http.ResponseWriter, r *http.Request) { - api.WriteErrorResponse(r.Context(), w, api.Error{ - Code: "XNeoFSUnimplemented", - Description: "implement me " + mux.CurrentRoute(r).GetName(), - HTTPStatusCode: http.StatusNotImplemented, - }, r.URL) -} - func (h *handler) CopyObjectPartHandler(w http.ResponseWriter, r *http.Request) { api.WriteErrorResponse(r.Context(), w, api.Error{ Code: "XNeoFSUnimplemented", @@ -127,22 +119,6 @@ func (h *handler) GetObjectLegalHoldHandler(w http.ResponseWriter, r *http.Reque }, r.URL) } -func (h *handler) GetObjectHandler(w http.ResponseWriter, r *http.Request) { - api.WriteErrorResponse(r.Context(), w, api.Error{ - Code: "XNeoFSUnimplemented", - Description: "implement me " + mux.CurrentRoute(r).GetName(), - HTTPStatusCode: http.StatusNotImplemented, - }, r.URL) -} - -func (h *handler) CopyObjectHandler(w http.ResponseWriter, r *http.Request) { - api.WriteErrorResponse(r.Context(), w, api.Error{ - Code: "XNeoFSUnimplemented", - Description: "implement me " + mux.CurrentRoute(r).GetName(), - HTTPStatusCode: http.StatusNotImplemented, - }, r.URL) -} - func (h *handler) PutObjectRetentionHandler(w http.ResponseWriter, r *http.Request) { api.WriteErrorResponse(r.Context(), w, api.Error{ Code: "XNeoFSUnimplemented", @@ -159,22 +135,6 @@ func (h *handler) PutObjectLegalHoldHandler(w http.ResponseWriter, r *http.Reque }, r.URL) } -func (h *handler) PutObjectHandler(w http.ResponseWriter, r *http.Request) { - api.WriteErrorResponse(r.Context(), w, api.Error{ - Code: "XNeoFSUnimplemented", - Description: "implement me " + mux.CurrentRoute(r).GetName(), - HTTPStatusCode: http.StatusNotImplemented, - }, r.URL) -} - -func (h *handler) DeleteObjectHandler(w http.ResponseWriter, r *http.Request) { - api.WriteErrorResponse(r.Context(), w, api.Error{ - Code: "XNeoFSUnimplemented", - Description: "implement me " + mux.CurrentRoute(r).GetName(), - HTTPStatusCode: http.StatusNotImplemented, - }, r.URL) -} - func (h *handler) GetBucketPolicyHandler(w http.ResponseWriter, r *http.Request) { api.WriteErrorResponse(r.Context(), w, api.Error{ Code: "XNeoFSUnimplemented",