Capstan is a Golang web framework that shares some similarities with others in its segment.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

378 lines
10KB

  1. package capstan
  2. import (
  3. "context"
  4. "crypto/tls"
  5. "crypto/x509"
  6. "fmt"
  7. "net"
  8. "net/http"
  9. "strings"
  10. "time"
  11. "git.destrealm.org/go/capstan/internal/sys"
  12. . "git.destrealm.org/go/capstan/errors"
  13. )
  14. // TODO: Consider refactoring named listeners to store their network address
  15. // instead.
  16. // Apply http.Server instances to all stored listeners. Errors will be returned
  17. // via the error channel errc.
  18. //
  19. // This increments the Router waitgroup once for each configured listener.
  20. func (r *Router) listen(errc chan error) {
  21. for _, listener := range r.listeners {
  22. r.AttachListener(listener)
  23. }
  24. }
  25. // Setup listeners to all configured addresses. Currently this only sets up 3
  26. // listeners for each of the following, if configured: ListenAddress,
  27. // ListenAddressTLS, and ListenSocket.
  28. //
  29. // If you wish to attach custom listeners, you must call AddListener (below) and
  30. // it should implement both net.Listener and syscall.Conn. While simply
  31. // implementing net.Listener should be sufficient, it may fail for high
  32. // availability configurations that expect graceful restart capabilities.
  33. func (r *Router) setupListeners() error {
  34. var requiresPriv bool
  35. config := r.app.Config()
  36. if !r.app.IsMaster() {
  37. return ErrNotMaster
  38. }
  39. r.proxy.Switch(r.mux)
  40. if len(r.listeners) == 0 {
  41. requiresPriv = isPrivPort(r.app.Config().ListenAddress)
  42. if listener, err := r.ListenHTTP(r.app.Config().ListenAddress); err != nil {
  43. return err
  44. } else {
  45. r.namedListeners.tcp = listener
  46. r.AddListener(listener)
  47. }
  48. requiresPriv = isPrivPort(r.app.Config().ListenAddressTLS)
  49. if listener, err := r.ListenTLS(r.app.Config().ListenAddressTLS); err != nil {
  50. return err
  51. } else {
  52. r.namedListeners.tls = listener
  53. r.AddListener(listener)
  54. }
  55. if listener, err := r.ListenSocket(r.app.Config().ListenSocket); err != nil {
  56. return err
  57. } else {
  58. r.namedListeners.sock = listener
  59. r.AddListener(listener)
  60. }
  61. }
  62. if requiresPriv && sys.HasSuperuser() {
  63. if config.RunAs == "" {
  64. return fmt.Errorf("application is running as superuser with no RunAs configured")
  65. }
  66. err := sys.SwitchUser(config.RunAs)
  67. if err != nil {
  68. return fmt.Errorf("unable to drop privileges: %v", err)
  69. }
  70. }
  71. return nil
  72. }
  73. // AddListener accepts a listener against which the Chi handler will be attached
  74. // and will be used as the base listener for an HTTP or HTTPS service.
  75. //
  76. // This listener will be mapped to the configured network management as defined
  77. // in network.go and netlisten_*.go. If the specified listener does not
  78. // implement both net.Listen and syscall.Conn it will not be used for high
  79. // availability graceful restart. If you depend on a custom listener, you may
  80. // need to implement graceful restart yourself or implement syscall.Conn.
  81. func (r *Router) AddListener(listener net.Listener) {
  82. if listener != nil {
  83. r.listeners = append(r.listeners, listener)
  84. }
  85. }
  86. // AttachListener to the current route handler.
  87. //
  88. // This may be used by external code to enable listeners.
  89. func (r *Router) AttachListener(listener net.Listener) {
  90. var server HTTPServer
  91. server.Handler = r.proxy
  92. server.ReadHeaderTimeout = time.Second * time.Duration(r.app.Config().HeaderTimeout)
  93. server.IdleTimeout = time.Second * time.Duration(r.app.Config().IdleConnectionTimeout)
  94. server.MaxHeaderBytes = r.app.Config().MaxHeaderLength
  95. r.servers = append(r.servers, &server)
  96. r.wg.Add(1)
  97. go func(l net.Listener) {
  98. if err := server.Serve(l); err != http.ErrServerClosed {
  99. r.Close()
  100. }
  101. r.wg.Done()
  102. }(listener)
  103. }
  104. func (r *Router) AttachNamedListener(name string, listener net.Listener) {
  105. switch name {
  106. case "tls":
  107. r.namedListeners.tls = listener
  108. case "tcp":
  109. r.namedListeners.tcp = listener
  110. case "sock":
  111. fallthrough
  112. case "socket":
  113. fallthrough
  114. case "file":
  115. r.namedListeners.sock = listener
  116. }
  117. r.AttachListener(listener)
  118. }
  119. // Close all configured listeners gracefully. This calls Shutdown on each
  120. // http.Server and waits until all connections have closed before terminating.
  121. func (r *Router) Close() {
  122. r.app.Logger().Noticef("Shutting down (waiting max %d second(s) for connections to close)...", r.app.Config().ShutdownTimeout)
  123. for _, server := range r.servers {
  124. ctx, cancel := context.WithTimeout(r.shutdownCtx,
  125. time.Second*time.Duration(r.app.Config().ShutdownTimeout))
  126. defer cancel()
  127. err := server.Shutdown(ctx)
  128. if err != nil {
  129. r.app.Logger().Warning("Error shutting down server:", err)
  130. }
  131. }
  132. for _, listener := range r.listeners {
  133. listener.Close()
  134. }
  135. }
  136. // CloseNamed listener.
  137. //
  138. // This will gracefully shut down the named listener, if available. The special
  139. // names "tls," "tcp," and "file" are used to close reserved listeners for TLS,
  140. // TCP, and Unix domain socket traffic.
  141. //
  142. // This will also remove the listener from the router's listeners store.
  143. func (r *Router) CloseNamed(name string) (err error) {
  144. var listener net.Listener
  145. switch name {
  146. case "tls":
  147. listener = r.namedListeners.tls
  148. case "tcp":
  149. listener = r.namedListeners.tcp
  150. case "sock":
  151. fallthrough
  152. case "socket":
  153. fallthrough
  154. case "file":
  155. listener = r.namedListeners.sock
  156. }
  157. if listener == nil {
  158. return
  159. }
  160. l := make([]net.Listener, 0)
  161. for _, mapped := range r.listeners {
  162. if mapped != listener {
  163. l = append(l, mapped)
  164. }
  165. }
  166. r.listeners = l
  167. r.app.Logger().Error("Shutting down listener (waiting max 10 seconds for connections to close)...")
  168. ctx, _ := context.WithTimeout(r.shutdownCtx, time.Second*10)
  169. for _, server := range r.servers {
  170. if server.Listener() == listener {
  171. if err = server.Shutdown(ctx); err != nil {
  172. r.app.Logger().Warning("error shutting down listener:", err)
  173. return
  174. } else {
  175. StopListening(listener)
  176. }
  177. }
  178. }
  179. switch name {
  180. case "tls":
  181. r.namedListeners.tls = nil
  182. case "tcp":
  183. r.namedListeners.tcp = nil
  184. case "sock":
  185. fallthrough
  186. case "socket":
  187. fallthrough
  188. case "file":
  189. r.namedListeners.sock = nil
  190. }
  191. return
  192. }
  193. // Listen on all configured listeners and wait until they close.
  194. //
  195. // If an error is received by this function it will call Router.Close itself and
  196. // begin the shutdown process.
  197. func (r *Router) Listen() error {
  198. var err error
  199. errc := make(chan error)
  200. if err := r.setupListeners(); err != nil {
  201. return err
  202. }
  203. r.listen(errc)
  204. go func() {
  205. err = <-errc
  206. if err != nil {
  207. r.Close()
  208. r.wg.Done()
  209. return
  210. }
  211. }()
  212. r.wg.Wait()
  213. return nil
  214. }
  215. // ListenHTTP allows client code to configure a new port to listen on. This
  216. // should not be called if the server is already listening on an HTTP port. This
  217. // is called internally if ListenAddress is set.
  218. func (r *Router) ListenHTTP(addr string) (net.Listener, error) {
  219. // An HTTP port most liekly wasn't specified.
  220. if addr == "" {
  221. return nil, nil
  222. }
  223. return Listen("tcp", addr)
  224. }
  225. // ListenSocket allows client code to configure a new UNIX domain socket to
  226. // listen on. This should not be called if the server is already listening on
  227. // the same socket. This is called internally if ListenSocket is set.
  228. func (r *Router) ListenSocket(sock string) (net.Listener, error) {
  229. if sock == "" {
  230. return nil, nil
  231. }
  232. return Listen("unix", sock)
  233. }
  234. // ListenTLS allows client code to configure a new TLS port to listen on. This
  235. // should not be called if the server is already listening on a TLS port. This
  236. // is called internally if ListenAddressTLS is set.
  237. func (r *Router) ListenTLS(addr string) (net.Listener, error) {
  238. // TLS most likely wasn't specified.
  239. if addr == "" {
  240. return nil, nil
  241. }
  242. var err error
  243. var tc *tls.Config
  244. var pool *x509.CertPool
  245. ciphers := []uint16{
  246. tls.TLS_AES_256_GCM_SHA384,
  247. tls.TLS_AES_128_GCM_SHA256,
  248. tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
  249. tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
  250. tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
  251. tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
  252. tls.TLS_CHACHA20_POLY1305_SHA256,
  253. tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
  254. tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
  255. tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
  256. // No forward secrecy but disabling these may reduce client
  257. // compatibility.
  258. tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
  259. tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
  260. }
  261. config := r.app.Config()
  262. if config.TLSConfig != nil {
  263. tc = config.TLSConfig
  264. } else {
  265. host := config.Hostname
  266. if strings.Contains(host, ":") {
  267. host, _, err = net.SplitHostPort(host)
  268. if err != nil && host == "" {
  269. host = "localhost"
  270. } else if err != nil {
  271. return nil, err
  272. }
  273. }
  274. if config.MinVersion == 0 {
  275. config.MinVersion = tls.VersionTLS11
  276. }
  277. if config.MaxVersion == 0 {
  278. config.MaxVersion = tls.VersionTLS13
  279. }
  280. pool = x509.NewCertPool()
  281. tc = &tls.Config{
  282. Certificates: config.Certificates,
  283. RootCAs: pool,
  284. ServerName: host,
  285. MinVersion: config.MinVersion,
  286. MaxVersion: config.MaxVersion,
  287. CipherSuites: ciphers,
  288. NextProtos: []string{"h2"},
  289. // https://blog.cloudflare.com/exposing-go-on-the-internet/
  290. PreferServerCipherSuites: true,
  291. CurvePreferences: []tls.CurveID{
  292. tls.CurveP256,
  293. tls.X25519,
  294. },
  295. }
  296. }
  297. listener, err := Listen("tcp", addr)
  298. if err != nil {
  299. return nil, err
  300. }
  301. return tls.NewListener(listener, tc), nil
  302. }
  303. // HasSocket returns true if Capstan was configured to listen on a domain
  304. // socket.
  305. //
  306. // As with other Has* functions for listeners, this does not indicate a status
  307. // for manually-configured listeners and applies only to those that were started
  308. // via the Listen* methods (either automatically, via configuration, or
  309. // manually).
  310. func (r *Router) HasSocket() bool {
  311. return r.namedListeners.sock != nil
  312. }
  313. // HasTCP returns true if Capstan was configured to listen on a TCP socket.
  314. //
  315. // As with other Has* functions for listeners, this does not indicate a status
  316. // for manually-configured listeners and applies only to those that were started
  317. // via the Listen* methods (either automatically, via configuration, or
  318. // manually).
  319. func (r *Router) HasTCP() bool {
  320. return r.namedListeners.tcp != nil
  321. }
  322. // HasTLS returns true if Capstan was configured to listen on a TLS socket.
  323. //
  324. // As with other Has* functions for listeners, this does not indicate a status
  325. // for manually-configured listeners and applies only to those that were started
  326. // via the Listen* methods (either automatically, via configuration, or
  327. // manually).
  328. func (r *Router) HasTLS() bool {
  329. return r.namedListeners.tls != nil
  330. }