web-dev-qa-db-fra.com

Safari sur iOS 6 met-il en cache les résultats $ .ajax?

Depuis la mise à niveau vers iOS 6, la vue Web de Safari se libère de la mise en cache des appels $.ajax. Ceci est dans le contexte d'une application PhoneGap donc il utilise Safari WebView. Nos appels $.ajax sont des méthodes POST et le cache est défini sur false {cache:false}, mais cela se produit toujours. Nous avons essayé d'ajouter manuellement un TimeStamp aux en-têtes, mais cela n'a pas aidé.

Nous avons effectué davantage de recherches et constaté que Safari ne renvoyait que les résultats mis en cache pour les services Web dotés d'une signature de fonction statique et ne changeant pas d'un appel à l'autre. Par exemple, imaginez une fonction appelée quelque chose comme:

getNewRecordID(intRecordType)

Cette fonction reçoit les mêmes paramètres d’entrée, encore et encore, mais les données qu’elle renvoie doivent être différentes à chaque fois.

Doit être dans la hâte d’Apple de faire fonctionner iOS 6 Zip de manière impressionnante. Ils étaient trop satisfaits des paramètres de cache. Est-ce que quelqu'un d'autre a vu ce comportement sur iOS 6? Si oui, quelle en est la cause?


La solution de contournement que nous avons trouvée consistait à modifier la signature de la fonction comme suit:

getNewRecordID(intRecordType, strTimestamp)

puis transmettez toujours également un paramètre TimeStamp et ignorez simplement cette valeur côté serveur. Cela fonctionne autour du problème. J'espère que cela aidera une autre pauvre âme qui passe 15 heures comme moi dans ce dossier!

1053
user1684978

Après un peu d'investigation, Safari sur iOS6 met en cache les POST n'ayant aucun en-tête Cache-Control ni même "Cache-Control: max-age = 0".

Le seul moyen que j'ai trouvé d'empêcher cette mise en cache de se produire à un niveau global plutôt que d'avoir à pirater des chaînes de requête aléatoires sur les appels de fin de service consiste à définir "Cache-Control: no-cache".

Alors:

  • Aucun en-tête Cache-Control ou Expires = iOS6 Safari mettra en cache
  • Cache-Control max-age = 0 et un immédiat Expires = iOS6 Safari mettra en cache
  • Cache-Control: no-cache = iOS6 Safari NE mettra PAS en cache

Je soupçonne que Apple tire parti de la spécification HTTP de la section 9.5 sur le POST:

Les réponses à cette méthode ne peuvent pas être mises en cache, sauf si la réponse inclut les champs d'en-tête Cache-Control ou Expires appropriés. Cependant, la réponse 303 (voir autre) peut être utilisée pour demander à l'agent d'utilisateur de récupérer une ressource pouvant être mise en cache.

Donc, en théorie, vous pouvez mettre en cache POST réponses ... qui savait. Mais aucun autre fabricant de navigateurs n'a jamais pensé que ce serait une bonne idée jusqu'à présent. Mais cela ne tient PAS compte de la mise en cache quand aucun en-tête Cache-Control ou Expires n'est défini, mais seulement s'il en existe. Donc ça doit être un bug.

Ci-dessous se trouve ce que j’utilise dans le bon fragment de ma configuration Apache pour cibler l’ensemble de mon API, car je ne souhaite en fait rien mettre en cache, même pas. Ce que je ne sais pas, c'est comment définir cela uniquement pour les POST.

Header set Cache-Control "no-cache"

Mise à jour: Je viens de remarquer que je n’ai pas fait remarquer que c’est uniquement lorsque le POST est identique, changez donc les données ou l’URL POST et tout va bien. Ainsi, comme mentionné précédemment, vous pouvez simplement ajouter des données aléatoires à l'URL ou un peu de données POST.

Mise à jour: Vous pouvez limiter le "no-cache" aux POST si vous le souhaitez dans Apache:

SetEnvIf Request_Method "POST" IS_POST
Header set Cache-Control "no-cache" env=IS_POST
443
Kieran

