-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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
http: support additional content type #903
Conversation
Support `application/x-www-form-urlencoded` as an additional content type for `AtomicLevel.ServeHTTP`. This is the default content type for `curl -X POST`. With this change, interacting with the HTTP endpoint is a bit more user friendly: ``` curl -X PUT localhost:8080/log/level -d level=debug ``` Additionally, the unit tests for the HTTP handler are transformed to a table driven approach. fixes uber-go#902
Codecov Report
@@ Coverage Diff @@
## master #903 +/- ##
=======================================
Coverage 98.22% 98.23%
=======================================
Files 44 44
Lines 1915 1925 +10
=======================================
+ Hits 1881 1891 +10
Misses 27 27
Partials 7 7
Continue to review full report at Codecov.
|
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 change! The core logic looks mostly good. I have some suggestions around style.
http_handler.go
Outdated
// request could look like this: | ||
// | ||
// curl -X PUT localhost:8080/log/level -d level=debug | ||
// | ||
// For any other content type, the payload is expected to be JSON encoded and | ||
// look like: | ||
// | ||
// {"level":"info"} | ||
// | ||
// An example curl request could look like this: | ||
// | ||
// curl -X PUT localhost:8080/log/level -H "Content-Type: application/json" -d '{"level":"debug"}' |
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 great documentation, thank you!
http_handler.go
Outdated
} | ||
if req.Level == nil { | ||
return "Must specify a logging level." | ||
requestedLvl, err := func(body io.Reader) (*zapcore.Level, error) { |
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 appears to be an effort to reduce error handling duplication. That's a good call but can we perhaps move this to its own unexported method?
requestedLevel, err := lvl.decodePutRequest(r)
Additionally, since zapcore.Level is just an int8, we don't need to return its address:
func (lvl AtomicLevel) decodePutRequest(*http.Request) (zapcore.Level, error)
It's a pointer in payload
to detect the absence of the level in the JSON payload. So we can validate it's non-nil on the JSON path and dereference it.
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.
Good point. I tried to follow the previous style, but your suggestion is more maintainable and readable IMO.
http_handler.go
Outdated
if err := l.UnmarshalText([]byte(lvl)); err != nil { | ||
return nil, err | ||
} | ||
return &l, nil |
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.
Optionally, this can be shortened.
if err := l.UnmarshalText([]byte(lvl)); err != nil { | |
return nil, err | |
} | |
return &l, nil | |
err := l.UnmarshalText([]byte(lvl)) | |
return &l, err |
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 prefer being explicit here. Especially after switching to value type.
It is not immediately obvious whether the returned value is pre- or post-UnmarshalText without knowing that part of the spec by hart. (TBH I would need to look that up)
http_handler_test.go
Outdated
tests := map[string]struct { | ||
Method string | ||
ContentType string | ||
Body string | ||
ExpectedCode int | ||
ExpectedLevel zapcore.Level | ||
}{ |
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 making this a table test. If you wouldn't mind, we have a preference for slice-based table tests rather than maps:
tests := []struct {
desc string
method string
contentType string
...
}{
...
}
for _, tt := range tests {
..
(See also https://github.com/uber-go/guide/blob/master/style.md#test-tables)
http_handler_test.go
Outdated
ts := httptest.NewServer(lvl) | ||
defer ts.Close() | ||
|
||
req, err := http.NewRequest(test.Method, ts.URL, strings.NewReader(test.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.
can we name the test server srv or server?
ts := httptest.NewServer(lvl) | |
defer ts.Close() | |
req, err := http.NewRequest(test.Method, ts.URL, strings.NewReader(test.Body)) | |
server := httptest.NewServer(lvl) | |
defer server.Close() | |
req, err := http.NewRequest(test.Method, server.URL, strings.NewReader(test.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.
Done
http_handler_test.go
Outdated
}, | ||
} | ||
|
||
for name, test := range tests { |
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.
For table tests, consider renaming the test cases to tt
.
for _, tt := range tests {
http_handler.go
Outdated
enc.Encode(errorResponse{Error: err.Error()}) | ||
return | ||
} | ||
|
||
lvl.SetLevel(*req.Level) | ||
enc.Encode(req) | ||
|
||
lvl.SetLevel(*requestedLvl) | ||
enc.Encode(payload{Level: requestedLvl}) |
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.
Do we want a JSON output even for non-JSON input?
Maybe this is okay and we can add a plain text output later if we need it and the client has an Accepts: text/plain
?
CC @prashantv
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.
If we want to return non-JSON in the response, I would definitively go for content type negotiation through the Accepts
header.
In any case, I would vote for this to be part of a follow-up PR if it is desired.
Thanks for the swift feedback. I hope I get around to address the comments this week. |
These methods do not have need of the atomic level. Turn them into functions to avoid accidentally manipulating or accessing it.
http_handler.go
Outdated
if contentType == "application/x-www-form-urlencoded" { | ||
return lvl.decodePutURL(body) | ||
} | ||
return lvl.decodePutJSON(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.
Minor: It looks like these methods could be top-level functions since they don't do anything with the AtomicLevel. I'm gonna change that.
Sorry for the delay in reviewing. This looks great! |
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.
Change looks good, although one change we can make to simplify, but also provide an easier way to set the level: use ParseForm()
and FormValue(..)
to get the level field if it exists. It would simplify the code, while also allowing URL parameters to be used. Thoughts?
@prashantv nice. Did not know that exists. Should I also add tests for the URL params encoding? |
Yes, that would be great if you could add a test for passing the parameter in a URL (and mention it in the docs). Thanks! |
Done. |
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!
Support `application/x-www-form-urlencoded` as an additional content type for `AtomicLevel.ServeHTTP`. This is the default content type for `curl -X POST`. With this change, interacting with the HTTP endpoint is a bit more user friendly: ``` curl -X PUT localhost:8080/log/level -d level=debug ``` Additionally, the unit tests for the HTTP handler are transformed to a table driven approach. fixes #902 Co-authored-by: Abhinav Gupta <abg@uber.com>
Support `application/x-www-form-urlencoded` as an additional content type for `AtomicLevel.ServeHTTP`. This is the default content type for `curl -X POST`. With this change, interacting with the HTTP endpoint is a bit more user friendly: ``` curl -X PUT localhost:8080/log/level -d level=debug ``` Additionally, the unit tests for the HTTP handler are transformed to a table driven approach. fixes uber-go#902 Co-authored-by: Abhinav Gupta <abg@uber.com>
Support
application/x-www-form-urlencoded
as an additional contenttype for
AtomicLevel.ServeHTTP
.This is the default content type for
curl -X POST
.With this change, interacting with the HTTP endpoint is a bit more
user friendly:
Additionally, the unit tests for the HTTP handler are transformed to
a table driven approach.
fixes #902
This change is