diff --git a/.travis.yml b/.travis.yml index e79e6e4f858b18c0c3273a15df422bc10ba638a4..09658cb28818d716a46f2e9388a0259753c7e40b 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 2b326549a76c3d93fc3132953fa49f081fb2da2f..310ac9638d315d7d4f97d2992e5ca6b83971a596 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 5df341fafff20c2bfbc54a1fe5626b78be319a4c..c04447ff195c1f12e86ad65f2f636f2eae5f9b8d 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 901c7bb89470b61d4f7668e2d39bbf13db9340c4..b9f62c702ee41dac6413930a4cfdafcedbc3ad13 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 ec44cc9717668444e202899df49758eef427fe5f..30fdb067fc08fdb8d76478fd964fb91fd9e5e214 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 d1d28e3e8dde76cb13e95271a421b5ec170d1e15..1571f2763cf78be148f86ceae9f636c4658ff209 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)