4

一两个月前,我决定从我的站点中移除Varnish ,并用Nginx内置的缓存系统替代它。我本来已经在我的python站点上用了nginx来反向代理,所以摆脱Varnish意味着少了一个麻烦。我花了好几天时间阅读怎样配置nginx的缓存,翻遍了我的python站点的不同的配置文件(是的,很讽刺)。在阅读过程中我标记了一些有趣的nginx的模块,这中间就有 Image Filter module(图像过滤处理模块)。

我觉得结合nginx的反向代理,缓存和图像过滤处理三大模块来为我托管在S3上的图片来创建一个缩略图服务器会很优雅。如果你仔细的查看下面的(甚至整个站点的)<img>标签,你就可以看到nginx实战了。

clipboard.png

在本文中,我将描述我是怎样配置nginx来有效安全的为S3上的图片产生缩略图。作为额外奖励,我也会描述我是怎样使用 Secure Links module (安全链接模块)来防止人们恶意使用该服务器产生缩略图。

准备开始

为了让不同部分能协同工作,你的nginx需要编译进图像滤波,代理和安全链接三个模块。你可以通过nginx –V 来检查你所具有的模块。如果你使用的Ubuntu(Debian也行),你可以很轻易的安装其它任意的nginx模块(apt-get install 命令)。

一旦nginx准备好了,我们可以开始配置nginx了。

配置

首先需要申明的是我们的代理缓存。这个申明位于nginx.conf文件的http部分并且描述了基于文件的缓存所在,该缓存将会存储我们产生的缩略图。因为一次缓存未命中即意味着要从S3服务器上获取完整的图并且改变它的大小,我们希望配置缓存足够大以使其能包含我们大部分的缩略图。对于我的站点,我估计200MB足够了。

为了确定你的缓存,把下面的代码添加到nginx配置文件的http部分的某个地方:

# Nginx will create a cache capable of storing 16MB of keys and 200MB of data.
proxy_cache_path /tmp/nginx-thumbnails levels=1:2 keys_zone=thumbnail_cache:16M inactive=60d max_size=200M;

现在我们需要说明两个服务器定义:一个缓存服务器和一个调整大小服务器。后者将作为S3的反向代理,产生并提供调整大小后的图像。缓存服务器位于调整大小服务器的前面,缓存并提供调整大小后的图像。虽然我不认为必须要两个服务器,因为我的站点访问量并不是特别大,但在谷歌了一下后,我看见的一些文章表明,就是应该这样。

缓存服务器

缓存服务器会暴露给公共访问(我的位于m.charlesleifer.com)。因为该服务器的唯一任务就是缓存从调整大小服务器获得的响应,所以配置相当简单。下面是我的配置:

server {
  listen 80;
  server_name m.charlesleifer.com;

  location / {
    proxy_pass http://localhost:10199;
    proxy_cache thumbnail_cache;
    proxy_cache_key "$host$document_uri$is_args$arg_key";
    proxy_cache_lock on;
    proxy_cache_valid 30d;  # Cache valid thumbnails for 30 days.
    proxy_cache_valid any 15s;  # Everything else gets 15s.
    proxy_cache_use_stale error timeout invalid_header updating;
    proxy_http_version 1.1;
    expires 30d;
  }
}

无论何时缓存服务器得到一个请求,“thumbnail_cache”将首先被检测。如果没找到匹配项,那就将请求转发至调整大小服务器(运行在本地localhost)。它返回有效的响应,缓存服务器将缓存该响应30天,其它任何东西都只缓存15秒。

调整大小的服务器

所有有趣的事情都在调整大小服务器。它的任务是从S3上获得图片并基于URL上的参数实时调整图片大小。另外,该服务器会检查每个请求的安全秘钥以防止其他人随意产生缩略图。

因为该服务器配置有几个不同的部分,所以让我们从我们已经见过的部分开始:代理

server {
  listen 10199;
  server_name localhost;

  set $backend 'your.s3.bucket_name.s3.amazonaws.com';

  resolver 8.8.8.8;  # Use Google for DNS.
  resolver_timeout 5s;

  proxy_buffering off;
  proxy_http_version 1.1;
  proxy_pass_request_body off;  # Not needed by AWS.
  proxy_pass_request_headers off;

  # Clean up the headers going to and from S3.
  proxy_hide_header "x-amz-id-2";
  proxy_hide_header "x-amz-request-id";
  proxy_hide_header "x-amz-storage-class";
  proxy_hide_header "Set-Cookie";
  proxy_ignore_headers "Set-Cookie";
  proxy_set_header Host $backend;
  proxy_method GET;
}

这里除了告诉我们该服务器如何与S3通讯外并没有多余的其他内容,所以我们继续看下一部分。接下来要配置的是nginx的图像滤波模块。只需要几个指令,其中一些我们会定义到服务器级别。

下面是代理设置,添加如下的图像滤波模块:

server {
  # ...

  image_filter_jpeg_quality 85;  # Adjust to your preferences.
  image_filter_buffer 12M;
  image_filter_interlace on;
}

最后,我们定义一个代码块,完成如下功能:

  1. 检查形式合法的URL
  2. 校验请求的签名
  3. 从URL中提取图片大小参数
  4. 从S3获得图像并把它载入 image_filter_buffer
  5. 调整图片大小并响应

第二项相当有趣。一篇类似的文章的作者使用Lua来校验请求的签名,但那样好像步骤比较繁琐。 Nginx的 secure_link 扩展相当容易使用。

secure_link模块原理是:用被请求的图片的URL和只有你的app知道的密钥串连接起来的串来产生一个哈希(hash)。由于hash长度扩展,我们最后附加我们的密钥而不是预先考虑它。因为你知道秘钥,所以无论何时你想在你的应用中展示图片缩略图时你都可以产生有效的hash。

下面是配置的最后一部分:

server {
  # ...
  error_page 404 =404 /empty.gif;

  location ~ ^/t/([\d-]+)x([\d-]+)/(.*) {
    secure_link $arg_key;  # The hash is stored in the `key` querystring arg.
    secure_link_md5 "$uri my-secret-key";
    if ($secure_link = "") {
      # The security check failed, invalid key!
      return 404;
    }
    set $image_path '$3';
    image_filter resize $1 $2;

    proxy_pass http://$backend/$3;
  }
}

这就是所有的。

产生hash

如果你使用python,下面是我写的代码用以在特定URI下产生hash:

import base64
import hashlib

def thumbnail_url(filename, width, height='-'):
    uri = '/t/%sx%s/%s' % (width, height, filename)
    md5_digest = hashlib.md5(uri + ' my-secret-key').digest()
    key = base64.b64encode(md5_digest)
    # Make the key look like Nginx expects.
    key = key.replace('+', '-').replace('/', '_').rstrip('=')

    return 'http://m.charlesleifer.com%s?key=%s' % (uri, key)

感谢阅读

感谢你花时间阅读这篇文章,我希望你找到了乐趣。请留下评论,我会尽力回答。如果你发现了我上面的说明有什么错误也请让我知道,我会更新本文。


MageekChiu
4.4k 声望1.7k 粉丝

T