/ / Existe-t-il une API cliente JSON REST événementielle pour Java? - java, json, reste

Existe-t-il une API cliente JSON REST événementielle pour Java? - java, json, reste

J'ai une application Java qui utilise l'API RestTemplate de Spring pour écrire des consommateurs concis et lisibles des services JSON REST:

En substance:

 RestTemplate rest = new RestTemplate(clientHttpRequestFactory);
ResponseEntity<ItemList> response = rest.exchange(url,
HttpMethod.GET,
requestEntity,
ItemList.class);

for(Item item : response.getBody().getItems()) {
handler.onItem(item);
}

La réponse JSON contient une liste d'éléments, et comme vous pouvez le voir, j'ai une conception événementielle dans mon propre code pour gérer chaque élément tour à tour. Cependant, la liste entière est en mémoire dans le cadre de response, lequel RestTemplate.exchange() produit.

J'aimerais que l'application puisse gérer les réponses contenant un grand nombre d'éléments - disons 50 000, et dans ce cas, la mise en œuvre présente deux problèmes:

  1. Aucun élément n'est traité tant que la réponse HTTP entière n'a pas été transférée, ce qui ajoute une latence indésirable.
  2. L'énorme objet de réponse se trouve en mémoire et ne peut pas être "GC" d jusqu'à ce que le dernier élément ait été traité.

Existe-t-il une API client Java JSON / REST raisonnablement mature qui consomme les réponses de manière événementielle?

J'imagine que cela vous permettrait de faire quelque chose comme:

 RestStreamer rest = new RestStreamer(clientHttpRequestFactory);

// Tell the RestStreamer "when, while parsing a response, you encounter a JSON
// element matching JSONPath "$.items[*]" pass it to "handler" for processing.
rest.onJsonPath("$.items[*]").handle(handler);

// Tell the RestStreamer to make an HTTP request, parse it as a stream.
// We expect "handler" to get passed an object each time the parser encounters
// an item.
rest.execute(url, HttpMethod.GET, requestEntity);

J'apprécie de pouvoir rouler ma propre implémentationde ce comportement avec les API JSON en streaming de Jackson, GSON etc. - mais j'aimerais qu'on me dise qu'il y a quelque chose qui le fait de manière fiable avec une API expressive concise, intégrée à l'aspect HTTP.

Réponses:

2 pour la réponse № 1

tu peux essayer JsonSurfer qui est conçu pour traiter le flux json dans un style événementiel.

JsonSurfer surfer = JsonSurfer.jackson();
Builder builder = config();
builder.bind("$.items[*]", new JsonPathListener() {
@Override
public void onValue(Object value, ParsingContext context) throws Exception {
// handle the value
}
});
surfer.surf(new InputStreamReader(response.getBody()), builder.build());

5 pour la réponse № 2

Quelques mois plus tard; retour pour répondre à ma propre question.

Je n'ai pas trouvé une API expressive pour faire ce que je veux, mais j'ai pu obtenir le comportement souhaité en obtenant le corps HTTP en tant que flux et en le consommant avec un Jackson JsonParser:

  ClientHttpRequest request =
clientHttpRequestFactory.createRequest(uri, HttpMethod.GET);
ClientHttpResponse response = request.execute();

return handleJsonStream(response.getBody(), handler);

... avec handleJsonStream conçu pour gérer JSON qui ressemble à ceci:

 { items: [
{ field: value; ... },
{ field: value, ... },
... thousands more ...
] }

... il valide les jetons menant au début du tableau; il crée un Item chaque fois qu'il rencontre un élément de tableau et le donne au gestionnaire.

 // important that the JsonFactory comes from an ObjectMapper, or it won"t be
// able to do readValueAs()
static JsonFactory jsonFactory = new ObjectMapper().getFactory();

public static int handleJsonStream(InputStream stream, ItemHandler handler) throws IOException {

JsonParser parser = jsonFactory.createJsonParser(stream);

verify(parser.nextToken(), START_OBJECT, parser);
verify(parser.nextToken(), FIELD_NAME, parser);
verify(parser.getCurrentName(), "items", parser);
verify(parser.nextToken(), START_ARRAY, parser);
int count = 0;
while(parser.nextToken() != END_ARRAY) {
verify(parser.getCurrentToken(), START_OBJECT, parser);
Item item = parser.readValueAs(Item.class);
handler.onItem(item);
count++;
}
parser.close(); // hope it"s OK to ignore remaining closing tokens.
return count;
}

verify() est juste une méthode statique privée qui lève une exception si les deux premiers arguments ne sont pas égaux.

L'essentiel de cette méthode est que, quel que soit le nombre d'éléments dans le flux, cette méthode ne fait référence qu'à un seul élément.


4 pour la réponse № 3

N'y a-t-il aucun moyen de rompre la demande? Il semble que vous devriez utiliser la pagination. Faites en sorte que vous puissiez demander les 100 premiers résultats, les 100 résultats suivants, etc. La demande doit prendre un index de départ et un numéro de comptage. C'est un comportement très courant pour les services REST et cela semble être la solution à votre problème.

L'intérêt de REST est qu'il est sans état, il semble que vous essayiez de le rendre dynamique. C'est un anathème pour REST, donc vous ne trouverez aucune bibliothèque écrite de cette façon.

La nature transactionnelle de REST est très intentionnelle de par sa conception et vous ne pourrez donc pas vous déplacer aussi facilement. Vous vous battrez contre le grain si vous essayez.


3 pour la réponse № 4

D'après ce que j'ai vu, les cadres enveloppants (comme ceux que vous utilisez) facilitent les choses en désérialisant la réponse en un objet. Dans votre cas, une collection d'objets.

Cependant, pour utiliser les choses en mode streaming, vous devrez peut-être accéder au flux de réponse HTTP sous-jacent. Je connais le mieux Jersey, qui expose https://jersey.java.net/nonav/apidocs/1.5/jersey/com/sun/jersey/api/client/ClientResponse.html#getEntityInputStream()

Il serait utilisé en invoquant

Client client = Client.create();
WebResource webResource = client.resource("http://...");
ClientResponse response = webResource.accept("application/json")
.get(ClientResponse.class);
InputStream is = response.getEntityInputStream();

Cela vous fournit le flux de données entrant. L'étape suivante consiste à écrire la partie de streaming. Étant donné que vous utilisez JSON, il existe des options à différents niveaux, notamment http://wiki.fasterxml.com/JacksonStreamingApi ou http://argo.sourceforge.net/documentation.html. Ils peuvent consommer InputStream.

Celles-ci ne font pas vraiment bon usage du pleindésérialisation qui peut être effectuée, mais vous pouvez les utiliser pour analyser un élément d'un tableau json et transmettre cet élément à un mappeur d'objets JSON typique (comme Jackson, GSON, etc.). Cela devient la logique de gestion des événements. Vous pouvez générer de nouveaux threads pour cela, ou faire tout ce dont votre cas d'utilisation a besoin.


2 pour la réponse № 5

Je ne prétendrai pas connaître tous les autres cadres (ou même la moitié) mais je vais aller avec la réponse

Probablement pas

Comme indiqué par d'autres, ce n'est pas la façon dont RESTpense normalement à ses interactions. REST est un grand marteau, mais si vous avez besoin de streaming, vous êtes (à mon humble avis) sur le territoire du tournevis, et le marteau pourrait encore fonctionner, mais il est susceptible de faire un gâchis. On peut argumenter qu'il est ou n'est pas compatible avec REST toute la journée, mais au final je serais très surpris de trouver un framework qui implémente cette fonctionnalité. Je serais encore plus surpris si la fonctionnalité est mature (même si le framework l'est) car en ce qui concerne REST, votre cas d'utilisation est au mieux un cas d'angle rare.

Si quelqu'un en trouve un, je serai heureux de me corriger et d'apprendre quelque chose de nouveau :)

Il serait peut-être préférable de penser en termes de comète ou de prises Web pour cette opération particulière. Cette question peut être utile car vous avez déjà le printemps. (les sockets Web sont pas vraiment viable si vous devez prendre en charge IE <10, dont la plupart des applications commerciales ont encore besoin ... malheureusement, j'ai un client avec un client clé toujours sur IE 7 dans mon travail personnel)


1 pour la réponse № 6

Vous pouvez envisager Restlet.

http://restlet.org/discover/features

Prend en charge le traitement des demandes asynchrones,découplé des opérations d'E / S. Contrairement à l'API Servlet, les applications Restlet n'ont pas de contrôle direct sur le flux de sortie, elles fournissent uniquement une représentation de sortie à écrire par le connecteur du serveur.


1 pour la réponse № 7

La meilleure façon d'y parvenir est d'utiliser un autre Runtime de streaming pour JVM qui permet de lire la réponse sur les websockets et j'en connais un appelé atmostphere

De cette façon, votre grand ensemble de données est envoyé et reçu par blocs des deux côtés et lu de la même manière en temps réel sans attendre la réponse entière.

Cela a un bon POC à ce sujet: http://keaplogik.blogspot.in/2012/05/atmosphere-websockets-comet-with-spring.html

Serveur:

    @RequestMapping(value="/twitter/concurrency")
@ResponseBody
public void twitterAsync(AtmosphereResource atmosphereResource){
final ObjectMapper mapper = new ObjectMapper();

this.suspend(atmosphereResource);

final Broadcaster bc = atmosphereResource.getBroadcaster();

logger.info("Atmo Resource Size: " + bc.getAtmosphereResources().size());

bc.scheduleFixedBroadcast(new Callable<String>() {

//@Override
public String call() throws Exception {

//Auth using keaplogik application springMVC-atmosphere-comet-webso key
final TwitterTemplate twitterTemplate =
new TwitterTemplate("WnLeyhTMjysXbNUd7DLcg",
"BhtMjwcDi8noxMc6zWSTtzPqq8AFV170fn9ivNGrc",
"537308114-5ByNH4nsTqejcg5b2HNeyuBb3khaQLeNnKDgl8",
"7aRrt3MUrnARVvypaSn3ZOKbRhJ5SiFoneahEp2SE");

final SearchParameters parameters = new SearchParameters("world").count(5).sinceId(sinceId).maxId(0);
final SearchResults results = twitterTemplate.searchOperations().search(parameters);

sinceId = results.getSearchMetadata().getMax_id();

List<TwitterMessage> twitterMessages = new ArrayList<TwitterMessage>();

for (Tweet tweet : results.getTweets()) {
twitterMessages.add(new TwitterMessage(tweet.getId(),
tweet.getCreatedAt(),
tweet.getText(),
tweet.getFromUser(),
tweet.getProfileImageUrl()));
}

return mapper.writeValueAsString(twitterMessages);
}

}, 10, TimeUnit.SECONDS);
}

Client: Atmosphere possède son propre fichier javascript pour gérer les différents types de transport et demandes Comet / Websocket. En l'utilisant, vous pouvez définir le point de terminaison de la méthode Spring URL Controller sur la demande. Une fois abonné au contrôleur, vous recevrez des envois, qui peuvent être traité en ajoutant une méthode request.onMessage. Voici un exemple de requête avec transport de websockets.

       var request = new $.atmosphere.AtmosphereRequest();
request.transport = "websocket";
request.url = "<c:url value="/twitter/concurrency"/>";
request.contentType = "application/json";
request.fallbackTransport = "streaming";

request.onMessage = function(response){
buildTemplate(response);
};

var subSocket = socket.subscribe(request);

function buildTemplate(response){

if(response.state = "messageReceived"){

var data = response.responseBody;

if (data) {

try {
var result =  $.parseJSON(data);

$( "#template" ).tmpl( result ).hide().prependTo( "#twitterMessages").fadeIn();

} catch (error) {
console.log("An error ocurred: " + error);
}
} else {
console.log("response.responseBody is null - ignoring.");
}
}
}

Il prend en charge tous les principaux navigateurs et clients mobiles natifs Apple étant les pionniers de cette technologie:

Comme mentionné ici, excellente prise en charge des environnements de déploiement sur les conteneurs Web et d'entreprise JEE:

http://jfarcand.wordpress.com/2012/04/19/websockets-or-comet-or-both-whats-supported-in-the-java-ee-land/