From 9d90942c70eaa4117c79d7d3c501f9ce88a7631d Mon Sep 17 00:00:00 2001 From: Steve Kemp Date: Tue, 12 Dec 2017 13:57:25 +0200 Subject: [PATCH] Allow /api/state/$state to return text/xml/json. This matches our other end-points, and would have saved me a bit of time this morning building up a list of hosts in the failed-state. This closes #39. --- API.md | 11 ++++++- cmd_serve.go | 48 +++++++++++++++++++++++----- cmd_serve_test.go | 80 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 130 insertions(+), 9 deletions(-) diff --git a/API.md b/API.md index fe84319..2ed21bc 100644 --- a/API.md +++ b/API.md @@ -63,7 +63,16 @@ API Endpoints ------------- In addition to the scripting posibilities available with the multi-format -responses there is also a simple end-point which is designed to return a +responses there is also a simple end-point which is designed to return a list of all the nodes in the given state: * `GET /api/state/$state` + +This will default to JSON, but you can choose JSON, XML, or pain-text, via the +Accept: header or `?accept=application/json` parameter, for example: + + + $ curl -H Accept:text/plain http://localhost:3001/api/state/unchanged + $ curl -H Accept:application/xml http://localhost:3001/api/state/unchanged + $ curl http://localhost:3001/api/state/unchanged?accept=text/plain + $ curl http://localhost:3001/api/state/unchanged?accept=application/xml diff --git a/cmd_serve.go b/cmd_serve.go index 1fabee7..ab8909e 100644 --- a/cmd_serve.go +++ b/cmd_serve.go @@ -42,7 +42,8 @@ func Exists(name string) bool { // // GET /api/state/$state // -// This only returns JSON, which is perhaps a mistake. +// This only will return plain-text by default, but JSON and XML are both +// possible via the `Accept:` header or `?accept=XX` parameter. // func APIState(res http.ResponseWriter, req *http.Request) { @@ -109,15 +110,46 @@ func APIState(res http.ResponseWriter, req *http.Request) { } // - // Convert the string-array to JSON, and return it. + // What kind of reply should we send? + // + // Accept either a "?accept=XXX" URL-parameter, or + // the Accept HEADER in the HTTP request // - res.Header().Set("Content-Type", "application/json") + accept := req.FormValue("accept") + if len(accept) < 1 { + accept = req.Header.Get("Accept") + } + + switch accept { + case "text/plain": + res.Header().Set("Content-Type", "text/plain") + + for _, o := range result { + fmt.Fprintf(res, "%s\n", o) + } + case "application/xml": + x, err := xml.MarshalIndent(result, "", " ") + if err != nil { + status = http.StatusInternalServerError + return + } + + res.Header().Set("Content-Type", "application/xml") + res.Write(x) + default: + + // + // Convert the string-array to JSON, and return it. + // + res.Header().Set("Content-Type", "application/json") + + if len(result) > 0 { + out, _ := json.Marshal(result) + fmt.Fprintf(res, "%s", out) + } else { + fmt.Fprintf(res, "[]") + } - if len(result) > 0 { - out, _ := json.Marshal(result) - fmt.Fprintf(res, "%s", out) - } else { - fmt.Fprintf(res, "[]") } } diff --git a/cmd_serve_test.go b/cmd_serve_test.go index ab9e8a9..bcdfe68 100644 --- a/cmd_serve_test.go +++ b/cmd_serve_test.go @@ -269,6 +269,86 @@ func TestKnownAPIState(t *testing.T) { } +// +// API state should accept XML, JSON, and plain-text +// +func TestAPITypes(t *testing.T) { + + // Create a fake database + FakeDB() + + // Add some data. + addFakeNodes() + + // Wire up the router. + r := mux.NewRouter() + r.HandleFunc("/api/state/{state}", APIState).Methods("GET") + r.HandleFunc("/api/state/{state}/", APIState).Methods("GET") + + // Get the test-server + ts := httptest.NewServer(r) + defer ts.Close() + + // + // We'll make one test for each known-state + // + type TestCase struct { + Type string + Response string + } + + tests := []TestCase{ + {"application/json", "[\"foo.example.com\"]"}, + {"application/xml", "foo.example.com"}, + {"text/plain", "foo.example.com\n"}, + {"", "[\"foo.example.com\"]"}, + } + + // + // Run each one. + // + for _, test := range tests { + + // + // Make the request + // + url := ts.URL + "/api/state/changed?accept=" + test.Type + + resp, err := http.Get(url) + if err != nil { + t.Fatal(err) + } + + // + // Get the body + // + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + + if err != nil { + t.Errorf("Failed to read response-body %v\n", err) + } + + content := fmt.Sprintf("%s", body) + + if status := resp.StatusCode; status != http.StatusOK { + t.Errorf("Unexpected status-code: %v", status) + } + if content != test.Response { + t.Fatalf("Unexpected body for %s: '%s'", test.Type, body) + } + } + + // + // Cleanup here because otherwise later tests will + // see an active/valid DB-handle. + // + db.Close() + db = nil + os.RemoveAll(path) + +} + // // Searching must be done via a POST. //