From 2d96d5ad57ff88005d3186d1e2cd40b405628b39 Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Sun, 26 Apr 2015 22:01:55 -0700 Subject: [PATCH] Enhance error responses for request limit and bring some code from api errors ~~~ mc: Failed to create bucket for URL [http://localhost:9000/newbucket-101]. Reason: [Reduce your request rate.]. ~~~ Client sees proper errors now. --- pkg/api/quota/bandwidth_cap.go | 23 ++----- pkg/api/quota/errors.go | 121 +++++++++++++++++++++++++++++++++ pkg/api/quota/quota_handler.go | 24 ------- pkg/api/quota/request_limit.go | 2 +- 4 files changed, 126 insertions(+), 44 deletions(-) create mode 100644 pkg/api/quota/errors.go diff --git a/pkg/api/quota/bandwidth_cap.go b/pkg/api/quota/bandwidth_cap.go index 331aa0dbe..7b47a48cd 100644 --- a/pkg/api/quota/bandwidth_cap.go +++ b/pkg/api/quota/bandwidth_cap.go @@ -18,11 +18,12 @@ package quota import ( "errors" - "github.com/minio-io/minio/pkg/iodine" "io" "net" "net/http" "time" + + "github.com/minio-io/minio/pkg/iodine" ) // bandwidthQuotaHandler @@ -31,28 +32,12 @@ type bandwidthQuotaHandler struct { quotas *quotaMap } -var bandwidthQuotaExceeded = ErrorResponse{ - Code: "BandwithQuotaExceeded", - Message: "Bandwidth Quota Exceeded", - Resource: "", - RequestID: "", - HostID: "", -} - -var bandwidthInsufficientToProceed = ErrorResponse{ - Code: "BandwidthQuotaWillBeExceeded", - Message: "Bandwidth quota will be exceeded with this request", - Resource: "", - RequestID: "", - HostID: "", -} - // ServeHTTP is an http.Handler ServeHTTP method func (h *bandwidthQuotaHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { host, _, _ := net.SplitHostPort(req.RemoteAddr) longIP := longIP{net.ParseIP(host)}.IptoUint32() if h.quotas.WillExceedQuota(longIP, req.ContentLength) { - writeError(w, req, bandwidthInsufficientToProceed, 429) + writeErrorResponse(w, req, BandWidthInsufficientToProceed, req.URL.Path) return } req.Body = "aReader{ @@ -98,7 +83,7 @@ func (q *quotaReader) Read(b []byte) (int, error) { } if q.quotas.IsQuotaMet(q.ip) { q.err = true - writeError(q.w, q.req, bandwidthQuotaExceeded, 429) + writeErrorResponse(q.w, q.req, BandWidthQuotaExceeded, q.req.URL.Path) return 0, iodine.New(errors.New("Quota Met"), nil) } n, err := q.ReadCloser.Read(b) diff --git a/pkg/api/quota/errors.go b/pkg/api/quota/errors.go new file mode 100644 index 000000000..d236aee79 --- /dev/null +++ b/pkg/api/quota/errors.go @@ -0,0 +1,121 @@ +/* + * Minimalist Object Storage, (C) 2015 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package quota + +import ( + "bytes" + "encoding/xml" + "net/http" +) + +// copied from api, no cyclic deps allowed + +// Error structure +type Error struct { + Code string + Description string + HTTPStatusCode int +} + +// ErrorResponse - error response format +type ErrorResponse struct { + XMLName xml.Name `xml:"Error" json:"-"` + Code string + Message string + Resource string + RequestID string + HostID string +} + +// Quota standard errors non exhaustive list +const ( + RequestTimeTooSkewed = iota + BandWidthQuotaExceeded + BandWidthInsufficientToProceed + SlowDown +) + +// Golang http doesn't implement these +const ( + StatusTooManyRequests = 429 +) + +func writeErrorResponse(w http.ResponseWriter, req *http.Request, errorType int, resource string) { + error := getErrorCode(errorType) + errorResponse := getErrorResponse(error, resource) + // set headers + writeErrorHeaders(w) + w.WriteHeader(error.HTTPStatusCode) + // write body + encodedErrorResponse := encodeErrorResponse(errorResponse) + w.Write(encodedErrorResponse) +} + +func writeErrorHeaders(w http.ResponseWriter) { + w.Header().Set("Server", "Minio") + w.Header().Set("Accept-Ranges", "bytes") + w.Header().Set("Content-Type", "application/xml") + w.Header().Set("Connection", "close") +} + +// Error code to Error structure map +var errorCodeResponse = map[int]Error{ + BandWidthQuotaExceeded: { + Code: "BandwidthQuotaExceeded", + Description: "Bandwidth Quota Exceeded", + HTTPStatusCode: StatusTooManyRequests, + }, + BandWidthInsufficientToProceed: { + Code: "BandwidthQuotaWillBeExceeded", + Description: "Bandwidth quota will be exceeded with this request", + HTTPStatusCode: StatusTooManyRequests, + }, + SlowDown: { + Code: "SlowDown", + Description: "Reduce your request rate.", + HTTPStatusCode: StatusTooManyRequests, + }, +} + +// Write error response headers +func encodeErrorResponse(response interface{}) []byte { + var bytesBuffer bytes.Buffer + encoder := xml.NewEncoder(&bytesBuffer) + encoder.Encode(response) + return bytesBuffer.Bytes() +} + +// errorCodeError provides errorCode to Error. It returns empty if the code provided is unknown +func getErrorCode(code int) Error { + return errorCodeResponse[code] +} + +// getErrorResponse gets in standard error and resource value and +// provides a encodable populated response values +func getErrorResponse(err Error, resource string) ErrorResponse { + var data = ErrorResponse{} + data.Code = err.Code + data.Message = err.Description + if resource != "" { + data.Resource = resource + } + // TODO implement this in future + data.RequestID = "3L137" + data.HostID = "3L137" + + return data +} diff --git a/pkg/api/quota/quota_handler.go b/pkg/api/quota/quota_handler.go index e012fba5c..461d3d919 100644 --- a/pkg/api/quota/quota_handler.go +++ b/pkg/api/quota/quota_handler.go @@ -17,11 +17,8 @@ package quota import ( - "bytes" "encoding/binary" - "encoding/xml" "net" - "net/http" "sync" "time" ) @@ -95,24 +92,3 @@ func (p longIP) IptoUint32() (result uint32) { } return binary.BigEndian.Uint32(ip) } - -// copied from api, no cyclic deps allowed - -// ErrorResponse - error response format -type ErrorResponse struct { - XMLName xml.Name `xml:"Error" json:"-"` - Code string - Message string - Resource string - RequestID string - HostID string -} - -func writeError(w http.ResponseWriter, req *http.Request, errorResponse ErrorResponse, status int) { - var buf bytes.Buffer - encoder := xml.NewEncoder(&buf) - w.WriteHeader(status) - encoder.Encode(errorResponse) - encoder.Flush() - w.Write(buf.Bytes()) -} diff --git a/pkg/api/quota/request_limit.go b/pkg/api/quota/request_limit.go index 2a612a3f5..d85c593e8 100644 --- a/pkg/api/quota/request_limit.go +++ b/pkg/api/quota/request_limit.go @@ -33,7 +33,7 @@ func (h *requestLimitHandler) ServeHTTP(w http.ResponseWriter, req *http.Request host, _, _ := net.SplitHostPort(req.RemoteAddr) longIP := longIP{net.ParseIP(host)}.IptoUint32() if h.quotas.IsQuotaMet(longIP) { - return + writeErrorResponse(w, req, SlowDown, req.URL.Path) } h.quotas.Add(longIP, 1) h.handler.ServeHTTP(w, req)