Merge lp:~axwalk/gwacl/management-api-307-redirects into lp:gwacl

Proposed by Andrew Wilkins
Status: Merged
Approved by: Andrew Wilkins
Approved revision: 232
Merged at revision: 231
Proposed branch: lp:~axwalk/gwacl/management-api-307-redirects
Merge into: lp:gwacl
Diff against target: 185 lines (+104/-3)
3 files modified
x509dispatcher.go (+35/-1)
x509dispatcher_test.go (+56/-2)
x509session.go (+13/-0)
To merge this branch: bzr merge lp:~axwalk/gwacl/management-api-307-redirects
Reviewer Review Type Date Requested Status
Ian Booth Approve
Review via email: mp+204618@code.launchpad.net

Commit message

Add handling of temporary redirects in the management API.

Description of the change

Add handling of temporary redirects in the management API.

To post a comment you must log in.
Revision history for this message
Ian Booth (wallyworld) wrote :

Looks good. Did you test it live? I think that should be done before landing.

review: Approve
Revision history for this message
Andrew Wilkins (axwalk) wrote :

I tested bootstrap and destroy-env a bunch of times. I'll run some deployment tests before landing.

Revision history for this message
Andrew Wilkins (axwalk) wrote :

Seems there's still a problem.

232. By Andrew Wilkins

