This patchset also brings in lot of cleanup in terms of minioapi codebasemaster
parent
6e73ccec75
commit
ac4f07906c
@ -0,0 +1,32 @@ |
||||
package minioapi |
||||
|
||||
import ( |
||||
"net/http" |
||||
) |
||||
|
||||
type contentType int |
||||
|
||||
const ( |
||||
xmlType contentType = iota |
||||
jsonType |
||||
) |
||||
|
||||
var typeToString = map[contentType]string{ |
||||
xmlType: "application/xml", |
||||
jsonType: "application/json", |
||||
} |
||||
var acceptToType = map[string]contentType{ |
||||
"application/xml": xmlType, |
||||
"application/json": jsonType, |
||||
} |
||||
|
||||
func getContentType(req *http.Request) contentType { |
||||
if accept := req.Header.Get("Accept"); accept != "" { |
||||
return acceptToType[accept] |
||||
} |
||||
return xmlType |
||||
} |
||||
|
||||
func getContentString(content contentType) string { |
||||
return typeToString[content] |
||||
} |
@ -0,0 +1,170 @@ |
||||
package minioapi |
||||
|
||||
import ( |
||||
"encoding/xml" |
||||
"net/http" |
||||
) |
||||
|
||||
type Error struct { |
||||
Code string |
||||
Description string |
||||
HttpStatusCode int |
||||
} |
||||
|
||||
type ErrorResponse struct { |
||||
XMLName xml.Name `xml:"Error" json:"-"` |
||||
Code string |
||||
Message string |
||||
Resource string |
||||
RequestId string |
||||
} |
||||
|
||||
/// Error codes, non exhaustive list
|
||||
const ( |
||||
AccessDenied = iota |
||||
BadDigest |
||||
BucketAlreadyExists |
||||
EntityTooSmall |
||||
EntityTooLarge |
||||
IncompleteBody |
||||
InternalError |
||||
InvalidAccessKeyId |
||||
InvalidBucketName |
||||
InvalidDigest |
||||
InvalidRange |
||||
MalformedXML |
||||
MissingContentLength |
||||
MissingRequestBodyError |
||||
NoSuchBucket |
||||
NoSuchKey |
||||
NoSuchUpload |
||||
NotImplemented |
||||
RequestTimeTooSkewed |
||||
SignatureDoesNotMatch |
||||
TooManyBuckets |
||||
) |
||||
|
||||
var errorCodeResponse = map[int]Error{ |
||||
AccessDenied: { |
||||
Code: "AccessDenied", |
||||
Description: "Access Denied", |
||||
HttpStatusCode: http.StatusForbidden, |
||||
}, |
||||
BadDigest: { |
||||
Code: "BadDigest", |
||||
Description: "The Content-MD5 you specified did not match what we received.", |
||||
HttpStatusCode: http.StatusBadRequest, |
||||
}, |
||||
BucketAlreadyExists: { |
||||
Code: "BucketAlreadyExists", |
||||
Description: "The requested bucket name is not available.", |
||||
HttpStatusCode: http.StatusConflict, |
||||
}, |
||||
EntityTooSmall: { |
||||
Code: "EntityTooSmall", |
||||
Description: "Your proposed upload is smaller than the minimum allowed object size.", |
||||
HttpStatusCode: http.StatusBadRequest, |
||||
}, |
||||
EntityTooLarge: { |
||||
Code: "EntityTooLarge", |
||||
Description: "Your proposed upload exceeds the maximum allowed object size.", |
||||
HttpStatusCode: http.StatusBadRequest, |
||||
}, |
||||
IncompleteBody: { |
||||
Code: "IncompleteBody", |
||||
Description: "You did not provide the number of bytes specified by the Content-Length HTTP header", |
||||
HttpStatusCode: http.StatusBadRequest, |
||||
}, |
||||
InternalError: { |
||||
Code: "InternalError", |
||||
Description: "We encountered an internal error, please try again.", |
||||
HttpStatusCode: http.StatusInternalServerError, |
||||
}, |
||||
InvalidAccessKeyId: { |
||||
Code: "InvalidAccessKeyId", |
||||
Description: "The access key Id you provided does not exist in our records.", |
||||
HttpStatusCode: http.StatusForbidden, |
||||
}, |
||||
InvalidBucketName: { |
||||
Code: "InvalidBucketName", |
||||
Description: "The specified bucket is not valid.", |
||||
HttpStatusCode: http.StatusBadRequest, |
||||
}, |
||||
InvalidDigest: { |
||||
Code: "InvalidDigest", |
||||
Description: "The Content-MD5 you specified is not valid.", |
||||
HttpStatusCode: http.StatusBadRequest, |
||||
}, |
||||
InvalidRange: { |
||||
Code: "InvalidRange", |
||||
Description: "The requested range cannot be satisfied.", |
||||
HttpStatusCode: http.StatusRequestedRangeNotSatisfiable, |
||||
}, |
||||
MalformedXML: { |
||||
Code: "MalformedXML", |
||||
Description: "The XML you provided was not well-formed or did not validate against our published schema.", |
||||
HttpStatusCode: http.StatusBadRequest, |
||||
}, |
||||
MissingContentLength: { |
||||
Code: "MissingContentLength", |
||||
Description: "You must provide the Content-Length HTTP header.", |
||||
HttpStatusCode: http.StatusLengthRequired, |
||||
}, |
||||
MissingRequestBodyError: { |
||||
Code: "MissingRequestBodyError", |
||||
Description: "Request body is empty.", |
||||
HttpStatusCode: http.StatusLengthRequired, |
||||
}, |
||||
NoSuchBucket: { |
||||
Code: "NoSuchBucket", |
||||
Description: "The specified bucket does not exist.", |
||||
HttpStatusCode: http.StatusNotFound, |
||||
}, |
||||
NoSuchKey: { |
||||
Code: "NoSuchKey", |
||||
Description: "The specified key does not exist.", |
||||
HttpStatusCode: http.StatusNotFound, |
||||
}, |
||||
NoSuchUpload: { |
||||
Code: "NoSuchUpload", |
||||
Description: "The specified multipart upload does not exist.", |
||||
HttpStatusCode: http.StatusNotFound, |
||||
}, |
||||
NotImplemented: { |
||||
Code: "NotImplemented", |
||||
Description: "A header you provided implies functionality that is not implemented.", |
||||
HttpStatusCode: http.StatusNotImplemented, |
||||
}, |
||||
RequestTimeTooSkewed: { |
||||
Code: "RequestTimeTooSkewed", |
||||
Description: "The difference between the request time and the server's time is too large.", |
||||
HttpStatusCode: http.StatusForbidden, |
||||
}, |
||||
SignatureDoesNotMatch: { |
||||
Code: "SignatureDoesNotMatch", |
||||
Description: "The request signature we calculated does not match the signature you provided.", |
||||
HttpStatusCode: http.StatusForbidden, |
||||
}, |
||||
TooManyBuckets: { |
||||
Code: "TooManyBuckets", |
||||
Description: "You have attempted to create more buckets than allowed.", |
||||
HttpStatusCode: http.StatusBadRequest, |
||||
}, |
||||
} |
||||
|
||||
// errorCodeError provides errorCode to Error. It returns empty if
|
||||
// the code provided is unknown
|
||||
func errorCodeError(code int) Error { |
||||
return errorCodeResponse[code] |
||||
} |
||||
|
||||
func getErrorResponse(err Error, resource string) ErrorResponse { |
||||
var data = ErrorResponse{} |
||||
data.Code = err.Code |
||||
data.Message = err.Description |
||||
data.Resource = resource |
||||
// TODO implement this in future
|
||||
data.RequestId = "3LI37" |
||||
|
||||
return data |
||||
} |
@ -0,0 +1,90 @@ |
||||
package minioapi |
||||
|
||||
import ( |
||||
"net/http" |
||||
"strings" |
||||
|
||||
"github.com/minio-io/minio/pkg/utils/config" |
||||
"github.com/minio-io/minio/pkg/utils/crypto/signers" |
||||
) |
||||
|
||||
type vHandler struct { |
||||
conf config.Config |
||||
handler http.Handler |
||||
} |
||||
|
||||
// grab AccessKey from authorization header
|
||||
func stripAccessKey(r *http.Request) string { |
||||
fields := strings.Fields(r.Header.Get("Authorization")) |
||||
if len(fields) < 2 { |
||||
return "" |
||||
} |
||||
splits := strings.Split(fields[1], ":") |
||||
if len(splits) < 2 { |
||||
return "" |
||||
} |
||||
return splits[0] |
||||
} |
||||
|
||||
func validateHandler(conf config.Config, h http.Handler) http.Handler { |
||||
return vHandler{conf, h} |
||||
} |
||||
|
||||
func (h vHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { |
||||
accessKey := stripAccessKey(r) |
||||
if accessKey != "" { |
||||
if err := h.conf.ReadConfig(); err != nil { |
||||
w.WriteHeader(http.StatusInternalServerError) |
||||
} else { |
||||
user := h.conf.GetKey(accessKey) |
||||
ok, err := signers.ValidateRequest(user, r) |
||||
if ok { |
||||
h.handler.ServeHTTP(w, r) |
||||
} else { |
||||
w.WriteHeader(http.StatusUnauthorized) |
||||
w.Write([]byte(err.Error())) |
||||
} |
||||
} |
||||
} else { |
||||
//No access key found, handle this more appropriately
|
||||
//TODO: Remove this after adding tests to support signature
|
||||
//request
|
||||
h.handler.ServeHTTP(w, r) |
||||
//Add this line, to reply back for invalid requests
|
||||
//w.WriteHeader(http.StatusUnauthorized)
|
||||
//w.Write([]byte("Authorization header malformed")
|
||||
} |
||||
} |
||||
|
||||
func ignoreUnimplementedResources(h http.Handler) http.Handler { |
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
||||
if ignoreUnImplementedObjectResources(r) || ignoreUnImplementedBucketResources(r) { |
||||
w.WriteHeader(http.StatusNotImplemented) |
||||
} else { |
||||
h.ServeHTTP(w, r) |
||||
} |
||||
}) |
||||
} |
||||
|
||||
//// helpers
|
||||
|
||||
// Checks requests for unimplemented resources
|
||||
func ignoreUnImplementedBucketResources(req *http.Request) bool { |
||||
q := req.URL.Query() |
||||
for name := range q { |
||||
if unimplementedBucketResourceNames[name] { |
||||
return true |
||||
} |
||||
} |
||||
return false |
||||
} |
||||
|
||||
func ignoreUnImplementedObjectResources(req *http.Request) bool { |
||||
q := req.URL.Query() |
||||
for name := range q { |
||||
if unimplementedObjectResourceNames[name] { |
||||
return true |
||||
} |
||||
} |
||||
return false |
||||
} |
@ -0,0 +1,61 @@ |
||||
package minioapi |
||||
|
||||
import ( |
||||
"bytes" |
||||
"encoding/json" |
||||
"encoding/xml" |
||||
"net/http" |
||||
"strconv" |
||||
"time" |
||||
|
||||
mstorage "github.com/minio-io/minio/pkg/storage" |
||||
) |
||||
|
||||
// Write Common Header helpers
|
||||
func writeCommonHeaders(w http.ResponseWriter, acceptsType string) { |
||||
w.Header().Set("Server", "Minio") |
||||
w.Header().Set("Content-Type", acceptsType) |
||||
} |
||||
|
||||
func writeErrorResponse(w http.ResponseWriter, response interface{}, acceptsType contentType) []byte { |
||||
var bytesBuffer bytes.Buffer |
||||
var encoder encoder |
||||
// write common headers
|
||||
writeCommonHeaders(w, getContentString(acceptsType)) |
||||
switch acceptsType { |
||||
case xmlType: |
||||
encoder = xml.NewEncoder(&bytesBuffer) |
||||
case jsonType: |
||||
encoder = json.NewEncoder(&bytesBuffer) |
||||
} |
||||
encoder.Encode(response) |
||||
return bytesBuffer.Bytes() |
||||
} |
||||
|
||||
// Write Object Header helper
|
||||
func writeObjectHeaders(w http.ResponseWriter, metadata mstorage.ObjectMetadata) { |
||||
lastModified := metadata.Created.Format(time.RFC1123) |
||||
// write common headers
|
||||
writeCommonHeaders(w, metadata.ContentType) |
||||
w.Header().Set("ETag", metadata.ETag) |
||||
w.Header().Set("Last-Modified", lastModified) |
||||
w.Header().Set("Content-Length", strconv.FormatInt(metadata.Size, 10)) |
||||
w.Header().Set("Connection", "close") |
||||
} |
||||
|
||||
func writeObjectHeadersAndResponse(w http.ResponseWriter, response interface{}, acceptsType contentType) []byte { |
||||
var bytesBuffer bytes.Buffer |
||||
var encoder encoder |
||||
// write common headers
|
||||
writeCommonHeaders(w, getContentString(acceptsType)) |
||||
switch acceptsType { |
||||
case xmlType: |
||||
encoder = xml.NewEncoder(&bytesBuffer) |
||||
case jsonType: |
||||
encoder = json.NewEncoder(&bytesBuffer) |
||||
} |
||||
|
||||
w.Header().Set("Connection", "close") |
||||
encoder.Encode(response) |
||||
return bytesBuffer.Bytes() |
||||
} |
Loading…
Reference in new issue