Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Less hacky fix for /%2F bug #806

Merged
merged 1 commit into from
Jan 14, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions app/api_report_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@ import (
)

func topologyServer() *httptest.Server {
router := mux.NewRouter()
app.RegisterTopologyRoutes(StaticReport{}, router)
return httptest.NewServer(router)
handler := app.TopologyHandler(StaticReport{}, mux.NewRouter(), nil)
return httptest.NewServer(handler)
}

func TestAPIReport(t *testing.T) {
Expand Down
4 changes: 2 additions & 2 deletions app/api_topologies.go
Original file line number Diff line number Diff line change
Expand Up @@ -259,9 +259,9 @@ func (r *registry) captureRenderer(rep Reporter, f reportRenderHandler) http.Han
}
}

func (r *registry) captureRendererWithoutFilters(rep Reporter, f reportRenderHandler) http.HandlerFunc {
func (r *registry) captureRendererWithoutFilters(rep Reporter, topologyID string, f reportRenderHandler) http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) {
topology, ok := r.get(mux.Vars(req)["topology"])
topology, ok := r.get(topologyID)
if !ok {
http.NotFound(w, req)
return
Expand Down
4 changes: 2 additions & 2 deletions app/api_topologies_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@ func TestAPITopology(t *testing.T) {
func TestAPITopologyAddsKubernetes(t *testing.T) {
router := mux.NewRouter()
c := app.NewCollector(1 * time.Minute)
app.RegisterTopologyRoutes(c, router)
app.RegisterReportPostHandler(c, router)
ts := httptest.NewServer(router)
handler := app.TopologyHandler(c, router, nil)
ts := httptest.NewServer(handler)
defer ts.Close()

body := getRawJSON(t, ts, "/api/topology")
Expand Down
23 changes: 11 additions & 12 deletions app/api_topology.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"net/http"
"time"

"github.com/gorilla/mux"
"github.com/gorilla/websocket"

"github.com/weaveworks/scope/render"
Expand Down Expand Up @@ -50,18 +49,18 @@ func handleWs(rep Reporter, renderer render.Renderer, w http.ResponseWriter, r *
}

// Individual nodes.
func handleNode(rep Reporter, renderer render.Renderer, w http.ResponseWriter, r *http.Request) {
var (
vars = mux.Vars(r)
nodeID = vars["id"]
rpt = rep.Report()
node, ok = renderer.Render(rep.Report())[nodeID]
)
if !ok {
http.NotFound(w, r)
return
func handleNode(nodeID string) func(Reporter, render.Renderer, http.ResponseWriter, *http.Request) {
return func(rep Reporter, renderer render.Renderer, w http.ResponseWriter, r *http.Request) {
var (
rpt = rep.Report()
node, ok = renderer.Render(rep.Report())[nodeID]
)
if !ok {
http.NotFound(w, r)
return
}
respondWith(w, http.StatusOK, APINode{Node: render.MakeDetailedNode(rpt, node)})
}
respondWith(w, http.StatusOK, APINode{Node: render.MakeDetailedNode(rpt, node)})
}

var upgrader = websocket.Upgrader{
Expand Down
14 changes: 14 additions & 0 deletions app/api_topology_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,20 @@ func TestAPITopologyApplications(t *testing.T) {
equals(t, false, node.Node.Pseudo)
// Let's not unit-test the specific content of the detail tables
}

{
body := getRawJSON(t, ts, "/api/topology/applications-by-name/"+
url.QueryEscape(fixture.Client1Name))
var node app.APINode
if err := json.Unmarshal(body, &node); err != nil {
t.Fatal(err)
}
equals(t, fixture.Client1Name, node.Node.ID)
equals(t, fixture.Client1Name, node.Node.LabelMajor)
equals(t, "2 processes", node.Node.LabelMinor)
equals(t, false, node.Node.Pseudo)
// Let's not unit-test the specific content of the detail tables
}
}

func TestAPITopologyHosts(t *testing.T) {
Expand Down
86 changes: 62 additions & 24 deletions app/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,48 +29,86 @@ var (
// mistakenly unescapes the Path before parsing it. This breaks %2F (encoded
// forward slashes) in the paths.
func URLMatcher(pattern string) mux.MatcherFunc {
matchParts := strings.Split(pattern, "/")

return func(r *http.Request, rm *mux.RouteMatch) bool {
path := strings.SplitN(r.RequestURI, "?", 2)[0]
parts := strings.Split(path, "/")
if len(parts) != len(matchParts) {
return false
vars, match := matchURL(r, pattern)
if match {
rm.Vars = vars
}
return match
}
}

rm.Vars = map[string]string{}
for i, part := range parts {
unescaped, err := url.QueryUnescape(part)
if err != nil {
return false
}
match := matchParts[i]
if strings.HasPrefix(match, "{") && strings.HasSuffix(match, "}") {
rm.Vars[strings.Trim(match, "{}")] = unescaped
} else if matchParts[i] != unescaped {
return false
}
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 true
}
return vars, true
}

func gzipHandler(h http.HandlerFunc) http.HandlerFunc {
return handlers.GZIPHandlerFunc(h, nil)
}

// RegisterTopologyRoutes registers the various topology routes with a http mux.
func RegisterTopologyRoutes(c Reporter, router *mux.Router) {
get := router.Methods("GET").Subrouter()
// TopologyHandler registers the various topology routes with a http mux.
//
// The returned http.Handler has to be passed directly to http.ListenAndServe,
// and cannot be nested inside another gorrilla.mux.
//
// Routes which should be matched before the topology routes should be added
// to a router and passed in preRoutes. Routes to be matches after topology
// routes should be added to a router and passed to postRoutes.
func TopologyHandler(c Reporter, preRoutes *mux.Router, postRoutes http.Handler) http.Handler {
get := preRoutes.Methods("GET").Subrouter()
get.HandleFunc("/api", gzipHandler(apiHandler))
get.HandleFunc("/api/topology", gzipHandler(topologyRegistry.makeTopologyList(c)))
get.HandleFunc("/api/topology/{topology}",
gzipHandler(topologyRegistry.captureRenderer(c, handleTopology)))
get.HandleFunc("/api/topology/{topology}/ws",
topologyRegistry.captureRenderer(c, handleWs)) // NB not gzip!
get.MatcherFunc(URLMatcher("/api/topology/{topology}/{id}")).HandlerFunc(
gzipHandler(topologyRegistry.captureRendererWithoutFilters(c, handleNode)))
get.HandleFunc("/api/report", gzipHandler(makeRawReportHandler(c)))

if postRoutes != nil {
preRoutes.PathPrefix("/").Handler(postRoutes)
}

// We have to handle the node details path manually due to
// various bugs in gorilla.mux and Go URL parsing. See #804.
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
vars, match := matchURL(r, "/api/topology/{topology}/{id}")
if !match {
preRoutes.ServeHTTP(w, r)
return
}

topologyID := vars["topology"]
nodeID := vars["id"]
if nodeID == "ws" {
preRoutes.ServeHTTP(w, r)
return
}

handler := gzipHandler(topologyRegistry.captureRendererWithoutFilters(
c, topologyID, handleNode(nodeID),
))
handler.ServeHTTP(w, r)
})
}

// RegisterReportPostHandler registers the handler for report submission
Expand Down
10 changes: 4 additions & 6 deletions prog/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,12 @@ import (
)

// Router creates the mux for all the various app components.
func router(c app.Collector) *mux.Router {
func router(c app.Collector) http.Handler {
router := mux.NewRouter()
app.RegisterTopologyRoutes(c, router)
app.RegisterReportPostHandler(c, router)
app.RegisterControlRoutes(router)
app.RegisterPipeRoutes(router)
router.Methods("GET").PathPrefix("/").Handler(http.FileServer(FS(false)))
return router
return app.TopologyHandler(c, router, http.FileServer(FS(false)))
}

// Main runs the app
Expand All @@ -48,10 +46,10 @@ func appMain() {
app.UniqueID = strconv.FormatInt(rand.Int63(), 16)
app.Version = version
log.Printf("app starting, version %s, ID %s", app.Version, app.UniqueID)
http.Handle("/", router(app.NewCollector(*window)))
handler := router(app.NewCollector(*window))
go func() {
log.Printf("listening on %s", *listen)
log.Print(http.ListenAndServe(*listen, nil))
log.Print(http.ListenAndServe(*listen, handler))
}()

common.SignalHandlerLoop()
Expand Down
26 changes: 13 additions & 13 deletions render/expected/expected.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ var (
},
ServerProcessID: {
ID: ServerProcessID,
LabelMajor: "apache",
LabelMajor: fixture.ServerName,
LabelMinor: fmt.Sprintf("%s (%s)", fixture.ServerHostID, fixture.ServerPID),
Rank: fixture.ServerName,
Pseudo: false,
Expand Down Expand Up @@ -137,11 +137,11 @@ var (
}).Prune()

RenderedProcessNames = (render.RenderableNodes{
"curl": {
ID: "curl",
LabelMajor: "curl",
fixture.Client1Name: {
ID: fixture.Client1Name,
LabelMajor: fixture.Client1Name,
LabelMinor: "2 processes",
Rank: "curl",
Rank: fixture.Client1Name,
Pseudo: false,
Origins: report.MakeIDList(
fixture.Client54001NodeID,
Expand All @@ -150,17 +150,17 @@ var (
fixture.ClientProcess2NodeID,
fixture.ClientHostNodeID,
),
Node: report.MakeNode().WithAdjacent("apache"),
Node: report.MakeNode().WithAdjacent(fixture.ServerName),
EdgeMetadata: report.EdgeMetadata{
EgressPacketCount: newu64(30),
EgressByteCount: newu64(300),
},
},
"apache": {
ID: "apache",
LabelMajor: "apache",
fixture.ServerName: {
ID: fixture.ServerName,
LabelMajor: fixture.ServerName,
LabelMinor: "1 process",
Rank: "apache",
Rank: fixture.ServerName,
Pseudo: false,
Origins: report.MakeIDList(
fixture.Server80NodeID,
Expand All @@ -187,9 +187,9 @@ var (
Node: report.MakeNode().WithAdjacent(render.TheInternetID),
EdgeMetadata: report.EdgeMetadata{},
},
unknownPseudoNode1ID: unknownPseudoNode1("apache"),
unknownPseudoNode2ID: unknownPseudoNode2("apache"),
render.TheInternetID: theInternetNode("apache"),
unknownPseudoNode1ID: unknownPseudoNode1(fixture.ServerName),
unknownPseudoNode2ID: unknownPseudoNode2(fixture.ServerName),
render.TheInternetID: theInternetNode(fixture.ServerName),
}).Prune()

RenderedContainers = (render.RenderableNodes{
Expand Down
4 changes: 2 additions & 2 deletions test/fixture/report_fixture.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ var (
ServerPID = "215"
NonContainerPID = "1234"

Client1Name = "curl"
Client2Name = "curl"
Client1Name = "/usr/bin/curl"
Client2Name = "/usr/bin/curl"
ServerName = "apache"
NonContainerName = "bash"

Expand Down