1
0
mirror of https://github.com/umputun/reproxy.git synced 2025-06-30 22:13:42 +02:00
Files
reproxy/app/proxy/proxy.go

255 lines
6.8 KiB
Go
Raw Normal View History

2021-04-01 02:37:28 -05:00
package proxy
import (
"context"
2021-04-05 22:12:06 -05:00
"io"
2021-04-04 02:57:34 -05:00
"net"
2021-04-01 02:37:28 -05:00
"net/http"
"net/http/httputil"
"net/url"
2021-04-03 01:20:24 -05:00
"regexp"
"strconv"
2021-04-02 00:07:36 -05:00
"strings"
2021-04-01 02:37:28 -05:00
"time"
2021-04-02 03:13:49 -05:00
log "github.com/go-pkgz/lgr"
2021-04-03 00:22:54 -05:00
R "github.com/go-pkgz/rest"
2021-04-05 22:12:06 -05:00
"github.com/gorilla/handlers"
2021-04-03 00:22:54 -05:00
"github.com/pkg/errors"
2021-04-05 22:12:06 -05:00
2021-04-05 03:37:28 -05:00
"github.com/umputun/reproxy/app/discovery"
2021-04-01 02:37:28 -05:00
)
2021-04-03 01:20:24 -05:00
// Http is a proxy server for both http and https
2021-04-09 15:05:22 -05:00
type Http struct { //nolint golint
2021-04-01 02:37:28 -05:00
Matcher
Address string
TimeOut time.Duration
AssetsLocation string
AssetsWebRoot string
MaxBodySize int64
GzEnabled bool
ProxyHeaders []string
SSLConfig SSLConfig
Version string
AccessLog io.Writer
DisableSignature bool
2021-04-01 02:37:28 -05:00
}
2021-04-03 01:20:24 -05:00
// Matcher source info (server and route) to the destination url
// If no match found return ok=false
2021-04-01 02:37:28 -05:00
type Matcher interface {
2021-04-02 00:07:36 -05:00
Match(srv, src string) (string, bool)
2021-04-04 15:55:06 -05:00
Servers() (servers []string)
2021-04-09 15:05:22 -05:00
Mappers() (mappers []discovery.URLMapper)
2021-04-01 02:37:28 -05:00
}
2021-04-03 00:22:54 -05:00
// Run the lister and request's router, activate rest server
func (h *Http) Run(ctx context.Context) error {
2021-04-01 02:37:28 -05:00
2021-04-03 00:22:54 -05:00
if h.AssetsLocation != "" {
log.Printf("[DEBUG] assets file server enabled for %s, webroot %s", h.AssetsLocation, h.AssetsWebRoot)
2021-04-01 02:37:28 -05:00
}
2021-04-02 03:13:49 -05:00
var httpServer, httpsServer *http.Server
go func() {
<-ctx.Done()
if httpServer != nil {
if err := httpServer.Close(); err != nil {
log.Printf("[ERROR] failed to close proxy http server, %v", err)
}
}
if httpsServer != nil {
if err := httpsServer.Close(); err != nil {
log.Printf("[ERROR] failed to close proxy https server, %v", err)
}
}
}()
2021-04-03 00:22:54 -05:00
handler := R.Wrap(h.proxyHandler(),
2021-04-09 15:05:22 -05:00
R.Recoverer(log.Default()),
h.signatureHandler,
2021-04-03 00:22:54 -05:00
R.Ping,
2021-04-05 03:37:28 -05:00
h.healthMiddleware,
2021-04-05 22:12:06 -05:00
h.accessLogHandler(h.AccessLog),
2021-04-03 00:22:54 -05:00
R.SizeLimit(h.MaxBodySize),
R.Headers(h.ProxyHeaders...),
2021-04-02 03:13:49 -05:00
h.gzipHandler(),
)
if len(h.SSLConfig.FQDNs) == 0 {
h.SSLConfig.FQDNs = h.Servers() // fill all discovered if nothing defined
}
2021-04-02 03:13:49 -05:00
switch h.SSLConfig.SSLMode {
2021-04-03 00:22:54 -05:00
case SSLNone:
2021-04-02 03:13:49 -05:00
log.Printf("[INFO] activate http proxy server on %s", h.Address)
httpServer = h.makeHTTPServer(h.Address, handler)
httpServer.ErrorLog = log.ToStdLogger(log.Default(), "WARN")
2021-04-03 00:22:54 -05:00
return httpServer.ListenAndServe()
case SSLStatic:
2021-04-02 03:13:49 -05:00
log.Printf("[INFO] activate https server in 'static' mode on %s", h.Address)
httpsServer = h.makeHTTPSServer(h.Address, handler)
httpsServer.ErrorLog = log.ToStdLogger(log.Default(), "WARN")
2021-04-09 15:05:22 -05:00
httpServer = h.makeHTTPServer(h.toHTTP(h.Address, h.SSLConfig.RedirHTTPPort), h.httpToHTTPSRouter())
2021-04-02 03:13:49 -05:00
httpServer.ErrorLog = log.ToStdLogger(log.Default(), "WARN")
go func() {
2021-04-09 15:05:22 -05:00
log.Printf("[INFO] activate http redirect server on %s", h.toHTTP(h.Address, h.SSLConfig.RedirHTTPPort))
2021-04-02 03:13:49 -05:00
err := httpServer.ListenAndServe()
log.Printf("[WARN] http redirect server terminated, %s", err)
}()
2021-04-03 00:22:54 -05:00
return httpServer.ListenAndServeTLS(h.SSLConfig.Cert, h.SSLConfig.Key)
case SSLAuto:
2021-04-02 03:13:49 -05:00
log.Printf("[INFO] activate https server in 'auto' mode on %s", h.Address)
log.Printf("[DEBUG] FQDNs %v", h.SSLConfig.FQDNs)
2021-04-02 03:13:49 -05:00
m := h.makeAutocertManager()
httpsServer = h.makeHTTPSAutocertServer(h.Address, handler, m)
httpsServer.ErrorLog = log.ToStdLogger(log.Default(), "WARN")
2021-04-09 15:05:22 -05:00
httpServer = h.makeHTTPServer(h.toHTTP(h.Address, h.SSLConfig.RedirHTTPPort), h.httpChallengeRouter(m))
2021-04-02 03:13:49 -05:00
httpServer.ErrorLog = log.ToStdLogger(log.Default(), "WARN")
go func() {
2021-04-09 15:05:22 -05:00
log.Printf("[INFO] activate http challenge server on port %s", h.toHTTP(h.Address, h.SSLConfig.RedirHTTPPort))
2021-04-02 03:13:49 -05:00
err := httpServer.ListenAndServe()
log.Printf("[WARN] http challenge server terminated, %s", err)
}()
2021-04-03 00:22:54 -05:00
return httpsServer.ListenAndServeTLS("", "")
2021-04-02 03:13:49 -05:00
}
2021-04-03 00:22:54 -05:00
return errors.Errorf("unknown SSL type %v", h.SSLConfig.SSLMode)
2021-04-02 03:13:49 -05:00
}
2021-04-01 02:37:28 -05:00
func (h *Http) proxyHandler() http.HandlerFunc {
type contextKey string
reverseProxy := &httputil.ReverseProxy{
Director: func(r *http.Request) {
ctx := r.Context()
uu := ctx.Value(contextKey("url")).(*url.URL)
r.URL.Path = uu.Path
r.URL.Host = uu.Host
r.URL.Scheme = uu.Scheme
r.Header.Add("X-Forwarded-Host", uu.Host)
r.Header.Add("X-Origin-Host", r.Host)
2021-04-04 02:57:34 -05:00
h.setXRealIP(r)
2021-04-01 02:37:28 -05:00
},
2021-04-09 20:55:21 -05:00
Transport: &http.Transport{
ResponseHeaderTimeout: h.TimeOut,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).DialContext,
ForceAttemptHTTP2: true,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
},
}
2021-04-01 02:37:28 -05:00
// default assetsHandler disabled, returns error on missing matches
assetsHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Printf("[WARN] mo match for %s", r.URL)
http.Error(w, "Server error", http.StatusBadGateway)
})
if h.AssetsLocation != "" && h.AssetsWebRoot != "" {
2021-04-09 15:05:22 -05:00
fs, err := R.FileServer(h.AssetsWebRoot, h.AssetsLocation)
2021-04-01 02:37:28 -05:00
if err == nil {
assetsHandler = func(w http.ResponseWriter, r *http.Request) {
fs.ServeHTTP(w, r)
}
}
}
return func(w http.ResponseWriter, r *http.Request) {
2021-04-02 03:13:49 -05:00
server := r.URL.Hostname()
if server == "" {
server = strings.Split(r.Host, ":")[0]
}
2021-04-02 00:07:36 -05:00
u, ok := h.Match(server, r.URL.Path)
2021-04-01 02:37:28 -05:00
if !ok {
assetsHandler.ServeHTTP(w, r)
return
}
uu, err := url.Parse(u)
if err != nil {
http.Error(w, "Server error", http.StatusBadGateway)
return
}
ctx := context.WithValue(r.Context(), contextKey("url"), uu) // set destination url in request's context
reverseProxy.ServeHTTP(w, r.WithContext(ctx))
}
}
2021-04-02 03:13:49 -05:00
2021-04-09 15:05:22 -05:00
func (h *Http) toHTTP(address string, httpPort int) string {
2021-04-05 22:12:06 -05:00
rx := regexp.MustCompile(`(.*):(\d*)`)
return rx.ReplaceAllString(address, "$1:") + strconv.Itoa(httpPort)
}
func (h *Http) gzipHandler() func(next http.Handler) http.Handler {
if h.GzEnabled {
2021-04-09 15:26:06 -05:00
return handlers.CompressHandler
2021-04-05 22:12:06 -05:00
}
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
next.ServeHTTP(w, r)
})
}
}
func (h *Http) signatureHandler(next http.Handler) http.Handler {
if h.DisableSignature {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
next.ServeHTTP(w, r)
})
}
return R.AppInfo("reproxy", "umputun", h.Version)(next)
}
2021-04-05 22:12:06 -05:00
func (h *Http) accessLogHandler(wr io.Writer) func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return handlers.CombinedLoggingHandler(wr, next)
}
}
2021-04-02 03:13:49 -05:00
func (h *Http) makeHTTPServer(addr string, router http.Handler) *http.Server {
return &http.Server{
Addr: addr,
Handler: router,
ReadHeaderTimeout: 5 * time.Second,
WriteTimeout: 30 * time.Second,
IdleTimeout: 30 * time.Second,
}
}
2021-04-04 02:57:34 -05:00
func (h *Http) setXRealIP(r *http.Request) {
remoteIP := r.Header.Get("X-Forwarded-For")
if remoteIP == "" {
remoteIP = r.RemoteAddr
}
ip, _, err := net.SplitHostPort(remoteIP)
2021-04-04 02:57:34 -05:00
if err != nil {
return
}
userIP := net.ParseIP(ip)
if userIP == nil {
return
}
r.Header.Add("X-Real-IP", ip)
}