diff --git a/README.md b/README.md
index c3b537b2675..11c2a5618ba 100644
--- a/README.md
+++ b/README.md
@@ -236,6 +236,19 @@ You can also report detailed subscription information on a per connection basis
}
```
+Monitoring endpoints support JSONP for CORS so you can easily create single page
+web applications for monitoring. Simply pass `callback` query parameter to any
+endpoint. For example; `http://localhost:8222/connz?callback=cb`
+
+```javascript
+// JQuery example
+
+$.getJSON('http://localhost:8222/connz?callback=?', function(data) {
+ console.log(data);
+});
+
+```
+
## Building
This code currently requires at _least_ version 1.1 of Go, but we encourage
diff --git a/server/monitor.go b/server/monitor.go
index d0be11ec3e3..ed58994009c 100644
--- a/server/monitor.go
+++ b/server/monitor.go
@@ -140,8 +140,9 @@ func (s *Server) HandleConnz(w http.ResponseWriter, r *http.Request) {
if err != nil {
Errorf("Error marshalling response to /connz request: %v", err)
}
- w.Header().Set("Content-Type", "application/json")
- w.Write(b)
+
+ // Handle response
+ ResponseHandler(w, r, b)
}
func castToSliceString(input []interface{}) []string {
@@ -220,8 +221,9 @@ func (s *Server) HandleRoutez(w http.ResponseWriter, r *http.Request) {
if err != nil {
Errorf("Error marshalling response to /routez request: %v", err)
}
- w.Header().Set("Content-Type", "application/json")
- w.Write(b)
+
+ // Handle response
+ ResponseHandler(w, r, b)
}
// HandleStats process HTTP requests for subjects stats.
@@ -232,8 +234,9 @@ func (s *Server) HandleSubsz(w http.ResponseWriter, r *http.Request) {
if err != nil {
Errorf("Error marshalling response to /subscriptionsz request: %v", err)
}
- w.Header().Set("Content-Type", "application/json")
- w.Write(b)
+
+ // Handle response
+ ResponseHandler(w, r, b)
}
// Varz will output server information on the monitoring port at /varz.
@@ -287,16 +290,16 @@ func myUptime(d time.Duration) string {
// HandleRoot will show basic info and links to others handlers.
func (s *Server) HandleRoot(w http.ResponseWriter, r *http.Request) {
- fmt.Fprint(w, "gnatsd monitoring
")
- vlink := fmt.Sprintf("http://%s/varz", r.Host)
- fmt.Fprintf(w, "%s
", vlink, vlink)
- clink := fmt.Sprintf("http://%s/connz", r.Host)
- fmt.Fprintf(w, "%s
", clink, clink)
- rlink := fmt.Sprintf("http://%s/routez", r.Host)
- fmt.Fprintf(w, "%s
", rlink, rlink)
- slink := fmt.Sprintf("http://%s/subscriptionsz", r.Host)
- fmt.Fprintf(w, "%s
", slink, slink)
- fmt.Fprint(w, "")
+ fmt.Fprintf(w, `
+
+ gnatsd monitoring
+
+ http://%s/varz
+ http://%s/connz
+ http://%s/routez
+ http://%s/subscriptionsz
+
+ `, r.Host, r.Host, r.Host, r.Host, r.Host, r.Host, r.Host, r.Host)
}
// HandleVarz will process HTTP requests for server information.
@@ -322,8 +325,9 @@ func (s *Server) HandleVarz(w http.ResponseWriter, r *http.Request) {
if err != nil {
Errorf("Error marshalling response to /varz request: %v", err)
}
- w.Header().Set("Content-Type", "application/json")
- w.Write(b)
+
+ // Handle response
+ ResponseHandler(w, r, b)
}
// Grab RSS and PCPU
@@ -337,3 +341,19 @@ func updateUsage(v *Varz) {
v.CPU = pcpu
v.Cores = numCores
}
+
+// ResponseHandler handles responses for monitoring routes
+func ResponseHandler(w http.ResponseWriter, r *http.Request, data []byte) {
+ // Get callback from request
+ callback := r.URL.Query().Get("callback")
+ // If callback is not empty then
+ if callback != "" {
+ // Response for JSONP
+ w.Header().Set("Content-Type", "application/javascript")
+ fmt.Fprintf(w, "%s(%s)", callback, data)
+ } else {
+ // Otherwise JSON
+ w.Header().Set("Content-Type", "application/json")
+ w.Write(data)
+ }
+}
diff --git a/server/monitor_test.go b/server/monitor_test.go
index 8909a6f4c16..bb04118731b 100644
--- a/server/monitor_test.go
+++ b/server/monitor_test.go
@@ -8,6 +8,7 @@ import (
"io/ioutil"
"net/http"
"net/url"
+ "strings"
"testing"
"time"
@@ -99,6 +100,10 @@ func TestVarz(t *testing.T) {
if resp.StatusCode != 200 {
t.Fatalf("Expected a 200 response, got %d\n", resp.StatusCode)
}
+ ct := resp.Header.Get("Content-Type")
+ if ct != "application/json" {
+ t.Fatalf("Expected application/json content-type, got %s\n", ct)
+ }
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
@@ -151,6 +156,17 @@ func TestVarz(t *testing.T) {
if v.OutBytes != 5 {
t.Fatalf("Expected OutBytes of 5, got %v\n", v.OutBytes)
}
+
+ // Test JSONP
+ respj, errj := http.Get(fmt.Sprintf("http://localhost:%d/", DEFAULT_HTTP_PORT) + "varz?callback=callback")
+ if errj != nil {
+ t.Fatalf("Expected no error: Got %v\n", err)
+ }
+ ct = respj.Header.Get("Content-Type")
+ if ct != "application/javascript" {
+ t.Fatalf("Expected application/javascript content-type, got %s\n", ct)
+ }
+ defer respj.Body.Close()
}
func TestConnz(t *testing.T) {
@@ -165,6 +181,10 @@ func TestConnz(t *testing.T) {
if resp.StatusCode != 200 {
t.Fatalf("Expected a 200 response, got %d\n", resp.StatusCode)
}
+ ct := resp.Header.Get("Content-Type")
+ if ct != "application/json" {
+ t.Fatalf("Expected application/json content-type, got %s\n", ct)
+ }
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
@@ -249,6 +269,17 @@ func TestConnz(t *testing.T) {
if ci.OutBytes != 5 {
t.Fatalf("Expected OutBytes of 1, got %v\n", ci.OutBytes)
}
+
+ // Test JSONP
+ respj, errj := http.Get(fmt.Sprintf("http://localhost:%d/", DEFAULT_HTTP_PORT) + "connz?callback=callback")
+ if errj != nil {
+ t.Fatalf("Expected no error: Got %v\n", err)
+ }
+ ct = respj.Header.Get("Content-Type")
+ if ct != "application/javascript" {
+ t.Fatalf("Expected application/javascript content-type, got %s\n", ct)
+ }
+ defer respj.Body.Close()
}
func TestConnzWithSubs(t *testing.T) {
@@ -629,6 +660,10 @@ func TestConnzWithRoutes(t *testing.T) {
if resp.StatusCode != 200 {
t.Fatalf("Expected a 200 response, got %d\n", resp.StatusCode)
}
+ ct := resp.Header.Get("Content-Type")
+ if ct != "application/json" {
+ t.Fatalf("Expected application/json content-type, got %s\n", ct)
+ }
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
@@ -682,6 +717,17 @@ func TestConnzWithRoutes(t *testing.T) {
if route.DidSolicit != false {
t.Fatalf("Expected unsolicited route, got %v\n", route.DidSolicit)
}
+
+ // Test JSONP
+ respj, errj := http.Get(fmt.Sprintf("http://localhost:%d/", DEFAULT_HTTP_PORT) + "routez?callback=callback")
+ if errj != nil {
+ t.Fatalf("Expected no error: Got %v\n", err)
+ }
+ ct = respj.Header.Get("Content-Type")
+ if ct != "application/javascript" {
+ t.Fatalf("Expected application/javascript content-type, got %s\n", ct)
+ }
+ defer respj.Body.Close()
}
func TestSubsz(t *testing.T) {
@@ -699,6 +745,10 @@ func TestSubsz(t *testing.T) {
if resp.StatusCode != 200 {
t.Fatalf("Expected a 200 response, got %d\n", resp.StatusCode)
}
+ ct := resp.Header.Get("Content-Type")
+ if ct != "application/json" {
+ t.Fatalf("Expected application/json content-type, got %s\n", ct)
+ }
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
@@ -719,6 +769,38 @@ func TestSubsz(t *testing.T) {
t.Fatalf("Expected NumMatches of 1, got %d\n", sl.NumMatches)
}
+ // Test JSONP
+ respj, errj := http.Get(fmt.Sprintf("http://localhost:%d/", DEFAULT_HTTP_PORT) + "subscriptionsz?callback=callback")
+ ct = respj.Header.Get("Content-Type")
+ if errj != nil {
+ t.Fatalf("Expected no error: Got %v\n", err)
+ }
+ if ct != "application/javascript" {
+ t.Fatalf("Expected application/javascript content-type, got %s\n", ct)
+ }
+ defer respj.Body.Close()
+}
+
+// Tests handle root
+func TestHandleRoot(t *testing.T) {
+ s := runMonitorServer(DEFAULT_HTTP_PORT)
+ defer s.Shutdown()
+
+ nc := createClientConnSubscribeAndPublish(t)
+ defer nc.Close()
+
+ resp, err := http.Get(fmt.Sprintf("http://localhost:%d/", DEFAULT_HTTP_PORT))
+ if err != nil {
+ t.Fatalf("Expected no error: Got %v\n", err)
+ }
+ if resp.StatusCode != 200 {
+ t.Fatalf("Expected a 200 response, got %d\n", resp.StatusCode)
+ }
+ ct := resp.Header.Get("Content-Type")
+ if !strings.Contains(ct, "text/html") {
+ t.Fatalf("Expected text/html response, got %s\n", ct)
+ }
+ defer resp.Body.Close()
}
// Create a connection to test ConnInfo