Also check redirect status code for POST/DELETE, etc.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'x509dispatcher.go'
--- x509dispatcher.go 2013-08-09 13:59:43 +0000
+++ x509dispatcher.go 2014-02-05 04:46:43 +0000
@@ -5,9 +5,11 @@
55
6import (6import (
7 "bytes"7 "bytes"
8 "fmt"
8 "io/ioutil"9 "io/ioutil"
9 "launchpad.net/gwacl/fork/http"10 "launchpad.net/gwacl/fork/http"
10 . "launchpad.net/gwacl/logging"11 . "launchpad.net/gwacl/logging"
12 "net/url"
11)13)
1214
13type X509Request struct {15type X509Request struct {
@@ -84,7 +86,7 @@
84 httpRequest.Header.Set("Content-Type", request.ContentType)86 httpRequest.Header.Set("Content-Type", request.ContentType)
85 httpRequest.Header.Set("x-ms-version", request.APIVersion)87 httpRequest.Header.Set("x-ms-version", request.APIVersion)
86 retrier := session.retryPolicy.getForkedHttpRetrier(session.client)88 retrier := session.retryPolicy.getForkedHttpRetrier(session.client)
87 httpResponse, err := retrier.RetryRequest(httpRequest)89 httpResponse, err := handleRequestRedirects(retrier, httpRequest)
88 if err != nil {90 if err != nil {
89 return nil, err91 return nil, err
90 }92 }
@@ -108,3 +110,35 @@
108110
109 return response, nil111 return response, nil
110}112}
113
114func handleRequestRedirects(retrier *forkedHttpRetrier, request *http.Request) (*http.Response, error) {
115 const maxRedirects = 10
116 // Handle temporary redirects.
117 redirects := -1
118 for {
119 redirects++
120 if redirects >= maxRedirects {
121 return nil, fmt.Errorf("stopped after %d redirects", redirects)
122 }
123 response, err := retrier.RetryRequest(request)
124 // For GET and HEAD, we cause the request execution
125 // to return httpRedirectErr if a temporary redirect
126 // is returned, and then deal with it here.
127 if err, ok := err.(*url.Error); ok && err.Err == httpRedirectErr {
128 request.URL, err.Err = url.Parse(err.URL)
129 if err.Err != nil {
130 return nil, err
131 }
132 continue
133 }
134 // For other methods, we must check the response code.
135 if err == nil && response.StatusCode == http.StatusTemporaryRedirect {
136 request.URL, err = response.Location()
137 if err != nil {
138 return nil, err
139 }
140 continue
141 }
142 return response, err
143 }
144}
111145
=== modified file 'x509dispatcher_test.go'
--- x509dispatcher_test.go 2013-08-09 14:59:50 +0000
+++ x509dispatcher_test.go 2014-02-05 04:46:43 +0000
@@ -22,6 +22,7 @@
22// channel, and finally returns the given status code. If body is not nil, it22// channel, and finally returns the given status code. If body is not nil, it
23// will be returned as the request body.23// will be returned as the request body.
24func makeRecordingHTTPServer(requests chan *Request, status int, body []byte, headers http.Header) *httptest.Server {24func makeRecordingHTTPServer(requests chan *Request, status int, body []byte, headers http.Header) *httptest.Server {
25 var server *httptest.Server
25 returnRequest := func(w http.ResponseWriter, r *http.Request) {26 returnRequest := func(w http.ResponseWriter, r *http.Request) {
26 // Capture all the request body content for later inspection.27 // Capture all the request body content for later inspection.
27 requestBody, err := ioutil.ReadAll(r.Body)28 requestBody, err := ioutil.ReadAll(r.Body)
@@ -29,7 +30,8 @@
29 panic(err)30 panic(err)
30 }31 }
31 requests <- &Request{r, requestBody}32 requests <- &Request{r, requestBody}
3233 // Set a default Location so we can test redirect loops easily.
34 w.Header().Set("Location", server.URL+r.URL.Path)
33 for header, values := range headers {35 for header, values := range headers {
34 for _, value := range values {36 for _, value := range values {
35 w.Header().Set(header, value)37 w.Header().Set(header, value)
@@ -42,7 +44,8 @@
42 }44 }
43 serveMux := http.NewServeMux()45 serveMux := http.NewServeMux()
44 serveMux.HandleFunc("/", returnRequest)46 serveMux.HandleFunc("/", returnRequest)
45 return httptest.NewServer(serveMux)47 server = httptest.NewServer(serveMux)
48 return server
46}49}
4750
48func (*x509DispatcherSuite) TestGetRequestDoesHTTPGET(c *C) {51func (*x509DispatcherSuite) TestGetRequestDoesHTTPGET(c *C) {
@@ -184,3 +187,54 @@
184187
185 c.Check(response.Header[customHeader], DeepEquals, customValue)188 c.Check(response.Header[customHeader], DeepEquals, customValue)
186}189}
190
191func (*x509DispatcherSuite) TestRequestsFollowRedirects(c *C) {
192 httpRequests := make(chan *Request, 2)
193 serverConflict := makeRecordingHTTPServer(httpRequests, http.StatusConflict, nil, nil)
194 defer serverConflict.Close()
195 redirPath := "/else/where"
196 responseHeaders := make(http.Header)
197 responseHeaders.Set("Location", serverConflict.URL+redirPath)
198 serverRedir := makeRecordingHTTPServer(httpRequests, http.StatusTemporaryRedirect, nil, responseHeaders)
199 defer serverRedir.Close()
200 session, err := newX509Session("subscriptionid", "", "West US", NoRetryPolicy)
201 c.Assert(err, IsNil)
202 path := "/foo/bar"
203 version := "test-version"
204
205 // Test both GET and DELETE: DELETE does not normally
206 // automatically follow redirects, however Azure requires
207 // us to.
208 requests := []*X509Request{
209 newX509RequestGET(serverRedir.URL+path, version),
210 newX509RequestDELETE(serverRedir.URL+path, version),
211 }
212 for _, request := range requests {
213 response, err := performX509Request(session, request)
214 c.Assert(err, IsNil)
215 c.Assert(response.StatusCode, Equals, http.StatusConflict)
216 c.Assert(httpRequests, HasLen, 2)
217 c.Assert((<-httpRequests).URL.String(), Equals, path)
218 c.Assert((<-httpRequests).URL.String(), Equals, redirPath)
219 }
220}
221
222func (*x509DispatcherSuite) TestRequestsLimitRedirects(c *C) {
223 httpRequests := make(chan *Request, 10)
224 serverRedir := makeRecordingHTTPServer(httpRequests, http.StatusTemporaryRedirect, nil, nil)
225 defer serverRedir.Close()
226 session, err := newX509Session("subscriptionid", "", "West US", NoRetryPolicy)
227 c.Assert(err, IsNil)
228 path := "/foo/bar"
229 version := "test-version"
230 request := newX509RequestGET(serverRedir.URL+path, version)
231
232 response, err := performX509Request(session, request)
233 c.Assert(err, ErrorMatches, "stopped after 10 redirects")
234 c.Assert(response, IsNil)
235 c.Assert(httpRequests, HasLen, 10)
236 close(httpRequests)
237 for req := range httpRequests {
238 c.Assert(req.URL.String(), Equals, path)
239 }
240}
187241
=== modified file 'x509session.go'
--- x509session.go 2013-10-30 22:57:57 +0000
+++ x509session.go 2014-02-05 04:46:43 +0000
@@ -4,6 +4,7 @@
4package gwacl4package gwacl
55
6import (6import (
7 "errors"
7 "fmt"8 "fmt"
8 "launchpad.net/gwacl/fork/http"9 "launchpad.net/gwacl/fork/http"
9 "launchpad.net/gwacl/fork/tls"10 "launchpad.net/gwacl/fork/tls"
@@ -19,6 +20,11 @@
19 retryPolicy RetryPolicy20 retryPolicy RetryPolicy
20}21}
2122
23// httpRedirectErr is a unique error used to prevent
24// net/http from automatically following redirects.
25// See commentary on CheckRedirect in newX509Session.
26var httpRedirectErr = errors.New("redirect")
27
22// newX509Session creates and returns a new x509Session based on credentials28// newX509Session creates and returns a new x509Session based on credentials
23// and X509 certificate files.29// and X509 certificate files.
24// For testing purposes, certFile can be passed as the empty string and it30// For testing purposes, certFile can be passed as the empty string and it
@@ -43,6 +49,13 @@
43 // hit the above Go bug.49 // hit the above Go bug.
44 DisableKeepAlives: true,50 DisableKeepAlives: true,
45 },51 },
52 // See https://code.google.com/p/go/issues/detail?id=4800
53 // We need to follow temporary redirects (307s), while
54 // retaining headers. We also need to follow redirects
55 // for POST and DELETE automatically.
56 CheckRedirect: func(req *http.Request, via []*http.Request) error {
57 return httpRedirectErr
58 },
46 }59 }
4760
48 endpointURL := GetEndpoint(location).ManagementAPI()61 endpointURL := GetEndpoint(location).ManagementAPI()

Subscribers

People subscribed via source and target branches

to all changes: