Skip to content

Instantly share code, notes, and snippets.

@cbluth
Forked from dbrinegar/revproxy.go
Created February 5, 2024 09:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cbluth/f937518c23339a82bbba32acf29e03f5 to your computer and use it in GitHub Desktop.
Save cbluth/f937518c23339a82bbba32acf29e03f5 to your computer and use it in GitHub Desktop.
transparent reverse proxy with basic auth and auto-follow
package main
import (
"log"
"net/http"
"net/http/httputil"
"net/url"
"gopkg.in/gcfg.v1"
)
/*
example config.ini
[Proxy]
Listen = :8001
RemoteBase = https://some.service.com/protected/path
Username = username
Password = password
*/
type Config struct {
Proxy struct {
RemoteBase string
Username string
Password string
Listen string
}
}
// like httputil.NewSingleHostReverseProxy but with BasicAuth and Host header rewrite
func NewReverseProxy(target *url.URL, user string, pass string) *httputil.ReverseProxy {
targetQuery := target.RawQuery
director := func(req *http.Request) {
req.URL.Scheme = target.Scheme
req.URL.Host = target.Host
req.URL.Path = req.URL.Path
req.URL.RawQuery = targetQuery + req.URL.RawQuery
if _, ok := req.Header["User-Agent"]; !ok {
req.Header.Set("User-Agent", "") // no default agent
}
// extensions to NewSingleHostReverseProxy:
req.SetBasicAuth(user, pass)
req.Host = target.Host
}
return &httputil.ReverseProxy{Director: director}
}
func main() {
config := &Config{}
err := gcfg.ReadFileInto(config, "config.ini")
if err != nil {
panic(err)
}
remote, err := url.Parse(config.Proxy.RemoteBase)
if err != nil {
panic(err)
}
// turn remote path into our route, must begin and end with a slash to match patterns
route := remote.Path
remote.Path = ""
if route == "" {
route = "/"
}
if route[0] != '/' {
route = "/" + route
}
if route[len(route)-1] != '/' {
route = route + "/"
}
proxy := NewReverseProxy(remote, config.Proxy.Username, config.Proxy.Password)
proxy.ModifyResponse = func(r *http.Response) error {
if r.StatusCode != http.StatusOK {
log.Printf("request for %s got %v", r.Request.URL.String(), r.StatusCode)
}
if location, err := r.Location(); err == nil {
log.Printf("fetching: %+v", location.String())
resp, _ := http.DefaultClient.Get(location.String())
*r = *resp
}
return nil
}
log.Println("proxy for " + route)
http.HandleFunc(route, func(w http.ResponseWriter, r *http.Request) {
log.Print(r.URL.Path)
proxy.ServeHTTP(w, r)
})
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
log.Print("unhandled: " + r.URL.Path)
})
http.ListenAndServe(config.Proxy.Listen, nil)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment