Skip to content

Commit 51b22ef

Browse files
committed
internal/dl: add CORS support to JSON endpoint
This change allows users to request golang.org/dl/?mode=json via Cross-Origin Resource Sharing (CORS). It also removes the golangorg build tag as it did not seem necessary and adds tests for the “include” GET parameter. Updates golang/go#29206 Fixes golang/go#40253 Change-Id: I5306a264c4ac2a6e6f49cfb53db01eef6b7f4473 Reviewed-on: https://go-review.googlesource.com/c/website/+/243118 Run-TryBot: Andrew Bonventre <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Alexander Rakoczy <[email protected]> Reviewed-by: Dmitri Shuralyov <[email protected]>
1 parent a8d8c12 commit 51b22ef

File tree

2 files changed

+120
-16
lines changed

2 files changed

+120
-16
lines changed

internal/dl/server.go

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
// Use of this source code is governed by a BSD-style
33
// license that can be found in the LICENSE file.
44

5-
// +build golangorg
6-
75
package dl
86

97
import (
@@ -46,7 +44,7 @@ func RegisterHandlers(mux *http.ServeMux, dc *datastore.Client, mc *memcache.Cli
4644
var rootKey = datastore.NameKey("FileRoot", "root", nil)
4745

4846
func (h server) listHandler(w http.ResponseWriter, r *http.Request) {
49-
if r.Method != "GET" {
47+
if r.Method != "GET" && r.Method != "OPTIONS" {
5048
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
5149
return
5250
}
@@ -80,19 +78,7 @@ func (h server) listHandler(w http.ResponseWriter, r *http.Request) {
8078
}
8179

8280
if r.URL.Query().Get("mode") == "json" {
83-
var releases []Release
84-
switch r.URL.Query().Get("include") {
85-
case "all":
86-
releases = append(append(d.Stable, d.Archive...), d.Unstable...)
87-
default:
88-
releases = d.Stable
89-
}
90-
w.Header().Set("Content-Type", "application/json")
91-
enc := json.NewEncoder(w)
92-
enc.SetIndent("", " ")
93-
if err := enc.Encode(releases); err != nil {
94-
log.Printf("ERROR rendering JSON for releases: %v", err)
95-
}
81+
serveJSON(w, r, d)
9682
return
9783
}
9884

@@ -101,6 +87,32 @@ func (h server) listHandler(w http.ResponseWriter, r *http.Request) {
10187
}
10288
}
10389

90+
// serveJSON serves a JSON representation of d. It assumes that requests are
91+
// limited to GET and OPTIONS, the latter used for CORS requests, which this
92+
// endpoint supports.
93+
func serveJSON(w http.ResponseWriter, r *http.Request, d listTemplateData) {
94+
w.Header().Set("Access-Control-Allow-Origin", "*")
95+
w.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS")
96+
if r.Method == "OPTIONS" {
97+
// Likely a CORS preflight request.
98+
w.WriteHeader(http.StatusNoContent)
99+
return
100+
}
101+
var releases []Release
102+
switch r.URL.Query().Get("include") {
103+
case "all":
104+
releases = append(append(d.Stable, d.Archive...), d.Unstable...)
105+
default:
106+
releases = d.Stable
107+
}
108+
w.Header().Set("Content-Type", "application/json")
109+
enc := json.NewEncoder(w)
110+
enc.SetIndent("", " ")
111+
if err := enc.Encode(releases); err != nil {
112+
log.Printf("ERROR rendering JSON for releases: %v", err)
113+
}
114+
}
115+
104116
// googleCN reports whether request r is considered
105117
// to be served from golang.google.cn.
106118
// TODO: This is duplicated within internal/proxy. Move to a common location.

internal/dl/server_test.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
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 dl
6+
7+
import (
8+
"encoding/json"
9+
"net/http/httptest"
10+
"sort"
11+
"testing"
12+
)
13+
14+
func TestServeJSON(t *testing.T) {
15+
data := listTemplateData{
16+
Stable: []Release{{Version: "Stable"}},
17+
Unstable: []Release{{Version: "Unstable"}},
18+
Archive: []Release{{Version: "Archived"}},
19+
}
20+
testCases := []struct {
21+
desc string
22+
method string
23+
target string
24+
status int
25+
versions []string
26+
}{
27+
{
28+
desc: "basic",
29+
method: "GET",
30+
target: "/",
31+
status: 200,
32+
versions: []string{"Stable"},
33+
},
34+
{
35+
desc: "include all versions",
36+
method: "GET",
37+
target: "/?include=all",
38+
status: 200,
39+
versions: []string{"Stable", "Unstable", "Archived"},
40+
},
41+
{
42+
desc: "CORS preflight request",
43+
method: "OPTIONS",
44+
target: "/",
45+
status: 204,
46+
},
47+
}
48+
for _, tc := range testCases {
49+
t.Run(tc.desc, func(t *testing.T) {
50+
r := httptest.NewRequest(tc.method, tc.target, nil)
51+
w := httptest.NewRecorder()
52+
serveJSON(w, r, data)
53+
54+
resp := w.Result()
55+
defer resp.Body.Close()
56+
if got, want := resp.StatusCode, tc.status; got != want {
57+
t.Errorf("Response status code = %d; want %d", got, want)
58+
}
59+
for k, v := range map[string]string{
60+
"Access-Control-Allow-Origin": "*",
61+
"Access-Control-Allow-Methods": "GET, OPTIONS",
62+
} {
63+
if got, want := resp.Header.Get(k), v; got != want {
64+
t.Errorf("%s = %q; want %q", k, got, want)
65+
}
66+
}
67+
if tc.versions == nil {
68+
return
69+
}
70+
71+
if got, want := resp.Header.Get("Content-Type"), "application/json"; got != want {
72+
t.Errorf("Content-Type = %q; want %q", got, want)
73+
}
74+
var rs []Release
75+
if err := json.NewDecoder(resp.Body).Decode(&rs); err != nil {
76+
t.Fatalf("json.Decode: got unexpected error: %v", err)
77+
}
78+
sort.Slice(rs, func(i, j int) bool {
79+
return rs[i].Version < rs[j].Version
80+
})
81+
sort.Strings(tc.versions)
82+
if got, want := len(rs), len(tc.versions); got != want {
83+
t.Fatalf("Number of releases = %d; want %d", got, want)
84+
}
85+
for i := range rs {
86+
if got, want := rs[i].Version, tc.versions[i]; got != want {
87+
t.Errorf("Got version %q; want %q", got, want)
88+
}
89+
}
90+
})
91+
}
92+
}

0 commit comments

Comments
 (0)