// startProxy launches an HTTP proxy for client communication which proxies to other etcd nodes. func startProxy(cfg *config) error { cls, err := setupCluster(cfg) if err != nil { return fmt.Errorf("error setting up initial cluster: %v", err) } if cfg.durl != "" { s, err := discovery.GetCluster(cfg.durl, cfg.dproxy) if err != nil { return err } if cls, err = etcdserver.NewClusterFromString(cfg.durl, s); err != nil { return err } } pt, err := transport.NewTransport(cfg.clientTLSInfo) if err != nil { return err } // TODO(jonboulle): update peerURLs dynamically (i.e. when updating // clientURLs) instead of just using the initial fixed list here peerURLs := cls.PeerURLs() uf := func() []string { cls, err := etcdserver.GetClusterFromPeers(peerURLs) if err != nil { log.Printf("proxy: %v", err) return []string{} } return cls.ClientURLs() } ph := proxy.NewHandler(pt, uf) ph = &cors.CORSHandler{ Handler: ph, Info: cfg.corsInfo, } if cfg.isReadonlyProxy() { ph = proxy.NewReadonlyHandler(ph) } // Start a proxy server goroutine for each listen address for _, u := range cfg.lcurls { l, err := transport.NewListener(u.Host, u.Scheme, cfg.clientTLSInfo) if err != nil { return err } host := u.Host go func() { log.Print("proxy: listening for client requests on ", host) log.Fatal(http.Serve(l, ph)) }() } return nil }
// startProxy launches an HTTP proxy for client communication which proxies to other etcd nodes. func startProxy() { cls, err := setupCluster() if err != nil { log.Fatalf("etcd: error setting up initial cluster: %v", err) } pt, err := transport.NewTransport(clientTLSInfo) if err != nil { log.Fatal(err) } // TODO(jonboulle): update peerURLs dynamically (i.e. when updating // clientURLs) instead of just using the initial fixed list here peerURLs := cls.PeerURLs() uf := func() []string { cls, err := etcdserver.GetClusterFromPeers(peerURLs) if err != nil { log.Printf("etcd: %v", err) return []string{} } return cls.ClientURLs() } ph := proxy.NewHandler(pt, uf) ph = &cors.CORSHandler{ Handler: ph, Info: corsInfo, } if string(*proxyFlag) == flags.ProxyValueReadonly { ph = proxy.NewReadonlyHandler(ph) } lcurls, err := flags.URLsFromFlags(fs, "listen-client-urls", "bind-addr", clientTLSInfo) if err != nil { log.Fatal(err.Error()) } // Start a proxy server goroutine for each listen address for _, u := range lcurls { l, err := transport.NewListener(u.Host, clientTLSInfo) if err != nil { log.Fatal(err) } host := u.Host go func() { log.Print("etcd: proxy listening for client requests on ", host) log.Fatal(http.Serve(l, ph)) }() } }
// startProxy launches an HTTP proxy for client communication which proxies to other etcd nodes. func startProxy() { pt, err := transport.NewTransport(clientTLSInfo) if err != nil { log.Fatal(err) } ph, err := proxy.NewHandler(pt, (*cluster).PeerURLs()) if err != nil { log.Fatal(err) } ph = &pkg.CORSHandler{ Handler: ph, Info: cors, } if string(*proxyFlag) == flagtypes.ProxyValueReadonly { ph = proxy.NewReadonlyHandler(ph) } lcurls, err := pkg.URLsFromFlags(flag.CommandLine, "listen-client-urls", "bind-addr", clientTLSInfo) if err != nil { log.Fatal(err.Error()) } // Start a proxy server goroutine for each listen address for _, u := range lcurls { l, err := transport.NewListener(u.Host, clientTLSInfo) if err != nil { log.Fatal(err) } host := u.Host go func() { log.Print("Listening for client requests on ", host) log.Fatal(http.Serve(l, ph)) }() } }
// startProxy launches an HTTP proxy for client communication which proxies to other etcd nodes. func startProxy(cfg *config) error { urlsmap, _, err := getPeerURLsMapAndToken(cfg, "proxy") if err != nil { return fmt.Errorf("error setting up initial cluster: %v", err) } pt, err := transport.NewTimeoutTransport(cfg.peerTLSInfo, time.Duration(cfg.proxyDialTimeoutMs)*time.Millisecond, time.Duration(cfg.proxyReadTimeoutMs)*time.Millisecond, time.Duration(cfg.proxyWriteTimeoutMs)*time.Millisecond) if err != nil { return err } pt.MaxIdleConnsPerHost = proxy.DefaultMaxIdleConnsPerHost tr, err := transport.NewTimeoutTransport(cfg.peerTLSInfo, time.Duration(cfg.proxyDialTimeoutMs)*time.Millisecond, time.Duration(cfg.proxyReadTimeoutMs)*time.Millisecond, time.Duration(cfg.proxyWriteTimeoutMs)*time.Millisecond) if err != nil { return err } cfg.dir = path.Join(cfg.dir, "proxy") err = os.MkdirAll(cfg.dir, 0700) if err != nil { return err } var peerURLs []string clusterfile := path.Join(cfg.dir, "cluster") b, err := ioutil.ReadFile(clusterfile) switch { case err == nil: if cfg.durl != "" { plog.Warningf("discovery token ignored since the proxy has already been initialized. Valid cluster file found at %q", clusterfile) } urls := struct{ PeerURLs []string }{} err := json.Unmarshal(b, &urls) if err != nil { return err } peerURLs = urls.PeerURLs plog.Infof("proxy: using peer urls %v from cluster file %q", peerURLs, clusterfile) case os.IsNotExist(err): if cfg.durl != "" { s, err := discovery.GetCluster(cfg.durl, cfg.dproxy) if err != nil { return err } if urlsmap, err = types.NewURLsMap(s); err != nil { return err } } peerURLs = urlsmap.URLs() plog.Infof("proxy: using peer urls %v ", peerURLs) default: return err } clientURLs := []string{} uf := func() []string { gcls, err := etcdserver.GetClusterFromRemotePeers(peerURLs, tr) // TODO: remove the 2nd check when we fix GetClusterFromPeers // GetClusterFromPeers should not return nil error with an invalid empty cluster if err != nil { plog.Warningf("proxy: %v", err) return []string{} } if len(gcls.Members()) == 0 { return clientURLs } clientURLs = gcls.ClientURLs() urls := struct{ PeerURLs []string }{gcls.PeerURLs()} b, err := json.Marshal(urls) if err != nil { plog.Warningf("proxy: error on marshal peer urls %s", err) return clientURLs } err = ioutil.WriteFile(clusterfile+".bak", b, 0600) if err != nil { plog.Warningf("proxy: error on writing urls %s", err) return clientURLs } err = os.Rename(clusterfile+".bak", clusterfile) if err != nil { plog.Warningf("proxy: error on updating clusterfile %s", err) return clientURLs } if !reflect.DeepEqual(gcls.PeerURLs(), peerURLs) { plog.Noticef("proxy: updated peer urls in cluster file from %v to %v", peerURLs, gcls.PeerURLs()) } peerURLs = gcls.PeerURLs() return clientURLs } ph := proxy.NewHandler(pt, uf, time.Duration(cfg.proxyFailureWaitMs)*time.Millisecond, time.Duration(cfg.proxyRefreshIntervalMs)*time.Millisecond) ph = &cors.CORSHandler{ Handler: ph, Info: cfg.corsInfo, } if cfg.isReadonlyProxy() { ph = proxy.NewReadonlyHandler(ph) } // Start a proxy server goroutine for each listen address for _, u := range cfg.lcurls { l, err := transport.NewListener(u.Host, u.Scheme, cfg.clientTLSInfo) if err != nil { return err } host := u.String() go func() { plog.Info("proxy: listening for client requests on ", host) mux := http.NewServeMux() mux.Handle("/metrics", prometheus.Handler()) mux.Handle("/", ph) plog.Fatal(http.Serve(l, mux)) }() } return nil }
// startProxy launches an HTTP proxy for client communication which proxies to other etcd nodes. func startProxy(cfg *config) error { plog.Notice("proxy: this proxy supports v2 API only!") pt, err := transport.NewTimeoutTransport(cfg.PeerTLSInfo, time.Duration(cfg.ProxyDialTimeoutMs)*time.Millisecond, time.Duration(cfg.ProxyReadTimeoutMs)*time.Millisecond, time.Duration(cfg.ProxyWriteTimeoutMs)*time.Millisecond) if err != nil { return err } pt.MaxIdleConnsPerHost = httpproxy.DefaultMaxIdleConnsPerHost tr, err := transport.NewTimeoutTransport(cfg.PeerTLSInfo, time.Duration(cfg.ProxyDialTimeoutMs)*time.Millisecond, time.Duration(cfg.ProxyReadTimeoutMs)*time.Millisecond, time.Duration(cfg.ProxyWriteTimeoutMs)*time.Millisecond) if err != nil { return err } cfg.Dir = path.Join(cfg.Dir, "proxy") err = os.MkdirAll(cfg.Dir, fileutil.PrivateDirMode) if err != nil { return err } var peerURLs []string clusterfile := path.Join(cfg.Dir, "cluster") b, err := ioutil.ReadFile(clusterfile) switch { case err == nil: if cfg.Durl != "" { plog.Warningf("discovery token ignored since the proxy has already been initialized. Valid cluster file found at %q", clusterfile) } if cfg.DNSCluster != "" { plog.Warningf("DNS SRV discovery ignored since the proxy has already been initialized. Valid cluster file found at %q", clusterfile) } urls := struct{ PeerURLs []string }{} err = json.Unmarshal(b, &urls) if err != nil { return err } peerURLs = urls.PeerURLs plog.Infof("proxy: using peer urls %v from cluster file %q", peerURLs, clusterfile) case os.IsNotExist(err): var urlsmap types.URLsMap urlsmap, _, err = cfg.PeerURLsMapAndToken("proxy") if err != nil { return fmt.Errorf("error setting up initial cluster: %v", err) } if cfg.Durl != "" { var s string s, err = discovery.GetCluster(cfg.Durl, cfg.Dproxy) if err != nil { return err } if urlsmap, err = types.NewURLsMap(s); err != nil { return err } } peerURLs = urlsmap.URLs() plog.Infof("proxy: using peer urls %v ", peerURLs) default: return err } clientURLs := []string{} uf := func() []string { gcls, gerr := etcdserver.GetClusterFromRemotePeers(peerURLs, tr) if gerr != nil { plog.Warningf("proxy: %v", gerr) return []string{} } clientURLs = gcls.ClientURLs() urls := struct{ PeerURLs []string }{gcls.PeerURLs()} b, jerr := json.Marshal(urls) if jerr != nil { plog.Warningf("proxy: error on marshal peer urls %s", jerr) return clientURLs } err = pkgioutil.WriteAndSyncFile(clusterfile+".bak", b, 0600) if err != nil { plog.Warningf("proxy: error on writing urls %s", err) return clientURLs } err = os.Rename(clusterfile+".bak", clusterfile) if err != nil { plog.Warningf("proxy: error on updating clusterfile %s", err) return clientURLs } if !reflect.DeepEqual(gcls.PeerURLs(), peerURLs) { plog.Noticef("proxy: updated peer urls in cluster file from %v to %v", peerURLs, gcls.PeerURLs()) } peerURLs = gcls.PeerURLs() return clientURLs } ph := httpproxy.NewHandler(pt, uf, time.Duration(cfg.ProxyFailureWaitMs)*time.Millisecond, time.Duration(cfg.ProxyRefreshIntervalMs)*time.Millisecond) ph = &cors.CORSHandler{ Handler: ph, Info: cfg.CorsInfo, } if cfg.isReadonlyProxy() { ph = httpproxy.NewReadonlyHandler(ph) } // Start a proxy server goroutine for each listen address for _, u := range cfg.LCUrls { var ( l net.Listener tlscfg *tls.Config ) if !cfg.ClientTLSInfo.Empty() { tlscfg, err = cfg.ClientTLSInfo.ServerConfig() if err != nil { return err } } l, err := transport.NewListener(u.Host, u.Scheme, tlscfg) if err != nil { return err } host := u.String() go func() { plog.Info("proxy: listening for client requests on ", host) mux := http.NewServeMux() mux.Handle("/metrics", prometheus.Handler()) mux.Handle("/", ph) plog.Fatal(http.Serve(l, mux)) }() } return nil }
// startEtcd launches the etcd server and HTTP handlers for client/server communication. func startEtcd() { cls, err := setupCluster() if err != nil { log.Fatalf("etcd: error setting up initial cluster: %v", err) } if *dir == "" { *dir = fmt.Sprintf("%v.etcd", *name) log.Printf("etcd: no data-dir provided, using default data-dir ./%s", *dir) } if err := os.MkdirAll(*dir, privateDirMode); err != nil { log.Fatalf("etcd: cannot create data directory: %v", err) } if err := fileutil.IsDirWriteable(*dir); err != nil { log.Fatalf("etcd: cannot write to data directory: %v", err) } pt, err := transport.NewTransport(peerTLSInfo) if err != nil { log.Fatal(err) } acurls, err := flags.URLsFromFlags(fs, "advertise-client-urls", "addr", clientTLSInfo) if err != nil { log.Fatal(err.Error()) } cfg := &etcdserver.ServerConfig{ Name: *name, ClientURLs: acurls, DataDir: *dir, SnapCount: *snapCount, Cluster: cls, DiscoveryURL: *durl, ClusterState: *clusterState, Transport: pt, } s := etcdserver.NewServer(cfg) s.Start() ch := &cors.CORSHandler{ Handler: etcdhttp.NewClientHandler(s), Info: corsInfo, } ph := etcdhttp.NewPeerHandler(s) lpurls, err := flags.URLsFromFlags(fs, "listen-peer-urls", "peer-bind-addr", peerTLSInfo) if err != nil { log.Fatal(err.Error()) } for _, u := range lpurls { l, err := transport.NewListener(u.Host, peerTLSInfo) if err != nil { log.Fatal(err) } // Start the peer server in a goroutine urlStr := u.String() go func() { log.Print("etcd: listening for peers on ", urlStr) log.Fatal(http.Serve(l, ph)) }() } lcurls, err := flags.URLsFromFlags(fs, "listen-client-urls", "bind-addr", clientTLSInfo) if err != nil { log.Fatal(err.Error()) } // Start a client server goroutine for each listen address for _, u := range lcurls { l, err := transport.NewListener(u.Host, clientTLSInfo) if err != nil { log.Fatal(err) } urlStr := u.String() go func() { log.Print("etcd: listening for client requests on ", urlStr) log.Fatal(http.Serve(l, ch)) }() } }
// startProxy launches an HTTP proxy for client communication which proxies to other etcd nodes. func startProxy() error { apurls, err := flags.URLsFromFlags(fs, "initial-advertise-peer-urls", "addr", peerTLSInfo) if err != nil { return err } cls, err := setupCluster(apurls) if err != nil { return fmt.Errorf("error setting up initial cluster: %v", err) } if *durl != "" { s, err := discovery.GetCluster(*durl, *dproxy) if err != nil { return err } if cls, err = etcdserver.NewClusterFromString(*durl, s); err != nil { return err } } pt, err := transport.NewTransport(clientTLSInfo) if err != nil { return err } // TODO(jonboulle): update peerURLs dynamically (i.e. when updating // clientURLs) instead of just using the initial fixed list here peerURLs := cls.PeerURLs() uf := func() []string { cls, err := etcdserver.GetClusterFromPeers(peerURLs) if err != nil { log.Printf("proxy: %v", err) return []string{} } return cls.ClientURLs() } ph := proxy.NewHandler(pt, uf) ph = &cors.CORSHandler{ Handler: ph, Info: corsInfo, } if proxyFlag.String() == proxyFlagReadonly { ph = proxy.NewReadonlyHandler(ph) } lcurls, err := flags.URLsFromFlags(fs, "listen-client-urls", "bind-addr", clientTLSInfo) if err != nil { return err } // Start a proxy server goroutine for each listen address for _, u := range lcurls { l, err := transport.NewListener(u.Host, u.Scheme, clientTLSInfo) if err != nil { return err } host := u.Host go func() { log.Print("proxy: listening for client requests on ", host) log.Fatal(http.Serve(l, ph)) }() } return nil }
// startEtcd launches the etcd server and HTTP handlers for client/server communication. func startEtcd() (<-chan struct{}, error) { apurls, err := flags.URLsFromFlags(fs, "initial-advertise-peer-urls", "addr", peerTLSInfo) if err != nil { return nil, err } cls, err := setupCluster(apurls) if err != nil { return nil, fmt.Errorf("error setting up initial cluster: %v", err) } if *dir == "" { *dir = fmt.Sprintf("%v.etcd", *name) log.Printf("no data-dir provided, using default data-dir ./%s", *dir) } if err := os.MkdirAll(*dir, privateDirMode); err != nil { return nil, fmt.Errorf("cannot create data directory: %v", err) } if err := fileutil.IsDirWriteable(*dir); err != nil { return nil, fmt.Errorf("cannot write to data directory: %v", err) } pt, err := transport.NewTimeoutTransport(peerTLSInfo, rafthttp.ConnReadTimeout, rafthttp.ConnWriteTimeout) if err != nil { return nil, err } acurls, err := flags.URLsFromFlags(fs, "advertise-client-urls", "addr", clientTLSInfo) if err != nil { return nil, err } lpurls, err := flags.URLsFromFlags(fs, "listen-peer-urls", "peer-bind-addr", peerTLSInfo) if err != nil { return nil, err } if !peerTLSInfo.Empty() { log.Printf("etcd: peerTLS: %s", peerTLSInfo) } plns := make([]net.Listener, 0) for _, u := range lpurls { var l net.Listener l, err = transport.NewTimeoutListener(u.Host, u.Scheme, peerTLSInfo, rafthttp.ConnReadTimeout, rafthttp.ConnWriteTimeout) if err != nil { return nil, err } urlStr := u.String() log.Print("etcd: listening for peers on ", urlStr) defer func() { if err != nil { l.Close() log.Print("etcd: stopping listening for peers on ", urlStr) } }() plns = append(plns, l) } lcurls, err := flags.URLsFromFlags(fs, "listen-client-urls", "bind-addr", clientTLSInfo) if err != nil { return nil, err } if !clientTLSInfo.Empty() { log.Printf("etcd: clientTLS: %s", clientTLSInfo) } clns := make([]net.Listener, 0) for _, u := range lcurls { var l net.Listener l, err = transport.NewListener(u.Host, u.Scheme, clientTLSInfo) if err != nil { return nil, err } urlStr := u.String() log.Print("etcd: listening for client requests on ", urlStr) defer func() { if err != nil { l.Close() log.Print("etcd: stopping listening for client requests on ", urlStr) } }() clns = append(clns, l) } cfg := &etcdserver.ServerConfig{ Name: *name, ClientURLs: acurls, PeerURLs: apurls, DataDir: *dir, SnapCount: *snapCount, MaxSnapFiles: *maxSnapFiles, MaxWALFiles: *maxWalFiles, Cluster: cls, DiscoveryURL: *durl, DiscoveryProxy: *dproxy, NewCluster: clusterStateFlag.String() == clusterStateFlagNew, ForceNewCluster: *forceNewCluster, Transport: pt, } var s *etcdserver.EtcdServer s, err = etcdserver.NewServer(cfg) if err != nil { return nil, err } s.Start() if corsInfo.String() != "" { log.Printf("etcd: cors = %s", corsInfo) } ch := &cors.CORSHandler{ Handler: etcdhttp.NewClientHandler(s), Info: corsInfo, } ph := etcdhttp.NewPeerHandler(s) // Start the peer server in a goroutine for _, l := range plns { go func(l net.Listener) { log.Fatal(http.Serve(l, ph)) }(l) } // Start a client server goroutine for each listen address for _, l := range clns { go func(l net.Listener) { log.Fatal(http.Serve(l, ch)) }(l) } return s.StopNotify(), nil }
// startProxy launches an HTTP proxy for client communication which proxies to other etcd nodes. func startProxy(cfg *config) error { cls, err := setupCluster(cfg) if err != nil { return fmt.Errorf("error setting up initial cluster: %v", err) } if cfg.durl != "" { s, err := discovery.GetCluster(cfg.durl, cfg.dproxy) if err != nil { return err } if cls, err = etcdserver.NewClusterFromString(cfg.durl, s); err != nil { return err } } pt, err := transport.NewTransport(cfg.clientTLSInfo) if err != nil { return err } tr, err := transport.NewTransport(cfg.peerTLSInfo) if err != nil { return err } if cfg.dir == "" { cfg.dir = fmt.Sprintf("%v.etcd", cfg.name) log.Printf("no proxy data-dir provided, using default proxy data-dir ./%s", cfg.dir) } cfg.dir = path.Join(cfg.dir, "proxy") err = os.MkdirAll(cfg.dir, 0700) if err != nil { return err } var peerURLs []string clusterfile := path.Join(cfg.dir, "cluster") b, err := ioutil.ReadFile(clusterfile) switch { case err == nil: urls := struct{ PeerURLs []string }{} err := json.Unmarshal(b, &urls) if err != nil { return err } peerURLs = urls.PeerURLs log.Printf("proxy: using peer urls %v from cluster file ./%s", peerURLs, clusterfile) case os.IsNotExist(err): peerURLs = cls.PeerURLs() log.Printf("proxy: using peer urls %v ", peerURLs) default: return err } uf := func() []string { gcls, err := etcdserver.GetClusterFromPeers(peerURLs, tr) // TODO: remove the 2nd check when we fix GetClusterFromPeers // GetClusterFromPeers should not return nil error with an invaild empty cluster if err != nil { log.Printf("proxy: %v", err) return []string{} } if len(gcls.Members()) == 0 { return cls.ClientURLs() } cls = gcls urls := struct{ PeerURLs []string }{cls.PeerURLs()} b, err := json.Marshal(urls) if err != nil { log.Printf("proxy: error on marshal peer urls %s", err) return cls.ClientURLs() } err = ioutil.WriteFile(clusterfile+".bak", b, 0600) if err != nil { log.Printf("proxy: error on writing urls %s", err) return cls.ClientURLs() } err = os.Rename(clusterfile+".bak", clusterfile) if err != nil { log.Printf("proxy: error on updating clusterfile %s", err) return cls.ClientURLs() } if !reflect.DeepEqual(cls.PeerURLs(), peerURLs) { log.Printf("proxy: updated peer urls in cluster file from %v to %v", peerURLs, cls.PeerURLs()) } peerURLs = cls.PeerURLs() return cls.ClientURLs() } ph := proxy.NewHandler(pt, uf) ph = &cors.CORSHandler{ Handler: ph, Info: cfg.corsInfo, } if cfg.isReadonlyProxy() { ph = proxy.NewReadonlyHandler(ph) } // Start a proxy server goroutine for each listen address for _, u := range cfg.lcurls { l, err := transport.NewListener(u.Host, u.Scheme, cfg.clientTLSInfo) if err != nil { return err } host := u.Host go func() { log.Print("proxy: listening for client requests on ", host) log.Fatal(http.Serve(l, ph)) }() } return nil }
// startEtcd launches the etcd server and HTTP handlers for client/server communication. func startEtcd() { self := cluster.FindName(*name) if self == nil { log.Fatalf("etcd: no member with name=%q exists", *name) } if self.ID == raft.None { log.Fatalf("etcd: cannot use None(%d) as member id", raft.None) } if *dir == "" { *dir = fmt.Sprintf("%v_etcd_data", self.ID) log.Printf("main: no data-dir provided, using default data-dir ./%s", *dir) } if err := os.MkdirAll(*dir, privateDirMode); err != nil { log.Fatalf("main: cannot create data directory: %v", err) } pt, err := transport.NewTransport(peerTLSInfo) if err != nil { log.Fatal(err) } acurls, err := pkg.URLsFromFlags(flag.CommandLine, "advertise-client-urls", "addr", clientTLSInfo) if err != nil { log.Fatal(err.Error()) } cfg := &etcdserver.ServerConfig{ Name: *name, ClientURLs: acurls, DataDir: *dir, SnapCount: int64(*snapCount), Cluster: cluster, Transport: pt, } s := etcdserver.NewServer(cfg) s.Start() ch := &pkg.CORSHandler{ Handler: etcdhttp.NewClientHandler(s), Info: cors, } ph := etcdhttp.NewPeerHandler(s) lpurls, err := pkg.URLsFromFlags(flag.CommandLine, "listen-peer-urls", "peer-bind-addr", peerTLSInfo) if err != nil { log.Fatal(err.Error()) } for _, u := range lpurls { l, err := transport.NewListener(u.Host, peerTLSInfo) if err != nil { log.Fatal(err) } // Start the peer server in a goroutine urlStr := u.String() go func() { log.Print("Listening for peers on ", urlStr) log.Fatal(http.Serve(l, ph)) }() } lcurls, err := pkg.URLsFromFlags(flag.CommandLine, "listen-client-urls", "bind-addr", clientTLSInfo) if err != nil { log.Fatal(err.Error()) } // Start a client server goroutine for each listen address for _, u := range lcurls { l, err := transport.NewListener(u.Host, clientTLSInfo) if err != nil { log.Fatal(err) } urlStr := u.String() go func() { log.Print("Listening for client requests on ", urlStr) log.Fatal(http.Serve(l, ch)) }() } }