Charles Caldarale recommended UrlRewriteFilter and after experimenting with
it, I agree it's very nice: great performance, very flexible and handles
cross-context forwarding. The custom Valve option is still attractive
because it has slightly better performance, slightly better cross-context
support, and much simpler configuration--at the expense of generality of
course.

Can someone look at the following Valve logic and let me know if this is
safe on Tomcat 6? It has been working fine and is stable under load, but I'm
totally new to the Tomcat code base and would really appreciate another pair
of eyes. Thanks.


public class MapRESTRequest extends ValveBase {

public void invoke(Request request, Response response) {
    if ("/v1".equals(request.getContextPath())) {
        // map the in-bound REST URI to the app handling it
        String newRequestURI = "/new-app/some/derived/uri";

        org.apache.coyote.Request req = request.getCoyoteRequest();
        req.requestURI().setString(newRequestURI);
        req.decodedURI().setString(newRequestURI);

        MessageBytes uriMB = MessageBytes.newInstance();
        uriMB.duplicate(req.decodedURI());

        MessageBytes hostMB = MessageBytes.newInstance();
        hostMB.setString(request.getHost().getName());

        MappingData mappingData = request.getMappingData();
        mappingData.recycle();
        request.getConnector().getMapper().map(hostMB, uriMB, mappingData);

        request.setContext((Context) mappingData.context);
        request.setWrapper((Wrapper) mappingData.wrapper);
    }

    getNext().invoke(request, response);
}


If anyone is interested in this, I can share the source. What I have allows
you to implement a REST resource name space as a collection of web apps (I
mainly use Jersey for the apps). The public URLs are mapped onto the web
apps however you want by configuring each app's web.xml. Here's a web.xml
fragment that shows the configuration (this servlet is only used for Valve
configuration; it doesn't handle any requests itself):


    <servlet>
        <servlet-class>com.vulpes.tomcat.MapResources</servlet-class>
        <load-on-startup>2</load-on-startup>
        <init-param>
            <param-name>resourcePaths</param-name>

<param-value>/game/**;/profile/*/tokens;/profile/*/awards</param-value>
        </init-param>
    </servlet>


That mapping accepts request URLs like "/v1/foo/bar/game/123" and forwards
them to "/a-service/game/123".

Collisions between resources are resolved with deepest-match wins
(individual path elements are resolved with longest-match wins). A trie is
used for the mapping, so it is reasonably fast for large numbers of
patterns.

- Ken




On Sat, Aug 21, 2010 at 2:04 PM, Ken Fox <k...@vulpes.com> wrote:

> I'm looking for advice on the best way to map REST requests onto a
> collection of Tomcat apps all running in the same JVM. The REST name space
> was designed for client use and doesn't reflect how the apps implement it.
> For example, the resource "/v1/x/123" is implemented by app X, but the
> resource "/v1/x/123/y" is implemented by app Y.
>
> A proxy (e.g. Apache mod_proxy or Squid) in front of Tomcat can rewrite the
> URLs to go to the correct app, but this gives us some pretty ugly proxy
> configurations which have to be kept in lock-step with the Tomcat apps.
> Relying on a proxy also makes it a bit harder to use Amazon's load balancer
> because it doesn't do rewrites (I think we'd have to run a proxy on each
> Tomcat instance).
>
> I'm trying to implement the rewrite as a Valve (code outline below)
> registered with the Engine which will run before any Hosts or Contexts. This
> seems like a good approach and may even let me grab the JAX-RS annotations
> from the apps to dynamically build the rewrite rules.
>
> Does anyone have advice for REST name spaces in Tomcat in general?
>
> Has anyone had good experiences with a rewrite proxy in front of Tomcat on
> Amazon EC2 with Amazon's ELB?
>
> Has anybody tried a rewrite Valve similar to this? It has to modify the
> CoyoteRequest and generate new Request.mappingData which seems kind of
> risky. (Though I think it will work in Tomcat 7, I've only tried Tomcat 6.)
> This is my favorite approach so far.
>
> Thanks,
>
> - Ken
>
>
> public void invoke(Request request, Response response) {
>     if ("/v1".equals(request.getContextPath())) {
>         // map the in-bound REST URI to the app handling it
>         String newRequestURI = "/new-app/some/derived/uri";
>
>         org.apache.coyote.Request req = request.getCoyoteRequest();
>         req.requestURI().setString(newRequestURI);
>         req.decodedURI().setString(newRequestURI);
>
>         MessageBytes uriMB = MessageBytes.newInstance();
>         uriMB.duplicate(req.decodedURI());
>
>         MessageBytes hostMB = MessageBytes.newInstance();
>         hostMB.setString(request.getHost().getName());
>
>         MappingData mappingData = request.getMappingData();
>         mappingData.recycle();
>         request.getConnector().getMapper().map(hostMB, uriMB, mappingData);
>
>         request.setContext((Context) mappingData.context);
>         request.setWrapper((Wrapper) mappingData.wrapper);
>     }
>
>     getNext().invoke(request, response);
> }
>
>

Reply via email to