Skip to content

Replace cookie-based language with Accept-Language header for CDN/Varnish caching#439

Open
Copilot wants to merge 4 commits intomasterfrom
copilot/enable-site-caching
Open

Replace cookie-based language with Accept-Language header for CDN/Varnish caching#439
Copilot wants to merge 4 commits intomasterfrom
copilot/enable-site-caching

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 16, 2026

O que esse PR faz?

O backend emite set-cookie: language=pt_BR e Vary: Cookie em toda resposta, impedindo cache no BunnyCDN e Varnish — qualquer CDN interpreta isso como conteúdo personalizado por cookie.

Este PR elimina o cookie language, para de modificar a sessão em cada request (que dispara Vary: Cookie), e passa a usar o header padrão Accept-Language para detecção de idioma, emitindo Vary: Accept-Language.

Mudanças principais:

  • Remove add_language_code() after_request — para de emitir set-cookie: language=pt_BR
  • Move session["langs"]g.langs — evita marcar a sessão como modificada em cada request (que causa Vary: Cookie via save_session)
  • Reescreve get_locale() — lê session["lang"] somente se o usuário trocou idioma explicitamente via UI; caso contrário usa Accept-Language header com fallback para BABEL_DEFAULT_LOCALE
  • Adiciona Vary: Accept-Language — permite cache por idioma no CDN/Varnish
  • Atualiza caching.py — usa flask_babelex.get_locale() em vez de session.get("lang")
  • Atualiza 17 templatessession.langg.lang, session.langsg.langs

Fluxo após a mudança:

  • Visitante sem sessão → idioma via Accept-Language → sem cookie → cacheável
  • Usuário troca idioma via UI → set_locale() grava na sessão → funciona como antes
  • set_locale() é o único ponto que modifica a sessão

Onde a revisão poderia começar?

opac/webapp/main/views.py — as funções add_langs(), add_header(), get_locale() e a remoção de add_language_code().

Como este poderia ser testado manualmente?

  1. curl -I https://<host>/ — verificar que nãoset-cookie: language=... e que há Vary: Accept-Language (sem Vary: Cookie para requests sem sessão)
  2. Acessar o site sem cookies — idioma deve seguir o Accept-Language do navegador
  3. Trocar idioma via dropdown — deve funcionar normalmente (grava na sessão)
  4. Navegar após trocar idioma — idioma escolhido deve persistir
  5. Em aba anônima com Accept-Language: es — site deve aparecer em espanhol

Algum cenário de contexto que queira dar?

O Vary: Cookie era emitido por dois motivos:

  1. add_language_code() setava explicitamente o cookie language
  2. add_langs() fazia session["langs"] = ... em todo request, marcando a sessão como modificada → Flask chamava save_session() → adicionava Vary: Cookie

Com SESSION_REFRESH_EACH_REQUEST=False (já configurado) e sem modificar a sessão em requests normais, save_session() não adiciona Vary: Cookie.

Screenshots

N/A — mudança de headers HTTP, sem impacto visual.

Quais são tickets relevantes?

Issue #355

Referências

Original prompt

This section details on the original issue you should resolve

<issue_title>Possibilitar fazer cache do site</issue_title>
<issue_description>O site www.scielo.br na sua interface tem a alternância de idioma (português, inglês e espanhol). Na época do desenvolvimento, foi definido o uso de Cookie de sessão. Pode ser visto usando o comando: curl -I https://www.scielo.br
vary: Cookie
set-cookie: language=pt_BR; Path=/
set-cookie: opac_session=.eJwtjMEOgjAQBf9lzx4WoQZ6NPFuOJuQbdlWY9MStj0R_t2iHmde5m1gZXVTTm-OoGGgs-OudTwPaFrF1FlLqBQq42zPRmEz9zxf4ASBoq_Fkqfr-EcBvcH35xZ9eMmzepYDZaFHQXRNCtX9Gg33tObiy7EwCez7B0awLFg.aRZXJQ.aAolXnrkD8JgsGS7gaBKBuwtsjk; Domain=www.scielo.br; Secure; HttpOnly; Path=/

