5416: arv-git-httpd provides HTTP access to git repositories, using Arvados token...
[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         done     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         go func() {
58                 err = srv.Serve(tcpKeepAliveListener{srv.listener})
59                 if !srv.wantDown {
60                         srv.err = err
61                 }
62                 mutex.Lock()
63                 srv.done = true
64                 srv.cond.Broadcast()
65                 mutex.Unlock()
66         }()
67         return nil
68 }
69
70 // Wait returns when the server has shut down.
71 func (srv *server) Wait() error {
72         srv.cond.L.Lock()
73         defer srv.cond.L.Unlock()
74         for !srv.done {
75                 srv.cond.Wait()
76         }
77         return srv.err
78 }
79
80 // Close shuts down the server and returns when it has stopped.
81 func (srv *server) Close() error {
82         srv.wantDown = true
83         srv.listener.Close()
84         return srv.Wait()
85 }
86
87 // tcpKeepAliveListener is copied from net/http because not exported.
88 //
89 type tcpKeepAliveListener struct {
90         *net.TCPListener
91 }
92
93 func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
94         tc, err := ln.AcceptTCP()
95         if err != nil {
96                 return
97         }
98         tc.SetKeepAlive(true)
99         tc.SetKeepAlivePeriod(3 * time.Minute)
100         return tc, nil
101 }