反代 Google fonts 谷歌字体库

0.为什么会有本文

Google fonts 是谷歌免费提供的开放字体服务,在网站中简单的引入 Google fonts 的 CSS 就能简单、快速的在网页中使用大量美观、高可读性的开源字体而不需要在自己服务器上储存字体文件(众所周知字体文件体积不小,尤其是 CJK字族,那么引用 Google fonts 之后就可以减少服务器存储和接受请求的压力),因此 WordPress 主题中大量引用了 Google fonts ,在2014年6月开始大陆访问 Google 服务出现严重问题,Google fonts 服务随之受到影响,由于无法访问 Google 服务器,所引用的字体会回落到浏览器默认字体,以至于很多主题用户的浏览器上变得丑不堪言,有个简单粗暴的办法就是把需要的字体全部拖到网站服务器上,当然这就违背了 Google 建立开放字体服务的初衷,而且对于小水管服务器是压力巨大的(比如阿里云乞丐版就 1Mbps 带宽)。

于是以360为首的一些组织提供了 Google fonts 的反向代理(受聚聚指正360的更应该是镜像而不是反代),反向代理服务器将代替用户向 Google 请求字体资源并转交给用户,这一过程对用户是透明的,因此将 WordPress 中 Google fonts 的链接替换就可以在国内使用(这就要求了反向代理的服务器位于墙外且对双方都有不错的访问速度),但是由于360提供的 useso 和USTC LUG 提供的开源镜像站可能是因为繁忙或者各种原因,访问速度不是很快:QQ20150815-1@2x

(图为 360 的 useso 字体库在他们自己家奇云测的测速结果,点击放大)

尤其是在我忘记关闭 shadowsocks 的全局代理的时候,访问本站就会出现字体回退(我实在是搞不懂为什么会回退到楷体,楷体本身是印刷字体,加上中文中并没有使用斜体的习惯,字体的斜体是通过一定换算直接得到的,和很多西文字体中独立的 Italic type 没有可比性,作为屏幕显示字体着实是难看):

QQ20150815-3@2x

虽然在目前 googleapis.com 的域名在部分地区解析到了 Google 的北京 IP,但是总体可用性还是不高,还是有部分 DNS 解析到美帝去(然后妥妥的被墙),于是就实践了拖延了很久的自建 Google fonts 反代。

1.首先你需要一个可用的 nginx 环境

达成这个目标需要至少:

1.一个高速度低延迟的 VPS(Linode Tokyo、OAH 或者 Azure HK 等,本文使用的 hostus 香港 softlayer 机房的大水管 VPS)

2.一个 nginx-extras(包含了较多的 module 支持比如 SSL)。Apache2做反代没有尝试过,不在本文的讨论范围内。再说了nginx 本身就是以其优秀的反代性能而闻名(见维基百科 nginx 词条

3.一个域名

首先安装 nginx-extras,nginx-extras 包的模块已经装的很全了,一般是没必要自己编译的。

如果你不介意自行编译 nginx 并有能力处理那一堆相关依赖问题,可以看看来自兽兽的这篇教程,这篇文章使用的是 wen.lu 的开源模块 ngx_http_google_filter_module ,解决方案远比本文的优雅(唯一的麻烦就是 nginx 需要重新编译),我也会在另一台 VPS 上尝试这个方法。

Debian 系的 nginx 默认配置文件位于 /etc/nginx/sites-available,使用软链接链到  /etc/nginx/sites-enabled 启用。检查nginx 是否工作可以 netstat 80端口是否监听或者直接 curl localhost。

apt-get update
apt-get install nginx-extras
curl localhost

接下来新建一个配置文件 /etc/nginx/sites-enabled/fonts 填入以下内容,注意#号注释的地方需要修改:

upstream google {
    server fonts.googleapis.com:80;
}

upstream gstatic {
    server fonts.gstatic.com:80;
}

proxy_temp_path   xxx/fonts/tmp 1 2; #替换成 www 根目录
proxy_cache_path  xxx/fonts/cache  levels=1:2 keys_zone=cache1:100m inactive=30d max_size=1g;
#由于字体 CSS 这样的东西不会经常性的改动,可以开启反代的缓存减少向后端的请求

server {
    listen 80;
    listen [::]:80; #监听 IPv6 地址,不需要 IPv6 可以直接去掉
    log_format format1 '$remote_addr - $remote_user [$time_local]  '
        '"$request" $status $body_bytes_sent '
        '"$http_referer" "$http_user_agent" $upstream_cache_status';
    access_log /var/log/nginx/fonts_access.log format1;
    error_log /var/log/nginx/fonts_error.log;
    server_name fonts.xxx.com; #替换域名

    valid_referers server_name *.starduster.me; #防盗链,设置允许访问的域名
    if ($invalid_referer) {
        return 403; #不允许访问的域名返回403
    }

    location /css {
        sub_filter 'fonts.gstatic.com' 'fonts.xxx.com'; #替换域名
        sub_filter_once off;
        sub_filter_types text/css;
        proxy_pass_header Server;
        proxy_set_header Host fonts.googleapis.com;
        proxy_set_header Accept-Encoding '';
        proxy_redirect off;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Scheme $scheme;
        proxy_pass http://google;
    proxy_cache cache1;
    proxy_cache_key $host$uri$is_args$args;
    proxy_cache_valid 200 304 10m;
    expires 365d;
    }

    location / {
        proxy_pass_header Server;
        proxy_set_header Host fonts.gstatic.com;
        proxy_redirect off;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Scheme $scheme;
        proxy_pass http://gstatic;
        proxy_cache cache1;
        proxy_cache_key $host$uri$is_args$args;
        proxy_cache_valid 200 304 10m;
    expires 365d;
    }
}

如果你需要 HTTPS 访问,增加一个 server 区块,证书问题参见Nginx 配置安装 SSL 证书与配置在线代理

server {
    listen 443 ssl spdy; #将这一行改为"自己的IP地址:443"
    ssl on;
    ssl_certificate /xxx/ssl.crt; #SSL证书位置
    ssl_certificate_key /xxx/ssl.key; #SSL私钥位置
    ssl_session_timeout 5m;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    server_name fonts.xxx.com;

    valid_referers server_name #同上
    if ($invalid_referer) {
        return 403;
    }

    location /css {
        sub_filter 'http://fonts.gstatic.com' 'https://fonts.starduster.me'; #注意 https
        sub_filter_once off;
        sub_filter_types text/css;
        proxy_pass_header Server;
        proxy_set_header Host fonts.googleapis.com;
        proxy_set_header Accept-Encoding '';
        proxy_redirect off;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Scheme $scheme;
        proxy_pass http://google;
    proxy_cache cache1;
    proxy_cache_key $host$uri$is_args$args;
    proxy_cache_valid 200 304 10m;
    expires 365d;
    }

    location / {
        proxy_pass_header Server;
        proxy_set_header Host fonts.gstatic.com;
        proxy_redirect off;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Scheme $scheme;
        proxy_pass http://gstatic;
        proxy_cache cache1;
        proxy_cache_key $host$uri$is_args$args;
        proxy_cache_valid 200 304 10m;
    expires 365d;
    }
}

 

保存退出,使用nginx -t 测试配置文件是否正确后,启动 nginx service nginx start

2.测试反代是否正常工作

正常的 Google fonts 地址类似这样 http://fonts.googleapis.com/css?family=Open+Sans:300,700,400 (原地址可能打开有困难,请自备科学上网)

点击打开之后你将看到 类似这样的内容:

@font-face {
  font-family: 'Open Sans';
  font-style: normal;
  font-weight: 300;
  src: local('Open Sans Light'), local('OpenSans-Light'), url(http://fonts.gstatic.com/s/opensans/v13/DXI1ORHCpsQm3Vp6mXoaTQ7aC6SjiAOpAWOKfJDfVRY.woff2) format('woff2');
  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
}

将你的链接替换掉变成  http://fonts.xxx.com/css?family=Open+Sans:300,700,400 ,打开之后会发现内容里的 http://fonts.gstatic.com/s/~  变成了 http://fonts.xxx.com/s/~ 且链接可以访问、和源 Google 链接访问得到的文件一致,说明反代是有效的。

3.替换 WordPress 中的链接

如果你熟悉 PHP,可以直接修改 WordPress 主题模板。

也可以在网上随便找个把谷歌字体替换成 360 useso 的插件,编辑压缩包里的 PHP 文件直接改掉域名再打包回去,然后在 WordPress 后台上传。

替换之后可以打开一个新隐身窗口打开 F12查看 source 是不是确实改过来了。

QQ20150815-2@2x

(图为反代的字体库在全国的访问情况,还是好上不少的,现在我全局 ss 访问自己博客也不会出现字体回退问题,点击放大)

4.简单解释一下配置文件的含义

每个参数的第一节表示了它所属的模块,比如proxy_temp_path 属于 ngx_http_proxy_module,而 upstream_cache_status 属于 ngx_http_upstream_module ,同理 sub_filter 属于 ngx_http_sub_module

upstream  模块实现反向代理的功能,将真正的请求转发到后端服务器(也就是 Google fonts 的服务器)上,并从后端服务器上读取响应,发回客户端,可以在一个 upstream 里填多个服务器并分配权重,proxy_temp_path 表示反代过程中接收文件产生的临时文件的指定位置,1和2表示第一级目录使用1字符,第二级目录使用2字符:

QQ20150816-1@2x

 

proxy_cache_path 表示缓存所在位置,实际上这些静态资源是很少更新的,nginx 向 Google 请求一次之后的比较长的一段时间内可以直接返回缓存而不再次向 Google 请求,其中:

  • keys_zone=cache1:100m 表示这个zone名称为cache1,分配的内存大小为100MB
  • xxx/fonts/cache1 表示cache1这个zone的文件要存放的目录
  • levels=1:2 同上,表示目录层级
  • inactive=30d 表示这个zone中的缓存文件如果在30天内都没有被访问,那么文件会被cache manager进程删除掉
  • max_size=1g 表示这个zone的最大占用硬盘空间为1GB

log_format 访问日志格式,记录客服端地址、时间、请求 URL、返回 HTTP 状态值、请求实体大小、来源页(referer)、UA、只适用于 http 连接。

upstream_cache_status 表示资源缓存的状态,有HIT MISS EXPIRED三种状态。

sub_filter 过滤器,用于替换域名。

proxy_set_header 重新定义或添加发往后端服务器的请求头。

proxy_redirect 决定是否替换掉后端返回的 Location 响应头和 Refresh 响应头。

proxy_pass 设置后端服务器的协议和地址,可以使用 URI (统一资源标记符,区别于 URL,参见维基百科,因此可以使用类似本文的 proxy_pass $request; 方式,nginx 默认先查找 upstream 中的服务器组,找不到时使用 resolver 解析地址,因为有 upstream 这里没有设 resolver)。

proxy_cache_key 表示缓存的 key。

proxy_cache_valid 表示可以进行缓存的 HTTP 状态码。

valid_referers 允许的 referer,在列表外的域名都返回 403 forbidden 防止被滥用。

5.参考/引用


UPDATE 2015.08.17

添加了 SSL 相关配置,但是由于我博客由于各种各样的原因并没有加全站 SSL,故我并没有测试 https 下的反代实际效果,直接访问是没有发现问题的。

在这篇文章完成的时候我偶然发现本站使用的代码高亮插件引用了Google 前端公共库,像这样的 https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.js 请求就不包括在 Google fonts 域名下,自然不被这次的配置所包括,看来又要造一遍轮子,这里就体现了 ngx_http_google_filter_module 的优越性,那个替换是全局的,反代谷歌全家桶只需要一个 google on; 就搞定。

这个配置有点问题就是日后添加更多域名的时候在 SSL 证书上会出现问题,Google 给 googleapis.com 签的是 wildcard 泛域名证书所以所有网站通用一个 SSL 证书,而在反代的时候就会出现问题,每个域名添加一个 SSL 就太麻烦了,有个办法是反代的时候统一使用一个域名,然后 sub_filter 放到不同的目录。

这坑就先丢这里了,看了一下 nginx-extras 的编译参数,有好几个附加模块,下载编译解决依赖估计是个巨大的工程,下一次换个地方编译个 nginx 尝试通过 sub_filter 分配目录。


UPDATE 2017.03.01

不记得什么时候360关掉了他们的公共库业务,RIP


本文链接:https://www.starduster.me/2015/08/15/reverse-proxy-google-fonts/
本站基于 Creactive Commons BY-NC-SA 4.0 License 允许并欢迎您在注明来源和非商业使用前提下自由地对本文进行复制、分享或基于本文进行创作。
请注意:受限于笔者水平,本站内容可能存在主观臆断或事实错误,文中信息也可能因时间推移而不再准确,在此提醒读者结合自身判断谨慎地采纳。

3条评论


  1. 随着大陆用户疯狂的涌入 SLHK,链路质量已经下降到了不可忍受的程度,在 SLHK 搭建反代的实际效果现在差于直接解析到 Google 北京,sigh

    回复

回复 月色黄泉/StarDuster 取消回复

您的电子邮箱地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据