JSF behind reverse proxy


The problem „try to run a jsf application behind a reverse proxy“ it will not work if you change the context-path.

Imagine a local deployed jsf application „hello-world.war“. You can start browsing at http://localhost:8080/hello-word/. The browser handles the absolute paths for css and javascript-libs correctly and all forms will work. Remeber that all resource-urls converted to a „contex-absolut-url“ by the jsf-framework.

If you open http://localhost:8080/hello-world/index.jsf the the client html will contains resources like this:

<link rel=’stylesheet‘ type=‚text/css‘
href
=‘/hello-world/a4j_3_1_3.GAcss/tabPanel.xcss/DATB/eAGTWzQ.BgAD.AG8.jsf‘>
<script type=‚text/javascript‘ src=‚/hello-world/a4j_3_1_3.GAorg.ajax4jsf.javascript.PrototypeScript.jsf‘>
<form id=„j_id14“ name=„j_id14“ method=„post“ action=„/hello-world/index.jsf“>

(Note: I use richfaces 3.1.4 in the example)

The browser will append each absolute url (starting with ‚/‘) to the host to make the request. And here is the problem if you want to use this jsf-application behind a reverse proxy. Then all absolute URLs pointing to a wrong resource and nothing will work. To try this you can install a local apache web server the following to the httpd.conf:

Listen 9090
ProxyRequests On
ProxyPreserveHost On
ProxyVia full
ProxyPass /external-path/hello-world http://localhost:8080/hello-world/

(This will start the apache at localhost:9090 to avoid port-conflicts)

If you open http://localhost:9090/external-path/hello-world/index.jsf the the client html contains the same absolute urls and the browser try to load these resource from the „wrong server“: http://localhost:9090/hello-world/. If you imagine a real world szenario then the reverse proxy will use a „real world domain“ and the urls looks like this http://www.mycompany.de/external-path/hello-world/.

So whats the solution for this? I try my own ViewHandler to convert absolute to relative urls. Two things must be done:

1. Implement the ViewHandler

2. Register the ViewHandler

First the code … we extend javax.faces.application.ViewHandler and overwrite the two methods getActionURL and getResourceURL.

package de.ahoehma.jsf;

public class ReverseProxyViewHandler extends ViewHandler {

 @Override
 public String getActionURL(final FacesContext context, final String viewId) {
  return getRelativeURL(context, this.defaultHandler.getActionURL(context, viewId));
 }

 @Override
 public String getResourceURL(final FacesContext context, final String path) {
  return getRelativeURL(context, this.defaultHandler.getResourceURL(context, path));
 }

 /**
  * Transform the given URL to a relative URL <b>in the context of the current
  * faces request</b>. If the given URL is not absolute do nothing and return
  * the given url. The returned relative URL is "equal" to the original url but
  * will not start with a '/'. So the browser can request the "same" resource
  * but in a relative way and this is important behind reverse proxies!
  *
  * @param context
  * @param theURL
  * @return
  */
 private String getRelativeURL(final FacesContext context, final String theURL) {
  final HttpServletRequest request = ((HttpServletRequest) context.getExternalContext().getRequest());
  String result = theURL;
  if (theURL.startsWith("/")) {
   int subpath = StringUtils.countMatches(getPath(request), "/") - 1;
   String pathPrefix = "";
   if (subpath > 0) {
    while (subpath > 0) {
     pathPrefix += "/..";
     subpath--;
    }
    pathPrefix = StringUtils.removeStart(pathPrefix, "/");
   }
   result = pathPrefix + result;
  }
  return result;
 }

 /**
  * Get the url-path from the given request.
  *
  * @param request
  * @return clean path
  */
 private String getPath(final HttpServletRequest request) {
  try {
   // TODO handle more than two '/'
   return StringUtils.replace(new URI(request.getRequestURI()).getPath(), "//", "/");
  } catch (final URISyntaxException e) {
  // XXX URISyntaxException ignored
  return StringUtils.EMPTY;
  }
 }
}

The absolute2relative-algorithm prepends for each „/“ in the current request-url a „../“ to the resource/action-url … thats all 😀

And last register the view-handler. If you use A4J you have to add this to your web.xml:

<context-param>
<param-name>org.ajax4jsf.VIEW_HANDLERS</param-name>
<param-value>de.ahoehma.jsf.ReverseProxyViewHandler</param-value>
</context-param>

That’s all 😀

Now if you open http://localhost:9090/external-path/hello-world/index.jsf the client html contains only relative urls:

<link rel=’stylesheet‘ type=‚text/css‘
href
=‘../hello-world/a4j_3_1_3.GAcss/tabPanel.xcss/DATB/eAGTWzQ.BgAD.AG8.jsf‘>
<script type=‚text/javascript‘ src=‚../hello-world/a4j_3_1_3.GAorg.ajax4jsf.javascript.PrototypeScript.jsf‘>
<form id=„j_id14“ name=„j_id14“ method=„post“ action=„../hello-world/index.jsf“>