Go equivalent of Python's requests.Session for making many requests with the same basic authentication?
Asked Answered
M

0

6

Consider this example for making an HTTP request in Go with basic authentication:

package main

import (
    "encoding/base64"
    "fmt"
    "io/ioutil"
    "net/http"
    "net/http/httptest"
    "strings"
)

var userName = "myUserName"
var password = "myPassword"

func main() {
    ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        if !checkAuth(w, r) {
            http.Error(w, "You're not authorized!", http.StatusUnauthorized)
            return
        }
        w.Write([]byte("You're authorized!"))
    }))
    defer ts.Close()

    req, err := http.NewRequest("GET", ts.URL, nil)
    check(err)

    req.SetBasicAuth(userName, password+"foo")

    resp, err := http.DefaultClient.Do(req)
    check(err)
    defer resp.Body.Close()

    body, err := ioutil.ReadAll(resp.Body)
    check(err)

    fmt.Println(string(body))
}

// checkAuth checks authentication (cf. https://mcmap.net/q/353007/-idiomatic-way-of-requiring-http-basic-auth-in-go/21937924#21937924)
func checkAuth(w http.ResponseWriter, r *http.Request) bool {
    s := strings.SplitN(r.Header.Get("Authorization"), " ", 2)
    if len(s) != 2 {
        return false
    }

    b, err := base64.StdEncoding.DecodeString(s[1])
    if err != nil {
        return false
    }

    pair := strings.SplitN(string(b), ":", 2)
    if len(pair) != 2 {
        return false
    }

    return pair[0] == userName && pair[1] == password
}

func check(err error) {
    if err != nil {
        panic(err)
    }
}

Note that SetBasicAuth is a method of an *http.Request, so if I want to make many requests, I would have to call this method on each request.

In Python, you can define a requests.Session like in this example (from https://requests.readthedocs.io/en/master/user/advanced/#session-objects):

s = requests.Session()
s.auth = ('user', 'pass')
s.headers.update({'x-test': 'true'})

# both 'x-test' and 'x-test2' are sent
s.get('https://httpbin.org/headers', headers={'x-test2': 'true'})

Is there an idiomatic way of defining the equivalent of a requests.Session in Go (preferably using the standard library)? All I can think of is defining a custom client struct with its own Do() method:

type MyClient struct {
    UserName, Password string
}

func (client *MyClient) Do(req *http.Request) (*http.Response, error) {
    req.SetBasicAuth(client.UserName, client.Password)
    return http.DefaultClient.Do(req)
}

and invoking it in the above script like

client := MyClient{UserName: userName, Password: password}

resp, err := client.Do(req)

Would this be an idiomatic way to avoid multiple calls to SetBasicAuth()?

Microscopium answered 10/1, 2020 at 0:27 Comment(2)
Can't say if it would be idiomatic, but it works. I have the exact same scenario, with a similar solution but using a headers map instead because I pass content type, bearer tokens, etc. and the thing is called a Session instead of Client.Cool
Now you're thinking with interfaces!Matthaus

© 2022 - 2024 — McMap. All rights reserved.