Merge lp:~rvb/gwacl/handle-307 into lp:gwacl

Proposed by Raphaël Badin
Status: Merged
Approved by: Raphaël Badin
Approved revision: 149
Merged at revision: 152
Proposed branch: lp:~rvb/gwacl/handle-307
Merge into: lp:gwacl
Diff against target: 121 lines (+53/-5)
2 files modified
x509dispatcher.go (+5/-0)
x509dispatcher_test.go (+48/-5)
To merge this branch: bzr merge lp:~rvb/gwacl/handle-307
Reviewer Review Type Date Requested Status
Julian Edwards (community) Approve
Review via email: mp+172330@code.launchpad.net

Commit message

Follow temporary redirection.

Description of the change

When polling after having created or removed an instance, I've noticed that from time to time, the polling gets interrupted because it gets a 307 response (temporary redirect) from the Azure server. This branch fixes this problem by using curl's CURLOPT_FOLLOWLOCATION option so that curl will follow the redirection.

To post a comment you must log in.
Revision history for this message
Julian Edwards (julian-edwards) wrote :
Download full text (5.8 KiB)

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

 review: approve

Thanks for this, nice change. I must admit, I'm surprised that curl
doesn't do this by default, wget does.

Some minor points below.

On 01/07/13 23:08, Raphaël Badin wrote:
> === modified file 'x509dispatcher.go' --- x509dispatcher.go
> 2013-06-27 10:55:35 +0000 +++ x509dispatcher.go 2013-07-01 13:05:38
> +0000 @@ -178,6 +178,9 @@ return response, nil }
>
> +// The maximum number of redirection-followings allowed. +var
> _CURL_MAX_REDIRECTS = 10

Why did you choose 10? Document that here in the comment.

