123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205 |
- 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,
- })
- }
- }
|