// This does the actual data transfer. // The broker only closes the Read side. func broker(dst, src net.Conn, srcClosed chan bool, written, errors *int64) { _, err := io.Copy(dst, src) if err != nil { atomic.AddInt64(errors, 1) log.Errorf("ERROR: Copy error: %s", err) } if err := src.Close(); err != nil { atomic.AddInt64(errors, 1) log.Errorf("ERROR: Close error: %s", err) } srcClosed <- true }
func loadConfig() { for _, cfgPath := range []string{stateConfig, defaultConfig} { if cfgPath == "" { continue } cfgData, err := ioutil.ReadFile(cfgPath) if err != nil { log.Warnln("WARN: Reading config ", err) continue } var cfg client.Config err = json.Unmarshal(cfgData, &cfg) if err != nil { log.Warnln("WARN: Config error:", err) continue } log.Debug("DEBUG: Loaded config from:", cfgPath) if err := Registry.UpdateConfig(cfg); err != nil { log.Errorf("ERROR: Unable to load config: %s", err) } } }
func NewBackend(cfg client.BackendConfig) *Backend { b := &Backend{ Name: cfg.Name, Addr: cfg.Addr, CheckAddr: cfg.CheckAddr, Weight: cfg.Weight, Network: cfg.Network, stopCheck: make(chan interface{}), } // don't want a weight of 0 if b.Weight == 0 { b.Weight = 1 } if b.Network == "" { b.Network = "tcp" } switch b.Network { case "udp", "udp4", "udp6": var err error b.udpAddr, err = net.ResolveUDPAddr(b.Network, b.Addr) if err != nil { log.Errorf("ERROR: %s", err.Error()) b.up = false } } return b }
// Add a new service to the Registry. // Do not replace an existing service. func (s *ServiceRegistry) AddService(svcCfg client.ServiceConfig) error { s.Lock() defer s.Unlock() log.Debug("DEBUG: Adding service:", svcCfg.Name) if _, ok := s.svcs[svcCfg.Name]; ok { log.Debug("DEBUG: Service already exists:", svcCfg.Name) return ErrDuplicateService } s.setServiceDefaults(&svcCfg) svcCfg = svcCfg.SetDefaults() service := NewService(svcCfg) err := service.start() if err != nil { log.Errorf("ERROR: Unable to start service '%s'", svcCfg.Name) return err } s.svcs[service.Name] = service svcCfg.VirtualHosts = filterEmpty(svcCfg.VirtualHosts) for _, name := range svcCfg.VirtualHosts { vhost := s.vhosts[name] if vhost == nil { vhost = &VirtualHost{Name: name} s.vhosts[name] = vhost } vhost.Add(service) } return nil }
// This probably shouldn't be called ServeHTTP anymore func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request, addrs []string) { pr := &ProxyRequest{ ResponseWriter: rw, Request: req, Backends: addrs, } for _, f := range p.OnRequest { cont := f(pr) if !cont { return } } pr.StartTime = time.Now() res, err := p.doRequest(pr) pr.Response = res pr.ProxyError = err pr.FinishTime = time.Now() if err != nil { log.Errorf("ERROR: HTTP proxy error - %v", err) // We want to ensure that we have a non-nil response even on error for // the OnResponse callbacks. If the Callback chain completes, this will // be written to the client. res = &http.Response{ Header: make(map[string][]string), StatusCode: http.StatusBadGateway, Status: http.StatusText(http.StatusBadGateway), // this ensures Body isn't nil Body: ioutil.NopCloser(bytes.NewReader(nil)), } pr.Response = res } for _, h := range hopHeaders { res.Header.Del(h) } copyHeader(rw.Header(), res.Header) for _, f := range p.OnResponse { cont := f(pr) if !cont { return } } // calls all completed with true, write the Response back to the client. defer res.Body.Close() rw.WriteHeader(res.StatusCode) _, err = p.copyResponse(rw, res.Body) if err != nil { log.Warnf("WARN: id=%s transfer error: %s", req.Header.Get("X-Request-Id"), err) } }
func deleteService(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) err := Registry.RemoveService(vars["service"]) if err != nil { log.Errorf("ERROR: %s", err) http.Error(w, err.Error(), http.StatusNotFound) return } go writeStateConfig() w.Write(marshal(Registry.Config())) }
func getBackendStats(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) serviceName := vars["service"] backendName := vars["backend"] backend, err := Registry.BackendStats(serviceName, backendName) if err != nil { log.Errorf("ERROR: %s", err) http.Error(w, err.Error(), http.StatusNotFound) return } w.Write(marshal(backend)) }
// Update the global config state, including services and backends. // This does not remove any Services, but will add or update any provided in // the config. func (s *ServiceRegistry) UpdateConfig(cfg client.Config) error { // Set globals // TODO: we might need to unset something // TODO: this should remove services and backends to match the submitted config if cfg.Balance != "" { s.cfg.Balance = cfg.Balance } if cfg.CheckInterval != 0 { s.cfg.CheckInterval = cfg.CheckInterval } if cfg.Fall != 0 { s.cfg.Fall = cfg.Fall } if cfg.Rise != 0 { s.cfg.Rise = cfg.Rise } if cfg.ClientTimeout != 0 { s.cfg.ClientTimeout = cfg.ClientTimeout } if cfg.ServerTimeout != 0 { s.cfg.ServerTimeout = cfg.ServerTimeout } if cfg.DialTimeout != 0 { s.cfg.DialTimeout = cfg.DialTimeout } // apply the https rediect flag if httpsRedirect { s.cfg.HTTPSRedirect = true } invalidPorts := []string{ // FIXME: lookup bound addresses some other way. We may have multiple // http listeners, as well as all listening Services. // listenAddr[strings.Index(listenAddr, ":")+1:], adminListenAddr[strings.Index(adminListenAddr, ":")+1:], } errors := &multiError{} for _, svc := range cfg.Services { for _, port := range invalidPorts { if strings.HasSuffix(svc.Addr, port) { // TODO: report conflicts between service listeners errors.Add(fmt.Errorf("Port conflict: %s port %s already bound by shuttle", svc.Name, port)) continue } } // Add a new service, or update an existing one. if Registry.GetService(svc.Name) == nil { if err := Registry.AddService(svc); err != nil { log.Errorf("ERROR: Unable to add service %s - %s", svc.Name, err.Error()) errors.Add(err) continue } } else if err := Registry.UpdateService(svc); err != nil { log.Errorf("ERROR: Unable to update service %s - %s", svc.Name, err.Error()) errors.Add(err) continue } } go writeStateConfig() if errors.Len() == 0 { return nil } return errors }