0

I have a Jetty http server with some Jersey rest services. Those services are called from a React website that runs on a Node server.

Due to the cross origin nature of this setup, I had to add some HTTP headers. Basically, all my webservices return a createOkResult() which is created as follows.

@POST
@Path("orders/quickfilter")
@Consumes(MediaType.APPLICATION_JSON)
public Response getQuickFilterProductionOrders(String data) 
{
  ...
  return createOkResult(json.toString());
}

protected Response createOkResult(Object result)
{
  return buildCrossOrigin(Response.ok().entity(result));
}

protected static Response buildCrossOrigin(Response.ResponseBuilder responseBuilder)
{
  return responseBuilder.header("Access-Control-Allow-Origin", "*")
          .header("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT")
          .allow("OPTIONS")
          .build();
}

For the @GET webservices that works fine. But when I create an @POST service, I just can't get it working.

Webbrowsers (chrome and firefox) return these kind of errors:

Access to XMLHttpRequest at 'http://localhost:59187/rs/production/orders/quickfilter' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

So, at first sight I would be tempted to think that the headers are still missing. The thing is, when I test this service with a tool like Postman, then all headers turn out to be in place, and the service even returns the requested data.

postman screenshot

This is a screenshot of a POST request.

From my front-end (which runs on the node server), I use the axios API, which uses promises, and my request looks like this:

const url = "http://localhost:59187/rs/production/orders/quickfilter";
const data = JSON.stringify(request);
const headers = { headers: { "Content-Type": "application/json" } };
const promise = axios.post(url, data, headers);

Right now I have a HTTP error 500, If I remove the content type header, I get an unsupported media exception. So, I have reasons to believe that the content type is ok.

sideshowbarker
  • 81,827
  • 26
  • 193
  • 197
bvdb
  • 22,839
  • 10
  • 110
  • 123
  • 2
    Postman is not restricted to the cross origin policy that browsers are. You need to use a filter to add the CORS headers. A resource method won't work on a preflight request because the preflight is an OPTIONS request. See [this post](https://stackoverflow.com/a/28067653/2587435) to learn more. – Paul Samsotha Oct 24 '18 at 15:30
  • Thanks for the link, I will give that a try first thing in the morning. :) – bvdb Oct 24 '18 at 15:38
  • 2
    Read the whole thing, especially the 'update' to learn how it all works. – Paul Samsotha Oct 24 '18 at 15:38
  • Possible duplicate of [Why does my JavaScript get a "No 'Access-Control-Allow-Origin' header is present on the requested resource" error when Postman does not?](https://stackoverflow.com/questions/20035101/why-does-my-javascript-get-a-no-access-control-allow-origin-header-is-present) – sideshowbarker Nov 01 '18 at 01:13
  • @sideshowbarker yes and no. That existing question is more about "why is postman different" (i.e. the front-end), while my issue clearly turned out to be a lacking config in the back-end. But I can imagine, that it looks similar at first sight. since by coincidense, I also tried to rule out some things by using postman at first. - But really, this question is not about postman per se. – bvdb Nov 05 '18 at 10:07

1 Answers1

0

Paul Samsotha pointed me in the right direction. I ended up adding a filter to the ServletContextHandler. Unlike the linked article, I didn't really have to create that filter from scratch. There was an existing filter class that I could use: i.e. org.eclipse.jetty.servlets.CrossOriginFilter.

FilterHolder filterHolder = context.addFilter(CrossOriginFilter.class, "/*", EnumSet.allOf(DispatcherType.class));
filterHolder.setInitParameter(CrossOriginFilter.ALLOWED_METHODS_PARAM, "GET,PUT,POST,DELETE,OPTIONS");
filterHolder.setInitParameter(CrossOriginFilter.ALLOWED_ORIGINS_PARAM, "*");
filterHolder.setInitParameter(CrossOriginFilter.ALLOWED_HEADERS_PARAM, "Content-Type,Authorization,X-Requested-With,Content-Length,Accept,Origin");
filterHolder.setInitParameter(CrossOriginFilter.ALLOW_CREDENTIALS_PARAM, "true");
filterHolder.setInitParameter(CrossOriginFilter.CHAIN_PREFLIGHT_PARAM, "false");

Some of the above parameters can probably be left out, as they are default values. But what appeared to be crucial for me, was to set the CHAIN_PREFLIGHT_PARAM to false.

One nice side-effect, is that I can simplify the code of the actual services. They do not longer need to add special headers, by contrast they can now just return Response.ok().entity(result).build();.

bvdb
  • 22,839
  • 10
  • 110
  • 123