router.go 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. package app
  2. import (
  3. "bytes"
  4. "compress/gzip"
  5. "fmt"
  6. "io"
  7. "net/http"
  8. "net/url"
  9. "strings"
  10. "sync"
  11. "time"
  12. "context"
  13. "github.com/NYTimes/gziphandler"
  14. "github.com/gorilla/mux"
  15. log "github.com/sirupsen/logrus"
  16. "github.com/weaveworks/scope/common/hostname"
  17. "github.com/weaveworks/scope/common/xfer"
  18. "github.com/weaveworks/scope/report"
  19. )
  20. var (
  21. // Version - set at buildtime.
  22. Version = "dev"
  23. // UniqueID - set at runtime.
  24. UniqueID = "0"
  25. )
  26. // contextKey is a wrapper type for use in context.WithValue() to satisfy golint
  27. // https://github.com/golang/go/issues/17293
  28. // https://github.com/golang/lint/pull/245
  29. type contextKey string
  30. // RequestCtxKey is key used for request entry in context
  31. const RequestCtxKey contextKey = contextKey("request")
  32. // CtxHandlerFunc is a http.HandlerFunc, with added contexts
  33. type CtxHandlerFunc func(context.Context, http.ResponseWriter, *http.Request)
  34. func requestContextDecorator(f CtxHandlerFunc) http.HandlerFunc {
  35. return func(w http.ResponseWriter, r *http.Request) {
  36. ctx := context.WithValue(r.Context(), RequestCtxKey, r)
  37. f(ctx, w, r)
  38. }
  39. }
  40. // URLMatcher uses request.RequestURI (the raw, unparsed request) to attempt
  41. // to match pattern. It does this as go's URL.Parse method is broken, and
  42. // mistakenly unescapes the Path before parsing it. This breaks %2F (encoded
  43. // forward slashes) in the paths.
  44. func URLMatcher(pattern string) mux.MatcherFunc {
  45. return func(r *http.Request, rm *mux.RouteMatch) bool {
  46. vars, match := matchURL(r, pattern)
  47. if match {
  48. rm.Vars = vars
  49. }
  50. return match
  51. }
  52. }
  53. func matchURL(r *http.Request, pattern string) (map[string]string, bool) {
  54. matchParts := strings.Split(pattern, "/")
  55. path := strings.SplitN(r.RequestURI, "?", 2)[0]
  56. parts := strings.Split(path, "/")
  57. if len(parts) != len(matchParts) {
  58. return nil, false
  59. }
  60. vars := map[string]string{}
  61. for i, part := range parts {
  62. unescaped, err := url.QueryUnescape(part)
  63. if err != nil {
  64. return nil, false
  65. }
  66. match := matchParts[i]
  67. if strings.HasPrefix(match, "{") && strings.HasSuffix(match, "}") {
  68. vars[strings.Trim(match, "{}")] = unescaped
  69. } else if matchParts[i] != unescaped {
  70. return nil, false
  71. }
  72. }
  73. return vars, true
  74. }
  75. func gzipHandler(h http.HandlerFunc) http.Handler {
  76. return gziphandler.GzipHandler(h)
  77. }
  78. // RegisterTopologyRoutes registers the various topology routes with a http mux.
  79. func RegisterTopologyRoutes(router *mux.Router, r Reporter, capabilities map[string]bool) {
  80. get := router.Methods("GET").Subrouter()
  81. get.Handle("/api",
  82. gzipHandler(requestContextDecorator(apiHandler(r, capabilities))))
  83. get.Handle("/api/topology",
  84. gzipHandler(requestContextDecorator(topologyRegistry.makeTopologyList(r))))
  85. get.Handle("/api/topology/{topology}",
  86. gzipHandler(requestContextDecorator(topologyRegistry.captureRenderer(r, handleTopology)))).
  87. Name("api_topology_topology")
  88. get.Handle("/api/topology/{topology}/ws",
  89. requestContextDecorator(captureReporter(r, handleWebsocket))). // NB not gzip!
  90. Name("api_topology_topology_ws")
  91. get.MatcherFunc(URLMatcher("/api/topology/{topology}/{id}")).Handler(
  92. gzipHandler(requestContextDecorator(topologyRegistry.captureRenderer(r, handleNode)))).
  93. Name("api_topology_topology_id")
  94. get.Handle("/api/report",
  95. gzipHandler(requestContextDecorator(makeRawReportHandler(r))))
  96. get.Handle("/api/probes",
  97. gzipHandler(requestContextDecorator(makeProbeHandler(r))))
  98. }
  99. // RegisterReportPostHandler registers the handler for report submission
  100. func RegisterReportPostHandler(a Adder, router *mux.Router) {
  101. post := router.Methods("POST").Subrouter()
  102. post.HandleFunc("/api/report", requestContextDecorator(func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
  103. var (
  104. buf = &bytes.Buffer{}
  105. reader = io.TeeReader(r.Body, buf)
  106. )
  107. gzipped := strings.Contains(r.Header.Get("Content-Encoding"), "gzip")
  108. if !gzipped {
  109. reader = io.TeeReader(r.Body, gzip.NewWriter(buf))
  110. }
  111. contentType := r.Header.Get("Content-Type")
  112. var isMsgpack bool
  113. switch {
  114. case strings.HasPrefix(contentType, "application/msgpack"):
  115. isMsgpack = true
  116. case strings.HasPrefix(contentType, "application/json"):
  117. isMsgpack = false
  118. default:
  119. respondWith(ctx, w, http.StatusBadRequest, fmt.Errorf("Unsupported Content-Type: %v", contentType))
  120. return
  121. }
  122. rpt, err := report.MakeFromBinary(ctx, reader, gzipped, isMsgpack)
  123. if err != nil {
  124. respondWith(ctx, w, http.StatusBadRequest, err)
  125. return
  126. }
  127. // a.Add(..., buf) assumes buf is gzip'd msgpack
  128. if !isMsgpack {
  129. buf, _ = rpt.WriteBinary()
  130. }
  131. if err := a.Add(ctx, *rpt, buf.Bytes()); err != nil {
  132. log.Errorf("Error Adding report: %v", err)
  133. respondWith(ctx, w, http.StatusInternalServerError, err)
  134. return
  135. }
  136. w.WriteHeader(http.StatusOK)
  137. }))
  138. }
  139. // RegisterAdminRoutes registers routes for admin calls with a http mux.
  140. func RegisterAdminRoutes(router *mux.Router, reporter Reporter) {
  141. get := router.Methods("GET").Subrouter()
  142. get.Handle("/admin/summary", requestContextDecorator(func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
  143. summary, err := reporter.AdminSummary(ctx, time.Now())
  144. if err != nil {
  145. respondWith(ctx, w, http.StatusBadRequest, err)
  146. }
  147. fmt.Fprintln(w, summary)
  148. }))
  149. }
  150. var newVersion = struct {
  151. sync.Mutex
  152. *xfer.NewVersionInfo
  153. }{}
  154. // NewVersion is called to expose new version information to /api
  155. func NewVersion(version, downloadURL string) {
  156. newVersion.Lock()
  157. defer newVersion.Unlock()
  158. newVersion.NewVersionInfo = &xfer.NewVersionInfo{
  159. Version: version,
  160. DownloadURL: downloadURL,
  161. }
  162. }
  163. func apiHandler(rep Reporter, capabilities map[string]bool) CtxHandlerFunc {
  164. return func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
  165. report, err := rep.Report(ctx, time.Now())
  166. if err != nil {
  167. respondWith(ctx, w, http.StatusInternalServerError, err)
  168. return
  169. }
  170. newVersion.Lock()
  171. defer newVersion.Unlock()
  172. respondWith(ctx, w, http.StatusOK, xfer.Details{
  173. ID: UniqueID,
  174. Version: Version,
  175. Hostname: hostname.Get(),
  176. Plugins: report.Plugins,
  177. Capabilities: capabilities,
  178. NewVersion: newVersion.NewVersionInfo,
  179. })
  180. }
  181. }