Skip to content
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

API SSZ content negotiation #10760

Merged
merged 8 commits into from
Jun 2, 2022
Merged

API SSZ content negotiation #10760

merged 8 commits into from
Jun 2, 2022

Conversation

rkapka
Copy link
Contributor

@rkapka rkapka commented May 26, 2022

What type of PR is this?

Bug fix

What does this PR do? Why is it needed?

The way we currently check if a request expects an SSZ response is insufficient. The Accept HTTP header can be formed in many ways and we should support the syntax from RFC 7231.

Which issues(s) does this PR fix?

Fixes #10743

@rkapka rkapka added Ready For Review API Api related tasks labels May 26, 2022
@rkapka rkapka requested a review from a team as a code owner May 26, 2022 10:20
@rkapka rkapka requested review from kasey, nisdas and james-prysm May 26, 2022 10:20
if !ok {
return false
func sszRequested(req *http.Request) (bool, error) {
acceptableTypes := []contenttype.MediaType{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we accomplish this without a new dependency? It doesn't seem like this library is doing much heavy lifting for us.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 , less dependencies the better.

@@ -193,17 +201,53 @@ func handlePostSSZ(
return true
}

func sszRequested(req *http.Request) bool {
func sszRequested(req *http.Request) (bool, error) {
accept, ok := req.Header["Accept"]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: would be slightly more concise if you used https://pkg.go.dev/net/http#Header.Values

accept := req.Header.Values("Accept")
if len(accept) == 0 {
    return false, nil
}

return true
types := strings.Split(accept[0], ",")
// match a number with optional decimals
regex, err := regexp.Compile("q=\\d(\\.\\d)?")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This only needs to be compiled once for the life of the program - you can avoid recompiling on every call by declaring a variable above the function in package scope, using https://pkg.go.dev/regexp#MustCompile

params := values[1]
match := regex.Find([]byte(params))
if match != nil {
priority, err := strconv.ParseFloat(strings.Split(string(match), "=")[1], 32)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you're going to take on the overhead of using a regex I would suggest simplifying things by using the String regexp methods and using a capture group to extract the desired value, via https://pkg.go.dev/regexp#Regexp.FindAllStringSubmatch

prioRx := regexp.MustCompile(`q=(\d+(?:\.\d+)?)`) // note that I added a `+` to the `\d` shorthands, otherwise you'll only capture the first character
pg := prioRx.FindAllStringSubmatch(params, 1)
if len(pg) != 1 {
    continue
}
priority, err := strconv.ParseFloat(pg[0][1])
...

more self-contained example: https://go.dev/play/p/NRswRIwv2Tr

if err != nil {
return false, err
}
currentType, currentPriority := "", 1.0
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't this cause the initial empty value to take precedence over a string where all values include a priority? I think this causes a bug; if you had an accept string like application/octet-stream;q=0.95,application/json;q=0.9", the function would return false.

I think initializing currentPriority to 0 is going to simplify things. It would fix the bug just mentioned. You also won't need to check if currentType == "" { on line 227, because the priority comparison will implicitly handle that.

assert.Equal(t, true, result)
})

t.Run("other_content_type_preferred", func(t *testing.T) {
request := httptest.NewRequest("GET", "http://foo.example", nil)
request.Header["Accept"] = []string{fmt.Sprintf("%s,%s;q=0.9", jsonMediaType, octetStreamMediaType)}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a test case like application/octet-stream;q=0.95,application/json;q=0.9

kasey
kasey previously approved these changes Jun 2, 2022
@prylabs-bulldozer prylabs-bulldozer bot merged commit 302a5cf into develop Jun 2, 2022
@delete-merged-branch delete-merged-branch bot deleted the accept-octet-stream branch June 2, 2022 22:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
API Api related tasks
Projects
None yet
Development

Successfully merging this pull request may close these issues.

REST interface does not return SSZ when requested with Accept header
3 participants