From bbef7e3bb065dcaaaed0ae15492cfc83b62a7510 Mon Sep 17 00:00:00 2001 From: Saurabh Hirani <saurabh.hirani@gmail.com> Date: Mon, 10 Jun 2019 12:10:59 +0800 Subject: [PATCH] Adding /uuid support and checks for ListenAndServe errors --- .travis.yml | 1 + cmd/go-httpbin/main.go | 10 +++++++--- httpbin/handlers.go | 13 +++++++++++++ httpbin/handlers_test.go | 33 +++++++++++++++++++++++++++++++++ httpbin/helpers.go | 13 +++++++++++++ httpbin/httpbin.go | 6 ++++++ 6 files changed, 73 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index e79e6e4..09658cb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ go: - '1.9' - '1.10' - '1.11' +go_import_path: github.com/mccutchen/go-httpbin script: - make lint - make test diff --git a/cmd/go-httpbin/main.go b/cmd/go-httpbin/main.go index 2b32654..310ac96 100644 --- a/cmd/go-httpbin/main.go +++ b/cmd/go-httpbin/main.go @@ -89,18 +89,22 @@ func main() { Handler: h.Handler(), } + var listenErr error if httpsCertFile != "" && httpsKeyFile != "" { cert, err := tls.LoadX509KeyPair(httpsCertFile, httpsKeyFile) if err != nil { - log.Fatal("Failed to generate https key pair: ", err) + logger.Fatal("Failed to generate https key pair: ", err) } server.TLSConfig = &tls.Config{ Certificates: []tls.Certificate{cert}, } logger.Printf("go-httpbin listening on https://%s", listenAddr) - server.ListenAndServeTLS("", "") + listenErr = server.ListenAndServeTLS("", "") } else { logger.Printf("go-httpbin listening on http://%s", listenAddr) - server.ListenAndServe() + listenErr = server.ListenAndServe() + } + if listenErr != nil { + logger.Fatalf("Failed to listen: %s", listenErr) } } diff --git a/httpbin/handlers.go b/httpbin/handlers.go index 5df341f..c04447f 100644 --- a/httpbin/handlers.go +++ b/httpbin/handlers.go @@ -895,3 +895,16 @@ func (h *HTTPBin) DigestAuth(w http.ResponseWriter, r *http.Request) { }) writeJSON(w, resp, http.StatusOK) } + +// UUID responds with a generated UUID +func (h *HTTPBin) UUID(w http.ResponseWriter, r *http.Request) { + u, err := uuidv4() + if err != nil { + http.Error(w, fmt.Sprintf("Failed to generate uuid: %s", err), http.StatusInternalServerError) + return + } + resp, _ := json.Marshal(&uuidResponse{ + UUID: u, + }) + writeJSON(w, resp, http.StatusOK) +} diff --git a/httpbin/handlers_test.go b/httpbin/handlers_test.go index 901c7bb..b9f62c7 100644 --- a/httpbin/handlers_test.go +++ b/httpbin/handlers_test.go @@ -6,6 +6,7 @@ import ( "compress/gzip" "compress/zlib" "encoding/json" + "errors" "fmt" "io/ioutil" "log" @@ -15,6 +16,7 @@ import ( "net/url" "os" "reflect" + "regexp" "strconv" "strings" "testing" @@ -2157,6 +2159,37 @@ func TestXML(t *testing.T) { assertBodyContains(t, w, `<?xml version='1.0' encoding='us-ascii'?>`) } +func isValidUUIDv4(uuid string) error { + if len(uuid) != 36 { + return fmt.Errorf("uuid length: %d != 36", len(uuid)) + } + r := regexp.MustCompile("^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[8|9|a|b][a-f0-9]{3}-[a-f0-9]{12}$") + if !r.MatchString(uuid) { + return errors.New("Failed to match against uuidv4 regex") + } + return nil +} + +func TestUUID(t *testing.T) { + r, _ := http.NewRequest("GET", "/uuid", nil) + w := httptest.NewRecorder() + handler.ServeHTTP(w, r) + + assertStatusCode(t, w, http.StatusOK) + + // Test response unmarshalling + var resp *uuidResponse + err := json.Unmarshal(w.Body.Bytes(), &resp) + if err != nil { + t.Fatalf("failed to unmarshal body %s from JSON: %s", w.Body, err) + } + + // Test if the value is an actual UUID + if err := isValidUUIDv4(resp.UUID); err != nil { + t.Fatalf("Invalid uuid %s: %s", resp.UUID, err) + } +} + func TestNotImplemented(t *testing.T) { var tests = []struct { url string diff --git a/httpbin/helpers.go b/httpbin/helpers.go index ec44cc9..30fdb06 100644 --- a/httpbin/helpers.go +++ b/httpbin/helpers.go @@ -8,6 +8,7 @@ import ( "fmt" "io" "io/ioutil" + "math/rand" "net/http" "net/url" "strconv" @@ -230,3 +231,15 @@ func sha1hash(input string) string { h := sha1.New() return fmt.Sprintf("%x", h.Sum([]byte(input))) } + +func uuidv4() (string, error) { + buff := make([]byte, 16) + _, err := rand.Read(buff[:]) + if err != nil { + return "", err + } + buff[6] = (buff[6] & 0x0f) | 0x40 // Version 4 + buff[8] = (buff[8] & 0x3f) | 0x80 // Variant 10 + uuid := fmt.Sprintf("%x-%x-%x-%x-%x", buff[0:4], buff[4:6], buff[6:8], buff[8:10], buff[10:]) + return uuid, nil +} diff --git a/httpbin/httpbin.go b/httpbin/httpbin.go index d1d28e3..1571f27 100644 --- a/httpbin/httpbin.go +++ b/httpbin/httpbin.go @@ -76,6 +76,10 @@ type streamResponse struct { URL string `json:"url"` } +type uuidResponse struct { + UUID string `json:"uuid"` +} + // HTTPBin contains the business logic type HTTPBin struct { // Max size of an incoming request generated response body, in bytes @@ -148,6 +152,8 @@ func (h *HTTPBin) Handler() http.Handler { mux.HandleFunc("/image/", h.Image) mux.HandleFunc("/xml", h.XML) + mux.HandleFunc("/uuid", h.UUID) + // existing httpbin endpoints that we do not support mux.HandleFunc("/brotli", notImplementedHandler) -- GitLab