O cookie language=pt_BR está bloqueando o cache no BunnyCDN e no Varnish, porque o servidor passa a tratar a página como personalizada por idioma.

Por que o cookie de idioma impede o cache?
O backend envia:
set-cookie: language=pt_BR
E também:
Vary: Cookie

Isso significa para qualquer CDN e para o Varnish:
“Essa página muda dependendo dos cookies do cliente.”

Ou seja, o BunnyCDN e o Varnish entendem que:
• Usuário sem cookie → recebe idioma padrão
• Usuário com cookie → recebe versão em pt_BR
• Outro cookie → poderia receber outra versão
Por isso ele não pode cachear — senão poderia servir conteúdo do idioma errado para outro visitante.

Foi feito teste no varnish desativando o cookie e a alternância entre idiomas não funciona.

Opção para resolver isso é usar header Accept-Language, mas sem cookie:
• O Varnish/Bunny podem cachear
• Conteúdo não depende de cookie

Issue 355
</issue_description>

Comments on the Issue (you are @copilot in this section)


📱 Kick off Copilot coding agent tasks wherever you are with GitHub Mobile, available on iOS and Android.

Copilot AI and others added 2 commits March 16, 2026 11:45
…hing

- Remove language cookie (add_language_code after_request handler)
- Move session["langs"] to g.langs to avoid session modification on every request
- Modify get_locale() to not auto-write to session; only read session["lang"]
  (set by explicit language switch), fall back to Accept-Language header
- Add Vary: Accept-Language header and remove Vary: Cookie
- Set g.lang in before_request for template usage
- Update caching.py to use get_locale() instead of session.get("lang")
- Update all templates: session.lang → g.lang, session.langs → g.langs

Co-authored-by: robertatakenaka <505143+robertatakenaka@users.noreply.github.com>
…save_session)

Co-authored-by: robertatakenaka <505143+robertatakenaka@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix caching issue caused by language cookie Replace cookie-based language with Accept-Language header for CDN/Varnish caching Mar 16, 2026
Copilot AI requested a review from robertatakenaka March 16, 2026 12:26
@robertatakenaka robertatakenaka marked this pull request as ready for review March 16, 2026 16:11
Copilot AI review requested due to automatic review settings March 16, 2026 16:11
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Este PR muda a detecção de idioma para priorizar Accept-Language (com fallback), evitando escrita em sessão/cookie a cada request e permitindo cache em CDN/Varnish via Vary: Accept-Language.

Changes:

  • Remove o after_request que setava set-cookie: language=... e adiciona Vary: Accept-Language nas respostas.
  • Move session["langs"] para g.langs e passa g.lang para templates/links.
  • Atualiza geração de chave de cache (Redis) para usar flask_babelex.get_locale() e refatora diversas views/templates para dependerem de g.lang.

