/ / Czy istnieje interfejs API klienta JSON REST sterowany zdarzeniami dla Java? - java, json, reszta

Czy istnieje interfejs API klienta JSON REST sterowany zdarzeniami dla języka Java? - java, json, reszta

Mam aplikację Java, która korzysta z interfejsu API RestTemplate firmy Spring do pisania zwięzłych, czytelnych konsumentów usług JSON REST:

W istocie:

 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);
}

Odpowiedź JSON zawiera listę elementów i, jak widać, mam projekt oparty na zdarzeniach w moim własnym kodzie do obsługi każdego elementu po kolei. Jednak cała lista jest w pamięci jako część response, który RestTemplate.exchange() produkuje.

Chciałbym, aby aplikacja była w stanie obsłużyć odpowiedzi zawierające dużą liczbę elementów - powiedzmy 50 000, aw tym przypadku istnieją dwa problemy z implementacją w jej obecnym kształcie:

  1. Żaden element nie jest obsługiwany, dopóki cała odpowiedź HTTP nie zostanie przesłana - co powoduje niepożądane opóźnienie.
  2. Ogromny obiekt odpowiedzi znajduje się w pamięci i nie może być „GC” d, dopóki ostatni element nie zostanie obsłużony.

Czy istnieje dostatecznie dojrzały interfejs API klienta Java JSON / REST, który wykorzystuje odpowiedzi w sposób sterowany zdarzeniami?

Wyobrażam sobie, że pozwoli ci to zrobić coś takiego:

 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);

Rozumiem, że mogłem stworzyć własną implementacjętego zachowania podczas przesyłania strumieniowego interfejsów API JSON z Jackson, GSON itp. - ale chciałbym powiedzieć, że istnieje coś, co robi to niezawodnie dzięki zwięzłemu, ekspresyjnemu interfejsowi API zintegrowanemu z aspektem HTTP.

Odpowiedzi:

2 dla odpowiedzi № 1

możesz spróbować JsonSurfer który jest zaprojektowany do przetwarzania strumienia json w stylu sterowanym zdarzeniami.

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 dla odpowiedzi nr 2

Kilka miesięcy później; wróć, aby odpowiedzieć na własne pytanie.

Nie znalazłem ekspresyjnego API do robienia tego, co chcę, ale udało mi się osiągnąć pożądane zachowanie, pobierając ciało HTTP jako strumień i konsumując go z Jacksonem JsonParser:

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

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

... z uchwytemJsonStream zaprojektowany do obsługi JSON, który wygląda następująco:

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

... sprawdza poprawność tokenów prowadzących do początku tablicy; tworzy Item obiekt za każdym razem, gdy napotka element tablicy i przekazuje go do modułu obsługi.

 // 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() jest tylko prywatną metodą statyczną, która zgłasza wyjątek, jeśli dwa pierwsze argumenty nie są równe.

Kluczową rzeczą w tej metodzie jest to, że bez względu na liczbę elementów w strumieniu, każda z tych metod ma odniesienie do jednego elementu.


4 dla odpowiedzi nr 3

Czy nie ma sposobu na rozbicie żądania? Wygląda na to, że powinieneś używać stronicowania. Zrób to, abyś mógł zażądać pierwszych 100 wyników, kolejnych 100 wyników itd. Żądanie powinno przyjąć indeks początkowy i liczbę zliczeń. Jest to bardzo częste zachowanie usług REST i brzmi jak rozwiązanie twojego problemu.

Cały sens REST polega na tym, że jest on bezstanowy, brzmi to tak, jakbyś próbował uczynić go stanowym. To jest przekleństwo dla REST, więc nie znajdziesz żadnych bibliotek napisanych w ten sposób.

Transakcyjny charakter REST jest bardzo zamierzony z założenia, więc nie będziesz w stanie tego łatwo obejść. Jeśli spróbujesz, będziesz walczył przeciwko ziarnu.


3 dla odpowiedzi № 4

Z tego, co widziałem, owijanie frameworków (tak jak ty używasz) ułatwia to, deserializując odpowiedź na obiekt. W twoim przypadku zbiór obiektów.

Aby jednak korzystać z funkcji przesyłania strumieniowego, może być konieczne uzyskanie dostępu do podstawowego strumienia odpowiedzi HTTP. Najbardziej znam Jersey, która ją ujawnia https://jersey.java.net/nonav/apidocs/1.5/jersey/com/sun/jersey/api/client/ClientResponse.html#getEntityInputStream()

Zostałby wykorzystany przez wywołanie

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

Zapewnia to przepływ danych. Kolejnym krokiem jest napisanie części dotyczącej przesyłania strumieniowego. Biorąc pod uwagę, że używasz JSON, istnieją opcje na różnych poziomach, w tym http://wiki.fasterxml.com/JacksonStreamingApi lub http://argo.sourceforge.net/documentation.html. Mogą korzystać z InputStream.

Te naprawdę nie wykorzystują pełnideserializację, którą można wykonać, ale można ich użyć do przeanalizowania elementu tablicy json i przekazania tego elementu do typowego obiektu odwzorowującego obiekty JSON (takiego jak Jackson, GSON itp.). Staje się to logiką obsługi zdarzeń. Możesz odrodzić do tego nowe wątki lub zrobić wszystko, czego potrzebujesz.


2 dla odpowiedzi № 5

Nie twierdzę, że znam wszystkie pozostałe frameworki (lub nawet połowę), ale idę z odpowiedzią

Prawdopodobnie nie

Jak zauważyli inni, nie jest to sposób RESTzwykle myśli o swoich interakcjach. REST to świetny Młot, ale jeśli potrzebujesz transmisji strumieniowej, jesteś (IMHO) na terytorium śrubokręta, a młot nadal może sprawić, że zadziała, ale prawdopodobnie spowoduje bałagan. Można się spierać. że jest lub nie jest zgodny z REST przez cały dzień, ale w końcu byłbym bardzo zaskoczony, że znalazłem platformę, która zaimplementowała tę funkcję. Byłbym jeszcze bardziej zdziwiony, jeśli funkcja jest dojrzała (nawet jeśli jest to framework), ponieważ w odniesieniu do REST, twój przypadek użycia jest w najlepszym wypadku rzadki.

Jeśli ktoś coś wymyśli, chętnie stanę na nogi i nauczy się czegoś nowego :)

Być może najlepiej byłoby pomyśleć w kategoriach komety lub gniazd sieciowych dla tej konkretnej operacji. To pytanie może być pomocny, ponieważ masz już wiosnę. (websockets są nie bardzo opłacalne jeśli potrzebujesz obsługi IE <10, której większość aplikacji komercyjnych nadal wymaga ... niestety, mam jednego klienta z kluczowym klientem wciąż na IE 7 w mojej osobistej pracy)


1 dla odpowiedzi № 6

Możesz rozważyć Restlet.

http://restlet.org/discover/features

Obsługuje asynchroniczne przetwarzanie żądań,oddzielony od operacji we / wy. W przeciwieństwie do Servlet API, aplikacje Restlet nie mają bezpośredniej kontroli nad strumieniem wyjściowym, zapewniają jedynie reprezentację wyjściową do zapisania przez złącze serwera.


1 dla odpowiedzi № 7

Najlepszym sposobem na osiągnięcie tego jest użycie innego środowiska uruchomieniowego przesyłania strumieniowego dla JVM, które umożliwia odczytywanie odpowiedzi poza gniazdami sieciowymi i znam jedną z nich zwaną atmostphere

W ten sposób Twój duży zestaw danych jest wysyłany i odbierany we fragmentach po obu stronach i odczytywany w ten sam sposób w czasie rzeczywistym, bez oczekiwania na całą odpowiedź.

Ma to dobry POC w tym zakresie: http://keaplogik.blogspot.in/2012/05/atmosphere-websockets-comet-with-spring.html

Serwer:

    @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);
}

Klient: Atmosphere ma własny plik javascript do obsługi różnych typów transportu i żądań Comet / Websocket. Korzystając z tego, możesz ustawić punkt końcowy metody Spring URL Controller na żądanie. Po subskrybowaniu kontrolera otrzymasz wysyłkę, która może należy obsłużyć, dodając metodę request.onMessage. Oto przykładowe żądanie z transportem 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.");
}
}
}

Obsługuje wszystkie główne przeglądarki i rodzimych klientów mobilnych Apple, który jest pionierem tej technologii:

Jak wspomniano tutaj doskonałe wsparcie dla środowisk wdrażania w kontenerach JEE w sieci Web i dla przedsiębiorstw:

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