Merge branch 'master' into 5493-getting-started-modal
[arvados.git] / services / arv-git-httpd / server.go
1 package main
2
3 import (
4         "net"
5         "net/http"
6         "net/http/cgi"
7         "sync"
8         "time"
9 )
10
11 type server struct {
12         http.Server
13         Addr     string // host:port where the server is listening.
14         err      error
15         cond     *sync.Cond
16         running  bool
17         listener *net.TCPListener
18         wantDown bool
19 }
20
21 func (srv *server) Start() error {
22         gitHandler := &cgi.Handler{
23                 Path: theConfig.GitCommand,
24                 Dir:  theConfig.Root,
25                 Env: []string{
26                         "GIT_PROJECT_ROOT=" + theConfig.Root,
27                         "GIT_HTTP_EXPORT_ALL=",
28                 },
29                 InheritEnv: []string{"PATH"},
30                 Args:       []string{"http-backend"},
31         }
32
33         // The rest of the work here is essentially
34         // http.ListenAndServe() with two more features: (1) whoever
35         // called Start() can discover which address:port we end up
36         // listening to -- which makes listening on ":0" useful in
37         // test suites -- and (2) the server can be shut down without
38         // killing the process -- which is useful in test cases, and
39         // makes it possible to shut down gracefully on SIGTERM
40         // without killing active connections.
41
42         addr, err := net.ResolveTCPAddr("tcp", theConfig.Addr)
43         if err != nil {
44                 return err
45         }
46         srv.listener, err = net.ListenTCP("tcp", addr)
47         if err != nil {
48                 return err
49         }
50         srv.Addr = srv.listener.Addr().String()
51         mux := http.NewServeMux()
52         mux.Handle("/", &authHandler{gitHandler})
53         srv.Handler = mux
54
55         mutex := &sync.RWMutex{}
56         srv.cond = sync.NewCond(mutex.RLocker())
57         srv.running = true
58         go func() {
59                 err = srv.Serve(tcpKeepAliveListener{srv.listener})
60                 if !srv.wantDown {
61                         srv.err = err
62                 }
63                 mutex.Lock()
64                 srv.running = false
65                 srv.cond.Broadcast()
66                 mutex.Unlock()
67         }()
68         return nil
69 }
70
71 // Wait returns when the server has shut down.
72 func (srv *server) Wait() error {
73         if srv.cond == nil {
74                 return nil
75         }
76         srv.cond.L.Lock()
77         defer srv.cond.L.Unlock()
78         for srv.running {
79                 srv.cond.Wait()
80         }
81         return srv.err
82 }
83
84 // Close shuts down the server and returns when it has stopped.
85 func (srv *server) Close() error {
86         srv.wantDown = true
87         srv.listener.Close()
88         return srv.Wait()
89 }
90
91 // tcpKeepAliveListener is copied from net/http because not exported.
92 //
93 type tcpKeepAliveListener struct {
94         *net.TCPListener
95 }
96
97 func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
98         tc, err := ln.AcceptTCP()
99         if err != nil {
100                 return
101         }
102         tc.SetKeepAlive(true)
103         tc.SetKeepAlivePeriod(3 * time.Minute)
104         return tc, nil
105 }