J'espère que cela pourra être utile aux autres développeurs qui se cogneront la tête contre celui-ci. J'ai constaté que l'un des éléments suivants empêche Safari sur iOS 6 de mettre en cache la réponse POST:

  • ajout de [cache-control: no-cache] dans les en-têtes de requête
  • ajout d'un paramètre d'URL variable tel que l'heure actuelle
  • ajout de [pragma: no-cache] dans les en-têtes de réponse
  • ajout de [cache-control: no-cache] dans les en-têtes de réponse

Ma solution était la suivante dans mon Javascript (toutes mes demandes AJAX sont POST).

$.ajaxSetup({
    type: 'POST',
    headers: { "cache-control": "no-cache" }
});

J'ajoute également l'en-tête [pragma: no-cache] à beaucoup de réponses de mon serveur.

Si vous utilisez la solution ci-dessus, sachez que tous les appels $ .ajax () que vous effectuez sont globaux: false n'utilisera PAS les paramètres spécifiés dans $ .ajaxSetup (). Vous devrez donc ajouter les en-têtes de nouveau.

145
Dave

Solution simple pour toutes vos demandes de service Web, si vous utilisez jQuery:

$.ajaxPrefilter(function (options, originalOptions, jqXHR) {
    // you can use originalOptions.type || options.type to restrict specific type of requests
    options.data = jQuery.param($.extend(originalOptions.data||{}, { 
      timeStamp: new Date().getTime()
    }));
});

En savoir plus sur l'appel du préfiltre jQuery ici .

Si vous n'utilisez pas jQuery, vérifiez la documentation de la bibliothèque de votre choix. Ils peuvent avoir une fonctionnalité similaire.

66
Baz1nga

Je viens d'avoir ce problème aussi dans une application PhoneGap . Je l'ai résolu en utilisant la fonction JavaScript getTime() de la manière suivante:

var currentTime = new Date();
var n = currentTime.getTime();
postUrl = "http://www.example.com/test.php?nocache="+n;
$.post(postUrl, callbackFunction);

J'ai perdu quelques heures à comprendre cela. Il aurait été agréable à Apple d'avertir les développeurs de ce problème de mise en cache.

42
Bashevis

J'ai eu le même problème avec une application Web qui récupérait des données à partir du service Web ASP.NET.

Cela a fonctionné pour moi:

public WebService()
{
    HttpContext.Current.Response.Cache.SetCacheability(HttpCacheability.NoCache);
    ...
}
42
Tadej

Enfin, j'ai une solution à mon problème de téléchargement.

En JavaScript:

var xhr = new XMLHttpRequest();
xhr.open("post", 'uploader.php', true);
xhr.setRequestHeader("pragma", "no-cache");

Dans PHP :

header('cache-control: no-cache');
23
goker.cebeci

Depuis mon propre blog demandes iOS 6.0 mettant en cache Ajax POST:

Comment résoudre ce problème: Il existe différentes méthodes pour empêcher la mise en cache des demandes. La méthode recommandée consiste à ajouter un en-tête sans cache. Voici comment cela se fait.

jQuery:

Recherchez iOS 6.0 et définissez l'en-tête Ajax comme suit:

$.ajaxSetup({ cache: false });

ZeptoJS:

Recherchez iOS 6.0 et définissez l'en-tête Ajax comme suit:

