Merge branch '8784-dir-listings'
[arvados.git] / sdk / go / httpserver / httpserver.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: Apache-2.0
4
5 package httpserver
6
7 import (
8         "net"
9         "net/http"
10         "sync"
11         "time"
12 )
13
14 type Server struct {
15         http.Server
16         Addr     string // host:port where the server is listening.
17         err      error
18         cond     *sync.Cond
19         running  bool
20         listener *net.TCPListener
21         wantDown bool
22 }
23
24 // Start is essentially (*http.Server)ListenAndServe() with two more
25 // features: (1) by the time Start() returns, Addr is changed to the
26 // address:port we ended up listening to -- which makes listening on
27 // ":0" useful in test suites -- and (2) the server can be shut down
28 // without killing the process -- which is useful in test cases, and
29 // makes it possible to shut down gracefully on SIGTERM without
30 // killing active connections.
31 func (srv *Server) Start() error {
32         addr, err := net.ResolveTCPAddr("tcp", srv.Addr)
33         if err != nil {
34                 return err
35         }
36         srv.listener, err = net.ListenTCP("tcp", addr)
37         if err != nil {
38                 return err
39         }
40         srv.Addr = srv.listener.Addr().String()
41
42         mutex := &sync.RWMutex{}
43         srv.cond = sync.NewCond(mutex.RLocker())
44         srv.running = true
45         go func() {
46                 err = srv.Serve(tcpKeepAliveListener{srv.listener})
47                 if !srv.wantDown {
48                         srv.err = err
49                 }
50                 mutex.Lock()
51                 srv.running = false
52                 srv.cond.Broadcast()
53                 mutex.Unlock()
54         }()
55         return nil
56 }
57
58 // Close shuts down the server and returns when it has stopped.
59 func (srv *Server) Close() error {
60         srv.wantDown = true
61         srv.listener.Close()
62         return srv.Wait()
63 }
64
65 // Wait returns when the server has shut down.
66 func (srv *Server) Wait() error {
67         if srv.cond == nil {
68                 return nil
69         }
70         srv.cond.L.Lock()
71         defer srv.cond.L.Unlock()
72         for srv.running {
73                 srv.cond.Wait()
74         }
75         return srv.err
76 }
77
78 // tcpKeepAliveListener is copied from net/http because not exported.
79 type tcpKeepAliveListener struct {
80         *net.TCPListener
81 }
82
83 func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
84         tc, err := ln.AcceptTCP()
85         if err != nil {
86                 return
87         }
88         tc.SetKeepAlive(true)
89         tc.SetKeepAlivePeriod(3 * time.Minute)
90         return tc, nil
91 }