diff options
-rw-r--r-- | app.go | 46 | ||||
-rw-r--r-- | app_config.go | 16 | ||||
-rw-r--r-- | main.go | 99 | ||||
-rw-r--r-- | server.go | 59 |
4 files changed, 161 insertions, 59 deletions
@@ -2,15 +2,18 @@ package main import ( "crypto/tls" + "log" "net/http" "strings" "sync" + "time" ) const xForwardedProto = "X-Forwarded-Proto" const xForwardedProtoHTTPS = "https" type theApp struct { + appConfig domains domains lock sync.RWMutex } @@ -78,3 +81,46 @@ func (a *theApp) UpdateDomains(domains domains) { defer a.lock.Unlock() a.domains = domains } + +func (a *theApp) Run() { + var wg sync.WaitGroup + + if a.ListenHTTP != 0 { + wg.Add(1) + go func() { + defer wg.Done() + err := listenAndServe(a.ListenHTTP, a.ServeHTTP, nil) + if err != nil { + log.Fatal(err) + } + }() + } + + // Listen for HTTPS + if a.ListenHTTPS != 0 { + wg.Add(1) + go func() { + defer wg.Done() + err := listenAndServeTLS(a.ListenHTTPS, a.RootCertificate, a.RootKey, a.ServeHTTP, a.ServeTLS) + if err != nil { + log.Fatal(err) + } + }() + } + + // Listen for HTTP proxy requests + if a.listenProxy != 0 { + wg.Add(1) + go func() { + defer wg.Done() + err := listenAndServe(a.listenProxy, a.ServeProxy, nil) + if err != nil { + log.Fatal(err) + } + }() + } + + go watchDomains(a.UpdateDomains, time.Second) + + wg.Wait() +} diff --git a/app_config.go b/app_config.go new file mode 100644 index 00000000..d007ca39 --- /dev/null +++ b/app_config.go @@ -0,0 +1,16 @@ +package main + +type appConfig struct { + Domain string + RootDir string + + RootCertificate []byte + RootKey []byte + + ListenHTTP uintptr + ListenHTTPS uintptr + listenProxy uintptr + + HTTP2 bool + ServeHTTP bool +} @@ -5,8 +5,8 @@ import ( "fmt" "log" "path/filepath" - "sync" - "time" + "io/ioutil" + "net" ) // VERSION stores the information about the semantic version of application @@ -15,70 +15,81 @@ var VERSION = "dev" // REVISION stores the information about the git revision of application var REVISION = "HEAD" -var listenHTTP = flag.String("listen-http", ":80", "The address to listen for HTTP requests") -var listenHTTPS = flag.String("listen-https", "", "The address to listen for HTTPS requests") -var listenProxy = flag.String("listen-proxy", "", "The address to listen for proxy requests") var pagesDomain = flag.String("pages-domain", "gitlab-example.com", "The domain to serve static pages") -var pagesRootCert = flag.String("root-cert", "", "The default path to file certificate to serve static pages") -var pagesRootKey = flag.String("root-key", "", "The default path to file certificate to serve static pages") var serverHTTP = flag.Bool("serve-http", true, "Serve the pages under HTTP") var http2proto = flag.Bool("http2", true, "Enable HTTP2 support") var pagesRoot = flag.String("pages-root", "shared/pages", "The directory where pages are stored") -func resolve() { - fullPath, err := filepath.EvalSymlinks(*pagesRoot) +func evalSymlinks(directory string) (result string) { + result, err := filepath.EvalSymlinks(directory) if err != nil { log.Fatalln(err) } - *pagesRoot = fullPath + return +} + +func readFile(file string) (result []byte) { + result, err := ioutil.ReadFile(file) + if err != nil { + log.Fatalln(err) + } + return +} + +func createSocket(addr string) (l net.Listener, fd uintptr) { + l, err := net.Listen("tcp", addr) + if err != nil { + log.Fatalln(err) + } + + f, err := l.(*net.TCPListener).File() + if err != nil { + log.Fatalln(err) + } + + fd = f.Fd() + return } func main() { - var wg sync.WaitGroup - var app theApp + var listenHTTP = flag.String("listen-http", ":80", "The address to listen for HTTP requests") + var listenHTTPS = flag.String("listen-https", "", "The address to listen for HTTPS requests") + var listenProxy = flag.String("listen-proxy", "", "The address to listen for proxy requests") + var pagesRootCert = flag.String("root-cert", "", "The default path to file certificate to serve static pages") + var pagesRootKey = flag.String("root-key", "", "The default path to file certificate to serve static pages") fmt.Printf("GitLab Pages Daemon %s (%s)", VERSION, REVISION) fmt.Printf("URL: https://gitlab.com/gitlab-org/gitlab-pages") flag.Parse() - resolve() - // Listen for HTTP + var app theApp + + app.Domain = *pagesDomain + app.RootDir = evalSymlinks(*pagesRoot) + + if *pagesRootCert != "" { + app.RootCertificate = readFile(*pagesRootCert) + } + + if *pagesRootKey != "" { + app.RootKey = readFile(*pagesRootKey) + } + if *listenHTTP != "" { - wg.Add(1) - go func() { - defer wg.Done() - err := listenAndServe(*listenHTTP, app.ServeHTTP) - if err != nil { - log.Fatal(err) - } - }() + var l net.Listener + l, app.ListenHTTP = createSocket(*listenHTTP) + defer l.Close() } - // Listen for HTTPS if *listenHTTPS != "" { - wg.Add(1) - go func() { - defer wg.Done() - err := listenAndServeTLS(*listenHTTPS, *pagesRootCert, *pagesRootKey, app.ServeHTTP, app.ServeTLS) - if err != nil { - log.Fatal(err) - } - }() + var l net.Listener + l, app.ListenHTTPS = createSocket(*listenHTTPS) + defer l.Close() } - // Listen for HTTP proxy requests if *listenProxy != "" { - wg.Add(1) - go func() { - defer wg.Done() - err := listenAndServe(*listenProxy, app.ServeProxy) - if err != nil { - log.Fatal(err) - } - }() + var l net.Listener + l, app.ListenHTTPS = createSocket(*listenProxy) + defer l.Close() } - - go watchDomains(app.UpdateDomains, time.Second) - - wg.Wait() } @@ -4,29 +4,31 @@ import ( "crypto/tls" "golang.org/x/net/http2" "net/http" +"net" + "time" + "os" + "fmt" ) type tlsHandlerFunc func(*tls.ClientHelloInfo) (*tls.Certificate, error) -func listenAndServe(addr string, handler http.HandlerFunc) error { - // create server - server := &http.Server{Addr: addr, Handler: handler} +type tcpKeepAliveListener struct { + *net.TCPListener +} - if *http2proto { - err := http2.ConfigureServer(server, &http2.Server{}) - if err != nil { - return err - } +func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) { + tc, err := ln.AcceptTCP() + if err != nil { + return } - - return server.ListenAndServe() + tc.SetKeepAlive(true) + tc.SetKeepAlivePeriod(3 * time.Minute) + return tc, nil } -func listenAndServeTLS(addr string, certFile, keyFile string, handler http.HandlerFunc, tlsHandler tlsHandlerFunc) error { +func listenAndServe(fd uintptr, handler http.HandlerFunc, tlsConfig *tls.Config) error { // create server - server := &http.Server{Addr: addr, Handler: handler} - server.TLSConfig = &tls.Config{} - server.TLSConfig.GetCertificate = tlsHandler + server := &http.Server{Handler: handler, TLSConfig: tlsConfig} if *http2proto { err := http2.ConfigureServer(server, &http2.Server{}) @@ -35,5 +37,32 @@ func listenAndServeTLS(addr string, certFile, keyFile string, handler http.Handl } } - return server.ListenAndServeTLS(certFile, keyFile) + l, err := net.FileListener(os.NewFile(fd, "[socket]")) + if err != nil { + return fmt.Errorf("failed to listen on FD %d: %v", fd, err) + } + + if tlsConfig != nil { + tlsListener := tls.NewListener(tcpKeepAliveListener{l.(*net.TCPListener)}, server.TLSConfig) + return server.Serve(tlsListener) + } else { + return server.Serve(&tcpKeepAliveListener{l.(*net.TCPListener)}) + } +} + +func listenAndServeTLS(fd uintptr, cert, key []byte, handler http.HandlerFunc, tlsHandler tlsHandlerFunc) error { + certificate, err := tls.X509KeyPair(cert, key) + if err != nil { + return err + } + + tlsConfig := &tls.Config{} + tlsConfig.GetCertificate = tlsHandler + tlsConfig.NextProtos = []string { + "http/1.1", + } + tlsConfig.Certificates = []tls.Certificate{ + certificate, + } + return listenAndServe(fd, handler, tlsConfig) } |