$.ajax({
    type: 'POST',
    headers : { "cache-control": "no-cache" },
    url : ,
    data:,
    dataType : 'json',
    success : function(responseText) {…}

Du côté serveur

Java:

httpResponse.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");

Assurez-vous de l'ajouter en haut de la page avant d'envoyer des données au client.

.NET

Response.Cache.SetNoStore();

Ou

Response.Cache.SetCacheability(System.Web.HttpCacheability.NoCache);

PHP

header('Cache-Control: no-cache, no-store, must-revalidate'); // HTTP 1.1.
header('Pragma: no-cache'); // HTTP 1.0.
14
kiranvj

Cet extrait de code JavaScript fonctionne parfaitement avec jQuery et jQuery Mobile:

$.ajaxSetup({
    cache: false,
    headers: {
        'Cache-Control': 'no-cache'
    }
});

Il suffit de le placer quelque part dans votre code JavaScript (une fois que jQuery est chargé et mieux avant de faire les requêtes AJAX) et cela devrait aider.

7
Jonathan

Vous pouvez également résoudre ce problème en modifiant la fonction jQueryAjax en procédant comme suit (à partir de la version 1.7.1) en haut de la fonction Ajax (la fonction commence à la ligne 7212 ) Cette modification activera la fonctionnalité anti-cache intégrée de jQuery pour toutes les demandes POST.

(Le script complet est disponible à l'adresse http://dl.dropbox.com/u/58016866/jquery-1.7.1.js.)

Insérer sous la ligne 7221:

if (options.type === "POST") {
    options.cache = false;
}

Modifiez ensuite les éléments suivants (à partir de la ligne ~ 7497).

if (!s.hasContent) {
    // If data is available, append data to URL
    if (s.data) {
        s.url += (rquery.test(s.url) ? "&" : "?") + s.data;
        // #9682: remove data so that it's not used in an eventual retry
        delete s.data;
    }

    // Get ifModifiedKey before adding the anti-cache parameter
    ifModifiedKey = s.url;

    // Add anti-cache in URL if needed
    if (s.cache === false) {
        var ts = jQuery.now(),
        // Try replacing _= if it is there
        ret = s.url.replace(rts, "$1_=" + ts);

        // If nothing was replaced, add timestamp to the end.
        s.url = ret + ((ret === s.url) ? (rquery.test(s.url) ? "&" : "?") + "_=" + ts : "");
    }
}

À:

// More options handling for requests with no content
if (!s.hasContent) {
    // If data is available, append data to URL
    if (s.data) {
        s.url += (rquery.test(s.url) ? "&" : "?") + s.data;
        // #9682: remove data so that it's not used in an eventual retry
        delete s.data;
    }

    // Get ifModifiedKey before adding the anti-cache parameter
    ifModifiedKey = s.url;
}

// Add anti-cache in URL if needed
if (s.cache === false) {
    var ts = jQuery.now(),
    // Try replacing _= if it is there
    ret = s.url.replace(rts, "$1_=" + ts);

    // If nothing was replaced, add timestamp to the end.
    s.url = ret + ((ret === s.url) ? (rquery.test(s.url) ? "&" : "?") + "_=" + ts : "");
}
6
Sam Shiles

Un moyen rapide de contourner les services GWT-RPC consiste à l'ajouter à toutes les méthodes distantes:

getThreadLocalResponse().setHeader("Cache-Control", "no-cache");
5
Lars Høidahl

Ceci est une mise à jour de la réponse de Baz1nga. Puisque options.data n'est pas un objet mais une chaîne, je viens de concaténer l'horodatage:

$.ajaxPrefilter(function (options, originalOptions, jqXHR) {
  if (originalOptions.type == "post" || options.type == "post") {

    if (options.data && options.data.length)
      options.data += "&";
    else
      options.data = "";

    options.data += "timeStamp=" + new Date().getTime();
  }
});
5
remcoder

Afin de résoudre ce problème pour les applications Web ajoutées à l'écran d'accueil, vous devez suivre les deux solutions de rechange votées. La mise en cache doit être désactivée sur le serveur Web pour empêcher la mise en cache de nouvelles demandes et une entrée aléatoire doit être ajoutée à chaque demande de publication pour que les demandes déjà mises en cache puissent être traitées. S'il vous plaît se référer à mon post:

iOS6 - Existe-t-il un moyen d'effacer les requêtes ajax POST mises en cache et ajoutées à l'écran d'accueil pour l'application Web?

AVERTISSEMENT: à toute personne ayant implémenté une solution de contournement en ajoutant un horodatage à leurs demandes sans désactiver la mise en cache sur le serveur. Si votre application est ajoutée à l'écran d'accueil, TOUTES les réponses seront désormais mises en cache, la suppression du cache de safari ne l'effacera pas et elle ne semblera pas expirer. À moins que quelqu'un ait le moyen de le nettoyer, cela ressemble à une fuite de mémoire potentielle!

4
fbader

Choses qui NE PAS FONCTIONNE pour moi avec un iPad 4/iOS 6:

Ma demande contenant: Cache-Control: no-cache

//asp.net's:
HttpContext.Current.Response.Cache.SetCacheability(HttpCacheability.NoCache)

Ajout de cache: false à mon appel ajax jQuery

 $.ajax(
        {
            url: postUrl,
            type: "POST",
            cache: false,
            ...

Seulement cela a fait le tour:

var currentTime = new Date();
var n = currentTime.getTime();
postUrl = "http://www.example.com/test.php?nocache="+n;
$.post(postUrl, callbackFunction);
3
Brian Ogden

C'est la solution pour GWT-RPC

class AuthenticatingRequestBuilder extends RpcRequestBuilder 
{
       @Override
       protected RequestBuilder doCreate(String serviceEntryPoint) 
       {
               RequestBuilder requestBuilder = super.doCreate(serviceEntryPoint);           
               requestBuilder.setHeader("Cache-Control", "no-cache");

               return requestBuilder;
       }
}

AuthenticatingRequestBuilder builder = new AuthenticatingRequestBuilder();
((ServiceDefTarget)myService).setRpcRequestBuilder(builder);    
3
Spiff

Ma solution de contournement dans ASP.NET (pagemethods, webservice, etc.)

protected void Application_BeginRequest(object sender, EventArgs e)
{
    Response.Cache.SetCacheability(HttpCacheability.NoCache);
}
2
Alexandre

Bien que l'ajout de paramètres cache-buster pour donner à la requête un aspect différent semble être une solution solide, je vous le déconseille, car cela nuirait à toute application reposant sur la mise en cache réelle. Faire en sorte que les API produisent les en-têtes corrects est la meilleure solution possible, même si cela est légèrement plus difficile que d'ajouter des contourneurs de cache aux appelants.

1
Ivo Jansch

Pour ceux qui utilisent Struts 1, voici comment j'ai résolu le problème.

web.xml

<filter>
    <filter-name>SetCacheControl</filter-name>
    <filter-class>com.example.struts.filters.CacheControlFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>SetCacheControl</filter-name>
    <url-pattern>*.do</url-pattern>
    <http-method>POST</http-method>
</filter-mapping>

com.example.struts.filters.CacheControlFilter.js

package com.example.struts.filters;

import Java.io.IOException;
import Java.util.Date;
import javax.servlet.*;
import javax.servlet.http.HttpServletResponse;

public class CacheControlFilter implements Filter {

        public void doFilter(ServletRequest request, ServletResponse response,
                     FilterChain chain) throws IOException, ServletException {

        HttpServletResponse resp = (HttpServletResponse) response;
        resp.setHeader("Expires", "Mon, 18 Jun 1973 18:00:00 GMT");
        resp.setHeader("Last-Modified", new Date().toString());
        resp.setHeader("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0, post-check=0, pre-check=0");
        resp.setHeader("Pragma", "no-cache");

        chain.doFilter(request, response);
    }

    public void init(FilterConfig filterConfig) throws ServletException {
    }

    public void destroy() {
    }

}
1
cbmeeks

Je pense que vous avez déjà résolu votre problème, mais laissez-moi vous donner une idée de la mise en cache Web.

Il est vrai que vous pouvez ajouter de nombreux en-têtes dans chaque langue que vous utilisez, côté serveur ou côté client, et vous pouvez utiliser de nombreux autres trucs pour éviter la mise en cache Web, mais pensez toujours que vous ne pouvez jamais savoir à partir de l'endroit où le client se connecte à votre serveur. on ne sait jamais s'il utilise une connexion "Hot-Spot" d'hôtel qui utilise Squid ou d'autres produits de mise en cache.

Si les utilisateurs utilisent un proxy pour masquer sa position réelle, etc., le seul moyen réel d'éviter la mise en cache est l'horodatage de la demande, même s'il est inutilisé.

Par exemple:

/ajax_helper.php?ts=3211321456

Ensuite, chaque gestionnaire de cache que vous devez transmettre n'a pas trouvé la même URL dans le référentiel de cache et re-télécharger le contenu de la page.

1
Lanello

J'ai pu résoudre mon problème en utilisant une combinaison de $ .ajaxSetup et en ajoutant un horodatage à l'URL de mon message (et non au paramètre/au corps du message). Ceci basé sur les recommandations des réponses précédentes

$(document).ready(function(){
    $.ajaxSetup({ type:'POST', headers: {"cache-control","no-cache"}});

    $('#myForm').submit(function() {
        var data = $('#myForm').serialize();
        var now = new Date();
        var n = now.getTime();
        $.ajax({
            type: 'POST',
            url: 'myendpoint.cfc?method=login&time='+n,
            data: data,
            success: function(results){
                if(results.success) {
                    window.location = 'app.cfm';
                } else {
                    console.log(results);
                    alert('login failed');
                }
            }
        });
    });
});
1
ShadeTreeDeveloper

Dans le Sinatra de Ruby

before '*' do
  if env['REQUEST_METHOD'] == 'POST'
    headers 'Cache-Control' => 'no-cache, no-store, must-revalidate'
  end
end
0
jchook

J'ai trouvé une solution de contournement qui me rend curieux de savoir pourquoi cela fonctionne. Avant de lire la réponse de Tadej concernant le service Web ASP.NET, j'essayais de trouver quelque chose qui fonctionnerait.

Et je ne dis pas que c'est une bonne solution, mais je voulais simplement la documenter ici.

page principale: inclut une fonction JavaScript, checkStatus (). La méthode appelle une autre méthode qui utilise un appel jQuery AJAX pour mettre à jour le contenu HTML. J'ai utilisé setInterval pour appeler checkStatus (). Bien sûr, j'ai rencontré le problème de la mise en cache.

Solution: utilisez une autre page pour appeler la mise à jour.

Sur la page principale, j'ai défini une variable booléenne, runUpdate, et ajouté ce qui suit à la balise body:

<iframe src="helper.html" style="display: none; visibility: hidden;"></iframe>

Dans le helper.html:

<meta http-equiv="refresh" content="5">
<script type="text/javascript">
    if (parent.runUpdate) { parent.checkStatus(); }
</script>

Donc, si checkStatus () est appelé depuis la page principale, je récupère le contenu mis en cache. Si j'appelle checkStatus depuis la page enfant, j'obtiens un contenu mis à jour.

0
CM Kanode

Selon l'application, vous pouvez maintenant résoudre le problème dans iOS 6 à l'aide de Safari> Avancé> Web Inspector, ce qui est utile dans cette situation.

Connectez le téléphone à Safari sur un Mac puis utilisez le menu du développeur pour résoudre l’application Web.

Effacez les données du site Web sur l'iPhone après la mise à jour vers iOS6, y compris celles spécifiques à l'application à l'aide d'un affichage Web. Une seule application avait un problème et cela a été résolu lors du test de la version bêta d'IOS6, depuis lors, aucun problème réel.

Vous devrez peut-être également consulter votre application. Consultez NSURLCache si vous vous trouvez dans une WebView dans une application personnalisée.

https://developer.Apple.com/library/ios/#documentation/Cocoa/Reference/Foundation/Classes/NSURLCache_Class/Reference/Reference.html#//Apple_ref/doc/uid/TP40003754

Je suppose que cela dépend de la vraie nature de votre problème, de sa mise en oeuvre, etc.

Ref: $ .ajax appelle

0
Steven Strauss

Bien que mes pages de connexion et d'inscription fonctionnent comme un charme dans Firefox, IE et Chrome ... Je me suis déjà heurté à ce problème dans Safari pour IOS et OSX, il y a quelques mois trouvé une solution de contournement sur le SO.

<body onunload="">

OU via javascript

<script type="text/javascript">
window.onunload = function(e){
    e.preventDefault();
    return;
};
</script>   

C'est un peu moche mais ça marche un peu.

Je ne sais pas pourquoi, mais si vous retournez null à l'événement onunload, la page n'est pas mise en cache dans Safari.

0
Adriano Rosa