From 8f99a5140a620438fec66e0cfcdc6561e7472220 Mon Sep 17 00:00:00 2001 From: Will McCutchen <will@mccutch.org> Date: Mon, 29 May 2017 19:19:32 -0700 Subject: [PATCH] Add /links --- httpbin/handlers.go | 46 +++++++++++++++++++++++++++ httpbin/handlers_test.go | 69 ++++++++++++++++++++++++++++++++++++++++ httpbin/httpbin.go | 3 ++ 3 files changed, 118 insertions(+) diff --git a/httpbin/handlers.go b/httpbin/handlers.go index 261c460..6bec584 100644 --- a/httpbin/handlers.go +++ b/httpbin/handlers.go @@ -732,3 +732,49 @@ func handleBytes(w http.ResponseWriter, r *http.Request, streaming bool) { 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>")) +} diff --git a/httpbin/handlers_test.go b/httpbin/handlers_test.go index 824cb6b..7b8bf2f 100644 --- a/httpbin/handlers_test.go +++ b/httpbin/handlers_test.go @@ -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) + }) + } +} diff --git a/httpbin/httpbin.go b/httpbin/httpbin.go index 9cabf8d..81460f1 100644 --- a/httpbin/httpbin.go +++ b/httpbin/httpbin.go @@ -133,6 +133,8 @@ func (h *HTTPBin) Handler() http.Handler { mux.HandleFunc("/cache/", h.CacheControl) mux.HandleFunc("/etag/", h.ETag) + mux.HandleFunc("/links/", h.Links) + // Make sure our ServeMux doesn't "helpfully" redirect these invalid // endpoints by adding a trailing slash. See the ServeMux docs for more // info: https://golang.org/pkg/net/http/#ServeMux @@ -147,6 +149,7 @@ func (h *HTTPBin) Handler() http.Handler { mux.HandleFunc("/stream", http.NotFound) mux.HandleFunc("/bytes", http.NotFound) mux.HandleFunc("/stream-bytes", http.NotFound) + mux.HandleFunc("/links", http.NotFound) return logger(cors(mux)) } -- GitLab