-
Notifications
You must be signed in to change notification settings - Fork 124
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: API response-headers compatibility with origin #131 #142
Conversation
Signed-off-by: Son Bui <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the contribution! I've left a few comments below that will help simplify these changes, please let me know if they make sense to you.
Also, have you explored what the original httpbin's behavior is in weird circumstances? Like, what happens with a request like /response-headers?Content-Length=999999999
? I think we need some test coverage for at least that edge case, to codify whatever behavior we decide on.
if len(vs) == 1 { | ||
body[k] = vs[0] | ||
} else { | ||
body[k] = vs | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please drop this logic. Per my comment here
But, to be clear, go-httpbin will continue to serialize header values (and form values and any other “zero or more” values inherent to HTTP) as JSON arrays for consistency, where original httpbin serializes them as single scalar values, arrays, or comma-separated strings, depending on the endpoint and incoming request parameters.
That might not be very clear. To restate it, what I mean is that go-httpbin returns all header values as JSON arrays, even if only one header is given.
I know that is not actually backwards-compatible with the original httpbin, but for now I've decided that I prefer the consistency (and implementation simplicity) of this approach and I'm okay with the incompatibility. (See #37 and #15 for related discussion.)
@@ -314,15 +314,26 @@ func (h *HTTPBin) Unstable(w http.ResponseWriter, r *http.Request) { | |||
// ResponseHeaders responds with a map of header values | |||
func (h *HTTPBin) ResponseHeaders(w http.ResponseWriter, r *http.Request) { | |||
args := r.URL.Query() | |||
body := make(map[string]interface{}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With my comment below, I don't think we need anything more than args
here.
body["Content-Type"] = w.Header().Get("Content-Type") | ||
body["Content-Length"] = 0 | ||
buf := &bytes.Buffer{} | ||
json.NewEncoder(buf).Encode(body) | ||
body["Content-Length"] = buf.Len() | ||
mustMarshalJSON(w, body) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a bit of a strange approach, double-encoding the body just to get the content length. Please rewrite it to do the encoding once. The existing mustMarshalJSON()
and writeResponse()
helper funcs might be useful here.
result := mustParseResponse[http.Header](t, resp) | ||
|
||
type bodyResponse struct { | ||
Foo string `json:"Foo"` | ||
Bar []string `json:"Bar"` | ||
ContentType string `json:"Content-Type"` | ||
ContentLength int `json:"Content-Length"` | ||
} | ||
|
||
result := mustParseResponse[bodyResponse](t, resp) | ||
|
||
assert.ContentType(t, resp, result.ContentType) | ||
assert.Equal[bool](t, result.ContentLength > 0, true, "JSON response Content-Length mismatch") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can do away with most of this specialization if we simplify the response as requested above.
(Also, I apologize in advance for slow replies on my part, I am traveling right now.) |
Codecov Report
@@ Coverage Diff @@
## main #142 +/- ##
==========================================
+ Coverage 98.04% 98.05% +0.01%
==========================================
Files 8 8
Lines 1585 1596 +11
==========================================
+ Hits 1554 1565 +11
Misses 22 22
Partials 9 9
|
The behavior is interesting (TLS-related noise elided below):
I can convince myself that letting the incoming request to this specific endpoint dictate the actual Content-Length of the response could be useful for testing weird behavior in HTTP client or proxy code, so I'd be in favor of matching the Python implementation there. |
@mccutchen How can I get just key - value?
I know many frameworks use this way. |
I'm sorry, but I don't understand what you mean. Can you provide a bit more context, or specific examples of what you'd like to see? At the risk of over-explaining: The HTTP protocol allows for both multiple headers and multiple URL query parameters with the same name. This may be relatively rare, but it is entirely normal and compliant. For go-httpbin — unlike the original httpbin — we do not special-case the way we serialize single headers vs multiple headers or single query params vs multiple query params with a given name. They are always serialized in go-httpbin's responses as arrays (single values as a 1-element array, multiple values as multiple-element arrays).
That is not part of the HTTP spec. It is framework specific and entirely unnecessary, given a spec-compliant HTTP implementations. I've never researched the history here, but I have always assumed that some frameworks made this implementation decision out of ignorance of the HTTP spec, but maybe there are historical reasons that necessitated this that I'm not aware of. Regardless, go-httpbin is not going to implement any special cases like this. (See #37 (comment) for prior discussion of this |
API response-headers compatibility with the origin
Fix: #141