Skip to content

Fix proxy_set_header for websocket and custom override locations#53

Open
mesudip wants to merge 1 commit into
masterfrom
chore/fix-proxy-set-header-override
Open

Fix proxy_set_header for websocket and custom override locations#53
mesudip wants to merge 1 commit into
masterfrom
chore/fix-proxy-set-header-override

Conversation

@mesudip

@mesudip mesudip commented Jun 16, 2026

Copy link
Copy Markdown
Owner

No description provided.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a ProxyHeaderProcessor to manage the reinsertion of default proxy headers when custom headers are injected or for websocket locations. It also updates the Jinja2 template to conditionally render these headers and adds comprehensive unit tests. The review feedback highlights a fragile type check in ProxyHeaderProcessor where checking for list instead of str can break other iterable types. Additionally, it suggests simplifying both the Python logic and the Jinja2 template by treating all websocket locations as requiring header reinsertion, which would eliminate redundant hardcoded blocks in the template.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

def _overridden_proxy_header_names(self, injected: Iterable[str] | str | None) -> set[str]:
if injected is None:
return set()
directives = injected if isinstance(injected, list) else [injected]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

Using isinstance(injected, list) to distinguish between a single string and an iterable of strings is fragile. Since str is also an Iterable, any other iterable type (such as set, tuple, or a custom sequence) will fail the list check and be incorrectly wrapped as a single-element list containing the iterable itself. This causes str(directive) to serialize the entire collection (e.g., "{'proxy_set_header ...'}"), which fails the regex match.

Instead, check if injected is a string to decide whether to wrap it.

Suggested change
directives = injected if isinstance(injected, list) else [injected]
directives = [injected] if isinstance(injected, str) else injected

continue

overridden_headers = self._overridden_proxy_header_names(location.extras.get("injected", []))
needs_reinsertion = bool(overridden_headers) or (location.websocket and not location.http)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

We can significantly simplify both the Python logic and the Jinja2 template by making needs_reinsertion True for all websocket locations (regardless of whether location.http is True or False).

Since any websocket location block must define at least Upgrade and Connection headers, Nginx's inheritance rules dictate that it will not inherit any global proxy_set_header directives. Therefore, all websocket locations always need the full set of default headers re-inserted.

If we change this condition to:

needs_reinsertion = bool(overridden_headers) or location.websocket

Then location.reinsert_global_config will be True for all websocket locations, and location.reinserted_proxy_set_headers will contain the complete list of headers.

This allows us to completely remove the redundant, hardcoded header blocks in vhosts_template/default.conf.jinja2 (lines 57-75), leaving only the clean loop over location.reinserted_proxy_set_headers.

Suggested change
needs_reinsertion = bool(overridden_headers) or (location.websocket and not location.http)
needs_reinsertion = bool(overridden_headers) or location.websocket

Comment on lines +56 to +75
proxy_redirect $scheme://$http_host{{ location.container.path if location.container.path else '/' }} $scheme://$http_host{{location.name}};{% endif %} {% if location.reinsert_global_config %}{% for header_name, header_value in location.reinserted_proxy_set_headers %}
proxy_set_header {{ header_name }} {{ header_value }};{% endfor %}{% elif location.websocket and location.http %}
proxy_set_header Host $http_host;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
proxy_set_header X-Forwarded-Ssl $proxy_x_forwarded_ssl;
proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port;{% elif location.websocket %}
proxy_http_version 1.1;
proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port;
proxy_set_header Proxy "";{% elif location.websocket %}
proxy_set_header Host $http_host;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
proxy_set_header X-Forwarded-Ssl $proxy_x_forwarded_ssl;
proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port;
proxy_set_header Proxy "";{% endif %}{% if location.websocket and not location.http %}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

If we simplify the Python logic in ProxyHeaderProcessor to set needs_reinsertion = True for all websocket locations (by changing needs_reinsertion = bool(overridden_headers) or location.websocket), we can completely eliminate the redundant, hardcoded header blocks here.

This leaves only the clean loop over location.reinserted_proxy_set_headers, greatly improving template maintainability.

            proxy_redirect $scheme://$http_host{{ location.container.path if location.container.path else '/' }} $scheme://$http_host{{location.name}};{% endif %} {% if location.reinsert_global_config %}{% for header_name, header_value in location.reinserted_proxy_set_headers %}
            proxy_set_header {{ header_name }} {{ header_value }};{% endfor %}{% endif %}{% if location.websocket and not location.http %}

@codecov

codecov Bot commented Jun 16, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.00000% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 70.61%. Comparing base (c8c60b0) to head (aff3d9e).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...nx_proxy/post_processors/proxy_header_processor.py 94.28% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master      #53      +/-   ##
==========================================
+ Coverage   70.31%   70.61%   +0.29%     
==========================================
  Files          31       32       +1     
  Lines        3261     3301      +40     
  Branches      542      550       +8     
==========================================
+ Hits         2293     2331      +38     
- Misses        801      802       +1     
- Partials      167      168       +1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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.

1 participant