Search code examples
gokubernetesspdy

How to read body from response with status 101 Switching Protocols


I am connected to a server, in Kubernetes cluster, with POST request and headers to upgrade the request. I am using the following function:

func PostRequest(client *http.Client, url string, bodyData []byte) (*http.Response, error){
    req, _ := http.NewRequest("POST", url, bytes.NewBuffer(bodyData))
    //req.Header.Set("Authorization", "Bearer " + BEARER_TOKEN)
    req.Header.Set("X-Stream-Protocol-Version", "v2.channel.k8s.io")
    req.Header.Set("X-Stream-Protocol-Version", "channel.k8s.io")
    req.Header.Set("Upgrade", "SPDY/3.1")
    req.Header.Set("Connection","upgrade")
    req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

    resp, err := (*client).Do(req)

    return resp, err
}

After I received the response, I tried to read it but it stuck when I read the body:

url2 := "https://<serveri_ip>:10250/exec/default/mypod/mycontainer?command=ls&command=/&input=1&output=1&tty=1"

resp, err := PostRequest(api.GlobalClient, url2, []byte(""))
fmt.Println(r.Status)
fmt.Println(r.Header)
bodyBytes, err := ioutil.ReadAll(r.Body) // -> it stuck here
fmt.Println(string(bodyBytes))  

I suppose it tried to open websocket, so I tried to use gorilla websocket library like that:

u := url.URL{Scheme: "ws", Host: "<node_ip>:10250", Path: "/exec/default/mypod/mycontainer?command=ls&command=/&input=1&output=1&tty=1"}

interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt)

c, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
if err != nil {
  log.Fatal("dial:", err)
}
defer c.Close()

But it printed an error:

2020/04/04 20:51:25 dial:websocket: bad handshake

What I am doing wrong?
How can I read response body from status "Switching Protocols"


Solution

  • I managed to do it with Kubernetes go-client library:

    package main
    
    import (
        "crypto/tls"
        "fmt"
        "net/http"
        "net/url"
        "os"
        "time"
    
        restclient "k8s.io/client-go/rest"
        "k8s.io/client-go/tools/remotecommand"
    )
    
    
    func main(){
    
        /*req.Header.Add("X-Stream-Protocol-Version", "v4.channel.k8s.io")
        req.Header.Add("X-Stream-Protocol-Version", "v3.channel.k8s.io")
        req.Header.Add("X-Stream-Protocol-Version", "v2.channel.k8s.io")
        req.Header.Add("X-Stream-Protocol-Version", "channel.k8s.io")
        req.Header.Add("Connection", "upgrade")
        req.Header.Add("Upgrade", "SPDY/3.1")*/
        //url2 := "https://123.123.123.123:10250/exec/default/my-pod/nginx?command=ls&command=/&input=1&output=1&tty=1"
        tr := &http.Transport{
            MaxIdleConns:       10,
            IdleConnTimeout:    30 * time.Second,
            DisableCompression: true,
            TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
        }
    
        config := &restclient.Config{
            Host:                "https://123.123.123.123:10250",
            APIPath:             "/exec/default/my-pod/nginx",
            TLSClientConfig:     restclient.TLSClientConfig{
                Insecure: true,
            },
            Transport:           tr,
        }
    
        url3 := &url.URL{
            Scheme:     "https",
            Opaque:     "",
            User:       nil,
            Host:       "123.123.123.123:10250",
            Path:       "/exec/default/my-pod/nginx",
            RawPath:    "",
            RawQuery:   "command=ls&command=/&input=1&output=1&tty=1",
        }
        exec, err := remotecommand.NewSPDYExecutor(config, "POST", url3)
        if err != nil {
            fmt.Println(err)
        }
    
        // Thanks for this blog post https://www.henryxieblogs.com/2019/05/
        err = exec.Stream(remotecommand.StreamOptions{
            Stdin:  os.Stdin,
            Stdout: os.Stdout,
            Stderr: os.Stderr,
            Tty:true,
        })
    
        fmt.Println(err)
    }