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

Add JSONP support for monitoring routes #103

Merged
merged 12 commits into from Aug 22, 2015
13 changes: 13 additions & 0 deletions README.md
Expand Up @@ -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
Expand Down
56 changes: 38 additions & 18 deletions server/monitor.go
Expand Up @@ -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 {
Expand Down Expand Up @@ -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.
Expand All @@ -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.
Expand Down Expand Up @@ -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, "<html lang=\"en\">gnatsd monitoring<br/><br/>")
vlink := fmt.Sprintf("http://%s/varz", r.Host)
fmt.Fprintf(w, "<a href=%s>%s</a><br/>", vlink, vlink)
clink := fmt.Sprintf("http://%s/connz", r.Host)
fmt.Fprintf(w, "<a href=%s>%s</a><br/>", clink, clink)
rlink := fmt.Sprintf("http://%s/routez", r.Host)
fmt.Fprintf(w, "<a href=%s>%s</a><br/>", rlink, rlink)
slink := fmt.Sprintf("http://%s/subscriptionsz", r.Host)
fmt.Fprintf(w, "<a href=%s>%s</a><br/>", slink, slink)
fmt.Fprint(w, "</html>")
fmt.Fprintf(w, `<html lang="en">
<body>
gnatsd monitoring
<br/><br/>
<a href=http://%s/varz>http://%s/varz</a><br/>
<a href=http://%s/connz>http://%s/connz</a><br/>
<a href=http://%s/routez>http://%s/routez</a><br/>
<a href=http://%s/subscriptionsz>http://%s/subscriptionsz</a><br/>
</body>
</html>`, r.Host, r.Host, r.Host, r.Host, r.Host, r.Host, r.Host, r.Host)
}

// HandleVarz will process HTTP requests for server information.
Expand All @@ -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
Expand All @@ -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)
}
}
82 changes: 82 additions & 0 deletions server/monitor_test.go
Expand Up @@ -8,6 +8,7 @@ import (
"io/ioutil"
"net/http"
"net/url"
"strings"
"testing"
"time"

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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) {
Expand All @@ -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 {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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) {
Expand All @@ -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 {
Expand All @@ -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
Expand Down