package handlers import ( "crypto/tls" "fmt" "io" "log" "net" "net/http" "strings" "github.com/gorilla/mux" "github.com/play-with-docker/play-with-docker/config" ) func getTargetInfo(vars map[string]string, req *http.Request) (string, string) { node := vars["node"] port := vars["port"] hostPort := strings.Split(req.Host, ":") // give priority to the URL host port if len(hostPort) > 1 && hostPort[1] != config.PortNumber { port = hostPort[1] } else if port == "" { port = "80" } if strings.HasPrefix(node, "pwd") { // Node is actually an ip, need to convert underscores by dots. ip := strings.Replace(strings.TrimPrefix(node, "pwd"), "_", ".", -1) if net.ParseIP(ip) == nil { // Not a valid IP, so treat this is a hostname. } else { node = ip } } return node, port } type tcpProxy struct { Director func(*http.Request) ErrorLog *log.Logger Dial func(network, addr string) (net.Conn, error) } func (p *tcpProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) { logFunc := log.Printf if p.ErrorLog != nil { logFunc = p.ErrorLog.Printf } outreq := new(http.Request) // shallow copying *outreq = *r p.Director(outreq) host := outreq.URL.Host dial := p.Dial if dial == nil { dial = net.Dial } if outreq.URL.Scheme == "wss" || outreq.URL.Scheme == "https" { var tlsConfig *tls.Config tlsConfig = &tls.Config{InsecureSkipVerify: true} dial = func(network, address string) (net.Conn, error) { return tls.Dial("tcp", host, tlsConfig) } } d, err := dial("tcp", host) if err != nil { http.Error(w, "Error forwarding request.", 500) logFunc("Error dialing websocket backend %s: %v", outreq.URL, err) return } // All request generated by the http package implement this interface. hj, ok := w.(http.Hijacker) if !ok { http.Error(w, "Not a hijacker?", 500) return } // Hijack() tells the http package not to do anything else with the connection. // After, it bcomes this functions job to manage it. `nc` is of type *net.Conn. nc, _, err := hj.Hijack() if err != nil { logFunc("Hijack error: %v", err) return } defer nc.Close() // must close the underlying net connection after hijacking defer d.Close() // write the modified incoming request to the dialed connection err = outreq.Write(d) if err != nil { logFunc("Error copying request to target: %v", err) return } errc := make(chan error, 2) cp := func(dst io.Writer, src io.Reader) { _, err := io.Copy(dst, src) errc <- err } go cp(d, nc) go cp(nc, d) <-errc } func NewTCPProxy() http.Handler { director := func(req *http.Request) { v := mux.Vars(req) node, port := getTargetInfo(v, req) if port == "443" { if strings.Contains(req.URL.Scheme, "http") { req.URL.Scheme = "https" } else { req.URL.Scheme = "wss" } } req.URL.Host = fmt.Sprintf("%s:%s", node, port) } return &tcpProxy{Director: director} } func NewSSLDaemonHandler() http.Handler { director := func(req *http.Request) { v := mux.Vars(req) node := v["node"] if strings.HasPrefix(node, "pwd") { // Node is actually an ip, need to convert underscores by dots. ip := strings.Replace(strings.TrimPrefix(node, "pwd"), "_", ".", -1) if net.ParseIP(ip) == nil { // Not a valid IP, so treat this is a hostname. } else { node = ip } } // Only proxy http for now req.URL.Scheme = "http" req.URL.Host = fmt.Sprintf("%s:%s", node, "2375") log.Printf("HTTPS Reverse proxying to %s\n", req.URL.Host) } return &tcpProxy{Director: director} }