This repository has been archived by the owner on Jan 8, 2024. It is now read-only.
/
http.go
142 lines (120 loc) · 3.98 KB
/
http.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
package server
import (
"context"
"crypto/tls"
"net"
"net/http"
"time"
assetfs "github.com/elazarl/go-bindata-assetfs"
"github.com/gorilla/mux"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/hashicorp/go-hclog"
"github.com/improbable-eng/grpc-web/go/grpcweb"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"github.com/hashicorp/waypoint/internal/server/httpapi"
"github.com/hashicorp/waypoint/pkg/server/gen"
"github.com/hashicorp/waypoint/pkg/serverclient"
)
type httpServer struct {
ln net.Listener
opts *options
log hclog.Logger
server *http.Server
}
// newHttpServer initializes a new http server.
// Uses grpc-web to wrap an existing grpc server.
func newHttpServer(grpcServer *grpc.Server, ln net.Listener, opts *options) (*httpServer, error) {
log := opts.Logger.Named("http").With("ln", ln.Addr().String())
// Wrap the grpc server so that it is grpc-web compatible
grpcWrapped := grpcweb.WrapServer(grpcServer,
grpcweb.WithCorsForRegisteredEndpointsOnly(false),
grpcweb.WithOriginFunc(func(string) bool { return true }),
grpcweb.WithAllowNonRootResource(true),
)
// This is the http.Handler for the UI
uifs := http.FileServer(&assetfs.AssetFS{
Asset: gen.Asset,
AssetDir: gen.AssetDir,
AssetInfo: gen.AssetInfo,
Prefix: "ui/dist",
Fallback: "index.html",
})
// grpcAddr is the address that we can connect back to our own
// gRPC server. This is used by the exec handler.
grpcAddr := opts.GRPCListener.Addr().String()
// Create grpc-gateway muxer
grpcHandler := runtime.NewServeMux()
grpcOpts := serverclient.BuildDialOptions()
grpcOpts = append(grpcOpts,
grpc.WithTransportCredentials(
credentials.NewTLS(&tls.Config{InsecureSkipVerify: true}),
),
)
err := gen.RegisterWaypointHandlerFromEndpoint(opts.Context, grpcHandler, grpcAddr, grpcOpts)
if err != nil {
return nil, err
}
// Create our full router
r := mux.NewRouter()
r.HandleFunc("/v1/exec", httpapi.HandleExec(grpcAddr, true))
r.HandleFunc("/v1/trigger/{id:[a-zA-Z0-9]+}", httpapi.HandleTrigger(grpcAddr, true))
r.PathPrefix("/grpc").Handler(grpcWrapped)
r.PathPrefix("/v1").Handler(grpcHandler)
r.PathPrefix("/").Handler(uifs)
// Create our root handler which is just our router. We then wrap it
// in various middlewares below.
var rootHandler http.Handler = r
// Wrap our handler to force TLS
rootHandler = forceTLSHandler(rootHandler)
// Wrap our handler to log
rootHandler = httpLogHandler(rootHandler, log)
// Create our http server
return &httpServer{
ln: ln,
opts: opts,
log: log,
server: &http.Server{
ReadHeaderTimeout: 5 * time.Second,
IdleTimeout: 120 * time.Second,
Handler: rootHandler,
BaseContext: func(net.Listener) context.Context {
return opts.Context
},
},
}, nil
}
// start starts an http server
func (s *httpServer) start() error {
// Serve traffic
s.log.Info("starting HTTP server", "addr", s.ln.Addr().String())
return s.server.Serve(s.ln)
}
// close stops the grpc server, gracefully if possible. Should be called exactly once.
// Warning: before closing the GRPC server, this HTTP server must first be closed.
// Attempting to gracefully stop the GRPC server first will cause it to drain HTTP connections,
// which will panic.
func (s *httpServer) close() {
log := s.log
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
// Graceful in a goroutine so we can timeout
gracefulCh := make(chan struct{})
go func() {
defer close(gracefulCh)
log.Debug("stopping")
if err := s.server.Shutdown(ctx); err != nil {
log.Error("failed graceful shutdown: %s", err)
}
}()
select {
case <-gracefulCh:
log.Debug("exited gracefully")
// After a timeout we just forcibly exit. Our HTTP endpoints should
// be fairly quick and their operations are atomic so we just kill
// the connections after a few seconds.
case <-time.After(2 * time.Second):
log.Debug("stopping forcefully after waiting unsuccessfully for graceful stop")
cancelFunc()
}
}