github.com/fanux/shipyard@v0.0.0-20161009071005-6515ce223235/controller/api/hijack.go (about) 1 package api 2 3 import ( 4 "bytes" 5 "crypto/tls" 6 "encoding/json" 7 "fmt" 8 "io" 9 "net" 10 "net/http" 11 "net/http/httputil" 12 "strings" 13 "time" 14 15 log "github.com/Sirupsen/logrus" 16 "github.com/samalba/dockerclient" 17 ) 18 19 func (a *Api) swarmHijack(tlsConfig *tls.Config, addr string, w http.ResponseWriter, r *http.Request) error { 20 if parts := strings.SplitN(addr, "://", 2); len(parts) == 2 { 21 addr = parts[1] 22 } 23 24 var ( 25 d net.Conn 26 err error 27 ) 28 29 if tlsConfig != nil { 30 d, err = tls.Dial("tcp", addr, tlsConfig) 31 } else { 32 d, err = net.Dial("tcp", addr) 33 } 34 if err != nil { 35 return err 36 } 37 hj, ok := w.(http.Hijacker) 38 if !ok { 39 return err 40 } 41 nc, _, err := hj.Hijack() 42 if err != nil { 43 return err 44 } 45 defer nc.Close() 46 defer d.Close() 47 48 err = r.Write(d) 49 if err != nil { 50 return err 51 } 52 53 errc := make(chan error, 2) 54 cp := func(dst io.Writer, src io.Reader) { 55 _, err := io.Copy(dst, src) 56 if conn, ok := dst.(interface { 57 CloseWrite() error 58 }); ok { 59 conn.CloseWrite() 60 } 61 errc <- err 62 } 63 go cp(d, nc) 64 go cp(nc, d) 65 <-errc 66 <-errc 67 68 return nil 69 } 70 71 func (a *Api) hijack(addr, method, path string, setRawTerminal bool, in io.ReadCloser, stdout, stderr io.Writer, started chan io.Closer, data interface{}) error { 72 execConfig := &dockerclient.ExecConfig{ 73 Tty: true, 74 Detach: false, 75 } 76 77 buf, err := json.Marshal(execConfig) 78 if err != nil { 79 return fmt.Errorf("error marshaling exec config: %s", err) 80 } 81 82 rdr := bytes.NewReader(buf) 83 84 req, err := http.NewRequest(method, path, rdr) 85 if err != nil { 86 return fmt.Errorf("error during hijack request: %s", err) 87 } 88 89 req.Header.Set("User-Agent", "Docker-Client") 90 req.Header.Set("Content-Type", "application/json") 91 req.Header.Set("Connection", "Upgrade") 92 req.Header.Set("Upgrade", "tcp") 93 req.Host = addr 94 95 var ( 96 dial net.Conn 97 dialErr error 98 execTLSConfig = a.manager.DockerClient().TLSConfig 99 ) 100 101 if a.allowInsecure { 102 execTLSConfig.InsecureSkipVerify = true 103 } 104 105 if execTLSConfig == nil { 106 dial, dialErr = net.Dial("tcp", addr) 107 } else { 108 log.Debug("using tls for exec hijack") 109 dial, dialErr = tls.Dial("tcp", addr, execTLSConfig) 110 } 111 112 if dialErr != nil { 113 return dialErr 114 } 115 116 // When we set up a TCP connection for hijack, there could be long periods 117 // of inactivity (a long running command with no output) that in certain 118 // network setups may cause ECONNTIMEOUT, leaving the client in an unknown 119 // state. Setting TCP KeepAlive on the socket connection will prohibit 120 // ECONNTIMEOUT unless the socket connection truly is broken 121 if tcpConn, ok := dial.(*net.TCPConn); ok { 122 tcpConn.SetKeepAlive(true) 123 tcpConn.SetKeepAlivePeriod(30 * time.Second) 124 } 125 if err != nil { 126 return err 127 } 128 clientconn := httputil.NewClientConn(dial, nil) 129 defer clientconn.Close() 130 131 // Server hijacks the connection, error 'connection closed' expected 132 clientconn.Do(req) 133 134 rwc, br := clientconn.Hijack() 135 defer rwc.Close() 136 137 if started != nil { 138 started <- rwc 139 } 140 141 var receiveStdout chan error 142 143 if stdout != nil || stderr != nil { 144 go func() (err error) { 145 if setRawTerminal && stdout != nil { 146 _, err = io.Copy(stdout, br) 147 } 148 return err 149 }() 150 } 151 152 go func() error { 153 if in != nil { 154 io.Copy(rwc, in) 155 } 156 157 if conn, ok := rwc.(interface { 158 CloseWrite() error 159 }); ok { 160 if err := conn.CloseWrite(); err != nil { 161 } 162 } 163 return nil 164 }() 165 166 if stdout != nil || stderr != nil { 167 if err := <-receiveStdout; err != nil { 168 return err 169 } 170 } 171 go func() { 172 for { 173 fmt.Println(br) 174 } 175 }() 176 177 return nil 178 }