2014-04-25 12 views
29

Obecnie piszę niektóre oprogramowanie w Go, które współdziała z interfejsem API REST. Punkt końcowy interfejsu REST API, który próbuję wysłać, zwraca przekierowanie HTTP 302 wraz z nagłówkiem lokalizacji HTTP, wskazując na identyfikator URI zasobu.Jak można sprawić, aby klient HTTP NIE wykonał automatycznych przekierowań?

Próbuję użyć mojego skryptu Go, aby pobrać nagłówek Location HTTP do późniejszego przetworzenia.

Oto co mam aktualnie robi, aby osiągnąć tę funkcjonalność obecnie:

package main 

import (
     "errors" 
     "fmt" 
     "io/ioutil" 
     "net/http" 
) 

var BASE_URL = "https://api.stormpath.com/v1" 
var STORMPATH_API_KEY_ID = "xxx" 
var STORMPATH_API_KEY_SECRET = "xxx" 

func noRedirect(req *http.Request, via []*http.Request) error { 
     return errors.New("Don't redirect!") 
} 

func main() { 

     client := &http.Client{ 
      CheckRedirect: noRedirect 
     } 
     req, err := http.NewRequest("GET", BASE_URL+"/tenants/current", nil) 
     req.SetBasicAuth(STORMPATH_API_KEY_ID, STORMPATH_API_KEY_SECRET) 

     resp, err := client.Do(req) 

     // If we get here, it means one of two things: either this http request 
     // actually failed, or we got an http redirect response, and should process it. 
     if err != nil { 
      if resp.StatusCode == 302 { 
       fmt.Println("got redirect") 
      } else { 
       panic("HTTP request failed.") 
      } 
     } 
     defer resp.Body.Close() 

} 

to czuje się trochę hack do mnie. Przesłaniając funkcję http.ClientCheckRedirect, jestem zasadniczo zmuszony do traktowania przekierowań HTTP, takich jak błędy (których nie są).

Widziałem kilka innych miejsc sugerujących użycie transportu HTTP zamiast klienta HTTP - ale nie jestem pewien, jak to zrobić, ponieważ potrzebuję klienta HTTP, ponieważ potrzebuję używać HTTP Basic Auth do komunikować się z tym interfejsem API REST.

Czy ktokolwiek może mi powiedzieć, w jaki sposób wysyłać żądania HTTP z podstawowym uwierzytelnianiem - bez wykonywania przekierowań - który nie wiąże się z wyrzucaniem błędów i obsługą błędów?

Dziękuję.

+0

