mirror of
https://github.com/minio/minio.git
synced 2025-01-22 20:23:14 -05:00
getObject: Add support for special response headers.
Supports now response-content-type, response-content-disposition, response-cache-control, response-expires.
This commit is contained in:
parent
de79440de2
commit
99fbc0fcb3
@ -18,6 +18,7 @@ package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
@ -29,6 +30,24 @@ const (
|
||||
maxPartsList = 1000
|
||||
)
|
||||
|
||||
// supportedGetReqParams - supported request parameters for GET
|
||||
// presigned request.
|
||||
var supportedGetReqParams = map[string]string{
|
||||
"response-expires": "Expires",
|
||||
"response-content-type": "Content-Type",
|
||||
"response-cache-control": "Cache-Control",
|
||||
"response-content-disposition": "Content-Disposition",
|
||||
}
|
||||
|
||||
// setResponseHeaders - set any requested parameters as response headers.
|
||||
func setResponseHeaders(w http.ResponseWriter, reqParams url.Values) {
|
||||
for k, v := range reqParams {
|
||||
if header, ok := supportedGetReqParams[k]; ok {
|
||||
w.Header()[header] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetObjectHandler - GET Object
|
||||
// ----------
|
||||
// This implementation of the GET operation retrieves object. To use GET,
|
||||
@ -69,7 +88,14 @@ func (api CloudStorageAPI) GetObjectHandler(w http.ResponseWriter, req *http.Req
|
||||
writeErrorResponse(w, req, InvalidRange, req.URL.Path)
|
||||
return
|
||||
}
|
||||
|
||||
// Set standard object headers.
|
||||
setObjectHeaders(w, metadata, hrange)
|
||||
|
||||
// Set any additional requested response headers.
|
||||
setResponseHeaders(w, req.URL.Query())
|
||||
|
||||
// Get the object.
|
||||
if _, err = api.Filesystem.GetObject(w, bucket, object, hrange.start, hrange.length); err != nil {
|
||||
errorIf(err.Trace(), "GetObject failed.", nil)
|
||||
return
|
||||
|
@ -308,6 +308,14 @@ func (r *Signature) DoesPresignedSignatureMatch() (bool, *probe.Error) {
|
||||
query.Set("X-Amz-Expires", strconv.Itoa(expireSeconds))
|
||||
query.Set("X-Amz-SignedHeaders", r.getSignedHeaders(r.extractSignedHeaders()))
|
||||
query.Set("X-Amz-Credential", r.AccessKeyID+"/"+r.getScope(t))
|
||||
|
||||
// Save other headers available in the request parameters.
|
||||
for k, v := range r.Request.URL.Query() {
|
||||
if strings.HasPrefix(strings.ToLower(k), "x-amz") {
|
||||
continue
|
||||
}
|
||||
query[k] = v
|
||||
}
|
||||
encodedQuery := query.Encode()
|
||||
|
||||
// Verify if date query is same.
|
||||
|
2
vendor/github.com/minio/minio-go/CONTRIBUTING.md
generated
vendored
2
vendor/github.com/minio/minio-go/CONTRIBUTING.md
generated
vendored
@ -14,7 +14,7 @@
|
||||
- Have test cases for the new code. If you have questions about how to do it, please ask in your pull request.
|
||||
- Run `go fmt`
|
||||
- Squash your commits into a single commit. `git rebase -i`. It's okay to force update your pull request.
|
||||
- Make sure `go test -race ./...` and `go build` completes.
|
||||
- Make sure `go test -short -race ./...` and `go build` completes.
|
||||
|
||||
* Read [Effective Go](https://github.com/golang/go/wiki/CodeReviewComments) article from Golang project
|
||||
- `minio-go` project is strictly conformant with Golang style
|
||||
|
2
vendor/github.com/minio/minio-go/README.md
generated
vendored
2
vendor/github.com/minio/minio-go/README.md
generated
vendored
@ -83,7 +83,7 @@ func main() {
|
||||
* [FGetObject(bucketName, objectName, filePath) error](examples/s3/fgetobject.go)
|
||||
|
||||
### Presigned Operations.
|
||||
* [PresignedGetObject(bucketName, objectName, time.Duration) (string, error)](examples/s3/presignedgetobject.go)
|
||||
* [PresignedGetObject(bucketName, objectName, time.Duration, url.Values) (string, error)](examples/s3/presignedgetobject.go)
|
||||
* [PresignedPutObject(bucketName, objectName, time.Duration) (string, error)](examples/s3/presignedputobject.go)
|
||||
* [PresignedPostPolicy(NewPostPolicy()) (map[string]string, error)](examples/s3/presignedpostpolicy.go)
|
||||
|
||||
|
56
vendor/github.com/minio/minio-go/api-presigned.go
generated
vendored
56
vendor/github.com/minio/minio-go/api-presigned.go
generated
vendored
@ -18,13 +18,26 @@ package minio
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
// supportedGetReqParams - supported request parameters for GET
|
||||
// presigned request.
|
||||
var supportedGetReqParams = map[string]struct{}{
|
||||
"response-expires": struct{}{},
|
||||
"response-content-type": struct{}{},
|
||||
"response-cache-control": struct{}{},
|
||||
"response-content-disposition": struct{}{},
|
||||
}
|
||||
|
||||
// presignURL - Returns a presigned URL for an input 'method'.
|
||||
// Expires maximum is 7days - ie. 604800 and minimum is 1.
|
||||
func (c Client) presignURL(method string, bucketName string, objectName string, expires time.Duration) (url string, err error) {
|
||||
func (c Client) presignURL(method string, bucketName string, objectName string, expires time.Duration, reqParams url.Values) (urlStr string, err error) {
|
||||
// Input validation.
|
||||
if method == "" {
|
||||
return "", ErrInvalidArgument("method cannot be empty.")
|
||||
}
|
||||
if err := isValidBucketName(bucketName); err != nil {
|
||||
return "", err
|
||||
}
|
||||
@ -35,35 +48,50 @@ func (c Client) presignURL(method string, bucketName string, objectName string,
|
||||
return "", err
|
||||
}
|
||||
|
||||
if method == "" {
|
||||
return "", ErrInvalidArgument("method cannot be empty.")
|
||||
}
|
||||
|
||||
// Convert expires into seconds.
|
||||
expireSeconds := int64(expires / time.Second)
|
||||
// Instantiate a new request.
|
||||
// Since expires is set newRequest will presign the request.
|
||||
req, err := c.newRequest(method, requestMetadata{
|
||||
reqMetadata := requestMetadata{
|
||||
presignURL: true,
|
||||
bucketName: bucketName,
|
||||
objectName: objectName,
|
||||
expires: expireSeconds,
|
||||
})
|
||||
}
|
||||
|
||||
// For "GET" we are handling additional request parameters to
|
||||
// override its response headers.
|
||||
if method == "GET" {
|
||||
// Verify if input map has unsupported params, if yes exit.
|
||||
for k := range reqParams {
|
||||
if _, ok := supportedGetReqParams[k]; !ok {
|
||||
return "", ErrInvalidArgument(k + " unsupported request parameter for presigned GET.")
|
||||
}
|
||||
}
|
||||
// Save the request parameters to be used in presigning for
|
||||
// GET request.
|
||||
reqMetadata.queryValues = reqParams
|
||||
}
|
||||
|
||||
// Instantiate a new request.
|
||||
// Since expires is set newRequest will presign the request.
|
||||
req, err := c.newRequest(method, reqMetadata)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return req.URL.String(), nil
|
||||
}
|
||||
|
||||
// PresignedGetObject - Returns a presigned URL to access an object without credentials.
|
||||
// Expires maximum is 7days - ie. 604800 and minimum is 1.
|
||||
func (c Client) PresignedGetObject(bucketName string, objectName string, expires time.Duration) (url string, err error) {
|
||||
return c.presignURL("GET", bucketName, objectName, expires)
|
||||
// PresignedGetObject - Returns a presigned URL to access an object
|
||||
// without credentials. Expires maximum is 7days - ie. 604800 and
|
||||
// minimum is 1. Additionally you can override a set of response
|
||||
// headers using the query parameters.
|
||||
func (c Client) PresignedGetObject(bucketName string, objectName string, expires time.Duration, reqParams url.Values) (url string, err error) {
|
||||
return c.presignURL("GET", bucketName, objectName, expires, reqParams)
|
||||
}
|
||||
|
||||
// PresignedPutObject - Returns a presigned URL to upload an object without credentials.
|
||||
// Expires maximum is 7days - ie. 604800 and minimum is 1.
|
||||
func (c Client) PresignedPutObject(bucketName string, objectName string, expires time.Duration) (url string, err error) {
|
||||
return c.presignURL("PUT", bucketName, objectName, expires)
|
||||
return c.presignURL("PUT", bucketName, objectName, expires, nil)
|
||||
}
|
||||
|
||||
// PresignedPostPolicy - Returns POST form data to upload an object at a location.
|
||||
|
2
vendor/github.com/minio/minio-go/api-put-object-common.go
generated
vendored
2
vendor/github.com/minio/minio-go/api-put-object-common.go
generated
vendored
@ -55,7 +55,7 @@ func shouldUploadPart(objPart objectPart, objectParts map[int]objectPart) bool {
|
||||
return true
|
||||
}
|
||||
// if md5sum mismatches should upload the part.
|
||||
if objPart.ETag == uploadedPart.ETag {
|
||||
if objPart.ETag != uploadedPart.ETag {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
8
vendor/github.com/minio/minio-go/api.go
generated
vendored
8
vendor/github.com/minio/minio-go/api.go
generated
vendored
@ -325,6 +325,12 @@ func (c Client) do(req *http.Request) (*http.Response, error) {
|
||||
// execute the request.
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
// Handle this specifically for now until future Golang
|
||||
// versions fix this issue properly.
|
||||
urlErr, ok := err.(*url.Error)
|
||||
if ok && strings.Contains(urlErr.Err.Error(), "EOF") {
|
||||
return nil, fmt.Errorf("Connection closed by foreign host %s. Retry again.", urlErr.URL)
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
// If trace is enabled, dump http request and response.
|
||||
@ -516,7 +522,7 @@ type CloudStorageClient interface {
|
||||
PutObjectWithProgress(bucketName, objectName string, reader io.Reader, contentType string, progress io.Reader) (n int64, err error)
|
||||
|
||||
// Presigned operations.
|
||||
PresignedGetObject(bucketName, objectName string, expires time.Duration) (presignedURL string, err error)
|
||||
PresignedGetObject(bucketName, objectName string, expires time.Duration, reqParams url.Values) (presignedURL string, err error)
|
||||
PresignedPutObject(bucketName, objectName string, expires time.Duration) (presignedURL string, err error)
|
||||
PresignedPostPolicy(*PostPolicy) (formData map[string]string, err error)
|
||||
|
||||
|
34
vendor/github.com/minio/minio-go/api_functional_v2_test.go
generated
vendored
34
vendor/github.com/minio/minio-go/api_functional_v2_test.go
generated
vendored
@ -24,6 +24,7 @@ import (
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
@ -954,11 +955,12 @@ func TestFunctionalV2(t *testing.T) {
|
||||
t.Fatal("Error: ", err)
|
||||
}
|
||||
|
||||
presignedGetURL, err := c.PresignedGetObject(bucketName, objectName, 3600*time.Second)
|
||||
// Generate presigned GET object url.
|
||||
presignedGetURL, err := c.PresignedGetObject(bucketName, objectName, 3600*time.Second, nil)
|
||||
if err != nil {
|
||||
t.Fatal("Error: ", err)
|
||||
}
|
||||
|
||||
// Verify if presigned url works.
|
||||
resp, err := http.Get(presignedGetURL)
|
||||
if err != nil {
|
||||
t.Fatal("Error: ", err)
|
||||
@ -974,6 +976,34 @@ func TestFunctionalV2(t *testing.T) {
|
||||
t.Fatal("Error: bytes mismatch.")
|
||||
}
|
||||
|
||||
// Set request parameters.
|
||||
reqParams := make(url.Values)
|
||||
reqParams.Set("response-content-disposition", "attachment; filename=\"test.txt\"")
|
||||
// Generate presigned GET object url.
|
||||
presignedGetURL, err = c.PresignedGetObject(bucketName, objectName, 3600*time.Second, reqParams)
|
||||
if err != nil {
|
||||
t.Fatal("Error: ", err)
|
||||
}
|
||||
// Verify if presigned url works.
|
||||
resp, err = http.Get(presignedGetURL)
|
||||
if err != nil {
|
||||
t.Fatal("Error: ", err)
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatal("Error: ", resp.Status)
|
||||
}
|
||||
newPresignedBytes, err = ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Fatal("Error: ", err)
|
||||
}
|
||||
if !bytes.Equal(newPresignedBytes, buf) {
|
||||
t.Fatal("Error: bytes mismatch for presigned GET url.")
|
||||
}
|
||||
// Verify content disposition.
|
||||
if resp.Header.Get("Content-Disposition") != "attachment; filename=\"test.txt\"" {
|
||||
t.Fatalf("Error: wrong Content-Disposition received %s", resp.Header.Get("Content-Disposition"))
|
||||
}
|
||||
|
||||
presignedPutURL, err := c.PresignedPutObject(bucketName, objectName+"-presigned", 3600*time.Second)
|
||||
if err != nil {
|
||||
t.Fatal("Error: ", err)
|
||||
|
31
vendor/github.com/minio/minio-go/api_functional_v4_test.go
generated
vendored
31
vendor/github.com/minio/minio-go/api_functional_v4_test.go
generated
vendored
@ -24,6 +24,7 @@ import (
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
@ -983,11 +984,13 @@ func TestFunctional(t *testing.T) {
|
||||
t.Fatal("Error: ", err)
|
||||
}
|
||||
|
||||
presignedGetURL, err := c.PresignedGetObject(bucketName, objectName, 3600*time.Second)
|
||||
// Generate presigned GET object url.
|
||||
presignedGetURL, err := c.PresignedGetObject(bucketName, objectName, 3600*time.Second, nil)
|
||||
if err != nil {
|
||||
t.Fatal("Error: ", err)
|
||||
}
|
||||
|
||||
// Verify if presigned url works.
|
||||
resp, err := http.Get(presignedGetURL)
|
||||
if err != nil {
|
||||
t.Fatal("Error: ", err)
|
||||
@ -1003,6 +1006,32 @@ func TestFunctional(t *testing.T) {
|
||||
t.Fatal("Error: bytes mismatch.")
|
||||
}
|
||||
|
||||
// Set request parameters.
|
||||
reqParams := make(url.Values)
|
||||
reqParams.Set("response-content-disposition", "attachment; filename=\"test.txt\"")
|
||||
presignedGetURL, err = c.PresignedGetObject(bucketName, objectName, 3600*time.Second, reqParams)
|
||||
if err != nil {
|
||||
t.Fatal("Error: ", err)
|
||||
}
|
||||
// Verify if presigned url works.
|
||||
resp, err = http.Get(presignedGetURL)
|
||||
if err != nil {
|
||||
t.Fatal("Error: ", err)
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatal("Error: ", resp.Status)
|
||||
}
|
||||
newPresignedBytes, err = ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Fatal("Error: ", err)
|
||||
}
|
||||
if !bytes.Equal(newPresignedBytes, buf) {
|
||||
t.Fatal("Error: bytes mismatch for presigned GET URL.")
|
||||
}
|
||||
if resp.Header.Get("Content-Disposition") != "attachment; filename=\"test.txt\"" {
|
||||
t.Fatalf("Error: wrong Content-Disposition received %s", resp.Header.Get("Content-Disposition"))
|
||||
}
|
||||
|
||||
presignedPutURL, err := c.PresignedPutObject(bucketName, objectName+"-presigned", 3600*time.Second)
|
||||
if err != nil {
|
||||
t.Fatal("Error: ", err)
|
||||
|
17
vendor/github.com/minio/minio-go/request-signature-v2.go
generated
vendored
17
vendor/github.com/minio/minio-go/request-signature-v2.go
generated
vendored
@ -73,6 +73,11 @@ func preSignV2(req http.Request, accessKeyID, secretAccessKey string, expires in
|
||||
|
||||
// Get encoded URL path.
|
||||
path := encodeURL2Path(req.URL)
|
||||
if len(req.URL.Query()) > 0 {
|
||||
// Keep the usual queries unescaped for string to sign.
|
||||
query, _ := url.QueryUnescape(queryEncode(req.URL.Query()))
|
||||
path = path + "?" + query
|
||||
}
|
||||
|
||||
// Find epoch expires when the request will expire.
|
||||
epochExpires := d.Unix() + expires
|
||||
@ -93,12 +98,16 @@ func preSignV2(req http.Request, accessKeyID, secretAccessKey string, expires in
|
||||
query.Set("AWSAccessKeyId", accessKeyID)
|
||||
}
|
||||
|
||||
// Fill in Expires and Signature for presigned query.
|
||||
// Fill in Expires for presigned query.
|
||||
query.Set("Expires", strconv.FormatInt(epochExpires, 10))
|
||||
query.Set("Signature", signature)
|
||||
|
||||
// Encode query and save.
|
||||
req.URL.RawQuery = query.Encode()
|
||||
req.URL.RawQuery = queryEncode(query)
|
||||
|
||||
// Save signature finally.
|
||||
req.URL.RawQuery += "&Signature=" + urlEncodePath(signature)
|
||||
|
||||
// Return.
|
||||
return &req
|
||||
}
|
||||
|
||||
@ -283,7 +292,7 @@ func writeCanonicalizedResource(buf *bytes.Buffer, req http.Request) {
|
||||
// Request parameters
|
||||
if len(vv[0]) > 0 {
|
||||
buf.WriteByte('=')
|
||||
buf.WriteString(url.QueryEscape(vv[0]))
|
||||
buf.WriteString(strings.Replace(url.QueryEscape(vv[0]), "+", "%20", -1))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
4
vendor/vendor.json
vendored
4
vendor/vendor.json
vendored
@ -59,8 +59,8 @@
|
||||
},
|
||||
{
|
||||
"path": "github.com/minio/minio-go",
|
||||
"revision": "c5884ce9ce3ac73b025d0bc58c4d3d72870edc0b",
|
||||
"revisionTime": "2016-02-02T13:13:10+05:30"
|
||||
"revision": "280f16a52008d3ebba1bd64398b9b082e6738386",
|
||||
"revisionTime": "2016-02-07T03:45:25-08:00"
|
||||
},
|
||||
{
|
||||
"path": "github.com/minio/minio-xl/pkg/atomic",
|
||||
|
@ -20,7 +20,9 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -206,7 +208,10 @@ func (web *WebAPI) GetObjectURL(r *http.Request, args *GetObjectURLArgs, reply *
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
signedURLStr, e := client.PresignedGetObject(args.BucketName, args.ObjectName, time.Duration(60*60)*time.Second)
|
||||
reqParams := make(url.Values)
|
||||
// Set content disposition for browser to download the file.
|
||||
reqParams.Set("response-content-disposition", fmt.Sprintf(`attachment; filename="%s"`, filepath.Base(args.ObjectName)))
|
||||
signedURLStr, e := client.PresignedGetObject(args.BucketName, args.ObjectName, time.Duration(60*60)*time.Second, reqParams)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user