gofoomo/proxy/proxy.go

176 lines
4.6 KiB
Go

package proxy
import (
"crypto/tls"
"errors"
"log"
"net/http"
"net/http/httputil"
"github.com/foomo/gofoomo/foomo"
"github.com/foomo/tlsconfig"
"time"
)
// Handler proxy handler
type Handler interface {
HandlesRequest(incomingRequest *http.Request) bool
ServeHTTP(w http.ResponseWriter, incomingRequest *http.Request)
}
// Listener deprecated
type Listener interface {
ListenServeHTTPStart(w http.ResponseWriter, incomingRequest *http.Request) http.ResponseWriter
ListenServeHTTPDone(w http.ResponseWriter, incomingRequest *http.Request)
}
// Based on values from https://blog.cloudflare.com/the-complete-guide-to-golang-net-http-timeouts/
// And https://blog.cloudflare.com/exposing-go-on-the-internet/
const (
READ_TIMEOUT = 30 * time.Second
WRITE_TIMEOUT = 30 * time.Second
IDLE_TIMEOUT = 120 * time.Second
)
// Proxy foomo proxy
type Proxy struct {
foomo *foomo.Foomo
ReverseProxy *httputil.ReverseProxy
handlers []Handler
listeners []Listener
auth *Auth
ServeHTTPFunc func(http.ResponseWriter, *http.Request)
}
// Server server for Proxy
type Server struct {
Proxy *Proxy
Config *Config
TLSConfig *tls.Config
Foomo *foomo.Foomo
}
// NewProxy constructor
func NewProxy(f *foomo.Foomo) *Proxy {
proxy := &Proxy{
foomo: f,
}
proxy.ServeHTTPFunc = proxy.serveHTTP
proxy.ReverseProxy = httputil.NewSingleHostReverseProxy(proxy.foomo.URL)
return proxy
}
func (proxy *Proxy) ServeHTTP(w http.ResponseWriter, incomingRequest *http.Request) {
proxy.ServeHTTPFunc(w, incomingRequest)
}
func (proxy *Proxy) serveHTTP(w http.ResponseWriter, incomingRequest *http.Request) {
if proxy.auth != nil && len(proxy.auth.Domain) > 0 {
//&& !proxy.foomo.BasicAuthForRequest(w, incomingRequest, proxy.auth.Domain, proxy.auth.Realm, "access denied") {
}
for _, listener := range proxy.listeners {
w = listener.ListenServeHTTPStart(w, incomingRequest)
}
for _, handler := range proxy.handlers {
if handler.HandlesRequest(incomingRequest) {
handler.ServeHTTP(w, incomingRequest)
return
}
}
incomingRequest.Host = proxy.foomo.URL.Host
// incomingRequest.URL.Opaque = incomingRequest.RequestURI + incomingRequest.
proxy.ReverseProxy.ServeHTTP(w, incomingRequest)
for _, listener := range proxy.listeners {
listener.ListenServeHTTPDone(w, incomingRequest)
}
}
// AddHandler add a handler
func (proxy *Proxy) AddHandler(handler Handler) {
proxy.handlers = append(proxy.handlers, handler)
}
// AddListener deprecated
func (proxy *Proxy) AddListener(listener Listener) {
proxy.listeners = append(proxy.listeners, listener)
}
// NewServerWithConfig constructor with a config file
func NewServerWithConfig(filename string) (p *Server, err error) {
config, err := ReadConfig(filename)
if err != nil {
return nil, err
}
return NewServer(config)
}
// NewServer constructor with config struct
func NewServer(config *Config) (p *Server, err error) {
p = &Server{
Config: config,
}
f, err := foomo.NewFoomo(config.Foomo.Dir, config.Foomo.RunMode, config.Foomo.Address)
if err != nil {
return nil, err
}
p.Foomo = f
p.Proxy = NewProxy(p.Foomo)
p.Proxy.auth = config.Server.Auth
p.TLSConfig = tlsconfig.NewServerTLSConfig(p.Config.Server.TLS.Mode)
return p, nil
}
func (p *Server) ListenAndServe() error {
return p.listenAndServeWithHandler(p.Proxy)
}
func (p *Server) ListenAndServeWithHandler(handler http.Handler) error {
return p.listenAndServeWithHandler(handler)
}
// ListenAndServe until things go bad, depending upon configuration this will\
// listen to http and https requests
func (p *Server) listenAndServeWithHandler(handler http.Handler) error {
c := p.Config.Server
errorChan := make(chan error)
startedHTTPS := false
startedHTTP := false
if p.Config.hasCertificates() {
log.Println("listening for https on", c.TLS.Address)
certificates, certificatesErr := p.Config.getCertificates()
if certificatesErr != nil {
return errors.New("could not load certificates: " + certificatesErr.Error())
}
p.TLSConfig.Certificates = certificates
p.TLSConfig.BuildNameToCertificate()
go func() {
tlsServer := &http.Server{
ReadTimeout: READ_TIMEOUT,
WriteTimeout: WRITE_TIMEOUT,
IdleTimeout: IDLE_TIMEOUT,
Addr: c.TLS.Address,
Handler: handler,
TLSConfig: p.TLSConfig,
}
errorChan <- tlsServer.ListenAndServeTLS("", "")
}()
startedHTTPS = true
}
if len(c.Address) > 0 {
log.Println("listening for http on", c.Address)
go func() {
errorChan <- http.ListenAndServe(c.Address, handler)
}()
startedHTTP = true
}
if !startedHTTP && !startedHTTPS {
return errors.New("nothing to listen to")
}
err := <-errorChan
return err
}