blob: 01766db1063b95d692666f9fb4de1b21537ac009 [file] [log] [blame]
Serge Bazanski3fd70d82018-10-14 08:12:46 -07001package mirko
2
3import (
Serge Bazanskiaa81aa22018-10-14 08:36:05 -07004 "context"
Serge Bazanski3fd70d82018-10-14 08:12:46 -07005 "flag"
6 "fmt"
7 "net"
8 "net/http"
Serge Bazanski4f7ee512018-10-14 18:20:59 +01009 "os"
10 "os/signal"
Serge Bazanski3fd70d82018-10-14 08:12:46 -070011 "time"
12
Serge Bazanskidd3d40f2018-10-25 12:14:18 +010013 "code.hackerspace.pl/hscloud/go/pki"
Serge Bazanski8fab2be2018-10-25 23:37:37 +020014 "code.hackerspace.pl/hscloud/go/statusz"
Serge Bazanski3fd70d82018-10-14 08:12:46 -070015 "github.com/golang/glog"
Serge Bazanski3fd70d82018-10-14 08:12:46 -070016 "golang.org/x/net/trace"
17 "google.golang.org/grpc"
18 "google.golang.org/grpc/reflection"
19)
20
21var (
22 flagListenAddress string
23 flagDebugAddress string
Serge Bazanski446c9e12018-10-14 17:06:09 +010024 flagDebugAllowAll bool
Serge Bazanski3fd70d82018-10-14 08:12:46 -070025)
26
27func init() {
Serge Bazanski69de9cb2018-10-14 08:49:04 -070028 flag.StringVar(&flagListenAddress, "listen_address", "127.0.0.1:4200", "gRPC listen address")
29 flag.StringVar(&flagDebugAddress, "debug_address", "127.0.0.1:4201", "HTTP debug/status listen address")
Serge Bazanskif77e7d32018-10-14 17:11:08 +010030 flag.BoolVar(&flagDebugAllowAll, "debug_allow_all", false, "HTTP debug/status available to everyone")
Serge Bazanskiaa81aa22018-10-14 08:36:05 -070031 flag.Set("logtostderr", "true")
Serge Bazanski3fd70d82018-10-14 08:12:46 -070032}
33
34type Mirko struct {
35 grpcListen net.Listener
36 grpcServer *grpc.Server
37 httpListen net.Listener
38 httpServer *http.Server
39 httpMux *http.ServeMux
Serge Bazanski4f7ee512018-10-14 18:20:59 +010040
Sergiusz Bazanski9dc4b682019-04-05 23:51:49 +020041 ctx context.Context
42 cancel context.CancelFunc
Serge Bazanski3fd70d82018-10-14 08:12:46 -070043}
44
45func New() *Mirko {
Serge Bazanski4f7ee512018-10-14 18:20:59 +010046 ctx, cancel := context.WithCancel(context.Background())
47 return &Mirko{
Sergiusz Bazanski9dc4b682019-04-05 23:51:49 +020048 ctx: ctx,
49 cancel: cancel,
Serge Bazanski4f7ee512018-10-14 18:20:59 +010050 }
Serge Bazanski3fd70d82018-10-14 08:12:46 -070051}
52
Serge Bazanski446c9e12018-10-14 17:06:09 +010053func authRequest(req *http.Request) (any, sensitive bool) {
54 host, _, err := net.SplitHostPort(req.RemoteAddr)
55 if err != nil {
56 host = req.RemoteAddr
57 }
58
59 if flagDebugAllowAll {
60 return true, true
61 }
62
63 switch host {
64 case "localhost", "127.0.0.1", "::1":
65 return true, true
66 default:
67 return false, false
68 }
69}
70
Serge Bazanski3fd70d82018-10-14 08:12:46 -070071func (m *Mirko) Listen() error {
72 grpc.EnableTracing = true
Serge Bazanski446c9e12018-10-14 17:06:09 +010073 trace.AuthRequest = authRequest
74
Serge Bazanski3fd70d82018-10-14 08:12:46 -070075 grpcLis, err := net.Listen("tcp", flagListenAddress)
76 if err != nil {
77 return fmt.Errorf("net.Listen: %v", err)
78 }
79 m.grpcListen = grpcLis
Serge Bazanskidd3d40f2018-10-25 12:14:18 +010080 m.grpcServer = grpc.NewServer(pki.WithServerHSPKI()...)
Serge Bazanski3fd70d82018-10-14 08:12:46 -070081 reflection.Register(m.grpcServer)
82
83 httpLis, err := net.Listen("tcp", flagDebugAddress)
84 if err != nil {
85 return fmt.Errorf("net.Listen: %v", err)
86 }
87
88 m.httpMux = http.NewServeMux()
89 // Canonical URLs
Serge Bazanski446c9e12018-10-14 17:06:09 +010090 m.httpMux.HandleFunc("/debug/status", func(w http.ResponseWriter, r *http.Request) {
Serge Bazanskibbb5b5f2018-10-14 17:13:16 +010091 any, _ := authRequest(r)
Serge Bazanski446c9e12018-10-14 17:06:09 +010092 if !any {
93 http.Error(w, "not allowed", http.StatusUnauthorized)
94 return
95 }
96 statusz.StatusHandler(w, r)
97 })
Serge Bazanski3fd70d82018-10-14 08:12:46 -070098 m.httpMux.HandleFunc("/debug/requests", trace.Traces)
99
100 // -z legacy URLs
101 m.httpMux.HandleFunc("/statusz", func(w http.ResponseWriter, r *http.Request) {
102 http.Redirect(w, r, "/debug/status", http.StatusSeeOther)
103 })
104 m.httpMux.HandleFunc("/rpcz", func(w http.ResponseWriter, r *http.Request) {
105 http.Redirect(w, r, "/debug/requests", http.StatusSeeOther)
106 })
107 m.httpMux.HandleFunc("/requestz", func(w http.ResponseWriter, r *http.Request) {
108 http.Redirect(w, r, "/debug/requests", http.StatusSeeOther)
109 })
110
111 // root redirect
112 m.httpMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
113 http.Redirect(w, r, "/debug/status", http.StatusSeeOther)
114 })
115
116 m.httpListen = httpLis
117 m.httpServer = &http.Server{
118 Addr: flagDebugAddress,
119 Handler: m.httpMux,
120 }
121
122 return nil
123}
124
Serge Bazanski4f7ee512018-10-14 18:20:59 +0100125// Trace logs debug information to either a context trace (if present)
126// or stderr (if not)
Serge Bazanskic99e0b92018-10-14 18:03:48 +0100127func Trace(ctx context.Context, f string, args ...interface{}) {
Serge Bazanskiaa81aa22018-10-14 08:36:05 -0700128 tr, ok := trace.FromContext(ctx)
129 if !ok {
130 fmtd := fmt.Sprintf(f, args...)
131 glog.Warningf("No trace in %v: %s", ctx, fmtd)
132 return
133 }
134 tr.LazyPrintf(f, args...)
135}
136
Serge Bazanski4f7ee512018-10-14 18:20:59 +0100137// GRPC returns the microservice's grpc.Server object
Serge Bazanski3fd70d82018-10-14 08:12:46 -0700138func (m *Mirko) GRPC() *grpc.Server {
139 if m.grpcServer == nil {
140 panic("GRPC() called before Listen()")
141 }
142 return m.grpcServer
143}
144
Serge Bazanski4f7ee512018-10-14 18:20:59 +0100145// HTTPMux returns the microservice's debug HTTP mux
Serge Bazanski3fd70d82018-10-14 08:12:46 -0700146func (m *Mirko) HTTPMux() *http.ServeMux {
147 if m.httpMux == nil {
148 panic("HTTPMux() called before Listen()")
149 }
150 return m.httpMux
151}
152
Serge Bazanski4f7ee512018-10-14 18:20:59 +0100153// Context returns a background microservice context that will be canceled
154// when the service is shut down
155func (m *Mirko) Context() context.Context {
156 return m.ctx
157}
158
159// Done() returns a channel that will emit a value when the service is
160// shut down. This should be used in the main() function instead of a select{}
161// call, to allow the background context to be canceled fully.
Sergiusz Bazanski9dc4b682019-04-05 23:51:49 +0200162func (m *Mirko) Done() <-chan struct{} {
163 return m.Context().Done()
Serge Bazanski4f7ee512018-10-14 18:20:59 +0100164}
165
166// Serve starts serving HTTP and gRPC requests
Serge Bazanski3fd70d82018-10-14 08:12:46 -0700167func (m *Mirko) Serve() error {
168 errs := make(chan error, 1)
169 go func() {
170 if err := m.grpcServer.Serve(m.grpcListen); err != nil {
171 errs <- err
172 }
173 }()
174 go func() {
175 if err := m.httpServer.Serve(m.httpListen); err != nil {
176 errs <- err
177 }
178 }()
179
Serge Bazanski4f7ee512018-10-14 18:20:59 +0100180 signalCh := make(chan os.Signal, 1)
181 signal.Notify(signalCh, os.Interrupt)
182 go func() {
183 select {
184 case <-signalCh:
185 m.cancel()
Serge Bazanski4f7ee512018-10-14 18:20:59 +0100186 }
187 }()
188
Serge Bazanski3fd70d82018-10-14 08:12:46 -0700189 ticker := time.NewTicker(1 * time.Second)
190 select {
191 case <-ticker.C:
192 glog.Infof("gRPC listening on %s", flagListenAddress)
193 glog.Infof("HTTP listening on %s", flagDebugAddress)
194 return nil
195 case err := <-errs:
196 return err
197 }
198}