Reviewed changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
opac/webapp/main/views.py Refatora seleção de locale, remove cookie language, adiciona Vary: Accept-Language e troca leituras de idioma nas views.
opac/webapp/utils/caching.py Atualiza as funções de cache key para obter o locale via Babel (em vez de sessão).
opac/webapp/templates/base.html Ajusta referência comentada de locale (JS) para usar g.lang.
opac/webapp/templates/includes/language.html Migra dropdown de idioma de session.lang[s] para g.lang[s].
opac/webapp/templates/macros/collection.html Usa g.lang para resolver nome da coleção por idioma.
opac/webapp/templates/collection/includes/nav.html Migra decisões/URLs baseadas em idioma para g.lang.
opac/webapp/templates/collection/includes/header.html Migra escolha de logo por idioma para g.lang.
opac/webapp/templates/collection/includes/levelMenu_search.html Migra parâmetro lang (search) para g.lang.
opac/webapp/templates/journal/includes/meta.html Migra missão/OG description para g.lang.
opac/webapp/templates/journal/includes/levelMenu.html Migra parâmetro lang (search) para g.lang.
opac/webapp/templates/journal/includes/header.html Migra escolha de logo por idioma para g.lang.
opac/webapp/templates/journal/includes/alternative_header.html Migra parâmetro lang (search) para g.lang.
opac/webapp/templates/journal/detail.html Migra mission/feeds/links condicionais para g.lang.
opac/webapp/templates/issue/toc.html Migra título/seção/links de autor para g.lang.
opac/webapp/templates/issue/includes/alternative_header.html Migra parâmetro lang (search) para g.lang.
opac/webapp/templates/article/includes/header.html Migra escolha de logo por idioma para g.lang.
opac/webapp/templates/article/includes/recent_articles_row.html Migra título por idioma para g.lang.
opac/webapp/templates/article/includes/alternative_header.html Migra parte dos links/branding para g.lang.
opac/webapp/templates/admin/opac_base.html Migra seleção de idioma no admin para g.lang.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 109 to +118
def get_locale():
langs = current_app.config.get("LANGUAGES")
lang_from_headers = request.accept_languages.best_match(list(langs.keys()))

if "lang" not in list(session.keys()):
session["lang"] = lang_from_headers
# If user explicitly set language via set_locale, use that
if "lang" in session:
return session["lang"]

if not lang_from_headers and not session["lang"]:
# Caso não seja possível detectar o idioma e não tenhamos a chave lang
# no seção, fixamos o idioma padrão.
session["lang"] = current_app.config.get("BABEL_DEFAULT_LOCALE")
# Otherwise, use Accept-Language header
lang_from_headers = request.accept_languages.best_match(list(langs.keys()))
if lang_from_headers:
Comment on lines 81 to 87
def add_header(response):
response.cache_control.max_age = current_app.config.get(
"CACHE_CONTROL_MAX_AGE_HEADER"
)
response.headers["x-content-type-options"] = "nosniff"
return response


@main.after_request
def add_language_code(response):
language = session.get("lang", get_locale())
response.set_cookie("language", language)
response.vary.add("Accept-Language")
return response
Comment on lines 108 to +122
@babel.localeselector
def get_locale():
langs = current_app.config.get("LANGUAGES")
lang_from_headers = request.accept_languages.best_match(list(langs.keys()))

if "lang" not in list(session.keys()):
session["lang"] = lang_from_headers
# If user explicitly set language via set_locale, use that
if "lang" in session:
return session["lang"]

if not lang_from_headers and not session["lang"]:
# Caso não seja possível detectar o idioma e não tenhamos a chave lang
# no seção, fixamos o idioma padrão.
session["lang"] = current_app.config.get("BABEL_DEFAULT_LOCALE")
# Otherwise, use Accept-Language header
lang_from_headers = request.accept_languages.best_match(list(langs.keys()))
if lang_from_headers:
return lang_from_headers

return session["lang"]
# Default locale
return current_app.config.get("BABEL_DEFAULT_LOCALE")
import hashlib

from flask import current_app, request, session
from flask import current_app, request
Comment on lines 74 to 76
<li>
<a href="{{ config.URL_SEARCH }}?q=*&lang={% if session.lang %}{{ session.lang[:2] }}{% endif %}&filter[in][]={{config.OPAC_COLLECTION}}">
<a href="{{ config.URL_SEARCH }}?q=*&lang={% if g.lang %}{{ g.lang[:2] }}{% endif %}&filter[in][]={{config.OPAC_COLLECTION}}">
Busca
Comment on lines 146 to 150
// Garante que o valor do campo share_url é a URL corrente.
$('#share_url').val(window.location.href);

//moment.locale('{{ session.lang }}');
//moment.locale('{{ g.lang }}');

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Possibilitar fazer cache do site

3 participants