gossltls1.2

How to fix proxyconnect tcp: tls: first record does not look like a TLS handshake


In How to consume a REST API in Go a fully working example code is provided to call a public REST API. But if I try the sample this error occurs:

error getting cat fact: 
      Get "https://catfact.ninja/fact": 
      proxyconnect tcp: tls: first record does not look like a TLS handshake

The documentation about http states

For control over proxies, TLS configuration, keep-alives, compression, and other settings, create a Transport:

and from the Transport documentation:

    // DialContext specifies the dial function 
    // for creating unencrypted TCP connections.
    // If DialContext is nil (and the deprecated Dial 
    // below is also nil), then the transport dials using 
    // package net.
    //
    // DialContext runs concurrently with calls to RoundTrip.
    // A RoundTrip call that initiates a dial may end up 
    // using a connection dialed previously when the 
    // earlier connection becomes idle before the later 
    // DialContext completes.
    DialContext func(ctx context.Context, network, addr string) (net.Conn, error)

So I assume that I have to configure Dialcontext to enable an insecure connection without TLS from my client to the proxy. But I do not know how to do this. Reading these:

did not help either. Some have the same error proxyconnect tcp: tls: first record does not look like a TLS handshake and explain the cause:

This is because the proxy answers with an plain HTTP error to the strange HTTP request (which is actually the start of the TLS handshake).

But Steffen's reply has no sample code how to set up DialContext func(ctx context.Context, network, addr string) and both Bogdan and cyberdelia suggest to set tls.Config{InsecureSkipVerify: true} for example like this

    tr := &http.Transport{
        TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
    }
    client := &http.Client{Transport: tr}

But the above has not effect. I still get the same error. And the connection is still calling https://* instead of http://*

This is the sample code and my attempt to include the above recommendations and adapt it:

var tr = &http.Transport{ TLSClientConfig: 
                           &tls.Config{InsecureSkipVerify: true}, } 
                           // lacks DialContext config
var client*  http.Client = &http.Client{Transport: tr} // modified * added
// var client *http.Client // code from tutorial

type CatFact struct {
    Fact   string `json:"fact"`
    Length int    `json:"length"`
}

func GetCatFact() {
    // changed from https to http
    url := "http://catfact.ninja/fact" 
    var catFact CatFact

    err := GetJson(url, &catFact)
    if err != nil {
        fmt.Printf("Error getting cat fact: %s\n", err.Error())
    } else {
        fmt.Printf("Super Cat Fact: %s\n", catFact.Fact)
    }
}

func main() {
    client = &http.Client{Timeout: 10 * time.Second}
    GetCatFact()
    // same error 
    // proxyconnect tcp: tls: first record does 
    //                   not look like a TLS handshake
    // still uses https 
    // for GET catfact.ninja
}

How can the connection be configured to use unencrypted connection from myClient over the proxy to the server? Will setting the DialContext func(ctx context.Context, network, addr string) help to do this? How can it be done?


Solution

  • If i remember correctly then this question was answered once. Since it is no longer the case i will add a sample to request a CatFact with a customDialContext

    // Custom dialing function to handle connections
    func customDialContext(ctx context.Context, network, addr string) 
                          (net.Conn, error) {
        conn, err := net.Dial(network, addr)
        return conn, err
    }
    

    The main function uses

    func main() {
        // Create a custom Transport with the desired settings
        tr := &http.Transport{
            Proxy:       http.ProxyFromEnvironment, 
            DialContext: customDialContext,         
            TLSClientConfig: &tls.Config{
                InsecureSkipVerify: true,
            },
        }
    
        // Create a new HTTP client using the custom Transport
        client := &http.Client{
            Transport: tr,
            Timeout:   10 * time.Second,
        }
    
        // Call the function to get a cat fact
        GetCatFact(client)
    }
    

    Helper functions

    Helper function GetCatFact()

    func GetCatFact(client *http.Client) {
        url := "https://catfact.ninja/fact" // Reverted back to https
        var catFact CatFact
    
        err := GetJson(url, &catFact, client)
        if err != nil {
            fmt.Printf("error getting cat fact: %s\n", err.Error())
        } else {
            fmt.Printf("A super interesting Cat Fact: %s\n", catFact.Fact)
        }
    }
    

    GetJson

    func GetJson(url string, target interface{}, client *http.Client) error {
        resp, err := client.Get(url)
        if err != nil {
            return fmt.Errorf("error sending GET request: %w", err)
        }
        defer resp.Body.Close()
    
        if resp.StatusCode != http.StatusOK {
            return fmt.Errorf("received non-OK HTTP status: %d", resp.StatusCode)
        }
    
        err = json.NewDecoder(resp.Body).Decode(target)
        if err != nil {
            return fmt.Errorf("error decoding JSON response: %w", err)
        }
    
        return nil
    }
    

    Json & Struct

    The json response body

    {
        "fact": "Both humans and cats have identical regions 
                 in the brain responsible for emotion.",
        "length": 81
    }
    

    and the struct

    type CatFact struct {
        Fact   string `json:"fact"`
        Length int    `json:"length"`
    }