Jersey (JAX-RS) implements a Cross domain filter

After seeing how to create a HTTP Basic Auth Filter, we will add this time a cross domain output filter.

Cross-Domain request is available threw CORS system, it allow developper to send request to some domains which are not directly related to their HTML page, this is one of the great new features of new Xhr level 2 (already available in most of browsers).
It’s a pretty usefull feature in many case, here we will show a simple example how to embed a REST Web Services into an app (in pure HTML/JS), without any transfert threw a sub-resource provider/proxy.

Understanding the Jersey filter

We already see in previous article the filter for input request, here it’s the opposite : we want to add support on output filter.
Basically, Jersey allow to add any number of filter at input level, or, output level. We will use here the output filter.
Like previous article, we will describe both : Tomcat (v7), and Java built in server.

Java built-in server
ResourceConfig rc = new PackagesResourceConfig("");
rc.getProperties().put(
	"com.sun.jersey.spi.container.ContainerResponseFilters",
	"com.sun.jersey.api.container.filter.LoggingFilter;com.myprogram.CrossDomainFilter"
);

HttpServer server = HttpServerFactory.create("http://localhost:9999/", rc);
server.start();
Tomcat
<init-param>
	<param-name>com.sun.jersey.spi.container.ContainerResponseFilters</param-name>
	<param-value>com.sun.jersey.api.container.filter.LoggingFilter;com.myprogram.CrossDomainFilter</param-value>
</init-param>

Quite the same as previous article, just the parameter now is named « ContainerResponseFilters » instead of « ContainerRequestFilters » indicate to Jersey we want this time use the output not the input. Again the « LoggingFilter » is a basic -already done- filter provided by Jersey, allowing to get a nice debug console of input/output.

Now we put our class « com.myprogram.CrossDomainFilter » in the filter chain, we can create it.

The output filter

The output filter is a little bit different, but like input filter quite easy to understand :

/**
 * Allow the system to serve xhr level 2 from all cross domain site
 * 
 * @author Deisss (LGPLv3)
 * @version 0.1
 */
public class CrossDomainFilter implements ContainerResponseFilter {
	/**
	 * Add the cross domain data to the output if needed
	 * 
	 * @param creq The container request (input)
	 * @param cres The container request (output)
	 * @return The output request with cross domain if needed
	 */
	@Override
	public ContainerResponse filter(ContainerRequest creq, ContainerResponse cres) {
		cres.getHttpHeaders().add("Access-Control-Allow-Origin", "*");
		cres.getHttpHeaders().add("Access-Control-Allow-Headers", "origin, content-type, accept, authorization");
		cres.getHttpHeaders().add("Access-Control-Allow-Credentials", "true");
		cres.getHttpHeaders().add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, HEAD");
		cres.getHttpHeaders().add("Access-Control-Max-Age", "1209600");
		return cres;
	}
}

As you see, every parameter are in String way, in fact you can put what you want.
Here is a basic explain about thoose parameters :

  • Origin : indicate which url is allowed or not. The * is the wildcard parameter, you can put for example http://localhost if you want to restrict to localhost only
  • Headers : you probably don’t need to change this one, it’s indicating what headers you will use. There is no wildcard for this one
  • Credentials : let it to true will be fine…
  • Methods : even if you don’t use head or options, you should let them like this : the CORS system send OPTIONS request to catch cross domain policy, if you don’t set it it will be refused
  • Max-Age : the max age policy to renew CORS check. Here it’s 14 days long

As you see, thoose parameters are used BY BROWSER, not your Ajax request. It means that browser will check BEFORE your request. And you can’t pass threw a wrong policy check…

Now your Jersey part should be working, let’s make a simple request to check everything is fine.

The CORS Client

I will describe here a pretty basic example using jQuery (because it’s a lot used, definitely not my favorite…). Also, you may need to setup a auth filter for that part : allowing all OPTIONS request to be throw as « HTTP OK ». If you need so, use this article. In this case, you need to escape all options, so at the beginning of the filter, add like this (see from methods.equals(« OPTIONS »)) :

    @Override
    public ContainerRequest filter(ContainerRequest containerRequest) throws WebApplicationException {
        //GET, POST, PUT, DELETE, ...
        String method = containerRequest.getMethod();
        // myresource/get/56bCA for example
        String path = containerRequest.getPath(true);
        if(method.equals("OPTIONS")) {
            throw new WebApplicationException(Status.OK);
        }

I need to do that, i don’t remember exactly why (if I remember it was just a 404 error making the system to not validate CORS policy). This will be enough to get everything running good with any HTML page allowing CORS. Of course if you are using OPTIONS in your application, this trick is not for you, you should adapt it !

Now on client side, you can see we send a simple, and regular AJAX request, yes, the browser is the key here, you don’t need to setup anything special, the browser will check everything instead of you :

/**
 * Call the server using ajax cross domain request compatible
 *
 * @author Deisss (LGPLv3)
 *
 *
 * @param path {String} The url to get
 * @param type {String} GET, POST, PUT, DELETE, ...
 * @param data {Object} Any data to supply
 * @param success {Function} The success method called in case of success
 * @param error {Function} The error method called in case of error
*/
function call(path, type, data, success, error){
	var l = login,
		p = password;

	$.ajax({
		url:			path,
		data:			(data) ? JSON.stringify(data) : "",
		dataType:		'json',
		type:			type,
		contentType:	'application/json; charset=UTF-8',
		crossDomain:	true,

		//Prepare the authorization request from it
		beforeSend : function(xhr) {
			var base = Base64.encode(l+ ":" + p);
			xhr.setRequestHeader("Authorization", "Basic " + base);
		},

		//Handle default error check
		statusCode: {
			400 : function(){
				alert('400 : bad request');
			},
			401 : function(){
				alert('401 : unauthorized');
			},
			403 : function(){
				alert('403 : forbidden');
			},
			404 : function(){
				alert('404 : not found');
			},
			415 : function(){
				alert('415 : type not allowed');
			},
			500 : function(){
				alert('500 : internal server error');
			}
		},

		//Callback
		success:		(success) ? success : null,
		error:			(error) ? error : null
	});
};

As you see… Nothing to do ! The cross domain is in fact used by the browser directly, the browser is the one which is taking care of policy. You just need to do regular ajax request. Here I also add the HTTP Basic Auth from previous article (so you can see how to do cross domain with Basic Auth).
From this example : don’t forget to use HTTPS in all case, because the HTTP Auth is not encrypted at all, so you should take care a lot about that…

Final Words

You are now ready to interact with Web Services threw HTML page directly, allowing to build your system faster by interacting directly with many of your Web Services !

13 Commentaires

  1. Pingback: CopyQuery | Question & Answer Tool for your Technical Queries

  2. Jp

    Hi, how can I achieve this using glassfish.jersey rather than com.sun.jersey?

  3. deisss

    Short version:

    import javax.ws.rs.ext.Provider;
    import javax.ws.rs.container.ContainerRequestContext;
    import javax.ws.rs.container.ContainerResponseContext;
    import javax.ws.rs.container.ContainerResponseFilter;
    
    /**
     * Allow the system to serve xhr level 2 from all cross domain site
     */
    @Provider
    public class CrossDomainFilter implements ContainerResponseFilter {
    	/**
    	 * Add the cross domain data to the output if needed
    	 * 
    	 * @param creq The container request (input)
    	 * @param cres The container request (output)
    	 * @return The output request with cross domain if needed
    	 */
    	@Override
    	public void filter(ContainerRequestContext creq, ContainerResponseContext cres) {
    		cres.getHeaders().add("Access-Control-Allow-Origin", "");
    		cres.getHeaders().add("Access-Control-Allow-Headers", "");
    		cres.getHeaders().add("Access-Control-Allow-Credentials", "");
    		cres.getHeaders().add("Access-Control-Allow-Methods", "");
    		cres.getHeaders().add("Access-Control-Max-Age", "");
    	}
    

    Will do the job for jersey 2.x branch 😉 Pay attention to the @provider annotation it’s quite important ! You may also need the @PreMatching from javax.ws.rs.container.PreMatching (not here, but for some filter it may be needed).

    • Jp

      Merci beaucoup!

    • Jp

      Hi, my client code is still not recognizing the headers

      com.sun.jersey.spi.container.ContainerResponseFilters
      com.sun.jersey.api.container.filter.LoggingFilter;com.myprogram.CrossDomainFilter

      how should this be modified for glassfish.jersey? Also where exactly do I put this, I currently have it nested under the default servlet. and this is what I have currently:

      javax.ws.rs.container.ContainerResponseFilter
      org.glassfish.jersey.filter.LoggingFilter;com.myprogram.CrossDomainFilter

  4. deisss

    For this part (I use com.myapplication.filter.CrossDomainFilter as package/class name):

    First, you need to register the package where your filter is located:

     <param-name>jersey.config.server.provider.packages</param-name>
    <param-value>
        com.myapplication.filter
    </param-value>
    

    Then the response filter is of course not javax.ws anymore but:
    org.glassfish.jersey.container.ContainerResponseFilters
    And the LoggingFilter is not usable anymore on Jersey 2.x

    So:

     <param-name>org.glassfish.jersey.container.ContainerResponseFilters</param-name>
    <param-value>
        com.myapplication.filter.CrossDomainFilter
    </param-value>
    

    Now you should have everything setup & ready !

  5. Nice one… could you please help how to configure this filter for weblogic server.

    Many thanks
    Jay

  6. I don’t know how to use this code to integrate it in my projet jersey, shoud I call an instance CrossDomainFilter in my service rest or what ? thank you

    • deisss

      You need to use the code part given at the really beginning of this article: either Java Tomcat, or Built-In Server. This is the code that will register all of the work below and make them available threw the project.

  7. Good job,

    if(method.equals(« OPTIONS »)) {
    throw new WebApplicationException(Status.OK);
    }

    Was what I needed.

    Many Thanks!!!

  8. Pingback: 如何使跨域请求的Web服务的方式? – CodingBlog

  9. Pingback: How to enable Cross domain requests on JAX-RS web services? - PhotoLens

Laisser un commentaire