// 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.Printf("Copy error: %s", err) } if err := src.Close(); err != nil { atomic.AddInt64(errors, 1) log.Printf("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("Error reading config:", err) continue } var cfg client.Config err = json.Unmarshal(cfgData, &cfg) if err != nil { log.Warnln("Config error:", err) continue } log.Debug("Loaded config from:", cfgPath) if err := Registry.UpdateConfig(cfg); err != nil { log.Printf("Unable to load config: error: %s", err) } } }
// Stop the Service's Accept loop by closing the Listener, // and stop all backends for this service. func (s *Service) stop() { s.Lock() defer s.Unlock() log.Printf("Stopping Listener for %s on %s:%s", s.Name, s.Network, s.Addr) for _, backend := range s.Backends { backend.Stop() } switch s.Network { case "tcp", "tcp4", "tcp6": // the service may have been bad, and the listener failed if s.tcpListener == nil { return } err := s.tcpListener.Close() if err != nil { log.Println(err) } case "udp", "udp4", "udp6": if s.udpListener == nil { return } err := s.udpListener.Close() if err != nil { log.Println(err) } } }
// Add or replace a Backend in this service func (s *Service) add(backend *Backend) { s.Lock() defer s.Unlock() log.Printf("Adding %s backend %s{%s} for %s at %s", backend.Network, backend.Name, backend.Addr, s.Name, s.Addr) backend.up = true backend.rwTimeout = s.ServerTimeout backend.dialTimeout = s.DialTimeout backend.checkInterval = time.Duration(s.CheckInterval) * time.Millisecond // We may add some allowed protocol bridging in the future, but for now just fail if s.Network[:3] != backend.Network[:3] { log.Errorf("ERROR: backend %s cannot use network '%s'", backend.Name, backend.Network) } // replace an existing backend if we have it. for i, b := range s.Backends { if b.Name == backend.Name { b.Stop() s.Backends[i] = backend backend.Start() return } } s.Backends = append(s.Backends, backend) backend.Start() }
func main() { if debug { log.DefaultLogger.Level = log.DEBUG } if version { println(buildVersion) return } log.Printf("Starting shuttle %s", buildVersion) loadConfig() var wg sync.WaitGroup wg.Add(1) go startAdminHTTPServer(&wg) if httpAddr != "" { wg.Add(1) go startHTTPServer(&wg) } if httpsAddr != "" { wg.Add(1) go startHTTPSServer(&wg) } wg.Wait() }
// Start the HTTP Router frontend. // Takes a channel to notify when the listener is started // to safely synchronize tests. func (r *HostRouter) Start(ready chan bool) { //FIXME: poor locking strategy r.Lock() var err error r.listener, err = newTimeoutListener("tcp", r.server.Addr, 300*time.Second) if err != nil { log.Errorf("%s", err) r.Unlock() return } listener := r.listener if r.Scheme == "https" { listener = tls.NewListener(listener, r.server.TLSConfig) } r.Unlock() log.Printf("%s server listening at %s", strings.ToUpper(r.Scheme), r.server.Addr) if ready != nil { close(ready) } // This will log a closed connection error every time we Stop // but that's mostly a testing issue. log.Errorf("%s", r.server.Serve(listener)) }
func logProxyRequest(pr *ProxyRequest) bool { // TODO: we may to be able to switch this off if pr == nil || pr.Request == nil { return true } var id, method, clientIP, url, backend, agent string var status int duration := pr.FinishTime.Sub(pr.StartTime) id = pr.Request.Header.Get("X-Request-Id") method = pr.Request.Method url = pr.Request.Host + pr.Request.RequestURI agent = pr.Request.UserAgent() status = pr.Response.StatusCode clientIP = pr.Request.Header.Get("X-Forwarded-For") if clientIP == "" { clientIP = pr.Request.RemoteAddr } if pr.Response != nil && pr.Response.Request != nil && pr.Response.Request.URL != nil { backend = pr.Response.Request.URL.Host } err := fmt.Sprintf("%v", pr.ProxyError) fmtStr := "id=%s method=%s client-ip=%s url=%s backend=%s status=%d duration=%s agent=%s, err=%s" log.Printf(fmtStr, id, method, clientIP, url, backend, status, duration, agent, err) return true }
func (v *VirtualHost) Remove(svc *Service) { v.Lock() defer v.Unlock() found := -1 for i, s := range v.services { if s.Name == svc.Name { found = i break } } if found < 0 { log.Debugf("Service %s not found under VirtualHost %s", svc.Name, v.Name) return } // safe way to get the backends info for logging svcCfg := svc.Config() // Now removing this Service for _, backend := range svcCfg.Backends { log.Printf("Removing backend http://%s from VirtualHost %s", backend.Addr, v.Name) } v.services = append(v.services[:found], v.services[found+1:]...) }
// 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.Printf("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("id=%s transfer error: %s", req.Header.Get("X-Request-Id"), err) } }
// Fill out and verify service func (s *Service) start() (err error) { s.Lock() defer s.Unlock() if s.Backends == nil { s.Backends = make([]*Backend, 0) } switch s.Network { case "tcp", "tcp4", "tcp6": log.Printf("Starting TCP listener for %s on %s", s.Name, s.Addr) s.tcpListener, err = newTimeoutListener(s.Network, s.Addr, s.ClientTimeout) if err != nil { return err } go s.runTCP() case "udp", "udp4", "udp6": log.Printf("Starting UDP listener for %s on %s", s.Name, s.Addr) laddr, err := net.ResolveUDPAddr(s.Network, s.Addr) if err != nil { return err } s.udpListener, err = net.ListenUDP(s.Network, laddr) if err != nil { return err } go s.runUDP() default: return fmt.Errorf("Error: unknown network '%s'", s.Network) } return nil }
// Remove a Backend by name func (s *Service) remove(name string) bool { s.Lock() defer s.Unlock() for i, b := range s.Backends { if b.Name == name { log.Printf("Removing %s backend %s{%s} for %s at %s", b.Network, b.Name, b.Addr, s.Name, s.Addr) last := len(s.Backends) - 1 deleted := b s.Backends[i], s.Backends[last] = s.Backends[last], nil s.Backends = s.Backends[:last] deleted.Stop() return true } } return false }
// Insert a service // do nothing if the service already is registered func (v *VirtualHost) Add(svc *Service) { v.Lock() defer v.Unlock() for _, s := range v.services { if s.Name == svc.Name { log.Debugf("Service %s already registered in VirtualHost %s", svc.Name, v.Name) return } } // TODO: is this the best place to log these? svcCfg := svc.Config() for _, backend := range svcCfg.Backends { log.Printf("Adding backend http://%s to VirtualHost %s", backend.Addr, v.Name) } v.services = append(v.services, svc) }