func (session *Session) SendForTraversal(url string, data string) (string, error) { var ( resp *http.Response // http response buf bytes.Buffer // contains http response body err error ) resp, err = session.request(url, data) if err != nil { return "", err } log.Println(resp) defer func() { if resp.Body != nil { resp.Body.Close() } }() _, err = buf.ReadFrom(resp.Body) if err != nil { return "", err } session.StatusCode = resp.StatusCode location, err := resp.Location() if err != nil { return "", err } session.Location = location.String() return buf.String(), nil }
// save saves the body of a response corresponding to a request. func (c *CachedRoundTrip) save(req *http.Request, resp *http.Response) error { if resp.StatusCode == http.StatusMovedPermanently || resp.StatusCode == http.StatusTemporaryRedirect { u, err := resp.Location() if err != nil { return err } err = c.Cache.Put(req.URL, c.newEntry([]byte("REDIRECT:"+u.String()), resp)) if err != nil { return err } return nil } body, err := ioutil.ReadAll(resp.Body) if err != nil { return err } resp.Body.Close() err = c.Cache.Put(req.URL, c.newEntry(body, resp)) if err != nil { return err } resp.Body = ioutil.NopCloser(bytes.NewReader(body)) return nil }
// handleResp handles the responses from the etcd server // If status code is OK, read the http body and return it as byte array // If status code is TemporaryRedirect, update leader. // If status code is InternalServerError, sleep for 200ms. func (c *Client) handleResp(resp *http.Response) (bool, []byte) { defer resp.Body.Close() code := resp.StatusCode if code == http.StatusTemporaryRedirect { u, err := resp.Location() if err != nil { logger.Warning(err) } else { c.cluster.updateLeaderFromURL(u) } return false, nil } else if code == http.StatusInternalServerError { time.Sleep(time.Millisecond * 200) } else if validHttpStatusCode[code] { b, err := ioutil.ReadAll(resp.Body) if err != nil { return false, nil } return true, b } logger.Warning("bad status code ", resp.StatusCode) return false, nil }
// assertions func (s *FormsAuthSuite) assertRedirectToLogin(res *http.Response, c *check.C) { c.Assert(res.StatusCode, check.Equals, http.StatusSeeOther) location, err := res.Location() c.Assert(err, check.IsNil) expectedURL := fmt.Sprintf("%s?returnURL=%s", s.loginURL, url.QueryEscape(s.protectedURL)) c.Assert(location.RequestURI(), check.Equals, expectedURL) }
// UpgradeToken returns the assertion to upgrade the token on IAM to get the // 'purchased' scopes func (a *AssetsService) UpgradeToken() error { var ( req *http.Request res *http.Response location *url.URL err error ) req, err = a.client.NewRequest("GET", "assets", "/v1.0/asset/access", nil) if err != nil { return err } req.Header.Add("No-Redirect", "true") res, err = a.client.httpClient.Do(req) if err != nil { return err } location, err = res.Location() if err != nil { return err } req, err = a.client.NewRequest("GET", "iam", fmt.Sprintf("/v1.0/oauth/token/upgrade?%s", location.RawQuery), nil) if err != nil { return err } _, err = returnErrorHTTPSimple(a.client, req, err, 204) return err }
func (c *client) handleResponse(res *http.Response) { if res.StatusCode != 200 { return } st := res.Header.Get("ST") if st == "" { return } loc, err := res.Location() if err != nil { return } usn := res.Header.Get("USN") if usn == "" { usn = loc.String() } ev := Event{ Location: loc, ST: st, USN: usn, } select { // events not being waited for are simply dropped case c.eventChan <- ev: default: } }
func newAuthorization(authz *protocol.Authorization, resp *http.Response) (*Authorization, error) { st := authz.Status if st == "" { // Missing status value means "pending". ACME spec Sec. 5.3. st = protocol.StatusPending } id, err := newIdentifier(authz.Identifier) if err != nil { return nil, err } uri, err := resp.Location() if err == http.ErrNoLocation { // Fall back to request URI. // TODO: Check that the request wasn't for a new authorization. uri = resp.Request.URL } else if err != nil { return nil, err } ra, _ := retryAfter(resp.Header, 0) return &Authorization{ Authorization: *authz, Status: st, Identifier: id, URI: uri.String(), RetryAfter: ra, }, nil }
// followRedirects is a custom HTTP redirects handler which appends // cookie header to the request. // // r - The original response. // // Returns response from the new location. func followRedirects(r *http.Response) (*http.Response, error) { if location, err := r.Location(); err == nil && location != nil { c := &http.Client{} req, _ := http.NewRequest("GET", location.String(), nil) req.Header.Set("X-WebRocket-Cookie", Cookie) return c.Do(req) } return r, nil }
// ChangeToHttp On redirect change the response location from https to http. // It makes the client come back to us over http. func ChangeToHttp(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response { if IsResponseRedirected(resp) { location, _ := resp.Location() if location != nil { if location.Scheme == "https" { location.Scheme = "http" resp.Header.Set("Location", location.String()) println("ChangeToHttp response handler: ", ctx.Req.Host, "->", resp.Header.Get("Location")) } } } return resp }
func (t *verboseTransport) dumpResponse(resp *http.Response) { info := fmt.Sprintf("< HTTP %d", resp.StatusCode) location, err := resp.Location() if err == nil { info = fmt.Sprintf("%s\n< Location: %s", info, location.String()) } t.verbosePrintln(info) t.dumpHeaders(resp.Header, "<") body := t.dumpBody(resp.Body) if body != nil { // reset body since it's been read resp.Body = body } }
func (request *Request) pollForAsynchronousResponse(acceptedResponse *http.Response) (*http.Response, error) { var resp *http.Response = acceptedResponse for { if resp.StatusCode != http.StatusAccepted { return resp, nil } if retryAfter := resp.Header.Get("Retry-After"); retryAfter != "" { retryTime, err := strconv.Atoi(strings.TrimSpace(retryAfter)) if err != nil { return nil, err } request.client.logger.Printf("[INFO] Polling pausing for %d seconds as per Retry-After header", retryTime) time.Sleep(time.Duration(retryTime) * time.Second) } pollLocation, err := resp.Location() if err != nil { return nil, err } request.client.logger.Printf("[INFO] Polling %q for operation completion", pollLocation.String()) req, err := retryablehttp.NewRequest("GET", pollLocation.String(), bytes.NewReader([]byte{})) if err != nil { return nil, err } err = request.client.tokenRequester.addAuthorizationToRequest(req) if err != nil { return nil, err } resp, err := request.client.httpClient.Do(req) if err != nil { return nil, err } if resp.StatusCode == http.StatusAccepted { continue } return resp, err } }
// returnErrorByHTTPStatusCode returns the http error code or nil if it returns the // desired error func returnErrorByHTTPStatusCode(res *http.Response, desiredStatusCode int) (string, error) { var ( location *url.URL locationString string ) location, _ = res.Location() if location == nil { locationString = "" } else { locationString = location.String() } if res.StatusCode == desiredStatusCode { return locationString, nil } if http.StatusText(res.StatusCode) == "" { return "", fmt.Errorf("HTTP Error %d", res.StatusCode) } return locationString, fmt.Errorf("%d %s", res.StatusCode, http.StatusText(res.StatusCode)) }
// DowloadReleaseAsset downloads a release asset. // // DowloadReleaseAsset returns an io.ReadCloser that reads the contents of the // specified release asset. It is the caller's responsibility to close the ReadCloser. // // GitHub API docs : http://developer.github.com/v3/repos/releases/#get-a-single-release-asset func (s *RepositoriesService) DownloadReleaseAsset(owner, repo string, id int) (io.ReadCloser, error) { u := fmt.Sprintf("repos/%s/%s/releases/assets/%d", owner, repo, id) req, err := s.client.NewRequest("GET", u, nil) if err != nil { return nil, err } req.Header.Set("Accept", defaultMediaType) var resp *http.Response if s.client.client.Transport == nil { resp, err = http.DefaultTransport.RoundTrip(req) } else { resp, err = s.client.client.Transport.RoundTrip(req) } if err != nil { return nil, err } // GitHub API streamed the asset directly if resp.StatusCode == http.StatusOK { return resp.Body, nil } if resp.StatusCode != http.StatusFound { return nil, fmt.Errorf("Expected status code 200 or 302, got %d", resp.StatusCode) } // GitHub API redirected to pre-signed S3 URL downloadURL, err := resp.Location() if err != nil { return nil, err } resp, err = http.Get(downloadURL.String()) if err != nil { return nil, err } return resp.Body, nil }
// Wait for the job to finish, waiting waitTime on every loop func (c *Client) waitForResponseWithTimer(r *http.Response, waitTime time.Duration) (*http.Response, error) { // Get temp resource location, err := r.Location() if err != nil { return nil, err } for { // Create request req, err := http.NewRequest("GET", location.String(), nil) if err != nil { return nil, err } // Set token err = c.setToken(req) if err != nil { return nil, err } // Wait for response r, err = c.do(req) if err != nil { return nil, err } // Check if the request is pending if r.Header.Get("X-Pending") == "true" { if r.StatusCode != http.StatusOK { return nil, utils.GetErrorFromResponse(r) } time.Sleep(waitTime) } else { return r, nil } } }
func (ar *actionResolver) next(resp *http.Response) (*http.Request, error) { if resp.StatusCode != http.StatusTemporaryRedirect { return nil, nil } ar.redirectCount += 1 if ar.redirectCount >= redirectMax { return nil, errors.New("too many redirects") } loc, err := resp.Location() if err != nil { return nil, err } req, err := ar.action.HTTPRequest() if err != nil { return nil, err } req.URL = loc return req, nil }
func TestLogin(t *testing.T) { testCases := map[string]struct { CSRF csrf.CSRF Auth *testAuth Path string PostValues url.Values ExpectStatusCode int ExpectRedirect string ExpectContains []string ExpectThen string }{ "display form": { CSRF: &csrf.FakeCSRF{Token: "test"}, Auth: &testAuth{}, Path: "/login", ExpectStatusCode: 200, ExpectContains: []string{ `action="/login"`, `name="csrf" value="test"`, }, }, "display form with errors": { CSRF: &csrf.FakeCSRF{Token: "test"}, Auth: &testAuth{}, Path: "?then=foo&reason=failed&username=user", ExpectStatusCode: 200, ExpectContains: []string{ `action="/"`, `name="then" value="foo"`, `An unknown error has occurred`, `danger`, }, }, "redirect when POST fails CSRF": { CSRF: &csrf.FakeCSRF{Token: "test"}, Auth: &testAuth{}, Path: "/login", PostValues: url.Values{"csrf": []string{"wrong"}}, ExpectRedirect: "/login?reason=token+expired", }, "redirect with 'then' when POST fails CSRF": { CSRF: &csrf.FakeCSRF{Token: "test"}, Auth: &testAuth{}, Path: "/login?then=test", PostValues: url.Values{"csrf": []string{"wrong"}}, ExpectRedirect: "/login?reason=token+expired&then=test", }, "redirect when no username": { CSRF: &csrf.FakeCSRF{Token: "test"}, Auth: &testAuth{}, Path: "/login", PostValues: url.Values{ "csrf": []string{"test"}, }, ExpectRedirect: "/login?reason=user+required", }, "redirect when not authenticated": { CSRF: &csrf.FakeCSRF{Token: "test"}, Auth: &testAuth{Success: false}, Path: "/login", PostValues: url.Values{ "csrf": []string{"test"}, "username": []string{"user"}, }, ExpectRedirect: "/login?reason=access+denied", }, "redirect on auth error": { CSRF: &csrf.FakeCSRF{Token: "test"}, Auth: &testAuth{Err: errors.New("failed")}, Path: "/login", PostValues: url.Values{ "csrf": []string{"test"}, "username": []string{"user"}, }, ExpectRedirect: "/login?reason=unknown+error", }, "redirect preserving then param": { CSRF: &csrf.FakeCSRF{Token: "test"}, Auth: &testAuth{Err: errors.New("failed")}, Path: "/login", PostValues: url.Values{ "csrf": []string{"test"}, "username": []string{"user"}, "then": []string{"anotherurl"}, }, ExpectRedirect: "/login?reason=unknown+error&then=anotherurl", }, "login successful": { CSRF: &csrf.FakeCSRF{Token: "test"}, Auth: &testAuth{Success: true, User: &user.DefaultInfo{Name: "user"}}, Path: "/login?then=done", PostValues: url.Values{ "csrf": []string{"test"}, "username": []string{"user"}, }, ExpectThen: "done", }, } for k, testCase := range testCases { server := httptest.NewServer(NewLogin(testCase.CSRF, testCase.Auth, DefaultLoginFormRenderer)) var resp *http.Response if testCase.PostValues != nil { r, err := postForm(server.URL+testCase.Path, testCase.PostValues) if err != nil { t.Errorf("%s: unexpected error: %v", k, err) continue } resp = r } else { r, err := getURL(server.URL + testCase.Path) if err != nil { t.Errorf("%s: unexpected error: %v", k, err) continue } resp = r } defer resp.Body.Close() if testCase.ExpectStatusCode != 0 && testCase.ExpectStatusCode != resp.StatusCode { t.Errorf("%s: unexpected response: %#v", k, resp) continue } if testCase.ExpectRedirect != "" { uri, err := resp.Location() if err != nil { t.Errorf("%s: unexpected error: %v", k, err) continue } if uri.String() != server.URL+testCase.ExpectRedirect { t.Errorf("%s: unexpected redirect: %s", k, uri.String()) } } if testCase.ExpectThen != "" && (!testCase.Auth.Called || testCase.Auth.Then != testCase.ExpectThen) { t.Errorf("%s: did not find expected 'then' value: %#v", k, testCase.Auth) } if len(testCase.ExpectContains) > 0 { data, _ := ioutil.ReadAll(resp.Body) body := string(data) for i := range testCase.ExpectContains { if !strings.Contains(body, testCase.ExpectContains[i]) { t.Errorf("%s: did not find expected value %s: %s", k, testCase.ExpectContains[i], body) continue } } } } }
// SendRequest sends a HTTP request and returns a Response as defined by etcd func (c *Client) SendRequest(rr *RawRequest) (*RawResponse, error) { var req *http.Request var resp *http.Response var httpPath string var err error var respBody []byte var numReqs = 1 checkRetry := c.CheckRetry if checkRetry == nil { checkRetry = DefaultCheckRetry } cancelled := make(chan bool, 1) reqLock := new(sync.Mutex) if rr.Cancel != nil { cancelRoutine := make(chan bool) defer close(cancelRoutine) go func() { select { case <-rr.Cancel: cancelled <- true logger.Debug("send.request is cancelled") case <-cancelRoutine: return } // Repeat canceling request until this thread is stopped // because we have no idea about whether it succeeds. for { reqLock.Lock() c.httpClient.Transport.(*http.Transport).CancelRequest(req) reqLock.Unlock() select { case <-time.After(100 * time.Millisecond): case <-cancelRoutine: return } } }() } // If we connect to a follower and consistency is required, retry until // we connect to a leader sleep := 25 * time.Millisecond maxSleep := time.Second for attempt := 0; ; attempt++ { if attempt > 0 { select { case <-cancelled: return nil, ErrRequestCancelled case <-time.After(sleep): sleep = sleep * 2 if sleep > maxSleep { sleep = maxSleep } } } logger.Debug("Connecting to etcd: attempt ", attempt+1, " for ", rr.RelativePath) // get httpPath if not set if httpPath == "" { httpPath = c.getHttpPath(rr.RelativePath) } // Return a cURL command if curlChan is set if c.cURLch != nil { command := fmt.Sprintf("curl -X %s %s", rr.Method, httpPath) for key, value := range rr.Values { command += fmt.Sprintf(" -d %s=%s", key, value[0]) } if c.credentials != nil { command += fmt.Sprintf(" -u %s", c.credentials.username) } c.sendCURL(command) } logger.Debug("send.request.to ", httpPath, " | method ", rr.Method) req, err := func() (*http.Request, error) { reqLock.Lock() defer reqLock.Unlock() if rr.Values == nil { if req, err = http.NewRequest(rr.Method, httpPath, nil); err != nil { return nil, err } } else { body := strings.NewReader(rr.Values.Encode()) if req, err = http.NewRequest(rr.Method, httpPath, body); err != nil { return nil, err } req.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value") } return req, nil }() if err != nil { return nil, err } if c.credentials != nil { req.SetBasicAuth(c.credentials.username, c.credentials.password) } resp, err = c.httpClient.Do(req) // clear previous httpPath httpPath = "" defer func() { if resp != nil { resp.Body.Close() } }() // If the request was cancelled, return ErrRequestCancelled directly select { case <-cancelled: return nil, ErrRequestCancelled default: } numReqs++ // network error, change a machine! if err != nil { logger.Debug("network error: ", err.Error()) lastResp := http.Response{} if checkErr := checkRetry(c.cluster, numReqs, lastResp, err); checkErr != nil { return nil, checkErr } c.cluster.failure() continue } // if there is no error, it should receive response logger.Debug("recv.response.from ", httpPath) if validHttpStatusCode[resp.StatusCode] { // try to read byte code and break the loop respBody, err = ioutil.ReadAll(resp.Body) if err == nil { logger.Debug("recv.success ", httpPath) break } // ReadAll error may be caused due to cancel request select { case <-cancelled: return nil, ErrRequestCancelled default: } if err == io.ErrUnexpectedEOF { // underlying connection was closed prematurely, probably by timeout // TODO: empty body or unexpectedEOF can cause http.Transport to get hosed; // this allows the client to detect that and take evasive action. Need // to revisit once code.google.com/p/go/issues/detail?id=8648 gets fixed. respBody = []byte{} break } } if resp.StatusCode == http.StatusTemporaryRedirect { u, err := resp.Location() if err != nil { logger.Warning(err) } else { // set httpPath for following redirection httpPath = u.String() } resp.Body.Close() continue } if checkErr := checkRetry(c.cluster, numReqs, *resp, errors.New("Unexpected HTTP status code")); checkErr != nil { return nil, checkErr } resp.Body.Close() } r := &RawResponse{ StatusCode: resp.StatusCode, Body: respBody, Header: resp.Header, } return r, nil }
func TestImplicit(t *testing.T) { testCases := map[string]struct { CSRF csrf.CSRF Implicit *testImplicit Path string PostValues url.Values ExpectStatusCode int ExpectRedirect string ExpectContains []string ExpectThen string }{ "display confirm form": { CSRF: &csrf.FakeCSRF{Token: "test", Err: nil}, Implicit: &testImplicit{Success: true, User: &user.DefaultInfo{Name: "user"}}, Path: "/login", ExpectContains: []string{ `action="/login"`, `You are now logged in as`, }, }, "successful POST redirects": { CSRF: &csrf.FakeCSRF{Token: "test", Err: nil}, Implicit: &testImplicit{Success: true, User: &user.DefaultInfo{Name: "user"}}, Path: "/login?then=%2Ffoo", PostValues: url.Values{"csrf": []string{"test"}}, ExpectThen: "/foo", }, "redirect when POST fails CSRF": { CSRF: &csrf.FakeCSRF{Token: "test", Err: nil}, Implicit: &testImplicit{Success: true, User: &user.DefaultInfo{Name: "user"}}, Path: "/login", PostValues: url.Values{"csrf": []string{"wrong"}}, ExpectRedirect: "/login?reason=token+expired", }, "redirect when not authenticated": { CSRF: &csrf.FakeCSRF{Token: "test", Err: nil}, Implicit: &testImplicit{Success: false}, Path: "/login", PostValues: url.Values{"csrf": []string{"test"}}, ExpectRedirect: "/login?reason=access+denied", }, "redirect on auth failure": { CSRF: &csrf.FakeCSRF{Token: "test", Err: nil}, Implicit: &testImplicit{Err: errors.New("failed")}, Path: "/login", PostValues: url.Values{"csrf": []string{"test"}}, ExpectRedirect: "/login?reason=access+denied", }, "expect GET error": { CSRF: &csrf.FakeCSRF{Token: "test", Err: nil}, Implicit: &testImplicit{Err: errors.New("failed")}, ExpectContains: []string{`"message">An unknown error has occurred. Contact your administrator`}, }, } for k, testCase := range testCases { server := httptest.NewServer(NewConfirm(testCase.CSRF, testCase.Implicit, DefaultConfirmFormRenderer)) var resp *http.Response if testCase.PostValues != nil { r, err := postForm(server.URL+testCase.Path, testCase.PostValues) if err != nil { t.Errorf("%s: unexpected error: %v", k, err) continue } resp = r } else { r, err := getURL(server.URL + testCase.Path) if err != nil { t.Errorf("%s: unexpected error: %v", k, err) continue } resp = r } defer resp.Body.Close() if testCase.ExpectStatusCode != 0 && testCase.ExpectStatusCode != resp.StatusCode { t.Errorf("%s: unexpected response: %#v", k, resp) continue } if testCase.ExpectRedirect != "" { uri, err := resp.Location() if err != nil { t.Errorf("%s: unexpected error: %v", testCase.ExpectRedirect, err) continue } if uri.String() != server.URL+testCase.ExpectRedirect { t.Errorf("%s: unexpected redirect: %s", testCase.ExpectRedirect, uri.String()) } } if testCase.ExpectThen != "" && (!testCase.Implicit.Called || testCase.Implicit.Then != testCase.ExpectThen) { t.Errorf("%s: did not find expected 'then' value: %#v", k, testCase.Implicit) } if len(testCase.ExpectContains) > 0 { data, _ := ioutil.ReadAll(resp.Body) body := string(data) for i := range testCase.ExpectContains { if !strings.Contains(body, testCase.ExpectContains[i]) { t.Errorf("%s: did not find expected value %s: %s", k, testCase.ExpectContains[i], body) continue } } } } }
// SendRequest sends a HTTP request and returns a Response as defined by etcd func (c *Client) SendRequest(rr *RawRequest) (*RawResponse, error) { var req *http.Request var resp *http.Response var httpPath string var err error var respBody []byte reqs := make([]http.Request, 0) resps := make([]http.Response, 0) checkRetry := c.CheckRetry if checkRetry == nil { checkRetry = DefaultCheckRetry } cancelled := make(chan bool, 1) reqLock := new(sync.Mutex) if rr.Cancel != nil { cancelRoutine := make(chan bool) defer close(cancelRoutine) go func() { select { case <-rr.Cancel: cancelled <- true logger.Debug("send.request is cancelled") case <-cancelRoutine: return } // Repeat canceling request until this thread is stopped // because we have no idea about whether it succeeds. for { reqLock.Lock() c.httpClient.Transport.(*http.Transport).CancelRequest(req) reqLock.Unlock() select { case <-time.After(100 * time.Millisecond): case <-cancelRoutine: return } } }() } // if we connect to a follower, we will retry until we find a leader for attempt := 0; ; attempt++ { select { case <-cancelled: return nil, ErrRequestCancelled default: } logger.Debug("begin attempt", attempt, "for", rr.RelativePath) if rr.Method == "GET" && c.config.Consistency == WEAK_CONSISTENCY { // If it's a GET and consistency level is set to WEAK, // then use a random machine. httpPath = c.getHttpPath(true, rr.RelativePath) } else { // Else use the leader. httpPath = c.getHttpPath(false, rr.RelativePath) } // Return a cURL command if curlChan is set if c.cURLch != nil { command := fmt.Sprintf("curl -X %s %s", rr.Method, httpPath) for key, value := range rr.Values { command += fmt.Sprintf(" -d %s=%s", key, value[0]) } c.sendCURL(command) } logger.Debug("send.request.to ", httpPath, " | method ", rr.Method) reqLock.Lock() if rr.Values == nil { req, err = http.NewRequest(rr.Method, httpPath, nil) } else { req, err = http.NewRequest(rr.Method, httpPath, strings.NewReader(rr.Values.Encode())) req.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value") } reqLock.Unlock() if err != nil { return nil, err } reqs = append(reqs, *req) respchan := make(chan *http.Response, 1) errchan := make(chan error, 1) responded := make(chan bool) go func() { resp, err := c.httpClient.Do(req) close(responded) if resp != nil { respchan <- resp } else if err != nil { errchan <- err } else { errchan <- errors.New("http client responded with nil response and nil error") } }() if rr.Cancel == nil { go func() { select { case <-time.After(c.config.ResponseTimeout): reqLock.Lock() c.httpClient.Transport.(*http.Transport).CancelRequest(req) reqLock.Unlock() errchan <- errors.New("Timed out waiting for response") case <-responded: return } }() } // If the request was cancelled, return ErrRequestCancelled directly select { case resp = <-respchan: case <-cancelled: return nil, ErrRequestCancelled case err = <-errchan: logger.Debug("network error:", err.Error()) resps = append(resps, http.Response{}) if checkErr := checkRetry(c.cluster, reqs, resps, err); checkErr != nil { return nil, checkErr } c.cluster.switchLeader(attempt % len(c.cluster.Machines)) continue } // if there is no error, it should receive response resps = append(resps, *resp) defer resp.Body.Close() logger.Debug("recv.response.from", httpPath) if validHttpStatusCode[resp.StatusCode] { // try to read byte code and break the loop respBody, err = ioutil.ReadAll(resp.Body) if err == nil { logger.Debug("recv.success.", httpPath) break } } // if resp is TemporaryRedirect, set the new leader and retry if resp.StatusCode == http.StatusTemporaryRedirect { u, err := resp.Location() if err != nil { logger.Warning(err) } else { // Update cluster leader based on redirect location // because it should point to the leader address c.cluster.updateLeaderFromURL(u) logger.Debug("recv.response.relocate", u.String()) } resp.Body.Close() continue } if checkErr := checkRetry(c.cluster, reqs, resps, errors.New("Unexpected HTTP status code")); checkErr != nil { return nil, checkErr } resp.Body.Close() } r := &RawResponse{ StatusCode: resp.StatusCode, Body: respBody, Header: resp.Header, } return r, nil }
func (c *Client) SendRequest(rr *RawRequest) (*RawResponse, error) { var req *http.Request var resp *http.Response var httpPath string var err error var respBody []byte var numReqs = 1 checkRetry := c.CheckRetry if checkRetry == nil { checkRetry = DefaultCheckRetry } cancelled := make(chan bool, 1) reqLock := new(sync.Mutex) if rr.cancel != nil { cancelRoutine := make(chan bool) defer close(cancelRoutine) go func() { select { case <-rr.cancel: cancelled <- true logger.Debug("send.request is cancelled") case <-cancelRoutine: return } // Repeat canceling request until this thread is stopped // because we have no idea about whether it succeeds. for { reqLock.Lock() c.httpClient.Transport.(*http.Transport).CancelRequest(req) reqLock.Unlock() select { case <-time.After(100 * time.Millisecond): case <-cancelRoutine: return } } }() } // If we connect to a follower and consistency is required, retry until // we connect to a leader sleep := 25 * time.Millisecond maxSleep := time.Second for attempt := 0; ; attempt++ { if attempt > 0 { select { case <-cancelled: return nil, ErrRequestCancelled case <-time.After(sleep): sleep = sleep * 2 if sleep > maxSleep { sleep = maxSleep } } } logger.Debug("Connecting to eureka: attempt %d for %s", attempt+1, rr.relativePath) httpPath = c.getHttpPath(false, rr.relativePath) // logger.Debug("send.request.to %s | method %s ", httpPath, rr.method) req, err := func() (*http.Request, error) { reqLock.Lock() defer reqLock.Unlock() if req, err = http.NewRequest(rr.method, httpPath, bytes.NewReader(rr.body)); err != nil { return nil, err } req.Header.Set("Content-Type", "application/json") return req, nil }() if err != nil { return nil, err } resp, err = c.httpClient.Do(req) defer func() { if resp != nil { resp.Body.Close() } }() // If the request was cancelled, return ErrRequestCancelled directly select { case <-cancelled: return nil, ErrRequestCancelled default: } numReqs++ // network error, change a machine! if err != nil { logger.Error("network error: %v", err.Error()) lastResp := http.Response{} if checkErr := checkRetry(c.Cluster, numReqs, lastResp, err); checkErr != nil { return nil, checkErr } c.Cluster.switchLeader(attempt % len(c.Cluster.Machines)) continue } // if there is no error, it should receive response logger.Debug("recv.response.from " + httpPath) if validHttpStatusCode[resp.StatusCode] { // try to read byte code and break the loop respBody, err = ioutil.ReadAll(resp.Body) if err == nil { logger.Debug("recv.success " + httpPath + " " + resp.Status) break } // ReadAll error may be caused due to cancel request select { case <-cancelled: return nil, ErrRequestCancelled default: } if err == io.ErrUnexpectedEOF { // underlying connection was closed prematurely, probably by timeout // TODO: empty body or unexpectedEOF can cause http.Transport to get hosed; // this allows the client to detect that and take evasive action. Need // to revisit once code.google.com/p/go/issues/detail?id=8648 gets fixed. respBody = []byte{} break } } // if resp is TemporaryRedirect, set the new leader and retry if resp.StatusCode == http.StatusTemporaryRedirect { u, err := resp.Location() if err != nil { logger.Warning("%v", err) } else { // Update cluster leader based on redirect location // because it should point to the leader address c.Cluster.updateLeaderFromURL(u) logger.Debug("recv.response.relocate " + u.String()) } resp.Body.Close() continue } if checkErr := checkRetry(c.Cluster, numReqs, *resp, errors.New("Unexpected HTTP status code")); checkErr != nil { return nil, checkErr } resp.Body.Close() } r := &RawResponse{ StatusCode: resp.StatusCode, Body: respBody, Header: resp.Header, } return r, nil }
func TestGrant(t *testing.T) { testCases := map[string]struct { CSRF csrf.CSRF Auth *testAuth ClientRegistry *test.ClientRegistry AuthRegistry *test.ClientAuthorizationRegistry Path string PostValues url.Values ExpectStatusCode int ExpectCreatedAuthScopes []string ExpectUpdatedAuthScopes []string ExpectRedirect string ExpectContains []string ExpectThen string }{ "display form": { CSRF: &csrf.FakeCSRF{Token: "test"}, Auth: goodAuth("username"), ClientRegistry: goodClientRegistry("myclient", []string{"myredirect"}), AuthRegistry: emptyAuthRegistry(), Path: "/grant?client_id=myclient&scopes=myscope1%20myscope2&redirect_uri=/myredirect&then=/authorize", ExpectStatusCode: 200, ExpectContains: []string{ `action="/grant"`, `name="csrf" value="test"`, `name="client_id" value="myclient"`, `name="scopes" value="myscope1 myscope2"`, `name="redirect_uri" value="/myredirect"`, `name="then" value="/authorize"`, }, }, "Unauthenticated with redirect": { CSRF: &csrf.FakeCSRF{Token: "test"}, Auth: badAuth(nil), Path: "/grant?then=/authorize", ExpectStatusCode: 302, ExpectRedirect: "/authorize", }, "Unauthenticated without redirect": { CSRF: &csrf.FakeCSRF{Token: "test"}, Auth: badAuth(nil), Path: "/grant", ExpectStatusCode: 200, ExpectContains: []string{"reauthenticate"}, }, "Auth error with redirect": { CSRF: &csrf.FakeCSRF{Token: "test"}, Auth: badAuth(errors.New("Auth error")), Path: "/grant?then=/authorize", ExpectStatusCode: 302, ExpectRedirect: "/authorize", }, "Auth error without redirect": { CSRF: &csrf.FakeCSRF{Token: "test"}, Auth: badAuth(errors.New("Auth error")), Path: "/grant", ExpectStatusCode: 200, ExpectContains: []string{"reauthenticate"}, }, "error when POST fails CSRF": { CSRF: &csrf.FakeCSRF{Token: "test"}, Auth: goodAuth("username"), ClientRegistry: goodClientRegistry("myclient", []string{"myredirect"}), AuthRegistry: emptyAuthRegistry(), Path: "/grant", PostValues: url.Values{ "client_id": {"myclient"}, "scopes": {"myscope1 myscope2"}, "redirect_uri": {"/myredirect"}, "then": {"/authorize"}, "csrf": {"wrong"}, }, ExpectStatusCode: 200, ExpectContains: []string{"CSRF"}, }, "error displaying form with invalid client": { CSRF: &csrf.FakeCSRF{Token: "test"}, Auth: goodAuth("username"), ClientRegistry: badClientRegistry(nil), AuthRegistry: emptyAuthRegistry(), Path: "/grant", ExpectStatusCode: 200, ExpectContains: []string{"find client"}, }, "error submitting form with invalid client": { CSRF: &csrf.FakeCSRF{Token: "test"}, Auth: goodAuth("username"), ClientRegistry: badClientRegistry(nil), AuthRegistry: emptyAuthRegistry(), Path: "/grant", PostValues: url.Values{ "approve": {"true"}, "client_id": {"myclient"}, "scopes": {"myscope1 myscope2"}, "redirect_uri": {"/myredirect"}, "then": {"/authorize"}, "csrf": {"test"}, }, ExpectStatusCode: 200, ExpectContains: []string{"find client"}, }, "successful create grant with redirect": { CSRF: &csrf.FakeCSRF{Token: "test"}, Auth: goodAuth("username"), ClientRegistry: goodClientRegistry("myclient", []string{"myredirect"}), AuthRegistry: emptyAuthRegistry(), Path: "/grant", PostValues: url.Values{ "approve": {"true"}, "client_id": {"myclient"}, "scopes": {"myscope1 myscope2"}, "redirect_uri": {"/myredirect"}, "then": {"/authorize"}, "csrf": {"test"}, }, ExpectStatusCode: 302, ExpectCreatedAuthScopes: []string{"myscope1", "myscope2"}, ExpectRedirect: "/authorize", }, "successful create grant without redirect": { CSRF: &csrf.FakeCSRF{Token: "test"}, Auth: goodAuth("username"), ClientRegistry: goodClientRegistry("myclient", []string{"myredirect"}), AuthRegistry: emptyAuthRegistry(), Path: "/grant", PostValues: url.Values{ "approve": {"true"}, "client_id": {"myclient"}, "scopes": {"myscope1 myscope2"}, "redirect_uri": {"/myredirect"}, "csrf": {"test"}, }, ExpectStatusCode: 200, ExpectCreatedAuthScopes: []string{"myscope1", "myscope2"}, ExpectContains: []string{ "granted", "no redirect", }, }, "successful update grant with identical scopes": { CSRF: &csrf.FakeCSRF{Token: "test"}, Auth: goodAuth("username"), ClientRegistry: goodClientRegistry("myclient", []string{"myredirect"}), AuthRegistry: existingAuthRegistry([]string{"myscope2", "myscope1"}), Path: "/grant", PostValues: url.Values{ "approve": {"true"}, "client_id": {"myclient"}, "scopes": {"myscope1 myscope2"}, "redirect_uri": {"/myredirect"}, "then": {"/authorize"}, "csrf": {"test"}, }, ExpectStatusCode: 302, ExpectUpdatedAuthScopes: []string{"myscope1", "myscope2"}, ExpectRedirect: "/authorize", }, "successful update grant with additional scopes": { CSRF: &csrf.FakeCSRF{Token: "test"}, Auth: goodAuth("username"), ClientRegistry: goodClientRegistry("myclient", []string{"myredirect"}), AuthRegistry: existingAuthRegistry([]string{"existingscope2", "existingscope1"}), Path: "/grant", PostValues: url.Values{ "approve": {"true"}, "client_id": {"myclient"}, "scopes": {"newscope1 existingscope1"}, "redirect_uri": {"/myredirect"}, "then": {"/authorize"}, "csrf": {"test"}, }, ExpectStatusCode: 302, ExpectUpdatedAuthScopes: []string{"existingscope1", "existingscope2", "newscope1"}, ExpectRedirect: "/authorize", }, "successful reject grant": { CSRF: &csrf.FakeCSRF{Token: "test"}, Auth: goodAuth("username"), ClientRegistry: goodClientRegistry("myclient", []string{"myredirect"}), AuthRegistry: existingAuthRegistry([]string{"existingscope2", "existingscope1"}), Path: "/grant", PostValues: url.Values{ "deny": {"true"}, "client_id": {"myclient"}, "scopes": {"newscope1 existingscope1"}, "redirect_uri": {"/myredirect"}, "then": {"/authorize"}, "csrf": {"test"}, }, ExpectStatusCode: 302, ExpectRedirect: "/authorize?error=access_denied", }, } for k, testCase := range testCases { server := httptest.NewServer(NewGrant(testCase.CSRF, testCase.Auth, DefaultFormRenderer, testCase.ClientRegistry, testCase.AuthRegistry)) var resp *http.Response if testCase.PostValues != nil { r, err := postForm(server.URL+testCase.Path, testCase.PostValues) if err != nil { t.Errorf("%s: unexpected error: %v", k, err) continue } resp = r } else { r, err := getURL(server.URL + testCase.Path) if err != nil { t.Errorf("%s: unexpected error: %v", k, err) continue } resp = r } defer resp.Body.Close() if testCase.ExpectStatusCode != 0 && testCase.ExpectStatusCode != resp.StatusCode { t.Errorf("%s: unexpected response: %#v", k, resp) continue } if len(testCase.ExpectCreatedAuthScopes) > 0 { auth := testCase.AuthRegistry.CreatedAuthorization if auth == nil { t.Errorf("%s: expected created auth, got nil", k) continue } if !reflect.DeepEqual(testCase.ExpectCreatedAuthScopes, auth.Scopes) { t.Errorf("%s: expected created scopes %v, got %v", k, testCase.ExpectCreatedAuthScopes, auth.Scopes) } } if len(testCase.ExpectUpdatedAuthScopes) > 0 { auth := testCase.AuthRegistry.UpdatedAuthorization if auth == nil { t.Errorf("%s: expected updated auth, got nil", k) continue } if !reflect.DeepEqual(testCase.ExpectUpdatedAuthScopes, auth.Scopes) { t.Errorf("%s: expected updated scopes %v, got %v", k, testCase.ExpectUpdatedAuthScopes, auth.Scopes) } } if testCase.ExpectRedirect != "" { uri, err := resp.Location() if err != nil { t.Errorf("%s: unexpected error: %v", k, err) continue } if uri.String() != server.URL+testCase.ExpectRedirect { t.Errorf("%s: unexpected redirect: %s", k, uri.String()) } } if len(testCase.ExpectContains) > 0 { data, _ := ioutil.ReadAll(resp.Body) body := string(data) for i := range testCase.ExpectContains { if !strings.Contains(body, testCase.ExpectContains[i]) { t.Errorf("%s: did not find expected value %s: %s", k, testCase.ExpectContains[i], body) continue } } } } }
// SendRequest sends a HTTP request and returns a Response as defined by etcd func (c *Client) SendRequest(rr *RawRequest, json string) (*RawResponse, error) { var req *http.Request var resp *http.Response var httpPath string var err error var respBody []byte var numReqs = 1 log.Info("------------entry------") checkRetry := c.CheckRetry if checkRetry == nil { checkRetry = DefaultCheckRetry } cancelled := make(chan bool, 1) reqLock := new(sync.Mutex) if rr.Cancel != nil { cancelRoutine := make(chan bool) defer close(cancelRoutine) go func() { select { case <-rr.Cancel: cancelled <- true fmt.Println("send.request is cancelled") case <-cancelRoutine: return } // Repeat canceling request until this thread is stopped // because we have no idea about whether it succeeds. for { reqLock.Lock() c.httpClient.Transport.(*http.Transport).CancelRequest(req) reqLock.Unlock() select { case <-time.After(100 * time.Millisecond): case <-cancelRoutine: return } } }() } log.Info("Connecting to geard deamon: attempt") httpPath = rr.Url // Return a cURL command if curlChan is set c.OpenCURL() if c.cURLch != nil { command := fmt.Sprintf("curl") if rr.Method != "" { command += fmt.Sprintf(" -X %s", rr.Method) } if rr.Url != "" { command += fmt.Sprintf(" %s", rr.Url) } if json != "" { command += fmt.Sprintf(" -d '%s' ", json) } log.Info(command) c.sendCURL(command) } reqLock.Lock() if json == "" { if req, err = http.NewRequest(rr.Method, httpPath, nil); err != nil { return nil, err } } else { body := strings.NewReader(json) if req, err = http.NewRequest(rr.Method, httpPath, body); err != nil { return nil, err } req.Header.Set("Content-Type", rr.ContentType) } reqLock.Unlock() resp, err = c.httpClient.Do(req) defer func() { if resp != nil { resp.Body.Close() } }() // If the request was cancelled, return ErrRequestCancelled directly select { case <-cancelled: return nil, ErrRequestCancelled default: } numReqs++ // network error, change a machine! if err != nil { log.Error("network error:", err.Error()) lastResp := http.Response{} if checkErr := checkRetry(numReqs, lastResp, err); checkErr != nil { return nil, checkErr } //c.cluster.switchLeader(attempt % len(c.cluster.Machines)) //continue } // if there is no error, it should receive response log.Error("recv.response.from", httpPath) log.Info(resp) if validHttpStatusCode[resp.StatusCode] { // try to read byte code and break the loop log.Info("--------if entry----------") log.Info(resp.StatusCode) respBody, err = ioutil.ReadAll(resp.Body) if err == nil { log.Error("recv.success.", httpPath) //break } // ReadAll error may be caused due to cancel request select { case <-cancelled: return nil, ErrRequestCancelled default: } } // if resp is TemporaryRedirect, set the new leader and retry if resp.StatusCode == http.StatusTemporaryRedirect { u, err := resp.Location() if err != nil { log.Error(err) } else { // Update cluster leader based on redirect location // because it should point to the leader address //c.cluster.updateLeaderFromURL(u) log.Error("recv.response.relocate", u.String()) } resp.Body.Close() //continue } if checkErr := checkRetry(numReqs, *resp, errors.New("Unexpected HTTP status code")); checkErr != nil { return nil, checkErr } resp.Body.Close() //} r := &RawResponse{ StatusCode: resp.StatusCode, Body: respBody, Header: resp.Header, } return r, nil }