Comment empêcher la mise en cache des pages du navigateur dans Rails

151

Ubuntu -> Apache -> Phusion Passenger -> Rails 2.3

La partie principale de mon site réagit à vos clics. Ainsi, si vous cliquez sur un lien, il vous dirigera vers la destination et régénérera instantanément votre page.

Mais, si vous appuyez sur le bouton de retour, vous ne voyez pas la nouvelle page. Malheureusement, il n'apparaît pas sans une actualisation manuelle; il semble que le navigateur le met en cache. Je veux m'assurer que le navigateur ne cache pas la page.

Séparément, je fais veux fixer des dates d'expiration extrême futures pour tous mes actifs statiques.

Quelle est la meilleure façon de résoudre ce problème? Dois-je résoudre ce problème dans Rails? Apache? Javascript?

Merci pour toute votre aide, Jason


Hélas. Aucune de ces suggestions n'a forcé le comportement que je recherche.

Peut-être qu'il y a une réponse javascript? Je pourrais demander aux rails d'écrire un horodatage dans un commentaire, puis demander au javascript de vérifier si les temps sont dans les cinq secondes (ou ce qui fonctionne). Si oui, alors très bien, mais si non, alors rechargez la page?

Pensez-vous que cela fonctionnerait?

Merci pour votre aide,

Jason

Jason Butler
la source

Réponses:

335

Enfin compris - http://blog.serendeputy.com/posts/how-to-prevent-browsers-from-caching-a-page-in-rails/ inapplication_controller.rb

Après les rails 5:

class ApplicationController < ActionController::Base

  before_action :set_cache_headers

  private

  def set_cache_headers
    response.headers["Cache-Control"] = "no-cache, no-store"
    response.headers["Pragma"] = "no-cache"
    response.headers["Expires"] = "Mon, 01 Jan 1990 00:00:00 GMT"
  end
end

Rails 4 et versions antérieures:

class ApplicationController < ActionController::Base

  before_filter :set_cache_headers

  private

  def set_cache_headers
    response.headers["Cache-Control"] = "no-cache, no-store"
    response.headers["Pragma"] = "no-cache"
    response.headers["Expires"] = "Mon, 01 Jan 1990 00:00:00 GMT"
  end
end
Jason Butler
la source
3
Cela devrait-il être enveloppé dans un "if request.xhr?" donc il ne se règle que sur les actualisations ajax, mais les pages normales ne le font pas?
neufDépart
3
Vous n'avez vraiment besoin Cache-Control: no-storeque tant que le navigateur est compatible avec HTTP 1.1. Section 14.9.2 Ce qui peut être stocké par les caches
gaqzi
28
Le 1er janvier 1990, c'était un lundi!
Jan Hettich
2
Cela ne fonctionne pas pour moi, j'ai ajouté le même code dans application_controller.rb et après la déconnexion, je peux voir la dernière page par le bouton de retour. Veuillez me guider là où je me trompe?
Thorin
1
Est-ce que cela ne mettra pas non plus en cache JS et CSS dans l'application rails? JS et CSS seront-ils chargés à partir du serveur pour chaque requête?
furiabhavesh
14

utilisation:

expires_now()

http://api.rubyonrails.org/classes/ActionController/ConditionalGet.html#method-i-expires_now

Sacré
la source
3
expires_nowenvoie uniquement l'en- no-cachetête. Selon le navigateur, cela peut ne pas suffire. (Par exemple, Firefox veut un no-storepour les connexions non-HTTPS: developer.mozilla.org/en/docs/Using_Firefox_1.5_caching )
Daniel Rikowski
1
D'accord, ce n'est pas une solution valide, testée avec Rails 5.2 et Chrome 77. no-storeest également nécessaire.
AndrewSouthpaw
3

J'ai utilisé cette ligne avec un certain succès dans le contrôleur. Cela fonctionne dans Safari et Internet Explorer mais je ne l'ai pas vu fonctionner avec Firefox.

response.headers["Expires"] = "#{1.year.ago}"

Pour votre deuxième point, si vous utilisez les méthodes d'assistance des rails comme

stylesheet_link_tag

et laissez les paramètres par défaut sur votre serveur Web, les actifs sont généralement assez bien mis en cache.

erik
la source
3
1.year.agoest une surcharge inutile. Il suffit de choisir un moment arbitraire dans le passé commeFri, 01 Jan 1990 00:00:00 GMT
Archonic
6
@Archonic 1 janvier 1990 était un lundi!
Thomas R. Koll
1

La manière la plus propre serait d'écrire un middleware Rack, qui modifie l'en-tête Cache-Control en fonction d'une logique (par exemple, uniquement pour le type mime application / xml). Ou, pour une approche plus moche, mais toujours efficace, on pourrait changer la constante ActionDispatch :: Response :: DEFAULT_CACHE_CONTROL en 'no-cache'. Bien sûr, si la granularité du contrôleur et / ou de l'action est requise, il est préférable de le faire dans le contrôleur.

romain
la source
1

Point à noter: vous ne pouvez pas effacer conditionnellement le cache (comme si un before_filterappelait uniquement reset_cachesi l'utilisateur y était déjà). Vous devez vider le cache de manière inconditionnelle , car le navigateur ne fera pas de nouvelle demande juste pour voir si cette fois, il a besoin de se recharger, même s'il n'a pas eu besoin de la dernière fois.

Exemple:

before_filter :reset_cache, if: :user_completed_demographics?

ne fonctionnera pas pour empêcher les utilisateurs de revenir après y avoir été, car le navigateur utilise les en-têtes de cache d'origine sur le bouton Retour.

before_filter :reset_cache

fonctionnera, cependant (après avoir actualisé la page et vidé le cache avant que vous ne l'ajoutiez, évidemment), puisque, à la première demande, le navigateur obtiendra le no-cache, no-store, ...et l'appliquera aux futurs chargements de page.

BalinKingOfMoria réintègre les CM
la source
0

no_cache_control Gemme.

Si vous avez besoin de faire cela pour toutes les réponses, par exemple pour passer un test de pénétration (BURP, Detectify, etc.), vous pouvez installer ce Gem on Rails 4+ afin d'ajouter les en-têtes suivants à toutes les réponses:

Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: -1

Fonctionne comme un charme et est vraiment la bonne voie à suivre pour des applications Web HTTPS sécurisées qui nécessitent une authentification pour faire quoi que ce soit.

Joshua Pinter
la source