web-dev-qa-db-fra.com

Comment passer des objets complexes comme arguments à un service RESTful?

J'ai réussi à mettre en place un test rapide de création d'un service "REST-like" qui renvoie un objet sérialisé en JSON, et c'était assez facile et rapide (basé sur cet article ).

Mais tout en renvoyant des objets JSON-ified était facile comme Peach, je n'ai pas encore vu d'exemples traitant de paramètres d'entrée qui ne sont pas des primitives. Comment passer un objet complexe en argument? J'utilise Apache CXF, mais les exemples utilisant d'autres frameworks comme Jackson sont également les bienvenus :)

Le côté client serait probablement quelque chose comme construire un objet javascript, le passer dans JSON.stringify (complexObj) et passer cette chaîne comme l'un des paramètres.

Le service ressemblerait probablement à quelque chose comme ça

@Service("myService")
class RestService {
    @GET
    @Produces("application/json")
    @Path("/fooBar")
    public Result fooBar(@QueryParam("foo") double foo, @QueryParam("bar") double bar,
        @QueryParam("object") MyComplex object) throws WebServiceException {
    ...
    }
}

L'envoi d'objets sérialisés en tant que paramètres toucherait probablement rapidement la limite d'URL de 2 Ko imposée par Internet Explorer. Recommanderiez-vous d'utiliser POST dans ces cas, et aurais-je besoin de beaucoup changer dans les définitions de fonction?

35
oligofren

Après avoir creusé un peu, j'ai rapidement découvert qu'il y avait essentiellement deux options:

Option 1

Vous passez un "objet wrapper" contenant tous les autres paramètres au service. Vous devrez peut-être annoter cette classe wrapper avec des annotations JAXB comme @XmlRootElement pour que cela fonctionne avec le fournisseur basé sur Jettison, mais si vous utilisez Jackson à la place, il n'y a pas besoin. Définissez simplement le type de contenu sur le bon type et le bon lecteur de corps de message sera appelé. Cela ne fonctionnera que pour les services de type POST bien sûr (AFAIK).

Exemple

Ceci est juste un exemple de transformation du service mentionné dans la question d'origine en un à l'aide d'un objet wrapper.

@Service("myService")
class RestService {

    @POST
    @Produces("application/json")
    @Path("/fooBar")
    public Result fooBar(

          /** 
          * Using "" will inject all form params directly into a ParamsWrapper 
          * @see http://cxf.Apache.org/docs/jax-rs-basics.html
          */
          @FormParam("") FooBarParamsWrapper wrapper

        ) throws WebServiceException {
            doSomething(wrapper.foo);
    }
}

class ParamsWrapper {
  double foo, bar;
  MyComplexObject object;
}

Option 2

Vous pouvez fournir un format de chaîne spécial dans lequel vous empaquetez vos objets, puis implémenter un constructeur prenant une chaîne, une valeur statiqueOf (String s) ou une chaîne statique fromString (String s) dans la classe qui prendra cette chaîne et créera un objet à partir de cela. Ou tout à fait similaire, créez un ParameterHandler qui fait exactement la même chose.

AFAIK, seule la deuxième version vous permettra d'appeler vos services à partir d'un navigateur utilisant JSONP (puisque JSONP est une astuce réservée à GET). J'ai choisi cette route pour pouvoir passer des tableaux d'objets complexes dans l'URI.

Comme exemple de fonctionnement, prenez la classe de domaine et le service suivants

Exemple

@GET
@Path("myService")
public void myService(@QueryParam("a") MyClass [] myVals) {
    //do something
}

class MyClass {
    public int foo;
    public int bar;

   /** Deserializes an Object of class MyClass from its JSON representation */
   public static MyClass fromString(String jsonRepresentation) {
           ObjectMapper mapper = new ObjectMapper(); //Jackson's JSON marshaller
           MyClass o= null;
           try {
                   o = mapper.readValue(jsonRepresentation, MyClass.class );
           } catch (IOException e) {
                    throw new WebApplicationException()
           }
           return o;
   }
}

UN URI http://my-server.com/myService?a={"foo":1, "bar":2}&a={"foo":100, "bar":200} serait dans ce cas désérialisé en un tableau composé de deux objets MyClass.

Commentaire 2019: Voyant que cette réponse reçoit encore quelques hits en 2019, je pense que je devrais commenter. Avec le recul, je ne recommanderais pas l'option 2, car le fait de suivre ces étapes juste pour pouvoir faire des appels GET ajoute de la complexité qui n'en vaut probablement pas la peine. Si votre service prend une entrée aussi complexe, vous ne pourrez probablement pas utiliser la mise en cache côté client de toute façon, en raison du nombre de permutations de votre entrée. Je voudrais simplement configurer les en-têtes CORS (Cross-Origin-Sharing) appropriés sur le serveur et POST l'entrée. Ensuite, concentrez-vous sur la mise en cache de tout ce que vous pouvez sur le serveur.

32
oligofren

@BeanParam manque la réponse acceptée. Voir https://docs.jboss.org/resteasy/docs/3.0-rc-1/javadocs/javax/ws/rs/BeanParam.html pour plus de détails. Il vous permet de définir des paramètres de requête à l'intérieur d'un objet wrapper. Par exemple.

public class TestPOJO {

    @QueryParam("someQueryParam")
    private boolean someQueryParam;

    public boolean isSomeQueryParam() {
        return someQueryParam;
    }

    public boolean setSomeQueryParam(boolean value) {
        this.someQueryParam = value;
    }
}

... // inside the Resource class
@GET
@Path("test")
public Response getTest(@BeanParam TestPOJO testPOJO) {
    ...
}
3
r_ganaus

la meilleure et la plus simple solution consiste à envoyer votre objet sous forme de chaîne json et à mettre en œuvre côté serveur une méthode qui décodera ce json et le mappera à l'objet spécifié selon vos besoins .. et oui, il vaut mieux utiliser POST.

1
Koustuv Ganguly