diff --git a/httpbin/handlers.go b/httpbin/handlers.go index c9255e40f7d14d9dd997fc4b2caaf6e2573b549a..9b5fa3753a722769be31e28a01c653a89159282a 100644 --- a/httpbin/handlers.go +++ b/httpbin/handlers.go @@ -425,3 +425,27 @@ func (h *HTTPBin) Stream(w http.ResponseWriter, r *http.Request) { f.Flush() } } + +// Delay waits for n seconds before responding +func (h *HTTPBin) Delay(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.ParseFloat(parts[2], 64) + if err != nil { + http.Error(w, "Invalid float", http.StatusBadRequest) + } + + // n is seconds as a float, and we allow down to millisecond resolution + delay := time.Duration(n*1000) * time.Millisecond + if delay > h.options.MaxResponseTime { + delay = h.options.MaxResponseTime + } else if delay < 0 { + delay = 0 + } + + <-time.After(delay) + h.RequestWithBody(w, r) +} diff --git a/httpbin/handlers_test.go b/httpbin/handlers_test.go index a9e7aea0153ca209356fb85c7fd336c685239a41..88eeb33b3b4a2f760691524d0ed76b43508e5eb5 100644 --- a/httpbin/handlers_test.go +++ b/httpbin/handlers_test.go @@ -19,10 +19,12 @@ import ( "time" ) -const maxMemory = 1024 * 1024 +const maxMemory int64 = 1024 * 1024 +const maxResponseTime time.Duration = 1 * time.Second var app = NewHTTPBin(&Options{ - MaxMemory: maxMemory, + MaxMemory: maxMemory, + MaxResponseTime: maxResponseTime, }) var handler = app.Handler() @@ -1225,3 +1227,58 @@ func TestStream(t *testing.T) { }) } } + +func TestDelay(t *testing.T) { + var okTests = []struct { + url string + expectedDelay time.Duration + }{ + {"/delay/0", 0}, + {"/delay/0.5", 500 * time.Millisecond}, + {"/delay/1", maxResponseTime}, + {"/delay/1.5", maxResponseTime}, + {"/delay/-1", 0}, + {"/delay/-3.14", 0}, + } + for _, test := range okTests { + t.Run("ok"+test.url, func(t *testing.T) { + start := time.Now() + + r, _ := http.NewRequest("GET", test.url, nil) + w := httptest.NewRecorder() + handler.ServeHTTP(w, r) + + elapsed := time.Now().Sub(start) + + assertStatusCode(t, w, http.StatusOK) + assertHeader(t, w, "Content-Type", jsonContentType) + + var resp *bodyResponse + err := json.Unmarshal(w.Body.Bytes(), &resp) + if err != nil { + t.Fatalf("error unmarshalling response: %s", err) + } + + if elapsed < test.expectedDelay { + t.Fatalf("expected delay of %s, got %s", test.expectedDelay, elapsed) + } + }) + } + + var badTests = []struct { + url string + code int + }{ + {"/delay", http.StatusNotFound}, + {"/delay/foo", 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) + }) + } +} diff --git a/httpbin/httpbin.go b/httpbin/httpbin.go index 5de82089129633e933cb0bcb8d5b9d61934ddc8c..04cad814942e3e6578075a342377ed3a4b235038 100644 --- a/httpbin/httpbin.go +++ b/httpbin/httpbin.go @@ -3,6 +3,7 @@ package httpbin import ( "net/http" "net/url" + "time" ) const jsonContentType = "application/json; encoding=utf-8" @@ -71,7 +72,8 @@ type streamResponse struct { // Options are used to configure HTTPBin type Options struct { - MaxMemory int64 + MaxMemory int64 + MaxResponseTime time.Duration } // HTTPBin contains the business logic @@ -115,12 +117,14 @@ func (h *HTTPBin) Handler() http.Handler { mux.HandleFunc("/gzip", h.Gzip) mux.HandleFunc("/stream/", h.Stream) + mux.HandleFunc("/delay/", h.Delay) // 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 mux.HandleFunc("/absolute-redirect", http.NotFound) mux.HandleFunc("/basic-auth", http.NotFound) + mux.HandleFunc("/delay", http.NotFound) mux.HandleFunc("/digest-auth", http.NotFound) mux.HandleFunc("/hidden-basic-auth", http.NotFound) mux.HandleFunc("/redirect", http.NotFound) diff --git a/main.go b/main.go index e4f83104d1b398e7d9f47df25c4712596811fad9..47c9d90ed16fe316adbbf896e02465389eaf6a5d 100644 --- a/main.go +++ b/main.go @@ -3,13 +3,15 @@ package main import ( "log" "net/http" + "time" "github.com/mccutchen/go-httpbin/httpbin" ) func main() { h := httpbin.NewHTTPBin(&httpbin.Options{ - MaxMemory: 1024 * 1024 * 5, + MaxMemory: 1024 * 1024 * 5, + MaxResponseTime: 10 * time.Second, }) log.Printf("listening on 9999") err := http.ListenAndServe(":9999", h.Handler())