blob: d4d33116d630e6c593a64ae2c055de201e8038f4 [file] [log] [blame]
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package worker
import (
"bytes"
"context"
"io"
"net/http"
"path/filepath"
"time"
"github.com/google/safehtml/template"
"golang.org/x/exp/event"
"golang.org/x/vuln/internal/derrors"
"golang.org/x/vuln/internal/worker/log"
"golang.org/x/vuln/internal/worker/store"
)
var staticPath = template.TrustedSourceFromConstant("internal/worker/static")
type Server struct {
namespace string
st store.Store
indexTemplate *template.Template
}
func NewServer(ctx context.Context, namespace string, st store.Store) (_ *Server, err error) {
defer derrors.Wrap(&err, "NewServer(%q)", namespace)
s := &Server{namespace: namespace, st: st}
s.indexTemplate, err = parseTemplate(staticPath, template.TrustedSourceFromConstant("index.tmpl"))
if err != nil {
return nil, err
}
s.handle(ctx, "/", s.indexPage)
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir(staticPath.String()))))
s.handle(ctx, "/favicon.ico", func(w http.ResponseWriter, r *http.Request) error {
http.ServeFile(w, r, filepath.Join(staticPath.String(), "favicon.ico"))
return nil
})
return s, nil
}
func (s *Server) indexPage(w http.ResponseWriter, r *http.Request) error {
type data struct {
Namespace string
}
page := data{
Namespace: s.namespace,
}
return renderPage(r.Context(), w, page, s.indexTemplate)
}
func (s *Server) handle(ctx context.Context, pattern string, handler func(w http.ResponseWriter, r *http.Request) error) {
http.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
traceID := r.Header.Get("X-Cloud-Trace-Context")
log.Info(ctx, "request start",
event.Value("httpRequest", r),
event.String("traceID", traceID))
r = r.WithContext(log.WithLineLogger(r.Context()))
w2 := &responseWriter{ResponseWriter: w}
if err := handler(w2, r); err != nil {
s.serveError(ctx, w2, r, err)
}
log.Info(ctx, "request end",
event.Value("traceID", traceID),
event.Duration("latency", time.Since(start)),
event.Int64("status", translateStatus(w2.status)))
})
}
func (s *Server) serveError(ctx context.Context, w http.ResponseWriter, r *http.Request, err error) {
errString := err.Error()
log.Error(ctx, errString)
http.Error(w, errString, http.StatusInternalServerError)
}
type responseWriter struct {
http.ResponseWriter
status int
}
func (rw *responseWriter) WriteHeader(code int) {
rw.status = code
rw.ResponseWriter.WriteHeader(code)
}
func translateStatus(code int) int64 {
if code == 0 {
return http.StatusOK
}
return int64(code)
}
// Parse a template.
func parseTemplate(staticPath, filename template.TrustedSource) (*template.Template, error) {
if staticPath.String() == "" {
return nil, nil
}
templatePath := template.TrustedSourceJoin(staticPath, filename)
return template.New(filename.String()).ParseFilesFromTrustedSources(templatePath)
}
func renderPage(ctx context.Context, w http.ResponseWriter, page interface{}, tmpl *template.Template) (err error) {
defer derrors.Wrap(&err, "renderPage")
var buf bytes.Buffer
if err := tmpl.Execute(&buf, page); err != nil {
return err
}
if _, err := io.Copy(w, &buf); err != nil {
log.Error(ctx, "copying buffer to ResponseWriter", event.Value("error", err))
return err
}
return nil
}