Skip to content

Support X-Forwarded-Prefix header for reverse proxied deployments on non-standard paths (not /erddap)#357

Merged
ChrisJohnNOAA merged 1 commit intoERDDAP:mainfrom
srstsavage:support-x-forwarded-prefix
Sep 4, 2025
Merged

Support X-Forwarded-Prefix header for reverse proxied deployments on non-standard paths (not /erddap)#357
ChrisJohnNOAA merged 1 commit intoERDDAP:mainfrom
srstsavage:support-x-forwarded-prefix

Conversation

@srstsavage
Copy link
Collaborator

Description

Add support for the non-standard X-Forwarded-Prefix request header for use by reverse proxies (Apache httpd, nginx, etc) when ERDDAP is hosted on a non-standard path (i.e. other than /erddap).

Reverse proxies should add a X-Forwarded-Prefix request header specifying the non-standard path prefix if ERDDAP is not served at /erddap.

Apache http example:

<VirtualHost *:80>
    ProxyPreserveHost On
    RequestHeader set X-Forwarded-Prefix /subpath
    ProxyPass /subpath/erddap http://localhost:8080/erddap
</VirtualHost>

nginx example:

server {
  listen 80;

  location /subpath/erddap {
    proxy_set_header Host $http_host;
    proxy_set_header X-Forwarded-Prefix /subpath;
    proxy_pass http://localhost:8080/erddap;
  }
}

Type of change

Please delete options that are not relevant.

  • New feature (non-breaking change which adds functionality)

Checklist before requesting a review

  • I have performed a self-review of my code
  • My code follows the style guidelines of this project
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

@srstsavage
Copy link
Collaborator Author

srstsavage commented Sep 4, 2025

Docker compose stack demonstrating reverse proxies using Apache httpd and nginx with and without the X-Forwarded-Prefix header/non-standard subpath:

compose.yaml:

services:
  erddap:
    image: erddap:x-proto-prefix
  httpd:
    build:
      dockerfile_inline: |
        FROM httpd:2.4
        RUN echo "Include /erddap-proxy.conf" >> /usr/local/apache2/conf/httpd.conf
    ports:
      - 9980:80
      - 9981:81
    volumes:
      - ./httpd-virtual-host.conf:/erddap-proxy.conf:ro
  nginx:
    image: nginx:1.26
    ports:
      - 9982:80
    volumes:
      - ./nginx-server.conf:/etc/nginx/conf.d/default.conf:ro

httpd-virtual-host.conf:

Listen 81

LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so

<VirtualHost *:80>
    ProxyPreserveHost On
    ProxyPass /erddap http://erddap:8080/erddap
</VirtualHost>

<VirtualHost *:81>
    ProxyPreserveHost On
    RequestHeader set X-Forwarded-Prefix /subpath
    ProxyPass /subpath/erddap http://erddap:8080/erddap
</VirtualHost>

nginx-server.conf:

server {
  listen 80;

  location /erddap {
    proxy_set_header Host $http_host;
    proxy_pass http://erddap:8080/erddap;
  }

  location /subpath/erddap {
    proxy_set_header Host $http_host;
    proxy_set_header X-Forwarded-Prefix /subpath;
    proxy_pass http://erddap:8080/erddap;
  }
}
$ curl -I http://localhost:9980/erddap
HTTP/1.1 302 
Date: Thu, 04 Sep 2025 06:44:54 GMT
Server: Apache
Location: http://localhost:9980/erddap/index.html

$ curl -I http://localhost:9981/subpath/erddap
HTTP/1.1 302 
Date: Thu, 04 Sep 2025 06:45:10 GMT
Server: Apache
Location: http://localhost:9981/subpath/erddap/index.html

$ curl -I http://localhost:9982/erddap        
HTTP/1.1 302 
Server: nginx/1.26.3
Date: Thu, 04 Sep 2025 06:45:20 GMT
Connection: keep-alive
Location: http://localhost:9982/erddap/index.html

