mirror of
https://github.com/weaveworks/scope.git
synced 2026-05-05 16:59:36 +00:00
- Add interfaces to allow for alternative implementations for Collector, ControlRouter and PipeRouter. - Pass contexts on http handlers to these interfaces. Although not used by the current (local, in-memory) implementations, the idea is this will be used to pass headers to implementations which support multitenancy (by, for instance, putting an authenticating reverse proxy in form of the app, and then inspecting the headers of the request for a used id).
131 lines
3.1 KiB
Go
131 lines
3.1 KiB
Go
package app
|
|
|
|
import (
|
|
"net/http"
|
|
"time"
|
|
|
|
log "github.com/Sirupsen/logrus"
|
|
"github.com/gorilla/websocket"
|
|
"golang.org/x/net/context"
|
|
|
|
"github.com/weaveworks/scope/common/xfer"
|
|
"github.com/weaveworks/scope/render"
|
|
"github.com/weaveworks/scope/render/detailed"
|
|
)
|
|
|
|
const (
|
|
websocketLoop = 1 * time.Second
|
|
websocketTimeout = 10 * time.Second
|
|
)
|
|
|
|
// APITopology is returned by the /api/topology/{name} handler.
|
|
type APITopology struct {
|
|
Nodes render.RenderableNodes `json:"nodes"`
|
|
}
|
|
|
|
// APINode is returned by the /api/topology/{name}/{id} handler.
|
|
type APINode struct {
|
|
Node detailed.Node `json:"node"`
|
|
}
|
|
|
|
// Full topology.
|
|
func handleTopology(ctx context.Context, rep Reporter, renderer render.Renderer, w http.ResponseWriter, r *http.Request) {
|
|
respondWith(w, http.StatusOK, APITopology{
|
|
Nodes: renderer.Render(rep.Report(ctx)).Prune(),
|
|
})
|
|
}
|
|
|
|
// Websocket for the full topology. This route overlaps with the next.
|
|
func handleWs(ctx context.Context, rep Reporter, renderer render.Renderer, w http.ResponseWriter, r *http.Request) {
|
|
if err := r.ParseForm(); err != nil {
|
|
respondWith(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
loop := websocketLoop
|
|
if t := r.Form.Get("t"); t != "" {
|
|
var err error
|
|
if loop, err = time.ParseDuration(t); err != nil {
|
|
respondWith(w, http.StatusBadRequest, t)
|
|
return
|
|
}
|
|
}
|
|
handleWebsocket(ctx, w, r, rep, renderer, loop)
|
|
}
|
|
|
|
// Individual nodes.
|
|
func handleNode(nodeID string) func(context.Context, Reporter, render.Renderer, http.ResponseWriter, *http.Request) {
|
|
return func(ctx context.Context, rep Reporter, renderer render.Renderer, w http.ResponseWriter, r *http.Request) {
|
|
var (
|
|
rpt = rep.Report(ctx)
|
|
node, ok = renderer.Render(rep.Report(ctx))[nodeID]
|
|
)
|
|
if !ok {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
respondWith(w, http.StatusOK, APINode{Node: detailed.MakeNode(rpt, node)})
|
|
}
|
|
}
|
|
|
|
var upgrader = websocket.Upgrader{
|
|
CheckOrigin: func(r *http.Request) bool { return true },
|
|
}
|
|
|
|
func handleWebsocket(
|
|
ctx context.Context,
|
|
w http.ResponseWriter,
|
|
r *http.Request,
|
|
rep Reporter,
|
|
renderer render.Renderer,
|
|
loop time.Duration,
|
|
) {
|
|
conn, err := upgrader.Upgrade(w, r, nil)
|
|
if err != nil {
|
|
// log.Info("Upgrade:", err)
|
|
return
|
|
}
|
|
defer conn.Close()
|
|
|
|
quit := make(chan struct{})
|
|
go func(c *websocket.Conn) {
|
|
for { // just discard everything the browser sends
|
|
if _, _, err := c.NextReader(); err != nil {
|
|
log.Println("err:", err)
|
|
close(quit)
|
|
break
|
|
}
|
|
}
|
|
}(conn)
|
|
|
|
var (
|
|
previousTopo render.RenderableNodes
|
|
tick = time.Tick(loop)
|
|
wait = make(chan struct{}, 1)
|
|
)
|
|
rep.WaitOn(ctx, wait)
|
|
defer rep.UnWait(ctx, wait)
|
|
|
|
for {
|
|
newTopo := renderer.Render(rep.Report(ctx)).Prune()
|
|
diff := render.TopoDiff(previousTopo, newTopo)
|
|
previousTopo = newTopo
|
|
|
|
if err := conn.SetWriteDeadline(time.Now().Add(websocketTimeout)); err != nil {
|
|
log.Println("err:", err)
|
|
return
|
|
}
|
|
|
|
if err := xfer.WriteJSONtoWS(conn, diff); err != nil {
|
|
log.Errorf("cannot serialize topology diff: %s", err)
|
|
return
|
|
}
|
|
|
|
select {
|
|
case <-wait:
|
|
case <-tick:
|
|
case <-quit:
|
|
return
|
|
}
|
|
}
|
|
}
|