Skip to content

Commit 64ed1bc

Browse files
objectstorage: Do not parse NoContent responses
Some Swift instances respond with HTTP status code 204 when asked to list containers and there are no containers, or when asked to list objects on an empty container. Before this patch, Gophercloud would error because the header `content-type` is absent on the response. With this patch, objectstorage responses with HTTP status code 204 are immediately recognised as empty and their body is not read.
1 parent 740fda7 commit 64ed1bc

File tree

8 files changed

+77
-2
lines changed

8 files changed

+77
-2
lines changed

openstack/objectstorage/v1/containers/results.go

+4
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ type ContainerPage struct {
3030

3131
// IsEmpty returns true if a ListResult contains no container names.
3232
func (r ContainerPage) IsEmpty() (bool, error) {
33+
if r.StatusCode == 204 {
34+
return true, nil
35+
}
36+
3337
names, err := ExtractNames(r)
3438
return len(names) == 0, err
3539
}

openstack/objectstorage/v1/containers/testing/fixtures.go

+13
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,19 @@ func HandleListContainerNamesSuccessfully(t *testing.T) {
9494
})
9595
}
9696

97+
// HandleListZeroContainerNames204 creates an HTTP handler at `/` on the test handler mux that
98+
// responds with "204 No Content" when container names are requested. This happens on some, but not all,
99+
// objectstorage instances. This case is peculiar in that the server sends no `content-type` header.
100+
func HandleListZeroContainerNames204(t *testing.T) {
101+
th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
102+
th.TestMethod(t, r, "GET")
103+
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
104+
th.TestHeader(t, r, "Accept", "text/plain")
105+
106+
w.WriteHeader(http.StatusNoContent)
107+
})
108+
}
109+
97110
// HandleCreateContainerSuccessfully creates an HTTP handler at `/testContainer` on the test handler mux that
98111
// responds with a `Create` response.
99112
func HandleCreateContainerSuccessfully(t *testing.T) {

openstack/objectstorage/v1/containers/testing/requests_test.go

+12
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,18 @@ func TestListAllContainerNames(t *testing.T) {
7979
th.CheckDeepEquals(t, ExpectedListNames, actual)
8080
}
8181

82+
func TestListZeroContainerNames(t *testing.T) {
83+
th.SetupHTTP()
84+
defer th.TeardownHTTP()
85+
HandleListZeroContainerNames204(t)
86+
87+
allPages, err := containers.List(fake.ServiceClient(), &containers.ListOpts{Full: false}).AllPages()
88+
th.AssertNoErr(t, err)
89+
actual, err := containers.ExtractNames(allPages)
90+
th.AssertNoErr(t, err)
91+
th.CheckDeepEquals(t, []string{}, actual)
92+
}
93+
8294
func TestCreateContainer(t *testing.T) {
8395
th.SetupHTTP()
8496
defer th.TeardownHTTP()

openstack/objectstorage/v1/objects/results.go

+4
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ type ObjectPage struct {
7070

7171
// IsEmpty returns true if a ListResult contains no object names.
7272
func (r ObjectPage) IsEmpty() (bool, error) {
73+
if r.StatusCode == 204 {
74+
return true, nil
75+
}
76+
7377
names, err := ExtractNames(r)
7478
return len(names) == 0, err
7579
}

openstack/objectstorage/v1/objects/testing/fixtures.go

+13
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,19 @@ func HandleListObjectNamesSuccessfully(t *testing.T) {
162162
})
163163
}
164164

165+
// HandleListZeroObjectNames204 creates an HTTP handler at `/testContainer` on the test handler mux that
166+
// responds with "204 No Content" when object names are requested. This happens on some, but not all, objectstorage
167+
// instances. This case is peculiar in that the server sends no `content-type` header.
168+
func HandleListZeroObjectNames204(t *testing.T) {
169+
th.Mux.HandleFunc("/testContainer", func(w http.ResponseWriter, r *http.Request) {
170+
th.TestMethod(t, r, "GET")
171+
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
172+
th.TestHeader(t, r, "Accept", "text/plain")
173+
174+
w.WriteHeader(http.StatusNoContent)
175+
})
176+
}
177+
165178
// HandleCreateTextObjectSuccessfully creates an HTTP handler at `/testContainer/testObject` on the test handler mux
166179
// that responds with a `Create` response. A Content-Type of "text/plain" is expected.
167180
func HandleCreateTextObjectSuccessfully(t *testing.T, content string) {

openstack/objectstorage/v1/objects/testing/requests_test.go

+23
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,29 @@ func TestListObjectNames(t *testing.T) {
160160
th.CheckEquals(t, count, 1)
161161
}
162162

163+
func TestListZeroObjectNames204(t *testing.T) {
164+
th.SetupHTTP()
165+
defer th.TeardownHTTP()
166+
HandleListZeroObjectNames204(t)
167+
168+
count := 0
169+
options := &objects.ListOpts{Full: false}
170+
err := objects.List(fake.ServiceClient(), "testContainer", options).EachPage(func(page pagination.Page) (bool, error) {
171+
count++
172+
actual, err := objects.ExtractNames(page)
173+
if err != nil {
174+
t.Errorf("Failed to extract container names: %v", err)
175+
return false, err
176+
}
177+
178+
th.CheckDeepEquals(t, []string{}, actual)
179+
180+
return true, nil
181+
})
182+
th.AssertNoErr(t, err)
183+
th.CheckEquals(t, 0, count)
184+
}
185+
163186
func TestCreateObject(t *testing.T) {
164187
th.SetupHTTP()
165188
defer th.TeardownHTTP()

pagination/http.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,9 @@ func PageResultFrom(resp *http.Response) (PageResult, error) {
4444
func PageResultFromParsed(resp *http.Response, body interface{}) PageResult {
4545
return PageResult{
4646
Result: gophercloud.Result{
47-
Body: body,
48-
Header: resp.Header,
47+
Body: body,
48+
StatusCode: resp.StatusCode,
49+
Header: resp.Header,
4950
},
5051
URL: *resp.Request.URL,
5152
}

results.go

+5
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ type Result struct {
3030
// this will be the deserialized JSON structure.
3131
Body interface{}
3232

33+
// StatusCode is the HTTP status code of the original response. Will be
34+
// one of the OkCodes defined on the gophercloud.RequestOpts that was
35+
// used in the request.
36+
StatusCode int
37+
3338
// Header contains the HTTP header structure from the original response.
3439
Header http.Header
3540

0 commit comments

Comments
 (0)