Hoje surgiu uma daquelas situações interessantes no trabalho que me serviu para descodificar mais um pouco essa linguagem poderosa que é o protocolo de comunicação HTTP. É sem dúvida uma prova da capacidade de adaptação e flexibilidade dos engenheiros que desenvolveram o protocolo que esta especificação já de si antiga consiga se manter actual (com alguns acrescentos ligeiros) e responder à maior parte dos requisitos que vão aparecendo na rede. Mas não é pra professar a fé e espalhar o evangelho que estou aqui.
Hoje estava eu, na minha, tentando resolver uma situação na página relativa à documentação do nosso código do lado do cliente (javascript); mais propriamente, o ficheiro referente à camada de apresentação (CSS) estava a ser carregado directamente do domínio principal da aplicação. Eu achei estranho, porque de um modo geral, tudo o que é recursos (imagens, scripts, .css, etc...) costumam ser carregados de um outro domínio, geralmente considerado o domínio dos recursos (assets em bom inglês).
Porquê?
Escalabilidade. Repartindo a carga (load balancing em bom inglês) por vários servidores e salvaguardando o domínio principal para a entrega do nosso hipertexto, conseguimos desse modo optimizar a entrega do documento HTML ao cliente, permitindo ao agente utilizador (geralmente o navegador web) continuar a carregar o documento enquanto paralelamente carrega os recursos necessários para a sua renderização-apresentação (excepção feita aos scripts não-deferentes). O utilizador consegue visualizar informação mais rapidamente, o domínio principal optimiza a entrega do conteúdo mais simples, permitindo até compactá-lo, e servidores paralelos optimizados para a distribuição de conteúdo mais complexo tratam do resto, evitando o efeito de congestionamento "gargalo da garrafa" (bottleneck) caso fossem requisitados todos os recursos ao mesmo providenciador e deste modo salvaguardando o poder de execução do servidor principal para a resposta aos mais diversos pedidos provenientes de clientes interagindo com as páginas web, que de um modo geral (e especialmente em páginas carregadas de interacção por pedidos AJAX) têm que forçosamente requisitar informação do mesmo domínio, culpa da nossa velha conhecida política de mesma origem (same-domain-policy). Existem diversas formas de potenciar a concorrência de pedidos simultâneos, que variam de servidor para servidor web, como fazer caching desses recursos, mas de um modo geral, e dado que os URIs aceites nas tags HTML link e script não estão restringidos por esta política, a utilização de servidores paralelos optimizados para entrega de recursos continua a ser a resolução mais unânime para o problema da escabilidade.
Então porque é que aquele recurso estava a ser carregado do domínio principal? Pois, também não percebi. O que percebi foi que estava a ser escrito à pata na diagramação do documento que gerava a página da documentação. E o ActionView do Rails tem uma dessas funções mágicas (stylesheet_link_tag, para quem não conhece) à qual se dá o caminho relativo do nosso recurso de apresentação e o ActionView gera um caminho absoluto possível completo com um dos domínios referenciados como servidor de recursos nas configurações. Dado que a geração da documentação estava a ser feita através de um desses scripts que gera documentação estaticamente, tive só que incluir o módulo correcto no motor de renderização para que a função estivesse disponível. Claro que demorou o seu tempo todo este processo até à solução, e foi no entretanto que eu deparei-me com algo que me fez coçar a cabeça valentemente.
No processo de teste do caminho absoluto do recurso, ia comparando o caminho gerado com o caminho gerado para os recursos em produção do nosso sítio web principal. E foi aí que reparei que, em produção, os ficheiros .css estavam a ser carregados do domínio principal. Achei aquilo uma falha terrível, que só não era tão grave devido ao facto de ainda não termos assim tantos utilizadores, logo, o tráfego ainda não era proibitivo. No entanto, a minha avó já dizia que os problemas evitam-se, antes prevenir que remediar, etc e tal, entre outros ditos. Comecei a fazer perguntas. Não podia ser um simples erro de programador distraído. E aí obtive a minha resposta: foi tudo por causa das fontes CSS.
Para quem não conhece, uma fonte CSS é uma das propriedades CSS referente ao tipo de letra em que determinado texto encapsulado numa regra HTML será apresentado. Os navegadores web geralmente suportam uma miríade delas (Times New Roman, Arial, etc... nomes não tão estranhos para nós) nativamente, mas de vez em quando os designers acordam com o rabo virado para a lua e acham que o que as pessoas realmente querem ver é aquilo que não está tão facilmente disponível (tudo bem, ás vezes é assim mesmo), sendo que existe a possibilidade de costumizar fontes e torná-las disponíveis para a camada de apresentação, providenciando um URI a uma regra CSS chamada @font_face, possibilitando desta forma o carregamento assíncrono e a utilização por parte do navegador web de um tipo de letra que não suporta nativamente. Qualquer coisa como isto:
@font-face {
font-family: GeometricModern;
src: url(font.ttf);
}
p {
font-family: GeometricModern, sans-serif;
}
desta forma, podemos dar uso ao tipo de fonte GeometricModern. Parece fácil não é? Pois é, existe um "senão". Neste caso, o Firefox decidiu implementar uma restrição aos URIs que podem ser passados nas regras @font-face, nomeadamente este URI tem que ter como origem o domínio do documento do qual faz parte. O que isto quer dizer? Se um pedido for enviado a www.exemplo.com, e o domínio de onde o ficheiro css estiver a ser carregado tiver o domínio recursos.exemplo.com, o pedido pela fonte terá de ser feito ao domínio www.example.com, caso contrário, o navegador vai bloquear a fonte. No caso acima, iria dar barraca, porque (font.ttf) é um caminho relativo, logo, se o domínio de onde o ficheiro CSS estiver a ser carregado for recursos.exemplo.com, esse também vai ser o domínio do caminho da fonte.Eis que estava encontrada a justificação do colega responsável. O domínio da origem do documento era www.exemplo.com, e consequentemente seria também o domínio da origem dos recursos CSS, e consequentemente, das fontes, e tudo batia certo. Já agora, e porquê a restrição?
http://dev.w3.org/csswg/css3-fonts/#font-face-rule
Pois, mais um instrumento de cross-site-scripting em potência. Mas depois perguntei-me: "O Google não faz isto. O Facebook não faz isto. Como é que eles resolveram o assunto?". Li alguma documentação, bati um pouco mais com a cabeça na parede, suportei a pressão dos meus colegas de trabalho, que viram nesse momento o seu trabalho posto em causa e acusaram a minha investigação um pouco, como se estivesse a pisar os pés de alguém, e voltei à casa de partida. E foi aí que li a alínea relativamente bem vísivel da ligação aqui acima:
User agents must also implement the ability to relax this restriction using cross-site origin controls[CORS] Sites can explicitly allow cross-site downloading of font data using the
Access-Control-Allow-Origin HTTP header. E o HTTP veio para salvar o dia! Com esse campinho do cabeçalho da resposta do recurso, tinha tudo para dar certo. Só precisava de introduzir o seguinte no cabeçalho:
Access-Control-Allow-Origin: http:/www.exemplo.com
e era tudo o que bastava. A minha questão seguinte foi: Como fazer isso em Rails? É que o pedido e a resposta são montados na camada de middleware, à qual eu tenho um acesso relativamente restrito e uma compreensão relativamente baixa da complexidade do bicho. Mais umas pesquisas e descobri que existe uma gem que já fazia o que eu queria:
https://github.com/rubymaverick/font_assets/
Maravilha! Acrescentei, compilei, testei, e lá estava aquela fonte de merda horrível que não acrescentava pão mas que o designer achou tão necessária. Foi o pico de felicidade do meu dia.
Ainda fui verificar o suporte dos navegadores, mas depois lembrei-me que esta é por enquanto uma restrição exclusiva do Firefox. Restrição essa que vai existir eventualmente em todos os navegadores, incluindo esse monte de banha da cobra chamado Chrome. E assim resolveu-se um problema que se iria colocar eventualmente: como partilhar recursos entre domínios diferentes tendo a possibilidade de restringir esses domínios.
Lição do dia: o HTTP domina demais.
Sem comentários:
Enviar um comentário