I faced this same challenge for a project I was recently working on. 

The scenario is a Server side JSON API requiring cookie auth, implemented in 
Restlet running on a sub domain being used by a web client hosted on a separate 
sub-domain, or possibly a different domain and getting http 405 errors.

You will want to get a solid understanding of the http access control and 
cross-origin sharing standard to understand how browsers support cross-site 
requests. Read up here: https://developer.mozilla.org/En/HTTP_access_control 
also 
http://www.nczonline.net/blog/2010/05/25/cross-domain-ajax-with-cross-origin-resource-sharing/
 has good info.

The browser will "pre-flight" any cross-site call to your resource URI with a 
http options request and sends along info in the header for you to decide on if 
the access should be allowed. Typically your restlet server resource will not 
handle the http option method, so this is why you get the 405 error returned to 
the browser. Once you add support for http options requests to your resource 
you will need to properly set the access control headers in the http response.

Here's one way to support cross-origin requests with Restlet:

- Provide a filter to handle http options requests. You'll add this filter to 
individual restlet resources or you can filter all requests. The filter saves 
you from having a base resource class or by supporting on a resource by 
resource basis. But you can certainly do it either way. The filter can also be 
turned on/off which may be another advantage.

- This filter requires that there is some set of allowed origin domains 
configured with the Restlet application. In my case they get injected via 
Spring into a config class (MyConfig), but you can verify the origin domain 
anyway you want.

public class OriginFilter extends Filter {

   public OriginFilter(Context context) {
      super(context);
   }

   @Override
   protected int beforeHandle(Request request, Response response) {
      if(Method.OPTIONS.equals(request.getMethod())) {
         Form requestHeaders = (Form) 
request.getAttributes().get("org.restlet.http.headers");
         String origin = requestHeaders.getFirstValue("Origin", true);

         if(MyConfig.getAllowedOrigins().contains(origin)) {
            Form responseHeaders = (Form) 
response.getAttributes().get("org.restlet.http.headers");
            if (responseHeaders == null) {
                responseHeaders = new Form();
                response.getAttributes().put("org.restlet.http.headers", 
responseHeaders);
            }
            responseHeaders.add("Access-Control-Allow-Origin", origin);
            responseHeaders.add("Access-Control-Allow-Methods", 
"GET,POST,DELETE,OPTIONS");
            responseHeaders.add("Access-Control-Allow-Headers", "Content-Type");
            responseHeaders.add("Access-Control-Allow-Credentials", "true");
            responseHeaders.add("Access-Control-Max-Age", "60");
            response.setEntity(new EmptyRepresentation());
            return SKIP;
         }
      }

      return super.beforeHandle(request, response);
   }

   @Override
   protected void afterHandle(Request request, Response response) {
      if(!Method.OPTIONS.equals(request.getMethod())) {
         Form requestHeaders = (Form) 
request.getAttributes().get("org.restlet.http.headers");
         String origin = requestHeaders.getFirstValue("Origin", true);

         if(MyConfig.getAllowedOrigins().contains(origin)) {
            Form responseHeaders = (Form) 
response.getAttributes().get("org.restlet.http.headers");
            if (responseHeaders == null) {
                responseHeaders = new Form();
                response.getAttributes().put("org.restlet.http.headers", 
responseHeaders);
            }
            responseHeaders.add("Access-Control-Allow-Origin", origin);
            responseHeaders.add("Access-Control-Allow-Methods", 
"GET,POST,DELETE,OPTIONS");
            responseHeaders.add("Access-Control-Allow-Headers", "Content-Type");
            responseHeaders.add("Access-Control-Allow-Credentials", "true");
            responseHeaders.add("Access-Control-Max-Age", "60");
         }
      }
      super.afterHandle(request, response);
   }
}

Then in your app, if you want to support cross-origin resource sharing for all 
your routes ...

   @Override
   public Restlet createInboundRoot() {
      Router router = new Router(context);
      // attach your resources ...
      // router.attach(...);
      OriginFilter originFilter = new OriginFilter(getContext());
      originFilter.setNext(router);
      return originFilter;
   }

- For a http options request, the filter checks the http headers to see if the 
"Origin" header value is from a known and trusted host that the configuration 
class provides in the filter's beforeHandle() method. If you don't need to 
process a http options request elsewhere, you can skip processing and return 
the response headers.

- The response headers you set for allow-methods, max-age, etc will depend on 
your scenario, this sample filter hard codes values.

- If you are making any requests that require authentication, like with cookies 
you can't wildcard the origin value sent back in the response as some examples 
typically show. The origin value passed back in the response must agree with 
the origin value sent in the pre-flighted http options request/response, and 
the origin value sent in the http request header otherwise the browser will 
fail the xhr call. You must also set allow-credentials to true.

On the client side, if you're making ajax requests that require cookie auth you 
must send the xhr withCredentials property to true. 

With jQuery:

  $.ajax({
    contentType: 'application/json',
    type: 'POST',
    url:  'http://sub.myappdomain.com/some/resource',
    data: JSON.stringify({
      "some":"data",
      "another":"value"
    }),
    success: function() {
      alert("oh yeah!");
    },
    xhrFields:{
      withCredentials: true
    },
    dataType: 'json',
  });


Working with a client-side debugger like Firebug to look at the traffic and 
http requests and responses will help tremendously if you still are having 
problems.

This is one way to do it, if there is a better, or another recommended way, I'm 
not aware of it. But this works for my particular project and allows me to 
configure, enable and disable as I need to. Hope this helps you and anyone else 
out that needs to get cross-origin requests working in Restlet.


> Hi all,
> 
> does anyone has some update about that?
> 
> What is the recommend way to support cross-domain in Restlet 2 ?
> 
> Best regards,

------------------------------------------------------
http://restlet.tigris.org/ds/viewMessage.do?dsForumId=4447&dsMessageId=2890203

Reply via email to