2014-04-25 6 views
29

Attualmente sto scrivendo del software in Go che interagisce con un'API REST. L'endpoint dell'API REST che sto tentando di eseguire query restituisce un reindirizzamento HTTP 302 insieme a un'intestazione Location HTTP, che punta a un URI di risorsa.Come posso rendere il client HTTP Go NON seguire i reindirizzamenti automaticamente?

Sto provando a utilizzare il mio script Go per acquisire l'intestazione della posizione HTTP per l'elaborazione successiva.

Ecco quello che sto facendo attualmente per ottenere questa funzionalità attualmente:

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() 

} 

Questo si sente come un po 'di hack per me. Sovrascrivendo la funzione CheckRedirect di , sono essenzialmente obbligato a trattare i reindirizzamenti HTTP come errori (che non sono).

Ho visto molti altri posti che suggeriscono di usare un trasporto HTTP invece di un client HTTP - ma non sono sicuro di come farlo funzionare poiché ho bisogno del client HTTP perché ho bisogno di usare l'autenticazione di base HTTP per comunicare con questa API REST.

Qualcuno di voi può dirmi un modo per effettuare richieste HTTP con l'autenticazione di base - pur non seguendo i reindirizzamenti - che non comporta errori di lancio e gestione degli errori?

Grazie.

+0

Guardando la [fonte] (http://golang.org/src/pkg/net/http/client.go) non sembra. L'intestazione 'Location' viene richiamata * dopo * la chiamata' CheckRedirect' e non si ha accesso alla risposta provvisoria. –

+0

Credo che tu abbia ragione @DmitriGoldring - facendomi impazzire. Deve esserci un modo per farlo funzionare - Non posso immaginare che non ci sia un buon modo per farlo>< – rdegges

risposta

55

C'è una soluzione molto più semplice in questo momento:

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

In questo modo, il pacchetto http conosce automaticamente: "Ah, ho shouldn' t seguire eventuali reindirizzamenti ", ma non genera alcun errore. Dal commento nel codice sorgente:

Come caso speciale, se CheckRedirect torna ErrUseLastResponse, quindi la risposta più recente viene restituita con il suo corpo non chiusa, insieme con un errore pari a zero.

+1

Questo è fantastico! = D – rdegges

+5

Sembra che questo sia per Go 1.7 (ancora in RC al momento) –

+0

Già stato svalutato qualche tempo fa, ma sono di nuovo qui. Grazie! –

6

È possibile, ma la soluzione inverte leggermente il problema. Ecco un esempio scritto come un 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 
} 

Si dovrebbe essere in grado di mettere il codice sopra in essa la propria pacchetto chiamato "redirect" ed eseguirlo dopo il recupero delle dipendenze richieste usando

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 

Spero che questo aiuti!

+0

Il controllo del codice di stato è troppo severo. Invece di "se risp.StatusCode! = 200 && risp.StatusCode! = 302', dovresti testare' if resp.StatusCode> = 400' perché ci sono altri comuni che dovrebbero essere consentiti, ad es. 204, 303, 307. –

+1

Hai assolutamente ragione. Grazie per la segnalazione! Tuttavia, preferirei lasciare questa decisione al programmatore poiché conoscono meglio il comportamento previsto. Ho aggiunto un commento a questo effetto nel codice. –

+0

Hmm, forse. Questo è buono purché sappiano la differenza tra, diciamo 302 e 303. Molti non lo fanno. –

4

Per fare richiesta con Auth di base che non segue reindirizzare uso RoundTrip funzione che accetta * Request

questo codice

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)) 
} 

uscite

{ 
    "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

un'altra opzione, utilizzando il client iteself , senza 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: questa soluzione è per andare < 1,7

Problemi correlati