/
server.go
171 lines (148 loc) · 5.23 KB
/
server.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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
package srv
import (
"fmt"
"html/template"
"net/http"
"path"
"path/filepath"
"time"
"github.com/julienschmidt/httprouter"
pb "github.com/linkerd/linkerd2/controller/gen/public"
"github.com/linkerd/linkerd2/pkg/filesonly"
"github.com/linkerd/linkerd2/pkg/prometheus"
log "github.com/sirupsen/logrus"
)
const (
timeout = 10 * time.Second
)
type (
Server struct {
templateDir string
staticDir string
reload bool
templateContext templateContext
templates map[string]*template.Template
router *httprouter.Router
}
templateContext struct {
WebpackDevServer string
}
templatePayload struct {
Context templateContext
Contents interface{}
}
appParams struct {
Data *pb.VersionInfo
UUID string
ControllerNamespace string
Error bool
ErrorMessage string
PathPrefix string
}
)
// this is called by the HTTP server to actually respond to a request
func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
s.router.ServeHTTP(w, req)
}
func NewServer(addr, templateDir, staticDir, uuid, controllerNamespace, webpackDevServer string, reload bool, apiClient pb.ApiClient) *http.Server {
server := &Server{
templateDir: templateDir,
staticDir: staticDir,
templateContext: templateContext{webpackDevServer},
reload: reload,
}
server.router = &httprouter.Router{
RedirectTrailingSlash: true,
RedirectFixedPath: true,
HandleMethodNotAllowed: false, // disable 405s
}
wrappedServer := prometheus.WithTelemetry(server)
handler := &handler{
apiClient: apiClient,
render: server.RenderTemplate,
serveFile: server.serveFile,
uuid: uuid,
controllerNamespace: controllerNamespace,
}
httpServer := &http.Server{
Addr: addr,
ReadTimeout: timeout,
WriteTimeout: timeout,
Handler: wrappedServer,
}
// webapp routes
server.router.GET("/", handler.handleIndex)
server.router.GET("/overview", handler.handleIndex)
server.router.GET("/servicemesh", handler.handleIndex)
server.router.GET("/namespaces", handler.handleIndex)
server.router.GET("/namespaces/:namespace", handler.handleIndex)
server.router.GET("/deployments", handler.handleIndex)
server.router.GET("/replicationcontrollers", handler.handleIndex)
server.router.GET("/pods", handler.handleIndex)
server.router.GET("/authorities", handler.handleIndex)
server.router.GET("/namespaces/:namespace/pods/:pod", handler.handleIndex)
server.router.GET("/namespaces/:namespace/deployments/:deployment", handler.handleIndex)
server.router.GET("/namespaces/:namespace/replicationcontrollers/:replicationcontroller", handler.handleIndex)
server.router.GET("/tap", handler.handleIndex)
server.router.GET("/top", handler.handleIndex)
server.router.ServeFiles(
"/dist/*filepath", // add catch-all parameter to match all files in dir
filesonly.FileSystem(server.staticDir))
// webapp api routes
server.router.GET("/api/version", handler.handleApiVersion)
// Traffic Performance Summary. This route used to be called /api/stat
// but was renamed to avoid triggering ad blockers.
// See: https://github.com/linkerd/linkerd2/issues/970
server.router.GET("/api/tps-reports", handler.handleApiStat)
server.router.GET("/api/pods", handler.handleApiPods)
server.router.GET("/api/tap", handler.handleApiTap)
return httpServer
}
func (s *Server) RenderTemplate(w http.ResponseWriter, templateFile, templateName string, args interface{}) error {
log.Debugf("emitting template %s", templateFile)
template, err := s.loadTemplate(templateFile)
if err != nil {
log.Error(err.Error())
http.Error(w, "internal server error", http.StatusInternalServerError)
return nil
}
w.Header().Set("Content-Type", "text/html")
if templateName == "" {
return template.Execute(w, args)
} else {
return template.ExecuteTemplate(w, templateName, templatePayload{Context: s.templateContext, Contents: args})
}
}
func (s *Server) loadTemplate(templateFile string) (template *template.Template, err error) {
// load template from disk if necessary
template = s.templates[templateFile]
if template == nil || s.reload {
templatePath := safelyJoinPath(s.templateDir, templateFile)
includes, err := filepath.Glob(filepath.Join(s.templateDir, "includes", "*.tmpl.html"))
if err != nil {
return nil, err
}
// for cases where you're not calling a named template, the passed-in path needs to be first
templateFiles := append([]string{templatePath}, includes...)
log.Debugf("loading templates from %v", templateFiles)
template, err = template.ParseFiles(templateFiles...)
if err == nil && !s.reload {
s.templates[templateFile] = template
}
}
return template, err
}
func safelyJoinPath(rootPath, userPath string) string {
return filepath.Join(rootPath, path.Clean("/"+userPath))
}
func (s *Server) serveFile(w http.ResponseWriter, fileName string, templateName string, args interface{}) error {
dispositionHeaderVal := fmt.Sprintf("attachment; filename='%s'", fileName)
w.Header().Set("Content-Type", "text/yaml")
w.Header().Set("Content-Disposition", dispositionHeaderVal)
template, err := s.loadTemplate(templateName)
if err != nil {
return err
}
template.Execute(w, args)
return nil
}