Skip to content

Commit 329d947

Browse files
Miguel Acerojba
Miguel Acero
authored andcommitted
internal/frontend: add frontend server endpoint for proxying play.golang.org/share
This change adds a /play handler that mirrors requests to play.golang.org/share which is used in implementing playground integration in the frontend. This change also adds a test that includes a mock server using net/httptest to check for proper status codes. Updates golang/go#36865 Change-Id: Ia3e902808534189ada747b270fed908e766c224b Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/241164 Reviewed-by: Julie Qiu <[email protected]> Reviewed-by: Jonathan Amsterdam <[email protected]>
1 parent ea15bff commit 329d947

File tree

3 files changed

+156
-0
lines changed

3 files changed

+156
-0
lines changed

internal/frontend/playground.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Copyright 2020 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package frontend
6+
7+
import (
8+
"io"
9+
"net/http"
10+
11+
"golang.org/x/pkgsite/internal/log"
12+
)
13+
14+
// playgroundURL is the playground endpoint used for share links.
15+
const playgroundURL = "https://play.golang.org"
16+
17+
// handlePlay handles requests that mirror play.golang.org/share.
18+
func (s *Server) handlePlay(w http.ResponseWriter, r *http.Request) {
19+
makeFetchPlayRequest(w, r, playgroundURL)
20+
}
21+
22+
func httpErrorStatus(w http.ResponseWriter, status int) {
23+
http.Error(w, http.StatusText(status), status)
24+
}
25+
26+
func makeFetchPlayRequest(w http.ResponseWriter, r *http.Request, pgURL string) {
27+
ctx := r.Context()
28+
if r.Method != http.MethodPost {
29+
httpErrorStatus(w, http.StatusMethodNotAllowed)
30+
return
31+
}
32+
req, err := http.NewRequest("POST", pgURL+"/share", r.Body)
33+
if err != nil {
34+
log.Errorf(ctx, "ERROR share error: %v", err)
35+
httpErrorStatus(w, http.StatusInternalServerError)
36+
return
37+
}
38+
req.Header.Set("Content-Type", "text/plain; charset=utf-8")
39+
req = req.WithContext(r.Context())
40+
resp, err := http.DefaultClient.Do(req)
41+
if err != nil {
42+
log.Errorf(ctx, "ERROR share error: %v", err)
43+
httpErrorStatus(w, http.StatusInternalServerError)
44+
return
45+
}
46+
copyHeader := func(k string) {
47+
if v := resp.Header.Get(k); v != "" {
48+
w.Header().Set(k, v)
49+
}
50+
}
51+
copyHeader("Content-Type")
52+
copyHeader("Content-Length")
53+
defer resp.Body.Close()
54+
w.WriteHeader(resp.StatusCode)
55+
if _, err := io.Copy(w, resp.Body); err != nil {
56+
log.Errorf(ctx, "ERROR writing shareId: %v", err)
57+
}
58+
}

internal/frontend/playground_test.go

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// Copyright 2020 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package frontend
6+
7+
import (
8+
"io"
9+
"io/ioutil"
10+
"net/http"
11+
"net/http/httptest"
12+
"strings"
13+
"testing"
14+
)
15+
16+
const shareID = "arbitraryShareID"
17+
18+
func TestPlaygroundShare(t *testing.T) {
19+
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
20+
if r.Method != http.MethodPost {
21+
http.Error(w, "Expected a POST", http.StatusMethodNotAllowed)
22+
}
23+
_, err := io.WriteString(w, shareID)
24+
if err != nil {
25+
t.Fatal(err)
26+
}
27+
}))
28+
defer ts.Close()
29+
30+
testCases := []struct {
31+
pgURL string
32+
method string
33+
desc string
34+
body string
35+
code int
36+
shareID string
37+
}{
38+
{
39+
pgURL: ts.URL,
40+
method: http.MethodPost,
41+
desc: "Share endpoint: for Hello World func",
42+
body: `package main
43+
import (
44+
"fmt"
45+
)
46+
47+
func main() {
48+
fmt.Println("Hello, playground")
49+
}`,
50+
code: http.StatusOK,
51+
},
52+
{
53+
pgURL: ts.URL,
54+
method: http.MethodGet,
55+
desc: "Share endpoint: Failed GET Request, Accept POST only",
56+
body: "",
57+
code: http.StatusMethodNotAllowed,
58+
},
59+
{
60+
pgURL: "/*?",
61+
method: http.MethodPost,
62+
desc: "Share endpoint: Malformed URL returns internal server error",
63+
body: "",
64+
code: http.StatusInternalServerError,
65+
},
66+
}
67+
for _, tc := range testCases {
68+
t.Run(tc.desc, func(t *testing.T) {
69+
body := strings.NewReader(tc.body)
70+
71+
req, err := http.NewRequest(tc.method, "/play", body)
72+
if err != nil {
73+
t.Fatal(err)
74+
}
75+
req.Header.Set("Content-Type", "text/plain; charset=utf-8")
76+
w := httptest.NewRecorder()
77+
makeFetchPlayRequest(w, req, tc.pgURL)
78+
79+
res := w.Result()
80+
81+
if got, want := res.StatusCode, tc.code; got != want {
82+
t.Errorf("Status Code = %d; want %d", got, want)
83+
}
84+
85+
if res.StatusCode >= 200 && res.StatusCode < 300 {
86+
body, err := ioutil.ReadAll(res.Body)
87+
if err != nil {
88+
t.Fatal(err)
89+
}
90+
91+
if string(body) != shareID {
92+
t.Errorf("body = %s; want %s", body, shareID)
93+
}
94+
}
95+
})
96+
}
97+
}

internal/frontend/server.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ func (s *Server) Install(handle func(string, http.Handler), redisClient *redis.C
102102
http.ServeFile(w, r, fmt.Sprintf("%s/img/favicon.ico", http.Dir(s.staticPath.String())))
103103
}))
104104
handle("/fetch/", fetchHandler)
105+
handle("/play/", http.HandlerFunc(s.handlePlay))
105106
handle("/pkg/", http.HandlerFunc(s.handlePackageDetailsRedirect))
106107
handle("/search", searchHandler)
107108
handle("/search-help", s.staticPageHandler("search_help.tmpl", "Search Help - go.dev"))

0 commit comments

Comments
 (0)