Merge lp:~jtv/gwacl/pass-marker into lp:gwacl

Proposed by Jeroen T. Vermeulen
Status: Merged
Approved by: Jeroen T. Vermeulen
Approved revision: 101
Merged at revision: 98
Proposed branch: lp:~jtv/gwacl/pass-marker
Merge into: lp:gwacl
Diff against target: 143 lines (+96/-5)
3 files modified
storage_base.go (+14/-2)
storage_base_test.go (+75/-0)
utils.go (+7/-3)
To merge this branch: bzr merge lp:~jtv/gwacl/pass-marker
Reviewer Review Type Date Requested Status
Gavin Panella Approve
Review via email: mp+160939@code.launchpad.net

Commit message

Pass marker URI parameter to List Containers.

Description of the change

This is a crucial, but hard to test, part that I forgot in my original implementation.

Please excuse the crude URL composition. I don't think we currently have the flexibility to do it more nicely.

Jeroen

To post a comment you must log in.
Revision history for this message
Gavin Panella (allenap) wrote :

Looks good.

[1]

     uri := interpolateURL(
         "http://___.blob.core.windows.net/?comp=list", context.Account)
+    if marker != "" {
+        uri += "&" + interpolateURL("marker=___", marker)
+    }

The choice of ___ was not arbitrary here. It let's us do:

    u, err := url.Parse("http://___.blob.core.windows.net/?comp=list")
    if err != nil { ... tiresome crap ... }
    q := u.Query()
    q.Set("marker", marker)
    u.RawQuery = q.Encode()
    uri := interpolateURL(u.String(), context.Account)

Or you could do it this way:

    uri := interpolateURL(
        "http://___.blob.core.windows.net/?comp=list", context.Account)
    u, err := url.Parse(uri)
    if err != nil { ... tiresome crap ... }
    q := u.Query()
    q.Set("marker", marker)
    u.RawQuery = q.Encode()
    uri = u.String()

net.url.URL can be a PITA, but it can sometimes be coerced into doing
the right thing.

[2]

+    query := transport.Exchanges[0].Request.URL.RawQuery
+    values, err := url.ParseQuery(query)

This is funny, because you could also do:

    query := transport.Exchanges[0].Request.URL.Query()

There's no error returned from Query(), unlike ParseQuery(). I wonder
where that error went? I mean, it's not possible that the error is
thrown away, is it? Not in Go I say with gusto, because developers are
constantly reminded of the need to deal correctly with errors. It's
not like the standard library could contain the following exact
implementation of Query() is it?

// Query parses RawQuery and returns the corresponding values.
func (u *URL) Query() Values {
        v, _ := ParseQuery(u.RawQuery)
        return v
}

I must be dreaming.

review: Approve
Revision history for this message
Julian Edwards (julian-edwards) wrote :

On Thursday 25 Apr 2013 16:30:32 you wrote:
>
> There's no error returned from Query(), unlike ParseQuery(). I wonder
> where that error went? I mean, it's not possible that the error is
> thrown away, is it? Not in Go I say with gusto, because developers are
> constantly reminded of the need to deal correctly with errors. It's
> not like the standard library could contain the following exact
> implementation of Query() is it?
>
> // Query parses RawQuery and returns the corresponding values.
> func (u *URL) Query() Values {
> v, _ := ParseQuery(u.RawQuery)
> return v
> }
>
> I must be dreaming.

Love it.

Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

Damn, I thought you were making that up! But it really is what the standard library does..

Quick, to the blogmobile!

lp:~jtv/gwacl/pass-marker updated
100. By Jeroen T. Vermeulen

Review change: use proper URL query handling, and make sure the marker can safely contain '___'.

101. By Jeroen T. Vermeulen

Comment.

Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

I used the URL query handling, thanks. The first form you gave didn't look correct in the face of marker strings that happen to contain "___" so I used a version of the second form.

