web-dev-qa-db-fra.com

Comment puis-je faire en sorte que le client Go Go ne suive pas automatiquement les redirections?

J'écris actuellement des logiciels dans Go qui interagissent avec une API REST. Le point de terminaison API REST que j'essaie d'interroger renvoie une redirection HTTP 302 le long de avec un en-tête HTTP Location, pointant vers un URI de ressource.

J'essaie d'utiliser mon script Go pour récupérer l'en-tête Emplacement HTTP pour un traitement ultérieur.

Voici ce que je fais actuellement pour atteindre cette fonctionnalité actuellement:

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

}

Cela me semble un peu un hack. En remplaçant la fonction CheckRedirect de http.Client, Je suis essentiellement obligé de traiter les redirections HTTP comme des erreurs (ce qui n'est pas le cas).

J'ai vu plusieurs autres endroits suggérant d'utiliser un transport HTTP au lieu d'un client HTTP - mais je ne sais pas comment faire fonctionner cela car j'ai besoin du client HTTP car j'ai besoin d'utiliser HTTP Basic Auth pour communiquer avec ceci = REST API.

L'un de vous peut-il me dire un moyen de faire des requêtes HTTP avec l'authentification de base - sans suivre les redirections - qui n'implique pas de lancer des erreurs et de gérer les erreurs?

Merci.

44
rdegges

Il existe actuellement une solution beaucoup plus simple:

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

De cette façon, le package http sait automatiquement: "Ah, je ne dois suivre aucune redirection", mais ne renvoie aucune erreur. Du commentaire dans le code source:

Dans un cas particulier, si CheckRedirect renvoie ErrUseLastResponse, la réponse la plus récente est retournée avec son corps non fermé, avec une erreur nulle.

93
Etienne Bruines

Une autre option, en utilisant le client lui-même, sans 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   
}

PDATE: cette solution est pour go <1.7

11
uvgroovy

C'est possible, mais la solution inverse un peu le problème. Voici un exemple écrit comme un test de 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
}

Vous devriez pouvoir mettre le code ci-dessus dans son propre package appelé "redirections" et l'exécuter après avoir récupéré les dépendances requises à l'aide de

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

J'espère que cela t'aides!

Pour faire une demande avec Basic Auth qui ne suit pas la redirection, utilisez RoundTrip fonction qui accepte * Request

Ce code

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

les sorties

{
  "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"
  }
}
4
Alex Dvoretsky