Skip to content
Snippets Groups Projects
Commit 8f99a514 authored by Will McCutchen's avatar Will McCutchen
Browse files

Add /links

parent d8fd8c61
No related branches found
No related tags found
No related merge requests found
...@@ -732,3 +732,49 @@ func handleBytes(w http.ResponseWriter, r *http.Request, streaming bool) { ...@@ -732,3 +732,49 @@ func handleBytes(w http.ResponseWriter, r *http.Request, streaming bool) {
write(chunk) write(chunk)
} }
} }
// Links redirects to the first page in a series of N links
func (h *HTTPBin) Links(w http.ResponseWriter, r *http.Request) {
parts := strings.Split(r.URL.Path, "/")
if len(parts) != 3 && len(parts) != 4 {
http.Error(w, "Not found", http.StatusNotFound)
return
}
n, err := strconv.Atoi(parts[2])
if err != nil || n < 0 || n > 256 {
http.Error(w, "Invalid link count", http.StatusBadRequest)
return
}
// Are we handling /links/<n>/<offset>? If so, render an HTML page
if len(parts) == 4 {
offset, err := strconv.Atoi(parts[3])
if err != nil {
http.Error(w, "Invalid offset", http.StatusBadRequest)
}
doLinksPage(w, r, n, offset)
return
}
// Otherwise, redirect from /links/<n> to /links/<n>/0
r.URL.Path = r.URL.Path + "/0"
w.Header().Set("Location", r.URL.String())
w.WriteHeader(http.StatusFound)
}
// doLinksPage renders a page with a series of N links
func doLinksPage(w http.ResponseWriter, r *http.Request, n int, offset int) {
w.Header().Add("Content-Type", htmlContentType)
w.WriteHeader(http.StatusOK)
w.Write([]byte("<html><head><title>Links</title></head><body>"))
for i := 0; i < n; i++ {
if i == offset {
fmt.Fprintf(w, "%d ", i)
} else {
fmt.Fprintf(w, `<a href="/links/%d/%d">%d</a> `, n, i, i)
}
}
w.Write([]byte("</body></html>"))
}
...@@ -1833,3 +1833,72 @@ func TestStreamBytes(t *testing.T) { ...@@ -1833,3 +1833,72 @@ func TestStreamBytes(t *testing.T) {
}) })
} }
} }
func TestLinks(t *testing.T) {
var redirectTests = []struct {
url string
expectedLocation string
}{
{"/links/1", "/links/1/0"},
{"/links/100", "/links/100/0"},
}
for _, test := range redirectTests {
t.Run("ok"+test.url, func(t *testing.T) {
r, _ := http.NewRequest("GET", test.url, nil)
w := httptest.NewRecorder()
handler.ServeHTTP(w, r)
assertStatusCode(t, w, http.StatusFound)
assertHeader(t, w, "Location", test.expectedLocation)
})
}
var errorTests = []struct {
url string
expectedStatus int
}{
// invalid N
{"/links/3.14", http.StatusBadRequest},
{"/links/-1", http.StatusBadRequest},
{"/links/257", http.StatusBadRequest},
// invalid offset
{"/links/1/3.14", http.StatusBadRequest},
{"/links/1/foo", http.StatusBadRequest},
}
for _, test := range errorTests {
t.Run("error"+test.url, func(t *testing.T) {
r, _ := http.NewRequest("GET", test.url, nil)
w := httptest.NewRecorder()
handler.ServeHTTP(w, r)
assertStatusCode(t, w, test.expectedStatus)
})
}
var linksPageTests = []struct {
url string
expectedContent string
}{
{"/links/2/0", `<html><head><title>Links</title></head><body>0 <a href="/links/2/1">1</a> </body></html>`},
{"/links/2/1", `<html><head><title>Links</title></head><body><a href="/links/2/0">0</a> 1 </body></html>`},
// offsets too large and too small are ignored
{"/links/2/2", `<html><head><title>Links</title></head><body><a href="/links/2/0">0</a> <a href="/links/2/1">1</a> </body></html>`},
{"/links/2/10", `<html><head><title>Links</title></head><body><a href="/links/2/0">0</a> <a href="/links/2/1">1</a> </body></html>`},
{"/links/2/-1", `<html><head><title>Links</title></head><body><a href="/links/2/0">0</a> <a href="/links/2/1">1</a> </body></html>`},
}
for _, test := range linksPageTests {
t.Run("ok"+test.url, func(t *testing.T) {
r, _ := http.NewRequest("GET", test.url, nil)
w := httptest.NewRecorder()
handler.ServeHTTP(w, r)
assertStatusCode(t, w, http.StatusOK)
assertContentType(t, w, htmlContentType)
assertBodyEquals(t, w, test.expectedContent)
})
}
}
...@@ -133,6 +133,8 @@ func (h *HTTPBin) Handler() http.Handler { ...@@ -133,6 +133,8 @@ func (h *HTTPBin) Handler() http.Handler {
mux.HandleFunc("/cache/", h.CacheControl) mux.HandleFunc("/cache/", h.CacheControl)
mux.HandleFunc("/etag/", h.ETag) mux.HandleFunc("/etag/", h.ETag)
mux.HandleFunc("/links/", h.Links)
// Make sure our ServeMux doesn't "helpfully" redirect these invalid // Make sure our ServeMux doesn't "helpfully" redirect these invalid
// endpoints by adding a trailing slash. See the ServeMux docs for more // endpoints by adding a trailing slash. See the ServeMux docs for more
// info: https://golang.org/pkg/net/http/#ServeMux // info: https://golang.org/pkg/net/http/#ServeMux
...@@ -147,6 +149,7 @@ func (h *HTTPBin) Handler() http.Handler { ...@@ -147,6 +149,7 @@ func (h *HTTPBin) Handler() http.Handler {
mux.HandleFunc("/stream", http.NotFound) mux.HandleFunc("/stream", http.NotFound)
mux.HandleFunc("/bytes", http.NotFound) mux.HandleFunc("/bytes", http.NotFound)
mux.HandleFunc("/stream-bytes", http.NotFound) mux.HandleFunc("/stream-bytes", http.NotFound)
mux.HandleFunc("/links", http.NotFound)
return logger(cors(mux)) return logger(cors(mux))
} }
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment