Skip to content

Instantly share code, notes, and snippets.

@davidmsantos90
Last active August 7, 2017 11:01
Show Gist options
  • Select an option

  • Save davidmsantos90/04e00ed186445e21a5ce8d3c0ab3371e to your computer and use it in GitHub Desktop.

Select an option

Save davidmsantos90/04e00ed186445e21a5ce8d3c0ab3371e to your computer and use it in GitHub Desktop.

Servlet Filter

A Servlet Filter can be implemented by creating a Class that implements javax.servlet.Filter In this spike the logic that was on the WebContextServlet.java, has been moved to a WebContextFilter.java.

This way we were able to rely on the urlPatterns of the Filter, that are more flexible than the Servlet allows. And other positive outcome was the fact that this Filter implementation is very similar to Pentaho Server PentahoWebContextFilter.java, enabling a future story to unify this two separate implementations into one.

Conclusions

Issue to solve:

  • When a ResourceMapping and a Filter are registered in different bundles and we try to filter a request that matches the mapping alias, that filter will not be executed even if the request matches its urlPatterns.

=> Check if it works in a clean Karaf 4.x

In a clean Karaf the same issue was detected.

By testing different bundle combinations, we were able to conclude that for a Filter to execute without being blocked by a ResourceMapping both need to be on the same Http Context.

=> Check if it does not block cxf services (possible way out to blocking the CSRF story)

Calls to the cxf services are also blocking Filters.

This most probably is related with cxf having its own Http Context.. But cxf service has a filter implementation of its own that solves this problem.

=> Write separate filters in Jetty.xml for Pdi

Since Jetty runs inside Karaf, we couldn’t find where the web.xml is located, we found that jetty.xml is inside /karaf/etc/ but only contains server configuration information. To try to figure out how the filters are used in Karaf/Jetty, we did some tests and found out that the most restrictive filter wins, for example, given two filters to webcontext.js, and a filter with alias of "/{project.artifactId}/{project.version}/webcontext.js" and another filter with alias of "/{project.artifactId}/{project.version}" the first filter wins, because it is more restrictive.

Possible Solutions

What we need to garantee is that the Filter and the ResourceMapping have the same Http Context.

To achieve the best options seems to be:

  • Sharing the http context between bundles.
  • Having a WebContextFilter that gets registered in the bundles that need this functionality, solving the problem of having different Http Contexts and from the spike experience this looks to make implementation simpler.

Cxf filter example

We have some classes that we can use to implement the filter. In this example I implemented from ContainerRequestFilter to filter incoming requests:

@PreMatching
public class CxfFilter implements ContainerRequestFilter {

  @Override
  public void filter( ContainerRequestContext containerRequestContext ) throws IOException {
    // do something here...
  }
}

And to use the filter, register it in the blueprint.xml file:

<bean id="cxfFilter" class="example.namespace.CxfFilter"/>

  <jaxrs:server address="/det/core/data" id="dsService">
    <jaxrs:serviceBeans>
      <!-- endpoints here -->
    </jaxrs:serviceBeans>

    <!-- register the filter bean as a provider -->
    <jaxrs:providers>
      <ref component-id="cxfFilter"/>
    </jaxrs:providers>
  </jaxrs:server>

Share http context between bundles

Code required to setup two bundles that share the Http Context between them, using BundleActivators.

This solution was tested in both karaf 4.x and in our own karaf (3.x).

// Bundle A - PrimaryActivator.java implements BundleActivator
@Override
public void start( BundleContext bundleContext ) throws Exception {
  // 1. Get the WebContainer service
  WebContainer service = getWebContainerService( bundleContext );
  // 2. Create a shared http context (SharedWebContainerContext)
  this.sharedHttpContext = service.getDefaultSharedHttpContext();
  // 3. Register it has a service (to find and use it in other bundles)
  registerSharedHttpContext( bundleContext );
  // 4. Register a ResourceMapping with the sharedHttpContext
  registerResourceMapping( service );
  // 5. Register a Servlet with the sharedHttpContext
  registerMyServlet( service );
  // 6. Register your in the sharedHttpContext
  sharedHttpContext.registerBundle( bundleContext.getBundle() );
}
// Bundle B - SecondaryActivator.java implements BundleActivator
@Override
public void start( BundleContext bundleContext ) throws Exception {
  // 1. Get the WebContainer service
  WebContainer service = getWebContainerService( bundleContext );
  // 2. Get sharedHttpContext instance (Created in bundle A)
  this.sharedHttpContext = getSharedHttpContext( bundleContext );
  // 3. Register a filter with the sharedHttpContext
  registerMyFilter( service );
  // 4. Register your in the sharedHttpContext
  this.sharedHttpContext.registerBundle( bundleContext.getBundle() );
}
// Used in Bundle A & B
WebContainer getWebContainerService( BundleContext bundleContext ) {
  ServiceReference<WebContainer> webContainerRef = null;
  boolean started = false;
  while (!started) {
    webContainerRef = bundleContext.getServiceReference( WebContainer.class );
    started = webContainerRef != null;
  }
  return bundleContext.getService( webContainerRef );
}

// Used in Bundle A
void registerSharedHttpContext( BundleContext bundleContext ) {
  Dictionary<String, Object> params = new Hashtable<>();
  params.put( "httpContext.id", "sharedHttpContextID" );
  bundleContext.registerService(
    HttpContext.class, this.sharedHttpContext, initParamsHttpContext
  );
}

// Used in Bundle B
SharedWebContainerContext getSharedHttpContext( BundleContext bundleContext ) {
  Collection<ServiceReference<HttpContext>> serviceReferences =
      bundleContext.getServiceReferences(
          HttpContext.class, "(httpContext.id=sharedHttpContextID)"
      );

  if ( serviceReferences.size() > 1 ) { // error: should only exist one
    return null;
  }

  ServiceReference<HttpContext> httpContextRef = serviceReferences.iterator().next();
  return (SharedWebContainerContext) bundleContext.getService( httpContextRef );
}

// Used in Bundle A
void registerResourceMapping( WebContainer service ) {
  String resAlias = "/path/to/resources";
  String resPath  = "/folder";
  service.registerResources( resAlias, resPath, this.sharedHttpContext );
}

// Used in Bundle A
void registerMyServlet( WebContainer service ) {
  String[] servletAlias = new String[] { "/*" };
  service.registerServlet(
    new MyServlet();, servletAlias, null, this.sharedHttpContext
  );
}

// Used in Bundle B
void registerMyFilter( WebContainer service ) {
  String urlPattern = new String[] { "*/helloWorld.js" };
  service.registerFilter(
    new MyFilter(), urlPattern, null, null, this.sharedHttpContext
  );
}

Servlet tests with different alias

Least restrictive servlet (catch everything that is not catched on other filters):

<service id="webContextWildcard" interface="javax.servlet.Servlet"
         ref="WebContext">
    <service-properties>
        <entry key="alias" value="/*"/>
    </service-properties>
</service>

Serve webcontext.js from the root:

<service interface="javax.servlet.Servlet" id="webContextRootPath" 
         ref="WebContext">
    <service-properties>
        <entry key="alias" value="/webcontext.js"/>
    </service-properties>
</service>

Serve webcontext.js for a specific bundle:

<service interface="javax.servlet.Servlet" id="webContextBundleSpecific"
         ref="WebContext">
    <service-properties>
        <entry key="alias" value="/{project.artifactId}/{project.version}/webcontext.js"/>
    </service-properties>
</service>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment