From 1223a7364acfd61265fe6d3e74f3b58518d63064 Mon Sep 17 00:00:00 2001
From: Bill Mill <bill@billmill.org>
Date: Sun, 18 Jun 2023 09:34:35 -0400
Subject: [PATCH] feat: add method property to responses (#123)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

```console
# before: httpbingo lacks the "method" field but httpbin.org has it
$ curl -s https://httpbingo.org/anything | jq .method                                                                                                        9:31PM
null
$ curl -s https://httpbin.org/anything | jq .method
"GET"

# after: httpbingo has the method field!
$ curl -s http://0.0.0.0:8080/anything | jq .method
"GET"
```

This was mentioned in #6, but then not listed in #91

- I took the highly technical testing approach of adding a test for
`Method` anywhere in `handlers_test.go` that mentioned `Args`; do you
desire more tests? fewer tests?
- Anywhere else I should add this field that I didn't already?

For what it's worth, I discovered this when working on upgrading the
testing for our [httpsnippet
library](https://github.com/readmeio/httpsnippet); I wanted to use
`go-httpbin` but our tests expect the method field to be present in the
responses
---
 httpbin/handlers.go      |  5 +++++
 httpbin/handlers_test.go | 32 ++++++++++++++++++++++++++++++++
 httpbin/responses.go     |  2 ++
 3 files changed, 39 insertions(+)

diff --git a/httpbin/handlers.go b/httpbin/handlers.go
index 536d271..a15a8a6 100644
--- a/httpbin/handlers.go
+++ b/httpbin/handlers.go
@@ -47,6 +47,7 @@ func (h *HTTPBin) Get(w http.ResponseWriter, r *http.Request) {
 	writeJSON(http.StatusOK, w, &noBodyResponse{
 		Args:    r.URL.Query(),
 		Headers: getRequestHeaders(r),
+		Method:  r.Method,
 		Origin:  getClientIP(r),
 		URL:     getURL(r).String(),
 	})
@@ -71,6 +72,7 @@ func (h *HTTPBin) RequestWithBody(w http.ResponseWriter, r *http.Request) {
 	resp := &bodyResponse{
 		Args:    r.URL.Query(),
 		Headers: getRequestHeaders(r),
+		Method:  r.Method,
 		Origin:  getClientIP(r),
 		URL:     getURL(r).String(),
 	}
@@ -93,6 +95,7 @@ func (h *HTTPBin) Gzip(w http.ResponseWriter, r *http.Request) {
 	mustMarshalJSON(gzw, &noBodyResponse{
 		Args:    r.URL.Query(),
 		Headers: getRequestHeaders(r),
+		Method:  r.Method,
 		Origin:  getClientIP(r),
 		Gzipped: true,
 	})
@@ -115,6 +118,7 @@ func (h *HTTPBin) Deflate(w http.ResponseWriter, r *http.Request) {
 	mustMarshalJSON(zw, &noBodyResponse{
 		Args:     r.URL.Query(),
 		Headers:  getRequestHeaders(r),
+		Method:   r.Method,
 		Origin:   getClientIP(r),
 		Deflated: true,
 	})
@@ -749,6 +753,7 @@ func (h *HTTPBin) ETag(w http.ResponseWriter, r *http.Request) {
 	mustMarshalJSON(&buf, noBodyResponse{
 		Args:    r.URL.Query(),
 		Headers: getRequestHeaders(r),
+		Method:  r.Method,
 		Origin:  getClientIP(r),
 		URL:     getURL(r).String(),
 	})
diff --git a/httpbin/handlers_test.go b/httpbin/handlers_test.go
index 1fd1c2b..b54c904 100644
--- a/httpbin/handlers_test.go
+++ b/httpbin/handlers_test.go
@@ -150,6 +150,9 @@ func TestGet(t *testing.T) {
 		if resp.Args.Encode() != "" {
 			t.Fatalf("expected empty args, got %s", resp.Args.Encode())
 		}
+		if resp.Method != "GET" {
+			t.Fatalf("expected method to be GET, got %s", resp.Method)
+		}
 		if resp.Origin != "" {
 			t.Fatalf("expected empty origin, got %#v", resp.Origin)
 		}
@@ -179,6 +182,9 @@ func TestGet(t *testing.T) {
 		if resp.Args.Encode() != params.Encode() {
 			t.Fatalf("args mismatch: %s != %s", resp.Args.Encode(), params.Encode())
 		}
+		if resp.Method != "GET" {
+			t.Fatalf("expected method to be GET, got %s", resp.Method)
+		}
 	})
 
 	t.Run("only_allows_gets", func(t *testing.T) {
@@ -601,6 +607,9 @@ func testRequestWithBodyBinaryBody(t *testing.T, verb string, path string) {
 			if len(resp.Args) > 0 {
 				t.Fatalf("expected no query params, got %#v", resp.Args)
 			}
+			if resp.Method != verb {
+				t.Fatalf("expected method to be %s, got %s", verb, resp.Method)
+			}
 			if len(resp.Form) > 0 {
 				t.Fatalf("expected no form data, got %#v", resp.Form)
 			}
@@ -645,6 +654,9 @@ func testRequestWithBodyEmptyBody(t *testing.T, verb string, path string) {
 			if len(resp.Args) > 0 {
 				t.Fatalf("expected no query params, got %#v", resp.Args)
 			}
+			if resp.Method != verb {
+				t.Fatalf("expected method to be %s, got %s", verb, resp.Method)
+			}
 			if len(resp.Form) > 0 {
 				t.Fatalf("expected no form data, got %#v", resp.Form)
 			}
@@ -675,6 +687,9 @@ func testRequestWithBodyFormEncodedBody(t *testing.T, verb, path string) {
 	if len(resp.Args) > 0 {
 		t.Fatalf("expected no query params, got %#v", resp.Args)
 	}
+	if resp.Method != verb {
+		t.Fatalf("expected method to be %s, got %s", verb, resp.Method)
+	}
 	if len(resp.Form) != len(params) {
 		t.Fatalf("expected %d form values, got %d", len(params), len(resp.Form))
 	}
@@ -731,6 +746,9 @@ func testRequestWithBodyFormEncodedBodyNoContentType(t *testing.T, verb, path st
 	if len(resp.Args) > 0 {
 		t.Fatalf("expected no query params, got %#v", resp.Args)
 	}
+	if resp.Method != verb {
+		t.Fatalf("expected method to be %s, got %s", verb, resp.Method)
+	}
 	if len(resp.Form) != 0 {
 		t.Fatalf("expected no form values, got %d", len(resp.Form))
 	}
@@ -781,6 +799,9 @@ func testRequestWithBodyMultiPartBody(t *testing.T, verb, path string) {
 	if len(resp.Args) > 0 {
 		t.Fatalf("expected no query params, got %#v", resp.Args)
 	}
+	if resp.Method != verb {
+		t.Fatalf("expected method to be %s, got %s", verb, resp.Method)
+	}
 	if len(resp.Form) != len(params) {
 		t.Fatalf("expected %d form values, got %d", len(params), len(resp.Form))
 	}
@@ -846,6 +867,9 @@ func testRequestWithBodyJSON(t *testing.T, verb, path string) {
 	if len(resp.Args) > 0 {
 		t.Fatalf("expected no query params, got %#v", resp.Args)
 	}
+	if resp.Method != verb {
+		t.Fatalf("expected method to be %s, got %s", verb, resp.Method)
+	}
 	if len(resp.Form) != 0 {
 		t.Fatalf("expected no form values, got %d", len(resp.Form))
 	}
@@ -905,6 +929,10 @@ func testRequestWithBodyQueryParams(t *testing.T, verb, path string) {
 		t.Fatalf("expected args = %#v in response, got %#v", params.Encode(), resp.Args.Encode())
 	}
 
+	if resp.Method != "POST" {
+		t.Fatalf("expected method to be POST, got %s", resp.Method)
+	}
+
 	if len(resp.Form) > 0 {
 		t.Fatalf("expected form data, got %#v", resp.Form)
 	}
@@ -942,6 +970,10 @@ func testRequestWithBodyQueryParamsAndBody(t *testing.T, verb, path string) {
 		t.Fatalf("expected args = %#v in response, got %#v", args.Encode(), resp.Args.Encode())
 	}
 
+	if resp.Method != "POST" {
+		t.Fatalf("expected method to be POST, got %s", resp.Method)
+	}
+
 	if len(resp.Form) != len(form) {
 		t.Fatalf("expected %d form values, got %d", len(form), len(resp.Form))
 	}
diff --git a/httpbin/responses.go b/httpbin/responses.go
index 66790a4..ad08fca 100644
--- a/httpbin/responses.go
+++ b/httpbin/responses.go
@@ -27,6 +27,7 @@ type userAgentResponse struct {
 type noBodyResponse struct {
 	Args    url.Values  `json:"args"`
 	Headers http.Header `json:"headers"`
+	Method  string      `json:"method"`
 	Origin  string      `json:"origin"`
 	URL     string      `json:"url"`
 
@@ -39,6 +40,7 @@ type noBodyResponse struct {
 type bodyResponse struct {
 	Args    url.Values  `json:"args"`
 	Headers http.Header `json:"headers"`
+	Method  string      `json:"method"`
 	Origin  string      `json:"origin"`
 	URL     string      `json:"url"`
 
-- 
GitLab