> + // makeCurlRequest produces a curl.CURL representing the
> request. // Clean up the request by calling its Cleanup() method
> after you're done // with it. @@ -188,6 +191,8 @@
> ch.Setopt(curl.OPT_KEYPASSWD, "") ch.Setopt(curl.OPT_URL,
> request.URL) //ch.Setopt(curl.OPT_VERBOSE, true) +
> ch.Setopt(curl.OPT_FOLLOWLOCATION, true) +
> ch.Setopt(curl.OPT_MAXREDIRS, _CURL_MAX_REDIRECTS)
> ch.Setopt(curl.OPT_SSLVERSION, 3) versionHeader := "x-ms-version: "
> + request.APIVersion headers := []string{versionHeader}
>
> === modified file 'x509dispatcher_test.go' ---
> x509dispatcher_test.go 2013-03-26 10:23:51 +0000 +++
> x509dispatcher_test.go 2013-07-01 13:05:38 +0000 @@ -14,9 +14,12
> @@ // that serves at the given base URL, copies incoming requests
> into the given // channel, and finally returns the given status
> code. If body is not nil, it // will be returned as the request
> body. -func makeRecordingHTTPServer(requests chan *http.Request,
> status int, body []byte) *httptest.Server { +func
> makeRecordingHTTPServer(requests chan *http.Request, status int,
> body []byte, headers map[string]string

Consider using http.Header instead of map[string]string here (like
performRequest() in storage_base.go does), it is the same thing
underneath but conveys intent better.

) *httptest.Server {
> returnRequest := func(w http.ResponseWriter, r *http.Request) {
> requests <- r + for key, value := range headers { +
> w.Header().Set(key, value) + } w.WriteHeader(status) if body
> != nil { w.Write(body) @@ -55,7 +58,7 @@
>
> func (*x509DispatcherSuite) TestGetRequestDoesHTTPGET(c *C) {
> httpRequests := make(chan *http.Request, 1) - server :=
> makeRecordingHTTPServer(httpRequests, http.StatusOK, nil) +
> server := makeRecordingHTTPServer(httpRequests, http.StatusOK, nil,
> nil) defer server.Close() // No real certificate needed since we're
> testing on http, not https. session, err :=
> newX509Session("subscriptionid", "cert.pem") @@ -72,12 +75,49 @@
> c.Check(httpRequest.URL.String(), Equals, path) }
>
> +func (*x509DispatcherSuite) TestGetFollowsRedirects(c *C) { +
> redirectPath := "/redirect/path" + httpRequests := make(chan
> *http.Request, _CURL_MAX_REDIRECTS+1) + locationHeaders :=
> map[string]string{"Location": redirectPath} + server :=
> makeRecordingHTTPServer(httpRequests, http.StatusTemporaryRedirect,
> nil, locationHeaders) + defer server.Close() + // No real
> certificate needed since we're testing on http, not https. +
> session, err := newX509Session("subscriptionid", "cert.pem") +
> c.Assert(err, IsNil) + path := "/foo/bar...

Read more...

review: Approve
Revision history for this message
Raphaël Badin (rvb) wrote :

> -----BEGIN PGP SIGNED MESSAGE-----
> Hash: SHA1
>
> review: approve
>
> Thanks for this, nice change. I must admit, I'm surprised that curl
> doesn't do this by default, wget does.

Thanks for the review!

> > +// The maximum number of redirection-followings allowed. +var
> > _CURL_MAX_REDIRECTS = 10
>
> Why did you choose 10? Document that here in the comment.

Well, I confess I sort of chose it at random :). Looks like something between 7 and 10 is reasonable (judging by what people use) and it sounds reasonable to me :).

> Consider using http.Header instead of map[string]string here (like
> performRequest() in storage_base.go does), it is the same thing
> underneath but conveys intent better.

Okay.

> Consider making this a separate test, it muddies the focus of the rest
> of the test below. Short, focused are much easier to read and less flaky.

I considered that but I rejected it because I'm testing a very simple functionality in curl itself and the main goal of this test is not really to test the functionality in itself but to make sure that the option is turned on in CURL. Also, this test is pretty expensive to set up (in terms of number of lines) and that's why I prefer to do it in one test.

lp:~rvb/gwacl/handle-307 updated
149. By Raphaël Badin

Review fixes.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'x509dispatcher.go'
2--- x509dispatcher.go 2013-06-27 10:55:35 +0000
3+++ x509dispatcher.go 2013-07-02 07:18:24 +0000
4@@ -178,6 +178,9 @@
5 return response, nil
6 }
7
8+// The maximum number of redirection-followings allowed.
9+var _CURL_MAX_REDIRECTS = 10
10+
11 // makeCurlRequest produces a curl.CURL representing the request.
12 // Clean up the request by calling its Cleanup() method after you're done
13 // with it.
14@@ -188,6 +191,8 @@
15 ch.Setopt(curl.OPT_KEYPASSWD, "")
16 ch.Setopt(curl.OPT_URL, request.URL)
17 //ch.Setopt(curl.OPT_VERBOSE, true)
18+ ch.Setopt(curl.OPT_FOLLOWLOCATION, true)
19+ ch.Setopt(curl.OPT_MAXREDIRS, _CURL_MAX_REDIRECTS)
20 ch.Setopt(curl.OPT_SSLVERSION, 3)
21 versionHeader := "x-ms-version: " + request.APIVersion
22 headers := []string{versionHeader}
23
24=== modified file 'x509dispatcher_test.go'
25--- x509dispatcher_test.go 2013-03-26 10:23:51 +0000
26+++ x509dispatcher_test.go 2013-07-02 07:18:24 +0000
27@@ -14,9 +14,14 @@
28 // that serves at the given base URL, copies incoming requests into the given
29 // channel, and finally returns the given status code. If body is not nil, it
30 // will be returned as the request body.
31-func makeRecordingHTTPServer(requests chan *http.Request, status int, body []byte) *httptest.Server {
32+func makeRecordingHTTPServer(requests chan *http.Request, status int, body []byte, headers http.Header) *httptest.Server {
33 returnRequest := func(w http.ResponseWriter, r *http.Request) {
34 requests <- r
35+ for header, values := range headers {
36+ for _, value := range values {
37+ w.Header().Set(header, value)
38+ }
39+ }
40 w.WriteHeader(status)
41 if body != nil {
42 w.Write(body)
43@@ -55,7 +60,7 @@
44
45 func (*x509DispatcherSuite) TestGetRequestDoesHTTPGET(c *C) {
46 httpRequests := make(chan *http.Request, 1)
47- server := makeRecordingHTTPServer(httpRequests, http.StatusOK, nil)
48+ server := makeRecordingHTTPServer(httpRequests, http.StatusOK, nil, nil)
49 defer server.Close()
50 // No real certificate needed since we're testing on http, not https.
51 session, err := newX509Session("subscriptionid", "cert.pem")
52@@ -72,12 +77,50 @@
53 c.Check(httpRequest.URL.String(), Equals, path)
54 }
55
56+func (*x509DispatcherSuite) TestGetFollowsRedirects(c *C) {
57+ redirectPath := "/redirect/path"
58+ httpRequests := make(chan *http.Request, _CURL_MAX_REDIRECTS+1)
59+ locationHeaders := http.Header{}
60+ locationHeaders.Add("Location", redirectPath)
61+ server := makeRecordingHTTPServer(httpRequests, http.StatusTemporaryRedirect, nil, locationHeaders)
62+ defer server.Close()
63+ // No real certificate needed since we're testing on http, not https.
64+ session, err := newX509Session("subscriptionid", "cert.pem")
65+ c.Assert(err, IsNil)
66+ path := "/foo/bar"
67+ request := newX509RequestGET(server.URL + path)
68+
69+ _, err = performX509CurlRequest(session, request)
70+ c.Check(err, ErrorMatches, ".*Number of redirects hit maximum amount.*")
71+
72+ var httpRequest *http.Request
73+ // The original GET request has been performed.
74+ select {
75+ case httpRequest = <-httpRequests:
76+ default:
77+ c.Error("The original request has not been performed.")
78+ }
79+ c.Check(httpRequest.Method, Equals, "GET")
80+ c.Check(httpRequest.URL.String(), Equals, path)
81+
82+ // _CURL_MAX_REDIRECTS redirected requests have been performed.
83+ for i := 0; i < _CURL_MAX_REDIRECTS; i++ {
84+ select {
85+ case httpRequest := <-httpRequests:
86+ c.Check(httpRequest.Method, Equals, "GET")
87+ c.Check(httpRequest.URL.String(), Equals, redirectPath)
88+ default:
89+ c.Error("No redirection has happened.")
90+ }
91+ }
92+}
93+
94 func (*x509DispatcherSuite) TestPostRequestDoesHTTPPOST(c *C) {
95 httpRequests := make(chan *http.Request, 1)
96 requestBody := []byte{1, 2, 3}
97 responseBody := []byte{4, 5, 6}
98 requestContentType := "bogusContentType"
99- server := makeRecordingHTTPServer(httpRequests, http.StatusOK, responseBody)
100+ server := makeRecordingHTTPServer(httpRequests, http.StatusOK, responseBody, nil)
101 defer server.Close()
102 // No real certificate needed since we're testing on http, not https.
103 session, err := newX509Session("subscriptionid", "cert.pem")
104@@ -99,7 +142,7 @@
105
106 func (*x509DispatcherSuite) TestDeleteRequestDoesHTTPDELETE(c *C) {
107 httpRequests := make(chan *http.Request, 1)
108- server := makeRecordingHTTPServer(httpRequests, http.StatusOK, []byte{})
109+ server := makeRecordingHTTPServer(httpRequests, http.StatusOK, nil, nil)
110 defer server.Close()
111 // No real certificate needed since we're testing on http, not https.
112 session, err := newX509Session("subscriptionid", "cert.pem")
113@@ -120,7 +163,7 @@
114 httpRequests := make(chan *http.Request, 1)
115 requestBody := []byte{1, 2, 3}
116 responseBody := []byte{4, 5, 6}
117- server := makeRecordingHTTPServer(httpRequests, http.StatusOK, responseBody)
118+ server := makeRecordingHTTPServer(httpRequests, http.StatusOK, responseBody, nil)
119 defer server.Close()
120 // No real certificate needed since we're testing on http, not https.
121 session, err := newX509Session("subscriptionid", "cert.pem")

Subscribers

People subscribed via source and target branches

to all changes: