nginxのifを使ってクエリをパースしてURLをrewriteして、リサイズ/切り取りするlocationに飛ばすことでリアルタイムで生成サムネイルを生成することができる。アップロード先のディレクトリも汚れない。

本サイトでは静的ファイルはhttps://static.endaaman.meから配信している。そこにすでに導入しているのでそのconfを解説する。レポはこちら

server {
  listen 80;

  server_name static.endaaman.me;
  server_tokens off;

  charset UTF-8;
  sendfile on;
  default_type application/octet-stream;
  expires 1d;

  # 指定のパラメーターにリサイズする
  # /resize_100_100/hoge.jpg なら 100×100pxに収まるようにリサイズされる
  location ~ ^/resize_(\d+)_(\d+)/(.*)$ {
    allow 127.0.0.0/8;
    deny all;
    image_filter resize $1 $2;
    image_filter_jpeg_quality 75;
    image_filter_buffer 10M;
    alias /var/uploaded/enda/$3;
  }

  # 指定のパラメーターに切り取る
  # /resize_100_100/hoge.jpg なら 100×100pxで切り取る
  location ~ ^/crop_(\d+)_(\d+)/(.*)$ {
    allow 127.0.0.0/8;
    deny all;
    image_filter crop $1 $2;
    image_filter_jpeg_quality 75;
    image_filter_buffer 10M;
    alias /var/uploaded/enda/$3;
  }

  location ~ ^/(.*)$ {
    set $path $1;

    set $check_resize '';

    set $has_query 0;

    # 変形の方法を選択
    # ?resize がデフォルトで、リサイズ
    # ?crop なら切り取り
    set $type 'resize';
    if ($args ~ 'resize') {
      set $has_query 1;
      set $type 'resize';
    }
    if ($args ~ 'crop') {
      set $has_query 1;
      set $type 'crop';
    }

    # ?w と ?h を読みこむ
    set $size_params "";
    if ($arg_w ~ (\d+)) {
      set $size_params "w${size_params}";
      set $has_query 1;
    }
    if ($arg_h ~ (\d+)) {
      set $size_params "${size_params}h";
      set $has_query 1;
    }

    # ?rezie もしくは ?crop が指定されたもののサイズが指定されてないケース
    # デフォルトの300×300pxを選択
    if ($size_params = "") {
      # default thumb size
      set $width 300;
      set $height 300;
    }
    # ?w しか選択されていないばあいは高さも幅と同じ値をセット
    if ($size_params = "w") {
      set $width $arg_w;
      set $height $arg_w;
    }
    # ?h しか選択されていないばあいは幅も高さと同じ値をセット
    if ($size_params = "h") {
      set $width $arg_h;
      set $height $arg_h;
    }
    # ?w&h で両方指定されたばあい
    if ($size_params = "wh") {
      set $width $arg_w;
      set $height $arg_h;
    }

    # 条件.1 : ?resize ?crop ?w ?h いずれかが指定されている
    if ($has_query = 1) {
      set $check_resize "${check_resize}o";
    }
    # 条件.2 : 画像ファイルへのアクセスである
    if ($uri ~ "\.(png|jpg|jpeg|gif)") {
      set $check_resize "${check_resize}o";
    }
    # 条件.3 : ファイルが存在している
    if (-f $request_filename) {
      set $check_resize "${check_resize}o";
    }

    # URLを書き換えてアクセスし直すのでHostもproxy_passしないといけない
    proxy_set_header Host $host;

    proxy_temp_path /tmp/nginx/thumbs;

    # キャッシュの設定。ファイルは消えたり名前が変わることもあるのであまり長く持たない
    # ホスト、方法、サイズ、ファイル名が与えられれば一意に指定できる
    proxy_cache THUMB;
    proxy_cache_key "${host}_${type}_${width}_${height}_${path}";
    proxy_cache_valid 200 1d;
    proxy_cache_valid any 1m;

    alias /var/uploaded/enda/$path;

    # リサイズの条件を満たしていればURLを書き換えてproxy_pass
    if ($check_resize = 'ooo') {
      rewrite .* "/${type}_${width}_${height}/${path}" break;
      proxy_pass http://127.0.0.1;
    }
  }

}

これで

  1. /hoge.jpg → オリジナル
  2. /hoge.jpg?resize → サムネイル
  3. /hoge.pdf → 普通にファイルが配信される
  4. /hoge.pdf?resize → 普通にファイルが配信される
  5. /not_exists_file.txt → 404
  6. /not_exists_file.txt?resize → 404

という感じに、 2. の場合のみサムネイルを返してくれる。

デモ

コメントにも書いたけどデフォルトは、方法がリサイズ、サイズが縦横300px。

/lawn.jpg?resize 横300px縦300pxに収まるようにリサイズ

写真は北海道大学の中央ローン。オリジナルはでかいからこちらから。

/lawn.jpg?w=200 横200px 縦200pxに収まるようにリサイズ

/lawn.jpg?h=200 横200px縦200pxに収まるようにリサイズ

### /lawn.jpg?crop 300pxの正方形に切り取る

/lawn.jpg?crop&w=100 100x100pxに切り取る

### /lawn.jpg?crop&w=300&h=400 300x400pxに切り取る

キャッシュがちゃんとできてればたくさん並べても全然問題ない。もうIf Is Evilなんて言ってる場合じゃない。IF is GODだ。

毎回Nginxにngx_small_lightを組み込んでコンパイルしてたけど十分使い物になるのでその必要もなさそう。

素朴な疑問

Nginx内部の変数を確認するのに

http = require 'http'
http.createServer (request, response)->
    v = request.url.substr 1, request.url.length
    console.log v
    response.writeHead 200, {}
    response.write 'ok: \"' + v + '\"'
    do response.end
.listen 4000, ->
    console.log 'listening at :4000'

みたいなechoサーバーを建ててproxy_pass http://127.0.0.1:4000/$valueって感じにして出力を確認してたんたけど、皆さんどうやってconfファイルのデバッグってやってるんでしょう。echoモジュールとか使ってんのかな?