diff --git a/pkg/api/api_object_handlers.go b/pkg/api/api_object_handlers.go index 8b617d843..6b649d639 100644 --- a/pkg/api/api_object_handlers.go +++ b/pkg/api/api_object_handlers.go @@ -20,6 +20,7 @@ import ( "net/http" "github.com/gorilla/mux" + "github.com/minio-io/iodine" "github.com/minio-io/minio/pkg/drivers" "github.com/minio-io/minio/pkg/utils/log" ) @@ -46,18 +47,24 @@ func (server *minioAPI) getObjectHandler(w http.ResponseWriter, req *http.Reques } switch httpRange.start == 0 && httpRange.length == 0 { case true: - setObjectHeaders(w, metadata) - if _, err := server.driver.GetObject(w, bucket, object); err != nil { - // unable to write headers, we've already printed data. Just close the connection. + { + setObjectHeaders(w, metadata) + if _, err := server.driver.GetObject(w, bucket, object); err != nil { + // unable to write headers, we've already printed data. Just close the connection. + } } case false: - metadata.Size = httpRange.length - setRangeObjectHeaders(w, metadata, httpRange) - w.WriteHeader(http.StatusPartialContent) - _, err := server.driver.GetPartialObject(w, bucket, object, httpRange.start, httpRange.length) - if err != nil { - // unable to write headers, we've already printed data. Just close the connection. - log.Error.Println(err) + { + metadata.Size = httpRange.length + setRangeObjectHeaders(w, metadata, httpRange) + // contentRangeValue := "bytes " + strconv.FormatInt(httpRange.start, 10) + "-" + strconv.FormatInt(httpRange.length, 10) + "/" + strconv.FormatInt(httpRange.size, 10) + w.WriteHeader(http.StatusPartialContent) + _, err := server.driver.GetPartialObject(w, bucket, object, httpRange.start, httpRange.length) + if err != nil { + err = iodine.New(err, nil) + // unable to write headers, we've already printed data. Just close the connection. + log.Error.Println(err) + } } } } diff --git a/pkg/api/api_test.go b/pkg/api/api_test.go index c0318d7ff..7954e3947 100644 --- a/pkg/api/api_test.go +++ b/pkg/api/api_test.go @@ -45,7 +45,7 @@ type MySuite struct { var _ = Suite(&MySuite{ Driver: func() drivers.Driver { - return startDriver() + return startMockDriver() }, }) @@ -152,7 +152,6 @@ func (s *MySuite) TestObject(c *C) { responseBody, err := ioutil.ReadAll(response.Body) c.Assert(err, IsNil) - println(string(responseBody)) c.Assert(responseBody, DeepEquals, []byte("hello world")) metadata, err := driver.GetObjectMetadata("bucket", "object", "") @@ -172,7 +171,7 @@ func (s *MySuite) TestMultipleObjects(c *C) { } default: { - typedDriver = startDriver() + typedDriver = startMockDriver() } } metadata1 := drivers.ObjectMetadata{ @@ -307,7 +306,7 @@ func (s *MySuite) TestNotImplemented(c *C) { default: { // we never assert expectations - typedDriver = startDriver() + typedDriver = startMockDriver() } } httpHandler := api.HTTPHandler("", driver) @@ -331,7 +330,7 @@ func (s *MySuite) TestHeader(c *C) { default: { // we never assert expectations - typedDriver = startDriver() + typedDriver = startMockDriver() } } @@ -386,7 +385,7 @@ func (s *MySuite) TestPutBucket(c *C) { default: { // we never assert expectations - typedDriver = startDriver() + typedDriver = startMockDriver() } } @@ -428,7 +427,7 @@ func (s *MySuite) TestPutObject(c *C) { default: { // we never assert expectations - typedDriver = startDriver() + typedDriver = startMockDriver() } } httpHandler := api.HTTPHandler("", driver) @@ -460,7 +459,6 @@ func (s *MySuite) TestPutObject(c *C) { typedDriver.On("CreateObject", "bucket", "two", "", "", mock.Anything).Return(nil).Once() request, err = http.NewRequest("PUT", testServer.URL+"/bucket/two", bytes.NewBufferString("hello world")) - println(err) c.Assert(err, IsNil) response, err = client.Do(request) @@ -516,7 +514,7 @@ func (s *MySuite) TestListBuckets(c *C) { default: { // we never assert expectations - typedDriver = startDriver() + typedDriver = startMockDriver() } } httpHandler := api.HTTPHandler("", driver) @@ -621,7 +619,7 @@ func (s *MySuite) TestXMLNameNotInBucketListJson(c *C) { default: { // we never assert expectations - typedDriver = startDriver() + typedDriver = startMockDriver() } } httpHandler := api.HTTPHandler("", driver) @@ -661,7 +659,7 @@ func (s *MySuite) TestXMLNameNotInObjectListJson(c *C) { default: { // we never assert expectations - typedDriver = startDriver() + typedDriver = startMockDriver() } } httpHandler := api.HTTPHandler("", driver) @@ -701,7 +699,7 @@ func (s *MySuite) TestContentTypePersists(c *C) { default: { // we never assert expectations - typedDriver = startDriver() + typedDriver = startMockDriver() } } httpHandler := api.HTTPHandler("", driver) @@ -776,7 +774,62 @@ func (s *MySuite) TestContentTypePersists(c *C) { c.Assert(response.Header.Get("Content-Type"), Equals, "application/octet-stream") } -func startDriver() *mocks.Driver { +func (s *MySuite) TestPartialContent(c *C) { + driver := s.Driver() + var typedDriver *mocks.Driver + switch driver := driver.(type) { + case *mocks.Driver: + { + typedDriver = driver + defer driver.AssertExpectations(c) + } + default: + { + // we never assert expectations + typedDriver = startMockDriver() + } + } + httpHandler := api.HTTPHandler("", driver) + testServer := httptest.NewServer(httpHandler) + defer testServer.Close() + + metadata := drivers.ObjectMetadata{ + Bucket: "foo", + Key: "bar", + ContentType: "application/octet-stream", + Created: time.Now(), + // TODO Determine if md5 of range or full object needed + Md5: "e81c4e4f2b7b93b481e13a8553c2ae1b", + Size: 11, + } + + typedDriver.On("CreateBucket", "foo").Return(nil).Once() + + typedDriver.On("CreateObject", "foo", "bar", "", "", mock.Anything).Return(nil).Once() + driver.CreateBucket("foo") + driver.CreateObject("foo", "bar", "", "", bytes.NewBufferString("hello world")) + + // prepare for GET on range request + typedDriver.SetGetObjectWriter("foo", "bar", []byte("hello world")) + typedDriver.On("GetObjectMetadata", "foo", "bar", "").Return(metadata, nil).Once() + typedDriver.On("GetPartialObject", mock.Anything, "foo", "bar", int64(6), int64(2)).Return(int64(2), nil).Once() + + // prepare request + request, err := http.NewRequest("GET", testServer.URL+"/foo/bar", bytes.NewBufferString("")) + c.Assert(err, IsNil) + request.Header.Add("Accept", "application/json") + request.Header.Add("Range", "bytes=6-7") + client := http.Client{} + response, err := client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusPartialContent) + partialObject, err := ioutil.ReadAll(response.Body) + c.Assert(err, IsNil) + + c.Assert(string(partialObject), Equals, "wo") +} + +func startMockDriver() *mocks.Driver { return &mocks.Driver{ ObjectWriterData: make(map[string][]byte), } diff --git a/pkg/drivers/memory/memory.go b/pkg/drivers/memory/memory.go index f23aa5662..93fb52890 100644 --- a/pkg/drivers/memory/memory.go +++ b/pkg/drivers/memory/memory.go @@ -80,8 +80,16 @@ func (memory memoryDriver) GetObject(w io.Writer, bucket string, object string) } // GetPartialObject - GET object from memory buffer range -func (memory memoryDriver) GetPartialObject(w io.Writer, bucket, object string, start, end int64) (int64, error) { - return 0, drivers.APINotImplemented{API: "GetPartialObject"} +func (memory memoryDriver) GetPartialObject(w io.Writer, bucket, object string, start, length int64) (int64, error) { + var sourceBuffer bytes.Buffer + if _, err := memory.GetObject(&sourceBuffer, bucket, object); err != nil { + return 0, err + } + var nilBuffer bytes.Buffer + if _, err := io.CopyN(&nilBuffer, &sourceBuffer, start); err != nil { + return 0, err + } + return io.CopyN(w, &sourceBuffer, length) } // CreateBucketPolicy - Not implemented diff --git a/pkg/drivers/mocks/Driver.go b/pkg/drivers/mocks/Driver.go index 7971d16df..b3fc53828 100644 --- a/pkg/drivers/mocks/Driver.go +++ b/pkg/drivers/mocks/Driver.go @@ -6,6 +6,8 @@ import "github.com/stretchr/testify/mock" import ( "bytes" "io" + + "github.com/minio-io/iodine" ) // Driver is a mock @@ -56,7 +58,7 @@ func (m *Driver) GetBucketPolicy(bucket string) (drivers.BucketPolicy, error) { // SetGetObjectWriter is a mock func (m *Driver) SetGetObjectWriter(bucket, object string, data []byte) { m.ObjectWriterData[bucket+":"+object] = data - println(string(m.ObjectWriterData["bucket:object"])) + // println(string(m.ObjectWriterData["bucket:object"])) } // GetObject is a mock @@ -81,6 +83,17 @@ func (m *Driver) GetPartialObject(w io.Writer, bucket string, object string, sta r0 := ret.Get(0).(int64) r1 := ret.Error(1) + if r1 == nil { + if obj, ok := m.ObjectWriterData[bucket+":"+object]; ok { + source := bytes.NewBuffer(obj) + var nilSink bytes.Buffer + io.CopyN(&nilSink, source, start) + n, _ := io.CopyN(w, source, length) + r0 = n + } + } + r1 = iodine.New(r1, nil) + return r0, r1 }