mirror of
https://github.com/weaveworks/scope.git
synced 2026-02-14 18:09:59 +00:00
Typically this means the http caller has closed the connection, so no point responding to them. Also check at the point we send a response back, and log to OpenTracing.
206 lines
6.0 KiB
Go
206 lines
6.0 KiB
Go
package app
|
|
|
|
import (
|
|
"bytes"
|
|
"compress/gzip"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"context"
|
|
"github.com/NYTimes/gziphandler"
|
|
"github.com/gorilla/mux"
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
"github.com/weaveworks/scope/common/hostname"
|
|
"github.com/weaveworks/scope/common/xfer"
|
|
"github.com/weaveworks/scope/report"
|
|
)
|
|
|
|
var (
|
|
// Version - set at buildtime.
|
|
Version = "dev"
|
|
|
|
// UniqueID - set at runtime.
|
|
UniqueID = "0"
|
|
)
|
|
|
|
// contextKey is a wrapper type for use in context.WithValue() to satisfy golint
|
|
// https://github.com/golang/go/issues/17293
|
|
// https://github.com/golang/lint/pull/245
|
|
type contextKey string
|
|
|
|
// RequestCtxKey is key used for request entry in context
|
|
const RequestCtxKey contextKey = contextKey("request")
|
|
|
|
// CtxHandlerFunc is a http.HandlerFunc, with added contexts
|
|
type CtxHandlerFunc func(context.Context, http.ResponseWriter, *http.Request)
|
|
|
|
func requestContextDecorator(f CtxHandlerFunc) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
ctx := context.WithValue(r.Context(), RequestCtxKey, r)
|
|
f(ctx, w, r)
|
|
}
|
|
}
|
|
|
|
// URLMatcher uses request.RequestURI (the raw, unparsed request) to attempt
|
|
// to match pattern. It does this as go's URL.Parse method is broken, and
|
|
// mistakenly unescapes the Path before parsing it. This breaks %2F (encoded
|
|
// forward slashes) in the paths.
|
|
func URLMatcher(pattern string) mux.MatcherFunc {
|
|
return func(r *http.Request, rm *mux.RouteMatch) bool {
|
|
vars, match := matchURL(r, pattern)
|
|
if match {
|
|
rm.Vars = vars
|
|
}
|
|
return match
|
|
}
|
|
}
|
|
|
|
func matchURL(r *http.Request, pattern string) (map[string]string, bool) {
|
|
matchParts := strings.Split(pattern, "/")
|
|
path := strings.SplitN(r.RequestURI, "?", 2)[0]
|
|
parts := strings.Split(path, "/")
|
|
if len(parts) != len(matchParts) {
|
|
return nil, false
|
|
}
|
|
|
|
vars := map[string]string{}
|
|
for i, part := range parts {
|
|
unescaped, err := url.QueryUnescape(part)
|
|
if err != nil {
|
|
return nil, false
|
|
}
|
|
match := matchParts[i]
|
|
if strings.HasPrefix(match, "{") && strings.HasSuffix(match, "}") {
|
|
vars[strings.Trim(match, "{}")] = unescaped
|
|
} else if matchParts[i] != unescaped {
|
|
return nil, false
|
|
}
|
|
}
|
|
return vars, true
|
|
}
|
|
|
|
func gzipHandler(h http.HandlerFunc) http.Handler {
|
|
return gziphandler.GzipHandler(h)
|
|
}
|
|
|
|
// RegisterTopologyRoutes registers the various topology routes with a http mux.
|
|
func RegisterTopologyRoutes(router *mux.Router, r Reporter, capabilities map[string]bool) {
|
|
get := router.Methods("GET").Subrouter()
|
|
get.Handle("/api",
|
|
gzipHandler(requestContextDecorator(apiHandler(r, capabilities))))
|
|
get.Handle("/api/topology",
|
|
gzipHandler(requestContextDecorator(topologyRegistry.makeTopologyList(r))))
|
|
get.Handle("/api/topology/{topology}",
|
|
gzipHandler(requestContextDecorator(topologyRegistry.captureRenderer(r, handleTopology)))).
|
|
Name("api_topology_topology")
|
|
get.Handle("/api/topology/{topology}/ws",
|
|
requestContextDecorator(captureReporter(r, handleWebsocket))). // NB not gzip!
|
|
Name("api_topology_topology_ws")
|
|
get.MatcherFunc(URLMatcher("/api/topology/{topology}/{id}")).Handler(
|
|
gzipHandler(requestContextDecorator(topologyRegistry.captureRenderer(r, handleNode)))).
|
|
Name("api_topology_topology_id")
|
|
get.Handle("/api/report",
|
|
gzipHandler(requestContextDecorator(makeRawReportHandler(r))))
|
|
get.Handle("/api/probes",
|
|
gzipHandler(requestContextDecorator(makeProbeHandler(r))))
|
|
}
|
|
|
|
// RegisterReportPostHandler registers the handler for report submission
|
|
func RegisterReportPostHandler(a Adder, router *mux.Router) {
|
|
post := router.Methods("POST").Subrouter()
|
|
post.HandleFunc("/api/report", requestContextDecorator(func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
|
var (
|
|
buf = &bytes.Buffer{}
|
|
reader = io.TeeReader(r.Body, buf)
|
|
)
|
|
|
|
gzipped := strings.Contains(r.Header.Get("Content-Encoding"), "gzip")
|
|
if !gzipped {
|
|
reader = io.TeeReader(r.Body, gzip.NewWriter(buf))
|
|
}
|
|
|
|
contentType := r.Header.Get("Content-Type")
|
|
var isMsgpack bool
|
|
switch {
|
|
case strings.HasPrefix(contentType, "application/msgpack"):
|
|
isMsgpack = true
|
|
case strings.HasPrefix(contentType, "application/json"):
|
|
isMsgpack = false
|
|
default:
|
|
respondWith(ctx, w, http.StatusBadRequest, fmt.Errorf("Unsupported Content-Type: %v", contentType))
|
|
return
|
|
}
|
|
|
|
rpt, err := report.MakeFromBinary(ctx, reader, gzipped, isMsgpack)
|
|
if err != nil {
|
|
respondWith(ctx, w, http.StatusBadRequest, err)
|
|
return
|
|
}
|
|
|
|
// a.Add(..., buf) assumes buf is gzip'd msgpack
|
|
if !isMsgpack {
|
|
buf, _ = rpt.WriteBinary()
|
|
}
|
|
|
|
if err := a.Add(ctx, *rpt, buf.Bytes()); err != nil {
|
|
log.Errorf("Error Adding report: %v", err)
|
|
respondWith(ctx, w, http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusOK)
|
|
}))
|
|
}
|
|
|
|
// RegisterAdminRoutes registers routes for admin calls with a http mux.
|
|
func RegisterAdminRoutes(router *mux.Router, reporter Reporter) {
|
|
get := router.Methods("GET").Subrouter()
|
|
get.Handle("/admin/summary", requestContextDecorator(func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
|
summary, err := reporter.AdminSummary(ctx, time.Now())
|
|
if err != nil {
|
|
respondWith(ctx, w, http.StatusBadRequest, err)
|
|
}
|
|
fmt.Fprintln(w, summary)
|
|
}))
|
|
}
|
|
|
|
var newVersion = struct {
|
|
sync.Mutex
|
|
*xfer.NewVersionInfo
|
|
}{}
|
|
|
|
// NewVersion is called to expose new version information to /api
|
|
func NewVersion(version, downloadURL string) {
|
|
newVersion.Lock()
|
|
defer newVersion.Unlock()
|
|
newVersion.NewVersionInfo = &xfer.NewVersionInfo{
|
|
Version: version,
|
|
DownloadURL: downloadURL,
|
|
}
|
|
}
|
|
|
|
func apiHandler(rep Reporter, capabilities map[string]bool) CtxHandlerFunc {
|
|
return func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
|
report, err := rep.Report(ctx, time.Now())
|
|
if err != nil {
|
|
respondWith(ctx, w, http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
newVersion.Lock()
|
|
defer newVersion.Unlock()
|
|
respondWith(ctx, w, http.StatusOK, xfer.Details{
|
|
ID: UniqueID,
|
|
Version: Version,
|
|
Hostname: hostname.Get(),
|
|
Plugins: report.Plugins,
|
|
Capabilities: capabilities,
|
|
NewVersion: newVersion.NewVersionInfo,
|
|
})
|
|
}
|
|
}
|