Patrząc na [źródło] (http://golang.org/src/pkg/net/http/client.go) nie wygląda na to. Nagłówek 'Location' jest wyciągnięty * po * wywołaniu' CheckRedirect' i nie masz dostępu do odpowiedzi tymczasowej. –

+0

Wierzę, że masz rację: @DmitriGoldring - doprowadzając mnie do szału. MUSI być sposobem, aby to osiągnąć - nie wyobrażam sobie, że nie jest to dobry sposób>< – rdegges

Odpowiedz

55

Jest to znacznie prostsze rozwiązanie teraz:

client: &http.Client{ 
    CheckRedirect: func(req *http.Request, via []*http.Request) error { 
     return http.ErrUseLastResponse 
    }, 
} 

ten sposób pakiet http automatycznie wie: „Ach, ja powinnam” • podążaj za wszelkimi przekierowaniami ", ale nie wyrzuca żadnego błędu. Z komentarzem w kodzie źródłowym:

jako szczególny przypadek, jeśli CheckRedirect powraca ErrUseLastResponse, następnie najnowsza odpowiedź jest zwracany z jego ciała unclosed wraz z zerowym błędem.

+1

To jest fantastyczne! = D – rdegges

+5

Wygląda na to, że jest to wersja 1.7 (nadal w wersji RC). –

+0

Już wcześniej wznowiono emisję, ale jestem tu z powrotem. Dzięki! –

6

Jest to możliwe, ale rozwiązanie nieco odwraca problem. Oto próbka napisana jako test golang.

package redirects 

import (
    "github.com/codegangsta/martini-contrib/auth" 
    "github.com/go-martini/martini" 
    "net/http" 
    "net/http/httptest" 
    "testing" 
) 

func TestBasicAuthRedirect(t *testing.T) { 
    // Start a test server 
    server := setupBasicAuthServer() 
    defer server.Close() 

    // Set up the HTTP request 
    req, err := http.NewRequest("GET", server.URL+"/redirect", nil) 
    req.SetBasicAuth("username", "password") 
    if err != nil { 
     t.Fatal(err) 
    } 

    transport := http.Transport{} 
    resp, err := transport.RoundTrip(req) 
    if err != nil { 
     t.Fatal(err) 
    } 
    // Check if you received the status codes you expect. There may 
    // status codes other than 200 which are acceptable. 
    if resp.StatusCode != 200 && resp.StatusCode != 302 { 
     t.Fatal("Failed with status", resp.Status) 
    } 

    t.Log(resp.Header.Get("Location")) 
} 


// Create an HTTP server that protects a URL using Basic Auth 
func setupBasicAuthServer() *httptest.Server { 
    m := martini.Classic() 
    m.Use(auth.Basic("username", "password")) 
    m.Get("/ping", func() string { return "pong" }) 
    m.Get("/redirect", func(w http.ResponseWriter, r *http.Request) { 
     http.Redirect(w, r, "/ping", 302) 
    }) 
    server := httptest.NewServer(m) 
    return server 
} 

Powinieneś móc umieścić powyższy kod na swój własny pakiet o nazwie „przekierowania” i uruchomić go po pobraniu wymaganych zależności używając

mkdir redirects 
cd redirects 
# Add the above code to a file with an _test.go suffix 
go get github.com/codegangsta/martini-contrib/auth 
go get github.com/go-martini/martini 
go test -v 

nadzieję, że to pomaga!

+0

Kontrola kodu statusu jest zbyt rygorystyczna. Zamiast "if resp.StatusCode! = 200 && resp.StatusCode! = 302" powinieneś przetestować 'if resp.StatusCode> = 400', ponieważ istnieją inne typowe, które powinny być dozwolone, np. 204, 303, 307. –

+1

Masz absolutną rację. Dzięki za wskazanie tego! Wolę jednak pozostawić tę decyzję programistom, ponieważ lepiej znają oczekiwane zachowanie. Dodałem komentarz do tego efektu w kodzie. –

+0

Hmm, może. To dobrze, pod warunkiem, że znają różnicę między, powiedzmy 302 i 303. Wielu nie. –

4

Aby zamówić z podstawowymi Auth, która nie spełnia przekierować Użyj RoundTrip funkcję, która akceptuje * Request

ten kod

package main 

import (
    "fmt" 
    "io/ioutil" 
    "net/http" 
    "os" 
) 

func main() { 
    var DefaultTransport http.RoundTripper = &http.Transport{} 

    req, _ := http.NewRequest("GET", "http://httpbin.org/headers", nil) 
    req.SetBasicAuth("user", "password") 

    resp, _ := DefaultTransport.RoundTrip(req) 
    defer resp.Body.Close() 
    contents, err := ioutil.ReadAll(resp.Body) 
    if err != nil { 
     fmt.Printf("%s", err) 
     os.Exit(1) 
    } 
    fmt.Printf("%s\n", string(contents)) 
} 

wyjść

{ 
    "headers": { 
    "Accept-Encoding": "gzip", 
    "Authorization": "Basic dXNlcjpwYXNzd29yZA==", 
    "Connection": "close", 
    "Host": "httpbin.org", 
    "User-Agent": "Go 1.1 package http", 
    "X-Request-Id": "45b512f1-22e9-4e49-8acb-2f017e0a4e35" 
    } 
} 
11

Inna opcja, za pomocą klienta iteself , bez funkcji RoundTrip:

// create a custom error to know if a redirect happened 
var RedirectAttemptedError = errors.New("redirect") 

client := &http.Client{} 
// return the error, so client won't attempt redirects 
client.CheckRedirect = func(req *http.Request, via []*http.Request) error { 
     return RedirectAttemptedError 
} 
// Work with the client... 
resp, err := client.Head(urlToAccess) 

// test if we got the custom error 
if urlError, ok := err.(*url.Error); ok && urlError.Err == RedirectAttemptedError{ 
     err = nil 
} 

UPDATE: to rozwiązanie jest dla iść < 1,7

Powiązane problemy