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

Re-introduce GetResponseHTTPHeaders method #667

Merged
merged 10 commits into from
Feb 19, 2025

Conversation

Pushpalanka
Copy link
Contributor

Context:
Re-introduce the removed method from #628 using separate transform functions following this comment from @mjungsbluth.

Both signatures will be available to use as required,

func (result *EvalResult) GetResponseEnvoyHeaderValueOptions() ([]*ext_core_v3.HeaderValueOption, error) {
func (result *EvalResult) GetResponseHTTPHeaders() (http.Header, error) {

Signed-off-by: Pushpalanka Jayawardhana <[email protected]>
Signed-off-by: Pushpalanka Jayawardhana <[email protected]>
Signed-off-by: Pushpalanka Jayawardhana <[email protected]>
@Pushpalanka Pushpalanka marked this pull request as ready for review February 12, 2025 08:45

takeResponseHeaders := func(headers map[string]interface{}) error {
for key, value := range headers {
switch values := value.(type) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This seems to be a structural duplicate to the transformation for the envoy HttpOption. Do you want to unify this more?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes indeed. Addressed.

Copy link
Contributor

Choose a reason for hiding this comment

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

What I meant is that this switch statement in the for loop is essentially the same as in makeEnvoyHeaderValueOptionsFromHeadersMap so you could model this with a transformer + collector function and then the same for loop and structural swich could be used...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for clarifying. I hope it is there now.

Signed-off-by: Pushpalanka Jayawardhana <[email protected]>
case []string:
hvo = slices.Grow(hvo, len(val))
Copy link
Contributor

Choose a reason for hiding this comment

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

Removing this might yield a small performance hit as the array will need to grow for each sub-element in the Envoy case.

uses a pre allocation function to reduce the performance overhead when it's done along with append.

Signed-off-by: Pushpalanka Jayawardhana <[email protected]>
if *result == nil {
*result = make(http.Header)
*result = make(http.Header, additional)
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this grows the map itself and not the values for a specific key.
Maybe that is also not easily possiblr

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, that's true. Will ignore the additional value, but keep for the function signature to match.

@@ -455,6 +455,127 @@ func TestGetResponseHeaders(t *testing.T) {
if len(result) != 2 {
t.Fatalf("Expected two header but got %v", len(result))
}
//
Copy link
Contributor

Choose a reason for hiding this comment

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

Commented code to be removed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Mistake, will uncomment it. Introduced it to valid the case of []interface{} for header values.

@mjungsbluth
Copy link
Contributor

LGTM in general!


switch v := val.(type) {
case map[string]interface{}:
return []map[string]interface{}{v}, nil
Copy link
Contributor

Choose a reason for hiding this comment

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

Please, avoid additional allocations. We have a highload service based on OPA where each allocation drastically impacts the performance.

return getHeadersWithTransformation(result.Decision, fieldName, preallocateForHTTPHeaders, collectHTTPHeaders)
}

func getHeadersWithTransformation[T any](
Copy link
Contributor

@regeda regeda Feb 19, 2025

Choose a reason for hiding this comment

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

IMHO, a new approach looks a bit complicated and not easy to follow the logic.

Could you make just a function which converts the result from getHeadersFromDecision into http.Header?

Suggested change
func getHeadersWithTransformation[T any](
func (result *EvalResult) getHTTPHeadersFromDecision(fieldName string) (http.Header, error) {
opts, err := result.getHeadersFromDecision(fieldName)
if err != nil {
return nil, err
}
header := make(http.Header, len(opts))
for _, opt = range opts {
header.Add(opt.GetHeader().GetKey(), opt.GetHeader().GetValue())
}
return header, nil
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's is an effort to balance complexity and performance for both cases of returning HeaderValueOption and http.Header.
This suggested approach introduces an unnecessary intermediate conversion to ext_core_v3.HeaderValueOption for http.Header returning case affecting performance in that scenario.
This concern directs us towards the implemented approach which keep both the cases optimized for highload services.

Copy link
Contributor

Choose a reason for hiding this comment

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

Could you introduce the benchmark of getHeadersFromDecision before and after your refactoring?

Copy link
Contributor

Choose a reason for hiding this comment

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

Moreover, you already made an intermediate conversion using extractHeadersFromDecision function.

Copy link
Member

Choose a reason for hiding this comment

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

@regeda I respect your requirements, but it's probably better to follow up with PRs of your own to improve performance where needed rather than making requests for that in others contributions.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm kind of responsible for this breaking change.

I would help to set it back. PTAL #668

Copy link
Member

Choose a reason for hiding this comment

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

That's fair! Let's have this merged and follow up with additions based off of this.

Copy link
Member

Choose a reason for hiding this comment

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

Also, if you're interested in reducing allocations, you'll find a lot to like in OPA v1.2.0 🙂

return getHeadersWithTransformation(result.Decision, fieldName, preallocateForHTTPHeaders, collectHTTPHeaders)
}

func getHeadersWithTransformation[T any](
Copy link
Contributor

Choose a reason for hiding this comment

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

Moreover, you already made an intermediate conversion using extractHeadersFromDecision function.

Copy link
Collaborator

@srenatus srenatus left a comment

Choose a reason for hiding this comment

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

Thanks! LGTM 🎈

@@ -4,9 +4,6 @@ import (
"context"
"encoding/json"
"fmt"
"net/http"
"slices"
Copy link
Collaborator

Choose a reason for hiding this comment

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

I kind of appreciated the blocks here -- stdlib vs third-party. but that's not a blocker.

@anderseknert anderseknert merged commit a41ef12 into open-policy-agent:main Feb 19, 2025
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants