package main import ( "fmt" "io" "time" "strings" "errors" "flag" "net" "net/http" "net/url" log "github.com/sirupsen/logrus" db "sanine.net/git/phlox/db" ) var P db.Phlox func main() { p := &P var dbfile string flag.StringVar(&dbfile, "db", "/etc/phlox/phlox.conf", "path to the configuration db") flag.Parse() err := p.Open(dbfile) if err != nil { log.Fatal(err) } defer p.Close() addr, err := p.GetHostAddress() if err != nil { log.Fatal(err) } endpoints, err := p.AllEndpoints() if err != nil { log.Fatal(err) } log.Info("configuring reverse proxy...") for _, endpoint := range endpoints { configureEndpoint(endpoint.Path, endpoint.Address) } InitLogin() c := time.Tick(5 * time.Minute) go (func() { p := &P for ;; { _ = <-c // wait for 5 minutes p.CleanSessions(time.Hour) } })() log.Infof("serving on %v", addr) log.Fatal(http.ListenAndServe(addr, nil)) } type Endpoint struct { Path string Origin *url.URL } func configureEndpoint(path, address string) { log.Infof("proxying endpoint %v to %v", path, address) origin, err := url.Parse(address) if err != nil { log.Fatal(err) } end := Endpoint{ Path: path, Origin: origin, } http.HandleFunc(path + "/", func(w http.ResponseWriter, req *http.Request) { log.Infof("REQ: %v", req.URL.Path) proxy(w, req, end) }) } func proxy(w http.ResponseWriter, req *http.Request, end Endpoint) { p := &P cookie, err := req.Cookie("phlox-session-id") if errors.Is(err, http.ErrNoCookie) { // not logged in w.Header().Set("Location", "/login") w.WriteHeader(http.StatusTemporaryRedirect) return } // check cookie check, err := p.CheckSessionId(cookie.Value) if err != nil { w.WriteHeader(http.StatusInternalServerError) fmt.Fprintf(w, "internal server error") return } if !check { // not logged in w.Header().Set("Location", "/login") w.WriteHeader(http.StatusTemporaryRedirect) return } // update modified time p.TouchSessionId(cookie.Value) response := proxyRequest(w, req, end) if response != nil { proxyResponse(w, response) } } func proxyRequest(w http.ResponseWriter, req *http.Request, end Endpoint) *http.Response { // configure host address req.Host = end.Origin.Host req.URL.Host = end.Origin.Host // strip proxy endpoint path from request path req.URL.Path = strings.TrimPrefix(req.URL.Path, end.Path) // set X-Forwarded-For forwardedFor, _, _ := net.SplitHostPort(req.RemoteAddr) req.Header.Set("X-Forwarded-For", forwardedFor) // misc request cleanups req.URL.Scheme = end.Origin.Scheme req.RequestURI = "" // make request response, err := http.DefaultClient.Do(req) if err != nil { w.WriteHeader(http.StatusInternalServerError) fmt.Fprintf(w, "%v", err) log.Error(err) return nil } return response } func proxyResponse(w http.ResponseWriter, response *http.Response) { // copy header for key, values := range response.Header { for _, value := range values { w.Header().Add(key, value) } } // get trailer keys trailerKeys := []string{} for key := range response.Trailer { trailerKeys = append(trailerKeys, key) } if (len(trailerKeys) > 0) { w.Header().Set("Trailer", strings.Join(trailerKeys, ",")) } w.WriteHeader(response.StatusCode) // copy body to client io.Copy(w, response.Body) // write trailers for key, values := range response.Trailer { for _, value := range values { w.Header().Set(key, value) } } }