playground: add a version endpoint
Add a /version handler to serve information about the playground Go
version.
Also consolidate writing JSON responses into a common helper.
Change-Id: I1bb3de4c23320eb58306c93a51dbe9ae5176382d
Reviewed-on: https://go-review.googlesource.com/c/playground/+/365854
Trust: Robert Findley <rfindley@google.com>
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Russ Cox <rsc@golang.org>
diff --git a/fmt.go b/fmt.go
index 91027be..98811e4 100644
--- a/fmt.go
+++ b/fmt.go
@@ -20,7 +20,7 @@
Error string
}
-func handleFmt(w http.ResponseWriter, r *http.Request) {
+func (s *server) handleFmt(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
if r.Method == "OPTIONS" {
// This is likely a pre-flight CORS request.
@@ -69,7 +69,7 @@
}
}
- json.NewEncoder(w).Encode(fmtResponse{Body: string(fs.Format())})
+ s.writeJSONResponse(w, fmtResponse{Body: string(fs.Format())}, http.StatusOK)
}
func formatGoMod(file string, data []byte) ([]byte, error) {
diff --git a/fmt_test.go b/fmt_test.go
index ceea152..b6e3f19 100644
--- a/fmt_test.go
+++ b/fmt_test.go
@@ -14,6 +14,11 @@
)
func TestHandleFmt(t *testing.T) {
+ s, err := newServer(testingOptions(t))
+ if err != nil {
+ t.Fatalf("newServer(testingOptions(t)): %v", err)
+ }
+
for _, tt := range []struct {
name string
method string
@@ -125,7 +130,7 @@
}
req := httptest.NewRequest("POST", "/fmt", strings.NewReader(form.Encode()))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- handleFmt(rec, req)
+ s.handleFmt(rec, req)
resp := rec.Result()
if resp.StatusCode != 200 {
t.Fatalf("code = %v", resp.Status)
diff --git a/sandbox.go b/sandbox.go
index 3d26dbb..b7864be 100644
--- a/sandbox.go
+++ b/sandbox.go
@@ -135,7 +135,7 @@
// if we've timed out because of an error in the code snippet, or instability
// on the playground itself. Either way, we should try to show the user the
// partial output of their program.
- s.writeResponse(w, resp, http.StatusOK)
+ s.writeJSONResponse(w, resp, http.StatusOK)
return
}
for _, e := range internalErrors {
@@ -162,21 +162,7 @@
}
}
- s.writeResponse(w, resp, http.StatusOK)
- }
-}
-
-func (s *server) writeResponse(w http.ResponseWriter, resp *response, status int) {
- var buf bytes.Buffer
- if err := json.NewEncoder(&buf).Encode(resp); err != nil {
- s.log.Errorf("error encoding response: %v", err)
- http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
- return
- }
- w.WriteHeader(status)
- if _, err := io.Copy(w, &buf); err != nil {
- s.log.Errorf("io.Copy(w, &buf): %v", err)
- return
+ s.writeJSONResponse(w, resp, http.StatusOK)
}
}
diff --git a/server.go b/server.go
index 21a9270..1ecb134 100644
--- a/server.go
+++ b/server.go
@@ -5,7 +5,10 @@
package main
import (
+ "bytes"
+ "encoding/json"
"fmt"
+ "io"
"net/http"
"strings"
"time"
@@ -47,7 +50,8 @@
func (s *server) init() {
s.mux.HandleFunc("/", s.handleEdit)
- s.mux.HandleFunc("/fmt", handleFmt)
+ s.mux.HandleFunc("/fmt", s.handleFmt)
+ s.mux.HandleFunc("/version", s.handleVersion)
s.mux.HandleFunc("/vet", s.commandHandler("vet", vetCheck))
s.mux.HandleFunc("/compile", s.commandHandler("prog", compileAndRun))
s.mux.HandleFunc("/share", s.handleShare)
@@ -90,3 +94,20 @@
}
s.mux.ServeHTTP(w, r)
}
+
+// writeJSONResponse JSON-encodes resp and writes to w with the given HTTP
+// status.
+func (s *server) writeJSONResponse(w http.ResponseWriter, resp interface{}, status int) {
+ w.Header().Set("Content-Type", "application/json")
+ var buf bytes.Buffer
+ if err := json.NewEncoder(&buf).Encode(resp); err != nil {
+ s.log.Errorf("error encoding response: %v", err)
+ http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
+ return
+ }
+ w.WriteHeader(status)
+ if _, err := io.Copy(w, &buf); err != nil {
+ s.log.Errorf("io.Copy(w, &buf): %v", err)
+ return
+ }
+}
diff --git a/server_test.go b/server_test.go
index fdac9d7..10e8df2 100644
--- a/server_test.go
+++ b/server_test.go
@@ -13,6 +13,7 @@
"net/http"
"net/http/httptest"
"os"
+ "runtime"
"sync"
"testing"
"time"
@@ -133,6 +134,8 @@
{"HTTP example", http.MethodGet, "https://play.golang.org/doc/play/http.txt", http.StatusOK, nil, []byte("net/http")},
// Gotip examples should not be available on the non-tip playground.
{"Gotip example", http.MethodGet, "https://play.golang.org/doc/play/min.gotip.txt", http.StatusNotFound, nil, nil},
+
+ {"Versions json", http.MethodGet, "https://play.golang.org/version", http.StatusOK, nil, []byte(runtime.Version())},
}
for _, tc := range testCases {
diff --git a/version.go b/version.go
new file mode 100644
index 0000000..e2e164f
--- /dev/null
+++ b/version.go
@@ -0,0 +1,39 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+ "fmt"
+ "go/build"
+ "net/http"
+ "runtime"
+)
+
+func (s *server) handleVersion(w http.ResponseWriter, req *http.Request) {
+ w.Header().Set("Access-Control-Allow-Origin", "*")
+
+ tag := build.Default.ReleaseTags[len(build.Default.ReleaseTags)-1]
+ var maj, min int
+ if _, err := fmt.Sscanf(tag, "go%d.%d", &maj, &min); err != nil {
+ code := http.StatusInternalServerError
+ http.Error(w, http.StatusText(code), code)
+ return
+ }
+
+ version := struct {
+ Version, Release, Name string
+ }{
+ Version: runtime.Version(),
+ Release: tag,
+ }
+
+ if s.gotip {
+ version.Name = "Go dev branch"
+ } else {
+ version.Name = fmt.Sprintf("Go %d.%d", maj, min)
+ }
+
+ s.writeJSONResponse(w, version, http.StatusOK)
+}