$ curl -I http://localhost:9982/subpath/erddap
HTTP/1.1 302 
Server: nginx/1.26.3
Date: Thu, 04 Sep 2025 06:45:30 GMT
Connection: keep-alive
Location: http://localhost:9982/subpath/erddap/index.html

@srstsavage srstsavage marked this pull request as draft September 4, 2025 07:17
@srstsavage srstsavage force-pushed the support-x-forwarded-prefix branch from b7e5c02 to c07c59f Compare September 4, 2025 07:48
@srstsavage srstsavage marked this pull request as ready for review September 4, 2025 07:49
…non-standard paths (not /erddap)

Add support for the non-standard X-Forwarded-Prefix request header
for use by reverse proxies (Apache httpd, nginx, etc) when ERDDAP
is hosted on a non-standard path (i.e. other than /erddap).

Reverse proxies should add a X-Forwarded-Prefix request header specifying
the non-standard path prefix if ERDDAP is not served at /erddap.

Apache http example:

<VirtualHost *:81>
    ProxyPreserveHost On
    RequestHeader set X-Forwarded-Prefix /subpath
    ProxyPass /subpath/erddap http://localhost:8080/erddap
</VirtualHost>

nginx example:

server {
  listen 80;

  location /subpath/erddap {
    proxy_set_header Host $http_host;
    proxy_set_header X-Forwarded-Prefix /subpath;
    proxy_pass http://localhost:8080/erddap;
  }
}
&& request.getHeader("Host") != null
&& ("https".equals(request.getScheme()) || !request.getHeader("Host").contains(":"))) {
httpsUrl = "https://" + request.getHeader("Host") + "/" + config.warName;
httpsUrl = "https://" + getHostAndPathFromRequest(request) + "/" + config.warName;
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this going to end up with /erddap/erddap/ in the url? The config.warName defaults to "erddap". Or is 'erddap' not in the prefix?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

In the current implementation /erddap is not supposed to be in the prefix, i.e. X-Forwarded-Prefix would be set to /subpath for an ERDDAP at /subpath/erddap. In other words, X-Forwarded-Prefix should only be set for non-standard deployment paths, and it should be set to the path prefix before /erddap. We could change it to be the full path to the ERDDAP deployment, but then config.warName would be ignored which seems confusing.

Copy link
Contributor

Choose a reason for hiding this comment

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

That makes sense. The current approach is good.

I'm still reading through the associated documentation updates. Once I finish there, if I have no other questions I'll approve this.

@rmendels
Copy link
Collaborator

rmendels commented Sep 4, 2025

@srstsavage @ChrisJohnNOAA If you want a reason why this construct might be used, many people like our group are severely hampered in creating new hostnames, even if going to the same computer. It takes months of delays, a hearing by a panel, and then usually is declined. But if I want to create a second ERDDAP on the same machine I can do https://my_exisitng_host/new_erddap/erddap without approval. Also in looking that the list of servers in erddap.com there are a number of sites using the same construct, I don't know there rationale just that it exists.

@srstsavage
Copy link
Collaborator Author

Yep, I think services in general should be able to be deployed at arbitrary paths to support deployment scenarios like you describe. I didn't think it was possible due to assumptions in the ERDDAP code based on past comments, but seems to generally be working. There may be assumptions in the ERDDAP federation logic, but it might also just be looking for /erddap somewhere in the path and not necessarily at the root...

@ChrisJohnNOAA ChrisJohnNOAA merged commit 1e335d4 into ERDDAP:main Sep 4, 2025
3 checks passed
@rmendels
Copy link
Collaborator

rmendels commented Sep 8, 2025

@ChrisJohnNOAA @srstsavage Okay I just tested this at https://oceanwatch.pfeg.noaa.gov/noaa_wide/erddap. and it appears to be working okay. This is 2.28.1 with using headers sent to true.

Thanks

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.

3 participants