github.com/haraldrudell/parl@v0.4.176/phttp/https.go (about) 1 /* 2 © 2021–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/) 3 ISC License 4 */ 5 6 package phttp 7 8 import ( 9 "crypto" 10 "crypto/tls" 11 "io/fs" 12 "net" 13 "net/http" 14 "net/netip" 15 16 "github.com/haraldrudell/parl" 17 "github.com/haraldrudell/parl/perrors" 18 "github.com/haraldrudell/parl/pnet" 19 "github.com/haraldrudell/parl/pstrings" 20 ) 21 22 const ( 23 // default port for https: 443, or address for localhost IPv4 or IPv6 port 443 24 HttpsPort uint16 = 443 25 ) 26 27 // Http is an http server instance 28 // - based on [http.Server] 29 // - has listener thread 30 // - all errors sent on channel 31 // - idempotent deferrable panic-free shutdown 32 // - awaitable, observable 33 type Https struct { 34 Http 35 Cert parl.CertificateDer 36 Private crypto.Signer 37 } 38 39 // NewHttp creates http server for default “localhost:443” 40 // - if nearSocket.Addr is invalid, all interfaces for IPv6 if allowed, IPv4 otherwise is used 41 // - if nearSocket.Port is zero: 42 // - — if network is NetworkDefault: ephemeral port 43 // - — otherwise port 443 “:https” is used 44 // - for NetworkDefault, NetworkTCP is used 45 // - panic for bad Network 46 // 47 // Usage: 48 // 49 // var s = NewHttps(netip.AdddrPort{}, pnet.NetworkTCP) 50 // s.HandleFunc("/", myHandler) 51 // defer s.Shutdown() 52 // for err := range s.Listen() { 53 func NewHttps(nearSocket netip.AddrPort, network pnet.Network, certDER parl.CertificateDer, signer crypto.Signer) (hp *Https) { 54 if nearSocket.Port() == 0 && network != pnet.NetworkDefault { 55 nearSocket = netip.AddrPortFrom(nearSocket.Addr(), HttpsPort) 56 } 57 return &Https{ 58 Http: *NewHttp(nearSocket, network), 59 Cert: certDER, 60 Private: signer, 61 } 62 } 63 64 // Listen initiates listening and returns the error channel 65 // - can only be invoked once or panic 66 // - errCh closes on server shutdown 67 // - non-blocking, all errors are sent on the error channel 68 func (s *Https) Listen() (errCh <-chan error) { 69 if !s.NoListen.CompareAndSwap(false, true) { 70 panic(perrors.NewPF("multiple invocations")) 71 } 72 errCh = s.ErrCh.Ch() 73 // listen is deferred so just launch the thread 74 go s.httpsListenerThread() 75 return 76 } 77 78 const ( 79 httpsAddr = ":https" 80 http11 = "http/1.1" 81 ) 82 83 // httpsListenerThread is gorouitn starting listen and 84 // waiting for server to terminate 85 func (s *Https) httpsListenerThread() { 86 defer s.EndListenAwaitable.Close() 87 var err error 88 defer parl.Recover(func() parl.DA { return parl.A() }, &err, s.SendErr) 89 90 // get near tls socket listener 91 // *tls.listener — need this or it’s file certificates 92 var tlsListener net.Listener 93 if tlsListener, err = s.TLS(); err != nil { 94 return 95 } 96 defer s.maybeClose(&tlsListener, &err) 97 98 // set Near socket address 99 if s.Near, err = pnet.AddrPortFromAddr(tlsListener.Addr()); err != nil { 100 return 101 } 102 s.ListenAwaitable.Close() 103 104 // blocks here until Shutdown or Close 105 err = s.Server.Serve(tlsListener) 106 tlsListener = nil 107 108 // on regular close, http.ErrServerClosed 109 if err == http.ErrServerClosed { 110 err = nil // ignore error 111 return // successful return 112 } 113 err = perrors.Errorf("srv.ServeTLS: ‘%w’", err) 114 } 115 116 func (s *Https) TLS() (tlsListener net.Listener, err error) { 117 var httpServer = &s.Server 118 119 // make ServeTLS execute srv.setupHTTP2_ServeTLS go 1.16.6 120 badPath := "%" 121 if err = httpServer.ServeTLS(nil, badPath, badPath); err == nil { 122 err = perrors.New("missing error from srv.ServeTLS") 123 return 124 } 125 126 // ignore the error if expected badPath 127 if pathError, ok := err.(*fs.PathError); ok { 128 if pathError.Path == badPath { 129 err = nil 130 } 131 } 132 133 // failure on other errors 134 if err != nil { 135 err = perrors.Errorf("srv.ServeTLS: '%w'", err) 136 return 137 } 138 139 // get *net.TCPListener from srv.ListenAndServeTLS 140 141 // tcp listener from pnet 142 var listener net.Listener 143 if listener, err = pnet.Listen(s.Network, s.Server.Addr, &s.Cancel); err != nil { 144 return 145 } 146 var _ = net.Listen 147 148 // create a TLS listener from srv.ServeTLS 149 var tlsConfig *tls.Config 150 if httpServer.TLSConfig == nil { 151 tlsConfig = &tls.Config{} 152 } else { 153 tlsConfig = httpServer.TLSConfig.Clone() 154 } 155 if !pstrings.StrSliceContains(tlsConfig.NextProtos, http11) { 156 tlsConfig.NextProtos = append(tlsConfig.NextProtos, http11) 157 } 158 tlsConfig.Certificates = make([]tls.Certificate, 1) 159 tlsCertificate := &tlsConfig.Certificates[0] 160 tlsCertificate.Certificate = append(tlsCertificate.Certificate, s.Cert) // certificate not from file system 161 tlsCertificate.PrivateKey = s.Private // private key not from file system 162 tlsListener = tls.NewListener(listener, tlsConfig) 163 164 return 165 } 166 167 // closes listener if non-nil 168 func (s *Https) maybeClose(listenerp *net.Listener, errp *error) { 169 var listener = *listenerp 170 if listener == nil { 171 return 172 } 173 parl.Close(listener, errp) 174 }