Skip to content

Instantly share code, notes, and snippets.

@dmitshur
Last active January 28, 2018 08:48
Show Gist options
  • Save dmitshur/d3b1c1c2215bf5b34190 to your computer and use it in GitHub Desktop.
Save dmitshur/d3b1c1c2215bf5b34190 to your computer and use it in GitHub Desktop.
Server for HTTPS protocol. Redirects to canonical hosts, reverse proxies requests to internal backing servers.
// Server for HTTPS protocol. Redirects to canonical hosts, reverse proxies requests to internal backing servers.
package main
import (
"crypto/tls"
"flag"
"log"
"net/http"
"net/http/httputil"
"time"
)
var (
httpsFlag = flag.String("https", ":https", "Listen for HTTPS connections on this address.")
)
func main() {
flag.Parse()
httpsCanonicalHostRedirector := &canonicalHostRedirector{"https", newRouter()}
tlsConfig, err := loadCertificates()
if err != nil {
log.Fatalln(err)
}
server := &http.Server{Addr: *httpsFlag, TLSConfig: tlsConfig, Handler: httpsCanonicalHostRedirector}
err = server.ListenAndServeTLS("", "")
if err != nil {
log.Fatalln(err)
}
}
// canonicalHostRedirector redirects to a canonical host, when visiting a non-canonical one.
// Otherwise it defers to handler.
type canonicalHostRedirector struct {
scheme string // Scheme to redirect to. E.g., "https".
handler http.Handler // Handler to defer to when canonical host is used.
}
func (r *canonicalHostRedirector) ServeHTTP(w http.ResponseWriter, req *http.Request) {
var canonicalHost string
switch req.Host {
default: // Primary domain.
canonicalHost = "example.com"
case "anotherdomain.org", "www.anotherdomain.org":
canonicalHost = "anotherdomain.org"
case "foobar.thirdexample.org", "www.thirdexample.org", "thirdexample.org":
canonicalHost = "foobar.thirdexample.org"
}
// Redirect to canonical host, if needed.
// TODO: Is it worth it to try to do a case insensitive comparison here, to avoid it needlessly triggerring redirection?
// It can only happen with weird clients; latest Chrome will always send requests with lowercase domains.
if req.Host != canonicalHost {
u := *req.URL
u.Scheme = r.scheme
u.Host = canonicalHost
http.Redirect(w, req, u.String(), http.StatusTemporaryRedirect)
return
}
r.handler.ServeHTTP(w, req)
}
// newRouter returns a reverse proxy that routes to internal backing servers for each domain.
func newRouter() http.Handler {
director := func(req *http.Request) {
switch req.Host {
default: // Primary domain.
req.URL.Scheme = "http"
req.URL.Host = "127.0.0.1:10000"
case "anotherdomain.org":
req.URL.Scheme = "http"
req.URL.Host = "127.0.0.1:10001"
case "foobar.thirdexample.org":
req.URL.Scheme = "http"
req.URL.Host = "127.0.0.1:10002"
}
}
return &httputil.ReverseProxy{
Director: director,
FlushInterval: 1 * time.Second,
}
}
// HTTPS certificate details.
var domains = []struct {
certFile, keyFile string
}{
{
certFile: "/home/user/.lego/certificates/example.com.crt",
keyFile: "/home/user/.lego/certificates/example.com.key",
}, {
certFile: "/home/user/.lego/certificates/anotherdomain.org.crt",
keyFile: "/home/user/.lego/certificates/anotherdomain.org.key",
}, {
certFile: "/home/user/.lego/certificates/foobar.thirdexample.org.crt",
keyFile: "/home/user/.lego/certificates/foobar.thirdexample.org.key",
},
}
func loadCertificates() (*tls.Config, error) {
c := &tls.Config{}
for _, d := range domains {
cert, err := tls.LoadX509KeyPair(d.certFile, d.keyFile)
if err != nil {
return nil, err
}
c.Certificates = append(c.Certificates, cert)
}
c.BuildNameToCertificate()
return c, nil
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment