2014-12-30 17 views
11

Abbiamo un'applicazione web con una dashboard che esegue continuamente il polling per gli aggiornamenti. Sul lato server, la richiesta di aggiornamenti viene resa asincrona in modo tale da poter rispondere quando un aggiornamento avviene tramite un sistema listener/notify.La risposta AsyncContext non corrisponde alla richiesta originale in entrata?

Il problema che stiamo vedendo è che quando una di queste richieste di polling è essere risposto a, può in alcuni casi scrivere alla richiesta/risposta per un collegamento utente clic.

La richiesta in ingresso per l'aggiornamento asincrono assomiglia:

@RequestMapping("/getDashboardStatus.json") 
public void getDashboardStatus(HttpServletRequest request, ...) { 
    final AsyncContext asyncContext = request.startAsync(); 
    // 10 seconds 
    asyncContext.setTimeout(10000); 
    asyncContext.start(new Runnable() { 
     public void run() { 
      // .. (code here waits for an update to occur) .. 
      sendMostRecentDashboardJSONToResponse(asyncContext.getResponse()); 
      if (asyncContext.getRequest().isAsyncStarted()) { 
       asyncContext.complete(); 
      } 
     } 
    }); 
} 

Cosa c'è di strano , è che ci sono link in questa dashboard che vanno ad altre pagine. Ogni ~ 100 clic circa, uno di questi sarà invece di visualizzare la pagina scelta, in realtà visualizza il JSON inviato sopra!

Ad esempio, abbiamo un metodo MVC separata:

@RequestMapping("/result/{resultId}") 
public ModelAndView getResult(@PathVariable String resultId) { 
    return new ModelAndView(...); 
} 

E quando si fa clic su un link sul cruscotto che visita /result/1234, ogni luna blu, la pagina si caricherà con uno stato OK 200, ma invece di contenere l'HTML previsto, in realtà contiene il JSON per la richiesta di polling!

È consentita una sola richiesta per client? Una richiesta avviata da un link cliccato sostituisce eventuali richieste asincrone che sono già presenti sul lato server dallo stesso client?

Come possiamo gestire queste richieste per garantire che la risposta asincrona vada alla richiesta async?

Ho notato un metodo hasOriginalRequestAndResponse() sull'oggetto AsyncContext, ma ho difficoltà a capire dal Javadoc se è ciò che sto cercando.

Aggiornamento: Ho appena aggiunto un frammento in questo modo:

String requestURI = ((HttpServletRequest)asyncContext.getRequest()).getRequestURI()); 
System.out.println("Responding w/ Dashboard to: " + requestURI); 
sendMostRecentDashboardJSONToResponse(asyncContext.getResponse(), clientProfileKey); 

ed era in grado di riprodurre il problema, durante corretta comportamento, vedo:

Responding w/ Dashboard to: /app/getDashboardStatus.json 

Ma quando ho vedi JSON spinto alle richieste avviate da clic, vedo:

Responding w/ Dashboard to: null 
+0

C'è una discussione in corso sui tag utilizzati in questa domanda: http://meta.stackoverflow.com/questions/281443/how-general-must-a-problem-be-to-warrant-use-of -a-language-library-tag –

+0

@Craig Otis Hai provato a utilizzare 'request.startAsync (richiesta, risposta);' invece di 'request.startAsync()'? – fmodos

+0

@fmodos Ho letto i documenti per questo, ma in realtà non lo uso - non sembra che il comportamento cambierebbe in questo scenario? Sembra che gli argomenti di richiesta/risposta passati a quel metodo avrebbero dovuto essere gli stessi (o wrapper) delle richieste/risposte in arrivo, quindi sembra che avrebbe funzionato allo stesso modo. –

risposta

3

Ho capito questo fuori. La richiesta/risposta è effettivamente riciclata, quindi agganciando la risposta assegnata allo AsyncContext, stavo scrivendo una risposta che era associata a una richiesta diversa.

La chiamata startAsync() garantisce che gli oggetti richiesta/risposta non verranno riciclati fino al completamento del contesto asincrono.Nonostante la mia non trovando posto in cui il contesto sarebbe stato completato prematuramente o erroneamente, è stato in fase di completamento:

Con il timeout.

sono stato in grado di riprodurre in modo coerente questo problema in attesa di 10+ secondi con nessuna attività sul lato server, che consente la richiesta di aggiornamento JSON per timeout, e poi facendo clic su un link. Dopo il timeout, la richiesta/risposta associata al contesto asincrono è scaduta e pertanto completato e quindi riciclato.

Sono state rilevate due soluzioni .

Il primo è quello di aggiungere uno AsyncListener al contesto e tenere traccia di se il timeout si era verificato o meno. Quando il listener rileva un timeout, capovolgi uno boolean e controllalo prima di scrivere sulla risposta.

Il secondo deve semplicemente chiamare isAsyncStarted() sulla richiesta prima di scrivere nella risposta. Se il contesto è scaduto, questo metodo restituirà false. Se il contesto è ancora valido/in attesa, restituirà true.

0

Se leggete il capitolo Asynchronous Request Processing nella documentazione di riferimento di Primavera, si noterà che è possibile semplificare la vostra soluzione:

@RequestMapping(value = "/getDashboardStatus.json", 
       produces = MediaType.APPLICATION_JSON_VALUE) 
public Callable<MostRecentDashboard> getDashboardStatus() { 
    return new Callable() { 
     @Override 
     public MostRecentDashboard call() { 
      // .. (code here waits for an update to occur) .. 
      return ...; 
     } 
    }); 
} 

L'istanza MostRecentDashboard verrà serializzato utilizzando Jackson (a condizione che Jackson 2 è sul classpath).

Successivamente, è necessario un TaskExecutor che eseguirà l'Callable (è anche comunemente utilizzato per configurare il timeout predefinito), leggere il paragrafo Spring MVC Async Config per le opzioni. In alternativa, è possibile restituire un DeferredResult<MostRecentDashboard> se Spring non conosce il thread che assegna il valore dello MostRecentDashboard, ad es. le istanze MostRecentDashboard vengono lette da JMS, Redis o simili. Per ulteriori informazioni, leggi il post del blog Introducing Servlet 3, Async Support.

Come di Spring version 4.1, è possibile restituire un ListenableFuture<MostRecentDashboard> direttamente dal controller, che è utile se si recupera il MostRecentDashboard utilizzando un AsyncRestTemplate.

Problemi correlati