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  }