Cross Domain JQUERY POST Attempt

classic Classic list List threaded Threaded
6 messages Options
Reply | Threaded
Open this post in threaded view
|

Cross Domain JQUERY POST Attempt

Jason Richards
I have been trying to do a JSON post request with jquery to a restlet service. Every the post is attempted it shows up on the server as an HTML Options request with an error of 405. From what I have read this is a problem with Cross Domain scripting. How do you do a post to the restlet built in server with javascript or jQuery?

------------------------------------------------------
http://restlet.tigris.org/ds/viewMessage.do?dsForumId=4447&dsMessageId=2715100
Reply | Threaded
Open this post in threaded view
|

Re: Cross Domain JQUERY POST Attempt

Ian Dunlop
Hello,

I remember having to tell restlet to allow cross domain posts using a specific route for a crossdomain.xml file like this:

private static final String CLIENT_ACCESS_POLICY = "/crossdomain.xml";
router.attach(CLIENT_ACCESS_POLICY, ClientAccessPolicy.class);
and having the route defined like the code below.  In this one localhost ie 127.0.0.1 is allowed to post.  This code was written back in the early days of restlet 2 so might be a bit out of date now.

import java.io.IOException;
import java.util.logging.Level;
import org.apache.log4j.Logger;


import org.restlet.data.MediaType;
import org.restlet.ext.xml.DomRepresentation;


import org.restlet.representation.Representation;
import org.restlet.representation.Variant;



import org.restlet.resource.Get;
import org.restlet.resource.ServerResource;


import org.w3c.dom.Document;

import org.w3c.dom.Element;

public class ClientAccessPolicy extends ServerResource {
	
	private static final String LOGGER_NAME = "org.mortbay.log";
        private static Logger logger = Logger.getLogger(ClientAccessPolicy.class);


	@Get("xml")
	public Representation accessAllowed(Variant variant) {
		
		logger.info("client access");

		if (MediaType.TEXT_XML.equals(variant.getMediaType())) {

			try {

				DomRepresentation representation = new DomRepresentation(

				MediaType.TEXT_XML);

				// Generate a DOM document representing the list of

				// items.

				Document d = representation.getDocument();

				Element accesspolicy = d.createElement("cross-domain-policy");

				d.appendChild(accesspolicy);

				Element crossdomainaccess = d
						.createElement("allow-access-from");
				crossdomainaccess.setAttribute("domain", "127.0.0.1");
				crossdomainaccess.setAttribute("secure", "false");
		

				accesspolicy.appendChild(crossdomainaccess);

				d.normalizeDocument();

				// Returns the XML representation of this document.

				return representation;

			} catch (IOException e) {

				e.printStackTrace();

			}

		}

		return null;

	}

}


Cheers,

Ian

On 29 Mar 2011, at 22:33, Jason Richards wrote:

I have been trying to do a JSON post request with jquery to a restlet service. Every the post is attempted it shows up on the server as an HTML Options request with an error of 405. From what I have read this is a problem with Cross Domain scripting. How do you do a post to the restlet built in server with javascript or jQuery?

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

Ian Dunlop
myGrid Team
School of Computer Science
University of Manchester


Reply | Threaded
Open this post in threaded view
|

RE: Re: Cross Domain JQUERY POST Attempt

Danilo Munoz
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=2889933
Reply | Threaded
Open this post in threaded view
|

RE: Re: Cross Domain JQUERY POST Attempt

Jim Winfield
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 | Threaded
Open this post in threaded view
|

Re: Re: Cross Domain JQUERY POST Attempt

Danilo Munoz
Hi Jim!

Thanks a lot for your detailed help! It helped a lot!

Best regards,

On Sat, Dec 3, 2011 at 3:32 PM, Jim Winfield <[hidden email]> wrote:
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



--
Danilo Rosetto Muñoz
[hidden email]

Reply | Threaded
Open this post in threaded view
|

RE: Re: Cross Domain JQUERY POST Attempt

mtbar131
This post has NOT been accepted by the mailing list yet.
In reply to this post by Jim Winfield
Hello Jim,

Thanks for your detailed explanation.
I am a newbie in RESTlet and I am stuck
at the same point!
I tried the Filter Class that you have provided
but it is not working on RESTlet 2.2 because of
many "ClassCastExceptions" and I really don't konw
how to get over these exceptions!
Can you please help me? Or can you just tell me
how can I rewrite the same Filter class for version 2.2

Thanks
Amit