From 9431a54a27a30aaa221b012ffd5dda38e1c993e9 Mon Sep 17 00:00:00 2001 From: "Frederick F. Kautz IV" Date: Sun, 26 Apr 2015 19:25:48 -0700 Subject: [PATCH 1/2] finish me --- pkg/api/api_router.go | 10 +++---- pkg/api/quota/bandwidth_cap.go | 48 ++++++++++++++++++++++++++++------ pkg/api/quota/quota_handler.go | 24 +++++++++++++++++ 3 files changed, 69 insertions(+), 13 deletions(-) diff --git a/pkg/api/api_router.go b/pkg/api/api_router.go index 54fab0ad9..f4a1bd88e 100644 --- a/pkg/api/api_router.go +++ b/pkg/api/api_router.go @@ -92,10 +92,10 @@ func HTTPHandler(domain string, driver drivers.Driver) http.Handler { } h := validateHandler(conf, ignoreResourcesHandler(mux)) - h = quota.BandwidthCap(h, 1*1024*1024*1024, time.Duration(30*time.Minute)) - h = quota.BandwidthCap(h, 1024*1024*1024, time.Duration(24*time.Hour)) - h = quota.RequestLimit(h, 100, time.Duration(30*time.Minute)) - h = quota.RequestLimit(h, 1000, time.Duration(24*time.Hour)) - h = quota.ConnectionLimit(h, 5) + h = quota.BandwidthCap(h, 250*1024*1024, time.Duration(30*time.Minute)) + // h = quota.BandwidthCap(h, 1024*1024*1024, time.Duration(24*time.Hour)) + // h = quota.RequestLimit(h, 100, time.Duration(30*time.Minute)) + // h = quota.RequestLimit(h, 1000, time.Duration(24*time.Hour)) + // h = quota.ConnectionLimit(h, 5) return h } diff --git a/pkg/api/quota/bandwidth_cap.go b/pkg/api/quota/bandwidth_cap.go index 46c88665d..9b1231184 100644 --- a/pkg/api/quota/bandwidth_cap.go +++ b/pkg/api/quota/bandwidth_cap.go @@ -20,6 +20,7 @@ import ( "errors" "github.com/minio-io/minio/pkg/iodine" "io" + "log" "net" "net/http" "time" @@ -31,20 +32,31 @@ type bandwidthQuotaHandler struct { quotas *quotaMap } +var bandwidthQuotaExceeded = ErrorResponse{ + Code: "BandwithQuotaExceeded", + Message: "Bandwidth Quota Exceeded", + 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() - req.Body = quotaReader{ + req.Body = "aReader{ ReadCloser: req.Body, quotas: h.quotas, ip: longIP, + w: w, + req: req, } - w = quotaWriter{ + w = "aWriter{ ResponseWriter: w, quotas: h.quotas, ip: longIP, } + log.Println("serving") h.handler.ServeHTTP(w, req) } @@ -65,10 +77,20 @@ type quotaReader struct { io.ReadCloser quotas *quotaMap ip uint32 + w http.ResponseWriter + req *http.Request + err bool } -func (q quotaReader) Read(b []byte) (int, error) { +func (q *quotaReader) Read(b []byte) (int, error) { + if q.err { + log.Println("Shortcut, quitting") + return 0, iodine.New(errors.New("Quota Met"), nil) + } if q.quotas.IsQuotaMet(q.ip) { + q.err = true + log.Println("QUOTA!!!") + writeError(q.w, q.req, bandwidthQuotaExceeded, 429) return 0, iodine.New(errors.New("Quota Met"), nil) } n, err := q.ReadCloser.Read(b) @@ -76,17 +98,17 @@ func (q quotaReader) Read(b []byte) (int, error) { return n, iodine.New(err, nil) } -func (q quotaReader) Close() error { +func (q *quotaReader) Close() error { return iodine.New(q.ReadCloser.Close(), nil) } type quotaWriter struct { - http.ResponseWriter - quotas *quotaMap - ip uint32 + ResponseWriter http.ResponseWriter + quotas *quotaMap + ip uint32 } -func (q quotaWriter) Write(b []byte) (int, error) { +func (q *quotaWriter) Write(b []byte) (int, error) { if q.quotas.IsQuotaMet(q.ip) { return 0, iodine.New(errors.New("Quota Met"), nil) } @@ -96,6 +118,16 @@ func (q quotaWriter) Write(b []byte) (int, error) { q.quotas.Add(q.ip, int64(n-len(b))) return n, iodine.New(err, nil) } +func (q *quotaWriter) Header() http.Header { + return q.ResponseWriter.Header() +} + +func (q *quotaWriter) WriteHeader(status int) { + if q.quotas.IsQuotaMet(q.ip) { + return + } + q.ResponseWriter.WriteHeader(status) +} func segmentSize(duration time.Duration) time.Duration { var segmentSize time.Duration diff --git a/pkg/api/quota/quota_handler.go b/pkg/api/quota/quota_handler.go index 461d3d919..e012fba5c 100644 --- a/pkg/api/quota/quota_handler.go +++ b/pkg/api/quota/quota_handler.go @@ -17,8 +17,11 @@ package quota import ( + "bytes" "encoding/binary" + "encoding/xml" "net" + "net/http" "sync" "time" ) @@ -92,3 +95,24 @@ 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()) +} From 258bf55e31884cafa2a6686ab6033f7ab3a62a4e Mon Sep 17 00:00:00 2001 From: "Frederick F. Kautz IV" Date: Sun, 26 Apr 2015 20:06:55 -0700 Subject: [PATCH 2/2] Bandwidth quota now supports 100-Continue --- pkg/api/api_router.go | 10 +++++----- pkg/api/quota/bandwidth_cap.go | 16 ++++++++++++---- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/pkg/api/api_router.go b/pkg/api/api_router.go index f4a1bd88e..08ee0711f 100644 --- a/pkg/api/api_router.go +++ b/pkg/api/api_router.go @@ -92,10 +92,10 @@ func HTTPHandler(domain string, driver drivers.Driver) http.Handler { } h := validateHandler(conf, ignoreResourcesHandler(mux)) - h = quota.BandwidthCap(h, 250*1024*1024, time.Duration(30*time.Minute)) - // h = quota.BandwidthCap(h, 1024*1024*1024, time.Duration(24*time.Hour)) - // h = quota.RequestLimit(h, 100, time.Duration(30*time.Minute)) - // h = quota.RequestLimit(h, 1000, time.Duration(24*time.Hour)) - // h = quota.ConnectionLimit(h, 5) + h = quota.BandwidthCap(h, 25*1024*1024, time.Duration(30*time.Minute)) + h = quota.BandwidthCap(h, 100*1024*1024, time.Duration(24*time.Hour)) + h = quota.RequestLimit(h, 100, time.Duration(30*time.Minute)) + h = quota.RequestLimit(h, 1000, time.Duration(24*time.Hour)) + h = quota.ConnectionLimit(h, 5) return h } diff --git a/pkg/api/quota/bandwidth_cap.go b/pkg/api/quota/bandwidth_cap.go index 9b1231184..331aa0dbe 100644 --- a/pkg/api/quota/bandwidth_cap.go +++ b/pkg/api/quota/bandwidth_cap.go @@ -20,7 +20,6 @@ import ( "errors" "github.com/minio-io/minio/pkg/iodine" "io" - "log" "net" "net/http" "time" @@ -40,10 +39,22 @@ var bandwidthQuotaExceeded = ErrorResponse{ 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) + return + } req.Body = "aReader{ ReadCloser: req.Body, quotas: h.quotas, @@ -56,7 +67,6 @@ func (h *bandwidthQuotaHandler) ServeHTTP(w http.ResponseWriter, req *http.Reque quotas: h.quotas, ip: longIP, } - log.Println("serving") h.handler.ServeHTTP(w, req) } @@ -84,12 +94,10 @@ type quotaReader struct { func (q *quotaReader) Read(b []byte) (int, error) { if q.err { - log.Println("Shortcut, quitting") return 0, iodine.New(errors.New("Quota Met"), nil) } if q.quotas.IsQuotaMet(q.ip) { q.err = true - log.Println("QUOTA!!!") writeError(q.w, q.req, bandwidthQuotaExceeded, 429) return 0, iodine.New(errors.New("Quota Met"), nil) }