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

Add /stream/:n

parent cfbb4f63
No related branches found
No related tags found
No related merge requests found
......@@ -390,3 +390,38 @@ func (h *HTTPBin) DigestAuth(w http.ResponseWriter, r *http.Request) {
}
http.Error(w, "Not Implemented", http.StatusNotImplemented)
}
// Stream responds with max(n, 100) lines of JSON-encoded request data.
func (h *HTTPBin) Stream(w http.ResponseWriter, r *http.Request) {
parts := strings.Split(r.URL.Path, "/")
if len(parts) != 3 {
http.Error(w, "Not found", http.StatusNotFound)
return
}
n, err := strconv.Atoi(parts[2])
if err != nil {
http.Error(w, "Invalid integer", http.StatusBadRequest)
}
if n > 100 {
n = 100
} else if n < 1 {
n = 1
}
resp := &streamResponse{
Args: r.URL.Query(),
Headers: r.Header,
Origin: getOrigin(r),
URL: getURL(r).String(),
}
f := w.(http.Flusher)
for i := 0; i < n; i++ {
resp.ID = i
line, _ := json.Marshal(resp)
w.Write(line)
w.Write([]byte("\n"))
f.Flush()
}
}
package httpbin
import (
"bufio"
"bytes"
"compress/flate"
"compress/gzip"
......@@ -1163,3 +1164,64 @@ func TestDeflate(t *testing.T) {
t.Fatalf("expected compressed body")
}
}
func TestStream(t *testing.T) {
var okTests = []struct {
url string
expectedLines int
}{
{"/stream/20", 20},
{"/stream/100", 100},
{"/stream/1000", 100},
{"/stream/0", 1},
{"/stream/-100", 1},
}
for _, test := range okTests {
t.Run("ok"+test.url, func(t *testing.T) {
r, _ := http.NewRequest("GET", test.url, nil)
w := httptest.NewRecorder()
handler.ServeHTTP(w, r)
// The stdlib seems to automagically unchunk these responses and
// I'm not quite sure how to test this
// assertHeader(t, w, "Transfer-Encoding", "chunked")
var resp *streamResponse
var err error
i := 0
scanner := bufio.NewScanner(w.Body)
for scanner.Scan() {
err = json.Unmarshal(scanner.Bytes(), &resp)
if err != nil {
t.Fatalf("error unmarshalling response: %s", err)
}
if resp.ID != i {
t.Fatalf("bad id: %v != %v", resp.ID, i)
}
i++
}
if err := scanner.Err(); err != nil {
t.Fatalf("error scanning streaming response: %s", err)
}
})
}
var badTests = []struct {
url string
code int
}{
{"/stream", http.StatusNotFound},
{"/stream/foo", http.StatusBadRequest},
{"/stream/3.1415", http.StatusBadRequest},
}
for _, test := range badTests {
t.Run("bad"+test.url, func(t *testing.T) {
r, _ := http.NewRequest("GET", test.url, nil)
w := httptest.NewRecorder()
handler.ServeHTTP(w, r)
assertStatusCode(t, w, test.code)
})
}
}
......@@ -59,6 +59,16 @@ type deflateResponse struct {
Deflated bool `json:"deflated"`
}
// An actual stream response body will be made up of one or more of these
// structs, encoded as JSON and separated by newlines
type streamResponse struct {
ID int `json:"id"`
Args url.Values `json:"args"`
Headers http.Header `json:"headers"`
Origin string `json:"origin"`
URL string `json:"url"`
}
// Options are used to configure HTTPBin
type Options struct {
MaxMemory int64
......@@ -104,6 +114,8 @@ func (h *HTTPBin) Handler() http.Handler {
mux.HandleFunc("/deflate", h.Deflate)
mux.HandleFunc("/gzip", h.Gzip)
mux.HandleFunc("/stream/", h.Stream)
// 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
......@@ -114,6 +126,7 @@ func (h *HTTPBin) Handler() http.Handler {
mux.HandleFunc("/redirect", http.NotFound)
mux.HandleFunc("/relative-redirect", http.NotFound)
mux.HandleFunc("/status", http.NotFound)
mux.HandleFunc("/stream", http.NotFound)
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