And I added a test for that nasty case, so we don't get bitten by it later. It may sound improbable, but spend some time with C++ link-name mangling...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'storage_base.go'
2--- storage_base.go 2013-04-25 14:01:00 +0000
3+++ storage_base.go 2013-04-26 04:00:40 +0000
4@@ -273,8 +273,20 @@
5 // "next marker" returned by the previous call.
6 // The "next marker" will be empty on the last batch.
7 func (context *StorageContext) getListContainersBatch(marker string) (*ContainerEnumerationResults, string, error) {
8- uri := interpolateURL(
9- "http://___.blob.core.windows.net/?comp=list", context.Account)
10+ uri := interpolateURL("http://___.blob.core.windows.net/?comp=list", context.Account)
11+ if marker != "" {
12+ // Add the marker argument. Do it after interpolation, in case the
13+ // marker string might contain the interpolation's placeholder
14+ // sequence.
15+ parsedURL, err := url.Parse(uri)
16+ if err != nil {
17+ return nil, "", err
18+ }
19+ query := parsedURL.Query()
20+ query.Set("marker", marker)
21+ parsedURL.RawQuery = query.Encode()
22+ uri = parsedURL.String()
23+ }
24 req, err := http.NewRequest("GET", uri, nil)
25 if err != nil {
26 return nil, "", err
27
28=== modified file 'storage_base_test.go'
29--- storage_base_test.go 2013-04-25 09:54:06 +0000
30+++ storage_base_test.go 2013-04-26 04:00:40 +0000
31@@ -383,6 +383,81 @@
32 c.Check(containers.Containers[1].Name, Equals, lastContainer)
33 }
34
35+func (suite *TestListContainers) TestGetListContainersBatchPassesMarker(c *C) {
36+ transport := TestTransport2{}
37+ transport.AddExchange(&http.Response{StatusCode: http.StatusOK, Body: Empty}, nil)
38+ context := makeStorageContext()
39+ context.client = &http.Client{Transport: &transport}
40+
41+ // Call getListContainersBatch. This will fail because of the empty
42+ // response, but no matter. We only care about the request.
43+ _, _, err := context.getListContainersBatch("thismarkerhere")
44+ c.Assert(err, ErrorMatches, ".*Failed to deserialize data.*")
45+ c.Assert(transport.ExchangeCount, Equals, 1)
46+
47+ query := transport.Exchanges[0].Request.URL.RawQuery
48+ values, err := url.ParseQuery(query)
49+ c.Assert(err, IsNil)
50+ c.Check(values["marker"], DeepEquals, []string{"thismarkerhere"})
51+}
52+
53+func (suite *TestListContainers) TestGetListContainersBatchDoesNotPassEmptyMarker(c *C) {
54+ transport := TestTransport2{}
55+ transport.AddExchange(&http.Response{StatusCode: http.StatusOK, Body: Empty}, nil)
56+ context := makeStorageContext()
57+ context.client = &http.Client{Transport: &transport}
58+
59+ // The error is OK. We only care about the request.
60+ _, _, err := context.getListContainersBatch("")
61+ c.Assert(err, ErrorMatches, ".*Failed to deserialize data.*")
62+ c.Assert(transport.ExchangeCount, Equals, 1)
63+
64+ query := transport.Exchanges[0].Request.URL.RawQuery
65+ values, err := url.ParseQuery(query)
66+ c.Assert(err, IsNil)
67+ marker, present := values["marker"]
68+ c.Check(present, Equals, false)
69+ c.Check(marker, DeepEquals, []string(nil))
70+}
71+
72+func (suite *TestListContainers) TestGetListContainersBatchEscapesMarker(c *C) {
73+ transport := TestTransport2{}
74+ transport.AddExchange(&http.Response{StatusCode: http.StatusOK, Body: Empty}, nil)
75+ context := makeStorageContext()
76+ context.client = &http.Client{Transport: &transport}
77+
78+ // The error is OK. We only care about the request.
79+ _, _, err := context.getListContainersBatch("x&y")
80+ c.Assert(err, ErrorMatches, ".*Failed to deserialize data.*")
81+ c.Assert(transport.ExchangeCount, Equals, 1)
82+
83+ query := transport.Exchanges[0].Request.URL.RawQuery
84+ values, err := url.ParseQuery(query)
85+ c.Assert(err, IsNil)
86+ c.Check(values["marker"], DeepEquals, []string{"x&y"})
87+}
88+
89+// The marker string may safely contain the placeholder that we happen to use
90+// for URL interpolation.
91+func (suite *TestListContainers) TestGetListContainersBatchDoesNotInterpolateMarker(c *C) {
92+ transport := TestTransport2{}
93+ transport.AddExchange(&http.Response{StatusCode: http.StatusOK, Body: Empty}, nil)
94+ context := makeStorageContext()
95+ context.client = &http.Client{Transport: &transport}
96+ marker := "x" + interpolationPlaceholder + "y"
97+
98+ // An error about the http response is fine. What matters is that (1) we
99+ // don't fail while putting together the URL, and (2) we get the right URL.
100+ _, _, err := context.getListContainersBatch(marker)
101+ c.Assert(err, ErrorMatches, ".*Failed to deserialize data.*")
102+ c.Assert(transport.ExchangeCount, Equals, 1)
103+
104+ query := transport.Exchanges[0].Request.URL.RawQuery
105+ values, err := url.ParseQuery(query)
106+ c.Assert(err, IsNil)
107+ c.Check(values["marker"], DeepEquals, []string{marker})
108+}
109+
110 type TestListBlobs struct{}
111
112 var _ = Suite(&TestListBlobs{})
113
114=== modified file 'utils.go'
115--- utils.go 2013-04-12 15:03:26 +0000
116+++ utils.go 2013-04-26 04:00:40 +0000
117@@ -23,19 +23,23 @@
118 }
119 }
120
121-// interpolateURL replaces occurrences of ___ (three underscores) with the
122+// We use ___ (three underscores) as a placeholder for variables that are to be
123+// interpolated into a URL.
124+const interpolationPlaceholder = "___"
125+
126+// interpolateURL replaces occurrences of the interpolation placeolder with the
127 // components specified. Additionally, the components are percent-encoded
128 // (using net.url.QueryEscape) before being interpolated.
129 func interpolateURL(urlstring string, components ...string) string {
130 components_count := len(components)
131- replacement_count := strings.Count(urlstring, "___")
132+ replacement_count := strings.Count(urlstring, interpolationPlaceholder)
133 if components_count != replacement_count {
134 panic(fmt.Errorf("%d slot(s) but given %d replacement(s)",
135 replacement_count, components_count))
136 }
137 for _, component := range components {
138 component = url.QueryEscape(component)
139- urlstring = strings.Replace(urlstring, "___", component, 1)
140+ urlstring = strings.Replace(urlstring, interpolationPlaceholder, component, 1)
141 }
142 return urlstring
143 }

Subscribers

People subscribed via source and target branches