Pipo
Pipo

Reputation: 5093

How to handle CORS using JAX-RS with Resteasy, Angular and Wildfly10

I have a resteasy webservice on a wildfly 10 on the same machine as my angular client.

I tried to add a corsFilter but more than helping me being able to do get requests it didn't help me to solve my problem

package com.solarity.app; // {{ groupId}}.app

import com.solarity.rest.CompanyRestService;
import com.solarity.rest.PersonRestService;
import org.jboss.resteasy.plugins.interceptors.CorsFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;

@ApplicationPath("/")
public class InitApplication extends Application {

    /**
    * 
    */
    Set<Object> singletons;
    HashSet<Class<?>> webServiceClasses;

    public InitApplication() {
        super();
        webServiceClasses = new HashSet<>();
        webServiceClasses.add(PersonRestService.class);
        webServiceClasses.add(CompanyRestService.class);


        singletons = new LinkedHashSet<>();
        singletons.add(this.getCorsFilter());

    }

    @Override
    public Set<Class<?>> getClasses() {
        return webServiceClasses;
    }

    @Override
    public Set<Object> getSingletons() {
        return singletons;
    }

    private CorsFilter getCorsFilter() {
        CorsFilter result = new CorsFilter();
        result.getAllowedOrigins().add("http://localhost:4200");

        return result;
    }
}

I tried to implement an options method into my webservice without success...

package com.solarity.rest; // Note your package will be {{ groupId }}.rest

import com.solarity.entities.CompanyEntity;
import com.solarity.entities.PersonEntity;
import com.solarity.service.CompanyService;
import com.solarity.service.PersonService;
import com.solarity.util.ResponseUtil;
import org.apache.http.HttpStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

/**
* 
*
*/
@Path("/company")
public class CompanyRestService {

    protected Logger logger = LoggerFactory.getLogger(getClass());

    private CompanyService companyService = new CompanyService();



    @GET // This annotation indicates GET request
    @Path("/")
    @Produces(MediaType.APPLICATION_JSON)
    public Response getAll() {
        Object response = null;
        String errMsg = null;
        int responseStatus = -1;
        try {
            this.logger.debug("companyServcfindAll----------------debug");
            this.logger.warn("companyServcfindAll----------------WARN");
            response = companyService.findAll();
        } catch (Exception e) {
            errMsg = "Error getting all persons";
            logger.error(errMsg, e);
        }
        return ResponseUtil.getAlteredResponse(response, errMsg, responseStatus, HttpMethod.GET);
    }


    /**
    * curl -X DELETE http://localhost:8080/resteasyWebServices-1.0-SNAPSHOT/company/57 -i
    *
    * @param id
    * @return
    */
    @DELETE
    @Path("/{param}")
    public Response delete(@PathParam("param") Integer id){
        Object response = null;
        String errMsg = null;
        int responseStatus = -1;
        try {
            logger.debug("Deleting entity", id);
            companyService.delete(id);
            responseStatus = HttpStatus.SC_OK;
        } catch (Exception e) {
            errMsg = "Error Deleting Entity:" + id;
            logger.error(errMsg, e);
            response = errMsg;
            responseStatus = HttpStatus.SC_METHOD_FAILURE;
        }

        return ResponseUtil.getAlteredResponse(response, errMsg, responseStatus, HttpMethod.DELETE);
    }

    /**
    * Not working
    * @return
    */
    @OPTIONS
    @Path("{path : .*}")
    public Response options() {
        return Response.ok("")
                .header("Access-Control-Allow-Origin", "*")
                .header("Access-Control-Allow-Headers", "origin, content-type, accept, authorization")
                .header("Access-Control-Allow-Credentials", "true")
                .header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, HEAD")
                .header("Access-Control-Max-Age", "1209600")
                .build();
    }

}//end Class

Here is my ResponseUtils class

package com.solarity.util;

import org.apache.http.HttpStatus;

import javax.ws.rs.core.Response;

public class ResponseUtil {


    /**
    *
        Built to counter a Angular cross-reference problem
            Adapted for Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:8080/dlssResteasy1-1.0-SNAPSHOT/person/getPersonsAsJSON. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing).
            source answer https://stackoverflow.com/questions/23450494/how-to-enable-cross-domain-requests-on-jax-rs-web-services?answertab=votes#tab-top

        More Documentation about CORS on https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS


    * @param param the object to send if errorMsg is null
    * @param errorMsg if not null sends an error code with error Message
    * @param responseStatus response status which can be found from HttpStatus.* (if <= 0 will be taken from errorMsg, or ok)
    * @return an altered response which is customized
    */
    public static Response getAlteredResponse( Object param, String errorMsg, int responseStatus, String httpMethod ) {
        Response result = null;
        int rStatus = responseStatus;
        if (errorMsg != null && responseStatus <= 0) {
            rStatus = HttpStatus.SC_UNPROCESSABLE_ENTITY;
        } else if (errorMsg == null && responseStatus <= 0){
            rStatus = HttpStatus.SC_OK;
        }
        if ( errorMsg == null ) {
            result = Response
                    .status(rStatus)
                    .entity(param)
                    .build();
        }else{
            result = Response.status(rStatus)
                    .entity(errorMsg)
                    .build();
        }
        return result;
    }

}

Here is the result of debug into FF enter image description here

Upvotes: 0

Views: 2937

Answers (3)

MasterOfTheHouse
MasterOfTheHouse

Reputation: 1349

You can see the traffic with Chrome DevTools in the Network tab

Upvotes: 0

Pipo
Pipo

Reputation: 5093

First of all there is a documentation about CORS I HAD TO READ to understand, I couldn't avoid that as I hoped...

Two calls from Angular

Part of the answer of my problem was in fact two calls from Angular.

I didn't understand that everytime a call to subscribe on an httpclient.put() is done a call is done!

HttpClient Documentation

Calling the subscribe() method executes the observable, which is what initiates the DELETE request.

So What I did was:

  1. Call methodResult = httpclient.put('someUrl', someData, someHeader).subscribe({ data => { console.log('added') });
  2. On the caller of this method call again with abovePutMethod.subscribe( data => { doSomeThingWithComponentRefresh })

So doing only ONE call to subscribe solved my twice call problem


For the rest of the CORS Protocol

Angular Client

//UrlHelper
public static putHttpRequestOptions = {
    headers: new HttpHeaders({
    'Content-Type': 'application/json',
    })
};

//Function call somewhere
const result = this.httpClient.put(url, jsonStringValues, UrlHelper.putHttpRequestOptions);

Java Resteasy server

// InitApplication extends Application

public InitApplication() {
    super();
    webServiceClasses = new HashSet<>();
    webServiceClasses.add(PersonRestService.class);
    webServiceClasses.add(CompanyRestService.class);


    singletons = new LinkedHashSet<>();
    singletons.add(this.getCorsFilter());

}

private CorsFilter getCorsFilter() {
    CorsFilter result = new CorsFilter();
    result.getAllowedOrigins().add("*");
    result.setAllowedMethods("OPTIONS, GET, POST, DELETE, PUT, PATCH");
    result.setCorsMaxAge(86400);//Max in FF 86400=24h https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age
    //
    return result;
}


// RestWebService
@PUT
@Path("/")
@Consumes(MediaType.APPLICATION_JSON)
public Response put(CompanyEntity entity ){
    Object response = null;
    String errMsg = null;
    int responseStatus = -1;
    try {
        logger.debug("Received entity", entity);
        companyService.persist(entity);
        responseStatus = HttpStatus.SC_CREATED;
    } catch (Exception e) {
        errMsg = "Error adding Entity:" + entity;
        logger.error(errMsg, e);
        response = errMsg;
        responseStatus = HttpStatus.SC_METHOD_FAILURE;
    }

    return ResponseUtil.getAlteredResponse(response, errMsg, responseStatus, HttpMethod.PUT);
}

// Called on result of all RestWebServices (I'm sure there are better/best practices, feel free to comment me this section)
/**
 * @param param the object to send if errorMsg is null
 * @param errorMsg if not null sends an error code with error Message
 * @param responseStatus response status which can be found from HttpStatus.* (if <= 0 will be taken from errorMsg, or ok)
 * @return an altered response which is customized
 */
public static Response getAlteredResponse( Object param, String errorMsg, int responseStatus, String httpMethod ) {
    Response result = null;
    int rStatus = responseStatus;
    if (errorMsg != null && responseStatus <= 0) {
        rStatus = HttpStatus.SC_UNPROCESSABLE_ENTITY;
    } else if (errorMsg == null && responseStatus <= 0){
        rStatus = HttpStatus.SC_OK;
    }
    String accessControlAllowMethods = "GET, POST, PUT, DELETE, OPTIONS, HEAD";
    if ( errorMsg == null ) {
        result = Response
                .status(rStatus)
                .header("Access-Control-Allow-Origin", "*") //TODO: fix permission here!
                .header("Access-Control-Allow-Methods", accessControlAllowMethods)
                .header("Access-Control-Max-Age", "1728000")
                .entity(param)
                .build();
    }else{
        result = Response.status(rStatus)
                .header("Access-Control-Allow-Origin", "*") //TODO: fix permission here!
                .header("Access-Control-Allow-Methods", accessControlAllowMethods)
                .header("Access-Control-Max-Age", "1728000")
                .entity(errorMsg)
                .build();
    }
    return result;
}

Upvotes: 0

MasterOfTheHouse
MasterOfTheHouse

Reputation: 1349

  • Observe the OPTIONS request and response and be certain that the OPTION response coming from the server has the correct information. It tells the client what the server is accepting
  • Observe later the real request PUT GET POST , etc going to the server .Does it has all the headers that you want ?
  • You do not need to create and OPTIONS route .See here the RFC https://www.w3.org/TR/cors/

Add to this thread the copy of the OPTIONS request and response ( not the one you created but the one from the package your are using, if you are not using a package , look for one) , to see what is wrong with the configuration.

Add also the next POST,GET,PUT, etc. both the request and response

Upvotes: 1

Related Questions