func ExampleProxyMetrics() { config := camo.Config{} proxy, err := camo.New(config) if err != nil { fmt.Println("Error: ", err) os.Exit(1) } ps := &stats.ProxyStats{} proxy.SetMetricsCollector(ps) }
func main() { var gmx int if gmxEnv := os.Getenv("GOMAXPROCS"); gmxEnv != "" { gmx, _ = strconv.Atoi(gmxEnv) } else { gmx = runtime.NumCPU() } runtime.GOMAXPROCS(gmx) // command line flags var opts struct { HMACKey string `short:"k" long:"key" description:"HMAC key"` AddHeaders []string `short:"H" long:"header" description:"Extra header to return for each response. This option can be used multiple times to add multiple headers"` Stats bool `long:"stats" description:"Enable Stats"` NoLogTS bool `long:"no-log-ts" description:"Do not add a timestamp to logging"` AllowList string `long:"allow-list" description:"Text file of hostname allow regexes (one per line)"` MaxSize int64 `long:"max-size" default:"5120" description:"Max response image size (KB)"` ReqTimeout time.Duration `long:"timeout" default:"4s" description:"Upstream request timeout"` MaxRedirects int `long:"max-redirects" default:"3" description:"Maximum number of redirects to follow"` DisableKeepAlivesFE bool `long:"no-fk" description:"Disable frontend http keep-alive support"` DisableKeepAlivesBE bool `long:"no-bk" description:"Disable backend http keep-alive support"` BindAddress string `long:"listen" default:"0.0.0.0:8080" description:"Address:Port to bind to for HTTP"` BindAddressSSL string `long:"ssl-listen" description:"Address:Port to bind to for HTTPS/SSL/TLS"` SSLKey string `long:"ssl-key" description:"ssl private key (key.pem) path"` SSLCert string `long:"ssl-cert" description:"ssl cert (cert.pem) path"` Verbose bool `short:"v" long:"verbose" description:"Show verbose (debug) log level output"` Version []bool `short:"V" long:"version" description:"Print version and exit; specify twice to show license information"` } // parse said flags _, err := flags.Parse(&opts) if err != nil { if e, ok := err.(*flags.Error); ok { if e.Type == flags.ErrHelp { os.Exit(0) } } os.Exit(1) } if len(opts.Version) > 0 { fmt.Printf("%s %s (%s,%s-%s)\n", ServerName, ServerVersion, runtime.Version(), runtime.Compiler, runtime.GOARCH) if len(opts.Version) > 1 { fmt.Printf("\n%s\n", strings.TrimSpace(licenseText)) } os.Exit(0) } // start out with a very bare logger that only prints // the message (no special format or log elements) mlog.SetFlags(0) config := camo.Config{} if hmacKey := os.Getenv("GOCAMO_HMAC"); hmacKey != "" { config.HMACKey = []byte(hmacKey) } // flags override env var if opts.HMACKey != "" { config.HMACKey = []byte(opts.HMACKey) } if len(config.HMACKey) == 0 { mlog.Fatal("HMAC key required") } if opts.BindAddress == "" && opts.BindAddressSSL == "" { mlog.Fatal("One of listen or ssl-listen required") } if opts.BindAddressSSL != "" && opts.SSLKey == "" { mlog.Fatal("ssl-key is required when specifying ssl-listen") } if opts.BindAddressSSL != "" && opts.SSLCert == "" { mlog.Fatal("ssl-cert is required when specifying ssl-listen") } // set keepalive options config.DisableKeepAlivesBE = opts.DisableKeepAlivesBE config.DisableKeepAlivesFE = opts.DisableKeepAlivesFE if opts.AllowList != "" { b, err := ioutil.ReadFile(opts.AllowList) if err != nil { mlog.Fatal("Could not read allow-list", err) } config.AllowList = strings.Split(string(b), "\n") } AddHeaders := map[string]string{ "X-Content-Type-Options": "nosniff", "X-XSS-Protection": "1; mode=block", "Content-Security-Policy": "default-src 'none'", } for _, v := range opts.AddHeaders { s := strings.SplitN(v, ":", 2) if len(s) != 2 { mlog.Printf("ignoring bad header: '%s'", v) continue } s0 := strings.TrimSpace(s[0]) s1 := strings.TrimSpace(s[1]) if len(s0) == 0 || len(s1) == 0 { mlog.Printf("ignoring bad header: '%s'", v) continue } AddHeaders[s[0]] = s[1] } // now configure a standard logger mlog.SetFlags(mlog.Lstd) if opts.NoLogTS { mlog.SetFlags(mlog.Flags() ^ mlog.Ltimestamp) } if opts.Verbose { mlog.SetFlags(mlog.Flags() | mlog.Ldebug) mlog.Debug("debug logging enabled") } // convert from KB to Bytes config.MaxSize = opts.MaxSize * 1024 config.RequestTimeout = opts.ReqTimeout config.MaxRedirects = opts.MaxRedirects config.ServerName = ServerName proxy, err := camo.New(config) if err != nil { mlog.Fatal("Error creating camo", err) } dumbrouter := &router.DumbRouter{ ServerName: config.ServerName, AddHeaders: AddHeaders, CamoHandler: proxy, } if opts.Stats { ps := &stats.ProxyStats{} proxy.SetMetricsCollector(ps) mlog.Printf("Enabling stats at /status") dumbrouter.StatsHandler = stats.Handler(ps) } http.Handle("/", dumbrouter) if opts.BindAddress != "" { mlog.Printf("Starting server on: %s", opts.BindAddress) go func() { srv := &http.Server{ Addr: opts.BindAddress, ReadTimeout: 30 * time.Second} mlog.Fatal(srv.ListenAndServe()) }() } if opts.BindAddressSSL != "" { mlog.Printf("Starting TLS server on: %s", opts.BindAddressSSL) go func() { srv := &http.Server{ Addr: opts.BindAddressSSL, ReadTimeout: 30 * time.Second} mlog.Fatal(srv.ListenAndServeTLS(opts.SSLCert, opts.SSLKey)) }() } // just block. listen and serve will exit the program if they fail/return // so we just need to block to prevent main from exiting. select {} }