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