URL rewriting solution needed for JSF

2020-02-05 04:31发布

问题:

Suppose the following application landscape:

+-----------------+
| App server      |
+-----------------+
|                 |                                   +-------+
| ear1            |                                   |       |
|  +-web1 (/ctx1) +--<-- http://localhost/ctx1/xxx/ --+       +--<-- http://www.example.com/xxx/
|                 |                                   |       |
|                 |                                   | proxy |
| ear2            |                                   |       |
|  +-web2 (/ctx2) +--<-- http://localhost/ctx2/yyy/ --+       +--<-- http://abc.example.com/yyy/
|                 |                                   |       |
+-----------------+                                   +-------+

As you can see, proxy (nginx in my case) is forwarding requests to to a single application server instance, which in turn has multiple web modules with different context paths. Of course I dont want my public server to expose internal context roots and proxy does it's job well, wraps and unwraps http requests, etc. But there is still one big problem: JSF-generated html code (links, css, js resources, form actions) contains context paths, /ctx1 and /ctx2 in my case. That's what I want to avoid.

I nave no solution at this moment of time except of using more and more different instances (domains) of application server, causing my hardware resources to fade away. As i understand it, I need to extend my JSF applications with some wrappers, potentially registered in faces-config.xml, which would remove context prefix in generated html. Any other solutions are also welcome.

Please point me in the right direction.

回答1:

You can use OCPsoft Rewrite URLRewriteFilter for this (not PrettyFaces currently, but you can use them both at the same time until they formally join together after PrettyFaces 4 release - Rewrite is the core project for PrettyFaces 4)

Doing something like this should be fairly straightforward using a single configuration rule. You can obviously fiddle if this rule is either too strict or too general.

.defineRule()
.when(URL.matches("{prefix}" + context.getContextPath() + "{suffix}")
.perform(Substitute.with("{prefix}{suffix}"))

Check out the rewrite site. It's pretty easy to set up. http://ocpsoft.org/rewrite/



回答2:

I'm posting solution which may be helpful for others facing the same problem. All I needed to do is implementing my own javax.faces.application.ViewHandler and register it in faces-config.xml :

public class CustomViewHandler extends ViewHandlerWrapper {
  private ViewHandler wrappped;

  public CustomViewHandler(ViewHandler wrappped) {
    super();
    this.wrappped = wrappped;
  }

  @Override
  public ViewHandler getWrapped() {
    return wrappped;
  }

  @Override
  public String getActionURL(FacesContext context, String viewId) {
    String url =  super.getActionURL(context, viewId);
    return removeContextPath(context, url);
  }

  @Override
  public String getRedirectURL(FacesContext context, String viewId, Map<String, List<String>> parameters, boolean includeViewParams) {
    String url =  super.getRedirectURL(context, viewId, parameters, includeViewParams);
    return removeContextPath(context, url);
  }

  @Override
  public String getResourceURL(FacesContext context, String path) {
    String url = super.getResourceURL(context, path);
    return removeContextPath(context, url);
  }

  private String removeContextPath(FacesContext context, String url) {
    ServletContext servletContext = (ServletContext) context.getExternalContext().getContext();
    String contextPath = servletContext.getContextPath();
    if("".equals(contextPath)) return url; // root context path, nothing to remove
    return url.startsWith(contextPath) ? url.substring(contextPath.length()) : url;
  }
}

faces-config.xml :

<faces-config xmlns="http://java.sun.com/xml/ns/javaee"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd"
              version="2.0">
  <application>
    <view-handler>test.CustomViewHandler</view-handler>
  </application>
</faces-config>


回答3:

I was facing the same problem and tried your solution. While it was more or less working, there were still a few glitches. And to be honest, it feels more like fighting the symptoms as opposed to curing the illness.

So here's what finally worked for me:

Instead of setting the deployments apart through the path, I assigned each deployment to its own port:

foo.war <-- http://localhost:8080/ -- | Proxy | <-- http://www.foo.com -- | Client |
bar.war <-- http://localhost:8181/ -- | Proxy | <-- http://www.bar.com -- | Client |

This way, both deployments are able to use / as their context path, hence no need to edit the context path out.

To achieve this, you don't necessarily have to run two application servers. In my case (Wildfly 10.0) it was sufficient to define two undertow servers in the wildfly configuration, each with its own virtual host and http listener, like so:

<server name="foo-server">
   <http-listener name="foo-listener" proxy-address-forwarding="true" socket-binding="foo-http"/>
   <host name="foo-host" default-web-module="foo.war" alias="localhost, foo.com, wwww.foo.com"/>
</server>
<server name="bar-server">
   <http-listener name="bar-listener" proxy-address-forwarding="true" socket-binding="bar-http"/>
   <host name="bar-host" default-web-module="bar.war" alias="localhost, bar.com, wwww.bar.com"/>
</server>

<socket-binding name="foo-http" port="${jboss.http.port:8080}"/>
<socket-binding name="bar-http" port="${jboss.http.port:8181}"/>

You will also need a jboss-web.xml in your project:

<?xml version="1.0" encoding="UTF-8"?>
<jboss-web>
   <server-instance>foo-server</server-instance>
   <virtual-host>foo-host</virtual-host>
   <context-root>/</context-root>
</jboss-web>

The two servers are needed because you can't add a socket binding to a virtual host. So there is a slight overhead here, but negligible compared to running two complete application servers.

Edit 1:

It just occured to me that it is probably not even necessary to use different ports and using one undertow server per deplyoment is probably superfluous as well.

Since the proxy is able to forward the host as requested by the client to the application server, undertow should be able to pick out the proper virtual host via the alias parameter.

So basically, the proxy would relay any request to either foo.com or bar.com to localhost:8080 and let the AS sort things out.

I have not tested this, but here's how it could work (again, this is for Wildfly 10.0):

<server name="default-server">
   <http-listener name="http" proxy-address-forwarding="true" socket-binding="http"/>
   <host name="foo-host" default-web-module="foo.war" alias="foo.com, wwww.foo.com"/>
   <host name="bar-host" default-web-module="bar.war" alias="bar.com, wwww.bar.com"/>
</server>

And the jboss-web.xml would loose the server tag:

<?xml version="1.0" encoding="UTF-8"?>
<jboss-web>
   <virtual-host>foo-host</virtual-host>
   <context-root>/</context-root>
</jboss-web>

In case this works there would be no overhead involved at all.

Edit 2:

Just tested the simplified approach - yep, it works :)